20135321餘佳源——資訊安全系統設計基礎第十二周學習總結
标簽(空格分隔):20135321餘佳源
第十二周(11.23-11.29):
學習計時:共6小時
讀書:3
代碼:1
作業:1
部落格:1
一、學習目标
- 掌握程序控制
- 掌握信号處理的方法
- 掌握管道和fifo進行程序間通信的方法
二、學習資源
編譯、運作、閱讀、了解process.tar.gz壓縮包中的代碼
exec1
#include <stdio.h>
#include <unistd.h>
int main()
{
char *arglist[3];
arglist[0] = "ls";
arglist[1] = "-l";
arglist[2] = 0 ;//NULL
printf("* * * About to exec ls -l\n");
execvp( "ls" , arglist );
printf("* * * ls is done. bye");
return 0;
}
可以看到這個代碼中用了execvp函數。
表頭檔案:
#include
定義函數:
int execvp(const char file ,char const argv []);
exec1.c編譯并運作程式結果如下:

execvp()會從PATH 環境變量所指的目錄中查找符合參數file 的檔案名,找到後便執行該檔案,然後将第二個參數argv傳給該欲執行的檔案。
如果執行成功則函數不會傳回,執行失敗則直接傳回-1,失敗原因存于errno中。
可以看到,exevp函數調用成功沒有傳回,是以沒有列印出“* * * ls is done. bye”這句話。
exec2
exec2與exec1的差別就在于,execvp函數調用的語句變成了
execvp( arglist[0] , arglist );
不過由定義可得這兩個等價,是以運作結果是相同的。
編譯運作結果與exec1.c完全相同,說明arglist數組的第一項為要運作的程式的名稱。
exec3
代碼如下:
#include <stdio.h>
#include <unistd.h>
int main()
{
char *arglist[3];
char*myenv[3];
myenv[0] = "PATH=:/bin:";
myenv[1] = NULL;
arglist[0] = "ls";
arglist[1] = "-l";
arglist[2] = 0 ;
printf("* * * About to exec ls -l\n");
execlp("ls", "ls", "-l", NULL);
printf("* * * ls is done. bye\n");
}
這個代碼裡使用了execlp函數,用法如下:
頭檔案:
#include
定義函數:
int execlp(const char * file,const char * arg,....);
函數說明:
execlp()會從PATH 環境變量所指的目錄中查找符合參數file的檔案名,找到後便執行該檔案,然後将第二個以後的參數當做該檔案的argv[0]、argv[1]……,最後一個參數必須用空指針(NULL)作結束。如果用常數0來表示一個空指針,則必須将它強制轉換為一個字元指針,否則将它解釋為整形參數,如果一個整形數的長度與char * 的長度不同,那麼exec函數的實際參數就将出錯。如果函數調用成功,程序自己的執行代碼就會變成加載程式的代碼,execlp()後邊的代碼也就不會執行了.
傳回值:
如果執行成功則函數不會傳回,執行失敗則直接傳回-1,失敗原因存于errno 中。
也就是說,這個代碼指定了環境變量,然後依然執行了ls -l指令,成功後沒有傳回,是以最後一句話不會輸出。運作結果同exec1.
forkdemo1
這個代碼先是列印程序pid,然後調用fork函數生成子程序,休眠一秒後再次列印程序id,這時父程序列印子程序pid,子程序傳回0。
#include <stdio.h>
#include<sys/types.h>
#include<unistd.h>
int main()
{
int ret_from_fork, mypid;
mypid = getpid();
printf("Before: my pid is %d\n", mypid);
ret_from_fork = fork();
sleep(1);
printf("After: my pid is %d, fork() said %d\n",
getpid(), ret_from_fork);
return 0;
}
在Unix/Linux中用fork函數建立一個新的程序。程序是由目前已有程序調用fork函數建立,分叉的程序叫子程序,建立者叫父程序。該函數的特點是調用一次,傳回兩次,一次是在父程序,一次是在子程序。兩次傳回的差別是子程序的傳回值為0,父程序的傳回值是新子程序的ID。子程序與父程序繼續并發運作。如果父程序繼續建立更多的子程序,子程序之間是兄弟關系,同樣子程序也可以建立自己的子程序,這樣可以建立起定義關系的程序之間的一種層次關系。
forkdemo2
#include <stdio.h>
#include <unistd.h>
int main()
{
printf("before:my pid is %d\n", getpid() );
fork();
fork();
printf("aftre:my pid is %d\n", getpid() );
return 0;
}
這個代碼也是列印程序pid,然後調用fork函數生成子程序,因為調用兩次fork,一共産生四個子程序,是以會列印四個after輸出。
forkdemo3
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
int fork_rv;
printf("Before: my pid is %d\n", getpid());
fork_rv = fork(); /* create new process */
if ( fork_rv == -1 ) /* check for error */
perror("fork");
else if ( fork_rv == 0 ){
printf("I am the child. my pid=%d\n", getpid());
exit(0);
}
else{
printf("I am the parent. my child is %d\n", fork_rv);
exit(0);
}
return 0;
}
fork産生子程序,父程序傳回子程序pid,不為0,是以輸出父程序的那句話,子程序傳回0,是以會輸出子程序那句話。
forkdemo4
代碼:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
int fork_rv;
printf("Before: my pid is %d\n", getpid());
fork_rv = fork(); /* create new process */
if ( fork_rv == -1 ) /* check for error */
perror("fork");
else if ( fork_rv == 0 ){
printf("I am the child. my pid=%d\n", getpid());
printf("parent pid= %d, my pid=%d\n", getppid(), getpid());
exit(0);
}
else{
printf("I am the parent. my child is %d\n", fork_rv);
sleep(10);
exit(0);
}
return 0;
}
先列印程序pid,然後fork建立子程序,父程序傳回子程序pid,是以輸出parent一句,休眠十秒;子程序傳回0,是以輸出child與之後一句。
forkgdb
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int gi=0;
int main()
{
int li=0;
static int si=0;
int i=0;
pid_t pid = fork();
if(pid == -1){
exit(-1);
}
else if(pid == 0){
for(i=0; i<5; i++){
printf("child li:%d\n", li++);
sleep(1);
printf("child gi:%d\n", gi++);
printf("child si:%d\n", si++);
}
exit(0);
}
else{
for(i=0; i<5; i++){
printf("parent li:%d\n", li++);
printf("parent gi:%d\n", gi++);
sleep(1);
printf("parent si:%d\n", si++);
}
exit(0);
}
return 0;
}
父程序列印是先列印兩句,然後休眠一秒,然後列印一句,子程序先列印一句,然後休眠一秒,然後列印兩句。并且這兩個線程是并發的,是以可以看到在一個線程休眠的那一秒,另一個線程在執行,并且線程之間互相獨立互不幹擾。
psh1
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define MAXARGS 20
#define ARGLEN 100
int execute( char *arglist[] )
{
execvp(arglist[0], arglist);
perror("execvp failed");
exit(1);
}
char * makestring( char *buf )
{
char *cp;
buf[strlen(buf)-1] = '\0';
cp = malloc( strlen(buf)+1 );
if ( cp == NULL ){
fprintf(stderr,"no memory\n");
exit(1);
}
strcpy(cp, buf);
return cp;
}
int main()
{
char *arglist[MAXARGS+1];
int numargs;
char argbuf[ARGLEN];
numargs = 0;
while ( numargs < MAXARGS )
{
printf("Arg[%d]? ", numargs);
if ( fgets(argbuf, ARGLEN, stdin) && *argbuf != '\n' )
arglist[numargs++] = makestring(argbuf);
else
{
if ( numargs > 0 ){
arglist[numargs]=NULL;
execute( arglist );
numargs = 0;
}
}
}
return 0;
}
這個代碼就相當于你輸入要執行的指令,回車表示輸入結束,然後輸入的每個參數對應到函數中,再調用對應的指令。
psh2
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <signal.h>
#define MAXARGS 20
#define ARGLEN 100
char *makestring( char *buf )
{
char *cp;
buf[strlen(buf)-1] = '\0';
cp = malloc( strlen(buf)+1 );
if ( cp == NULL ){
fprintf(stderr,"no memory\n");
exit(1);
}
strcpy(cp, buf);
return cp;
}
void execute( char *arglist[] )
{
int pid,exitstatus;
pid = fork();
switch( pid ){
case -1:
perror("fork failed");
exit(1);
case 0:
execvp(arglist[0], arglist);
perror("execvp failed");
exit(1);
default:
while( wait(&exitstatus) != pid )
;
printf("child exited with status %d,%d\n",
exitstatus>>8, exitstatus&0377);
}
}
int main()
{
char *arglist[MAXARGS+1];
int numargs;
char argbuf[ARGLEN];
numargs = 0;
while ( numargs < MAXARGS )
{
printf("Arg[%d]? ", numargs);
if ( fgets(argbuf, ARGLEN, stdin) && *argbuf != '\n' )
arglist[numargs++] = makestring(argbuf);
else
{
if ( numargs > 0 ){
arglist[numargs]=NULL;
execute( arglist );
numargs = 0;
}
}
}
return 0;
}
比起1來,多了循環判斷,不退出的話就會一直要你輸入指令,并且對于子程式存在的狀态條件。
testbuf1
#include <stdio.h>
#include <stdlib.h>
int main()
{
printf("hello");
fflush(stdout);
while(1);
}
效果是先輸出hello,然後換行。之後不退出。
testbuf2
#include <stdio.h>
int main()
{
printf("hello\n");
while(1);
}
程式輸出hello,無法退出。
PS:可知:fflush(stdout)的效果和換行符\n是一樣的。
testbuf3
#include <stdio.h>
int main()
{
fprintf(stdout, "1234", 5);
fprintf(stderr, "abcd", 4);
}
将内容格式化輸出到标準錯誤、輸出流中。結果如圖:
testpid
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main()
{
printf("my pid: %d \n", getpid());
printf("my parent's pid: %d \n", getppid());
return 0;
}
輸出目前程序pid和目前程序的父程序的pid。
testpp
#include <stdio.h>
#include <stdlib.h>
int main()
{
char **pp;
pp[0] = malloc(20);
return 0;
}
testsystem
#include <stdlib.h>
int main ( int argc, char *argv[] )
{
system(argv[1]);
system(argv[2]);
return EXIT_SUCCESS;
} /* --------
system()——執行shell指令,也就是向dos發送一條指令。這裡是後面可以跟兩個參數,然後向dos發送這兩個指令,分别執行。如下圖,輸入ls和dir兩個指令後,可以看到分别執行了。
waitdemo1
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#define DELAY 4
void child_code(int delay)
{
printf("child %d here. will sleep for %d seconds\n", getpid(), delay);
sleep(delay);
printf("child done. about to exit\n");
exit(17);
}
void parent_code(int childpid)
{
int wait_rv=0; /* return value from wait() */
wait_rv = wait(NULL);
printf("done waiting for %d. Wait returned: %d\n",
childpid, wait_rv);
}
int main()
{
int newpid;
printf("before: mypid is %d\n", getpid());
if ( (newpid = fork()) == -1 )
perror("fork");
else if ( newpid == 0 )
child_code(DELAY);
else
parent_code(newpid);
return 0;
}
如果有子程序,則終止子程序,成功傳回子程序pid。
waitdemo2
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#define DELAY 10
void child_code(int delay)
{
printf("child %d here. will sleep for %d seconds\n", getpid(), delay);
sleep(delay);
printf("child done. about to exit\n");
exit(27);
}
void parent_code(int childpid)
{
int wait_rv;
int child_status;
int high_8, low_7, bit_7;
wait_rv = wait(&child_status);
printf("done waiting for %d. Wait returned: %d\n", childpid, wait_rv);
high_8 = child_status >> 8; /* 1111 1111 0000 0000 */
low_7 = child_status & 0x7F; /* 0000 0000 0111 1111 */
bit_7 = child_status & 0x80; /* 0000 0000 1000 0000 */
printf("status: exit=%d, sig=%d, core=%d\n", high_8, low_7, bit_7);
}
int main()
{
int newpid;
printf("before: mypid is %d\n", getpid());
if ( (newpid = fork()) == -1 )
perror("fork");
else if ( newpid == 0 )
child_code(DELAY);
else
parent_code(newpid);
}
這個比起1來就是多了一個子程序的狀态區分,把狀态拆分成三塊,exit,sig和core。
總結
- 對于exec函數:
在Linux中要使用exec函數族。系統調用execve()對目前程序進行替換,替換者為一個指定的程式,其參數包括檔案名(filename)、參數清單(argv)以及環境變量(envp)。exec函數族當然不止一個,但它們大緻相同,在 Linux中,它們分别是:execl,execlp,execle,execv,execve和execvp。一個程序一旦調用exec類函數,它本身就"死亡"了,系統把代碼段替換成新的程式的代碼,廢棄原有的資料段和堆棧段,并為新程式配置設定新的資料段與堆棧段,唯一留下的,就是程序号,也就是說,對系統而言,還是同一個程序,不過已經是另一個程式了。若是想啟動另一程式的執行但自己仍想繼續運作的話,那就得結合fork與exec的使用。
- 對于fork函數:
fork函數啟動一個新的程序,前面我們說過,這個程序幾乎是目前程序的一個拷貝:子程序和父程序使用相同的代碼段;子程序複制父程序的堆棧段和資料段。這樣,父程序的所有資料都可以留給子程序,但是,子程序一旦開始運作,雖然它繼承了父程序的一切資料,但實際上資料卻已經分開,互相之間不再有影響了,也就是說,它們之間不再共享任何資料了。
問題:
- 運作testpp時,出現段錯誤(核心已轉儲),不知道原因。
- 了解代碼時,對于管道實作父子程序之間通信不了解。
其基本原理是這樣的:假如原先在父程序中檔案描述符3和4通過管道1連接配接起來(3是讀端,4是寫端),則fork建立子程序後,子程序中的檔案描述符3和4也通過管道1連接配接起來(3是讀端,4是寫端)。這樣一來,在父程序通過檔案描述符4向管道寫入内容後,在子程序中就可以通過檔案描述符3從管道中讀出資料(當然在父程序中也可以通過檔案描述符3從管道中讀出資料)。
參考資料
- 《程序間通信-命名管道FIFO》(http://blog.csdn.net/xiajun07061225/article/details/8471777)
-
《linux i/o重定向與管道程式設計》
(http://blog.csdn.net/fulianzhou/article/details/48895327)
- 教材:第八章,詳細學習指導:http://group.cnblogs.com/topic/73069.html
- 闫佳歆同學的部落格:http://www.cnblogs.com/20135202yjx/p/5003653.html