<menu id="w8yyk"><menu id="w8yyk"></menu></menu>
  • <dd id="w8yyk"><nav id="w8yyk"></nav></dd>
    <menu id="w8yyk"></menu>
    <menu id="w8yyk"><code id="w8yyk"></code></menu>
    <menu id="w8yyk"></menu>
    <xmp id="w8yyk">
    <xmp id="w8yyk"><nav id="w8yyk"></nav>
  • 網站首頁 > 物聯資訊 > 技術分享

    linux 進程(二) --- 進程的創建及相關api

    2016-09-28 00:00:00 廣州睿豐德信息科技有限公司 閱讀
    睿豐德科技 專注RFID識別技術和條碼識別技術與管理軟件的集成項目。質量追溯系統、MES系統、金蝶與條碼系統對接、用友與條碼系統對接

    一、進程的創建fork()函數

    RFID設備管理軟件

     由fork創建的新進程被稱為子進程(child process)。該函數被調用一次,但返回兩次。兩次返回的區別是子進程的返回值是0,而父進程的返回值則是 新子進程的進程ID。將子進程ID返回給父進程的理由是:因為一個進程的子進程可以多于一個,所有沒有一個函數使一個進程可以獲得其所有子進程的進程ID。fork使子進程得到返回值0的理由是:一個進程只會有一個父進程,所以子進程總是可以調用getppid以獲得其父進程的進程ID(進程 ID  0總是由交換進程使用,所以一個子進程的進程ID不可能為0)。       子進程和父進程繼續執行fork之后的指令。子進程是父進程的復制品。例如,子進程獲得父進程數據空間、堆和棧的復制品。注意,這是子進程擁有的拷貝。父、子進程并共享這些存儲部分。如果正文段是只讀的,則父、子進程共享正文段。          現在很多的實現并不做一個父進程數據段和堆的完全拷貝,因為在fork之后經常跟隨著exec。作為替代,使用了寫時復制(copy-on-write,cow)的技術。這些區域由父、子進程共享,而且內核將他們的存取許可權改變位只讀的。如果有進程試圖修改這些區域,則內核包異常,典型的是虛存系統中的“頁”,做一個拷貝。   實例1:   #include <stdio.h> #include <stdlib.h> #include <unistd.h>   int glob = 6; char buf[] = "a write to stdout\n";   int main() { int var; int pid;   var = 88;   if(write(STDOUT_FILENO,buf,sizeof(buf) -1) != sizeof(buf) -1) { perror("fail to write"); return -1; }   printf("before fork\n");   if((pid = fork()) < 0) { perror("fail to fork"); return -1; }else  if(pid == 0) { glob ++; var ++; }else{ sleep(2); }   printf("pid = %d,glob = %d,var = %d\n",getpid(),glob,var); exit(0); }   運行結果:

    RFID設備管理軟件


    從上面可以看出,因為子進程和父進程擁有獨立的物理內存空間,所以當子進程對拷貝來的數據做修改的時候,并沒有影響到父進程。   注意:         1.一般來說,fork之后父進程先執行還是子進程先執行是不確定的。這取決于內核所使用的調度算法。                  2.從上面可以看到兩次的運行結果不一樣。我們知道write函數是不帶緩存的。因為在fork之前調用write,所以其數據寫到標準輸出一次。但是,標準 I/O庫是帶緩存的。如果標準輸出連到終端設備,則它是行緩存的,否則它是全緩存的。當以交互方式運行該程序時,只得到printf輸出的行一次,其原因是標準輸出緩存由新行符刷新。但是當將標準輸出重新定向到一個文件時,卻得到printf輸出行兩次。其原因是,在fork之前調用了printf一次,當調用fork時,該行數據仍在緩存中,然后在父進程數據空間復制到子進程中時,該緩存數據也被復制到子進程中。于是那時父、子進程各自有了帶該行內容的緩存。在exit之前的第二個printf將其數據添加到現存的緩存中。當每個進程終止時,其緩存中的內容被寫到相應文件中。     實例 2:   #include <stdio.h> #include <stdlib.h> #include <unistd.h>   int glob = 6;   int main() { int var; int pid;   var = 88;   printf("father:\n"); printf("&glob = %p\n",&glob); printf("&var = %p\n",&var); printf("__________________________________\n");   if((pid = fork()) < 0) { perror("fail to fork"); return -1;   }else  if(pid == 0) { printf("child var value not change\n:"); printf("&glob = %p\n",&glob); printf("&var = %p\n",&var);   glob ++; var ++;   printf("__________________________________\n"); printf("child var value change:\n"); printf("&glob = %p\n",&glob); printf("&var = %p\n",&var); }   exit(0); }   運行結果如下:

    RFID設備管理軟件

       從上面可以看出,根據copy-on-write的思想,在子進程中,改變父進程的數據時,會先 復制父進程的數據修然后再改,從而達到子進程對數據的修改不影響父進程。但是我們發現,復制的前后,其值的地址都是一樣的。為什么呢?子進程拷貝的時候也拷貝了父進程的虛擬內存"頁",這樣他們的虛擬地址都一樣,但是對應不同的物理內存空間。   二、copy-on-write工作原理       假設進程A創建子進程B,之后進程A和進程B共享A的地址空間,同時該地址空間中的頁面全部被標識為寫保護。此時B若寫address的頁面,由于寫保護的原因會引起寫異常,在異常處理中,內核將address所在的那個寫保護頁面復制為新的頁面,讓B的address頁表項指向該新的頁面,新頁面可寫。而A的address頁表項依然指向那個寫保護的頁面。然后當B在訪問address時就會直接訪問新的頁面了,不會在訪問到哪個寫保護的頁面。當A試圖寫address所在的頁面時,由于寫保護的原因此時也會引起異常,在異常處理中,內核如果發現該頁面只有一個擁有進程,此種情況下也就是A,則直接對該頁面取消寫保護,此后當A再訪問address時不會在有寫保護錯誤了。如果此時A又創建子進程C,則該address所在的頁面又被設置為寫保護,擁有進程A和C,同時其他頁面例如PAGEX依然維持寫保護,只是擁有進程A、B和C。如果此時A訪問PAGEX,則異常處理會創建一個新頁面并將PAGEX中的內容復制到該頁面,同時A相應 的pte指向該新頁面。如果此時C也訪問PAGEX,也會復制新頁面并且讓C對應的pte指向新頁面。如果B再訪問PAGEX,則由于此時PAGEX只有一個擁有進程B,故不再復制新頁面,而是直接取消該頁面的寫保護,由于B的pte本來就是直接指向該頁面,所以無需要在做其它工作。   三、exit和_exit   (1)正常終止:     (a)在main函數內執行return語句。這等效于調用exit。     (b)調用exit函數     (c)調用_exit系統調用函數   (2)異常終止:     (a)調用abort。它產生SIGABRT信號,所以是一種異常終止的一種特列。     (b)當進程接收到某個信號時。例如,進程越出其地址空間訪問存儲單元,或者除以0,內核就會為該進程產生相應的信號。   注意:不管進程如何終止,最后都會執行內核中的同一段代碼。這段代碼為相應進程關閉所有打開描述符,釋放它所使用的存儲器等。     exit和_exit的不同

    RFID設備管理軟件

    _exit()函數的作用最為簡單:直接進程停止運行,清除其使用的內存空間,并銷毀其在內核中的各種數據結構;   exit()函數與_exit()函數最大的區別就在于exit()函數在調用exit系統調用之前要檢查文件的打開情況,把文件緩沖區中的內容寫回文件,就是"清理I/O"緩沖。   探究 1._exit()   //_exit(0)   exit(0)  return 0

    RFID設備管理軟件

    編譯運行結果:

    RFID設備管理軟件

    從上面我們看到,test.txt的內容為空.為什么呢?因為標準I/O函數是帶緩存的,進行fputs的時候是先向緩存中寫的,只有當緩存滿的時候才會刷新的緩沖區的。從以上我們發現,當進程退出時,執行_exit()函數并沒有刷新緩沖區的數據,而是直接終止進程的。   探究2.exit()

    RFID設備管理軟件

    編譯運行結果:
    RFID設備管理軟件

    從上面我們可以看到,當exit()函數結束進程的時候,對緩存進行了處理,把緩存的數據寫到了磁盤文件中。   探究3.return   由讀者自己完成,其實return語句用在main函數中,和exit是一樣的。但是我們知道,return返回的值是給調用者的,它代表著一個函數的結束。   四、exec函數族   exec.c  調用exec其中的一個函數; gcc exec.c -o exec; ./exec exec函數族提供了一種在進程中啟動另一個程序執行的方法。它可以根據指定的文件名或目錄名找到可執行文件,并用它來取代原調用進程的數據段、代碼段、和堆棧段。在執行完之后,原調用進程的內容除了進程號外,其他全部都被替換了。   可執行文件既可以是二進制文件,也可以是任何Linux下可執行的腳本文件。   何時使用?   當進程認為自己不能再為系統和用戶做任何貢獻了就可以調用exec函數族中的函數,讓自己執行新的程序。 當前目錄: 可執行程序A    B(1,2,3)      如果某個進程想同時執行另一個程序,它就可以調用fork函數創建子進程,然后在子進程中調用任何一個exec函數。這樣看起來就好像通過執行應用程序而產生了一個新進程一樣。   execl("./B","B","1","2","3",NULL); char *const envp[] = {"B","1","2","3",NULL}   execv("./B",envp);

    RFID設備管理軟件

    RFID設備管理軟件
    RFID設備管理軟件

    注意:不管file,第一個參數必須是可執行文件的名字   可執行文件查找方式 表中的前四個函數的查找方式都是指定完整的文件目錄路勁,而最后兩個函數(以p結尾的函數)可以只給出文件名,系統會自動從環境變量"$PATH"所包含的路徑中進行查找。   參數表傳遞方式 兩種方式:一個一個列舉和將所有參數通過指針數組傳遞 一函數名的第5個字母按來區分,字母"l"(list)的表示一個一個列舉方式;字母"v"(vector)的表示將所有參數構造成指針數組傳遞,其語法為char *const argv[]   環境變量的使用 exec函數族可以默認使用系統的環境變量,也可以傳入指定的環境變量。這里,以"e"(Envirment)結尾的兩個函數execle、execve就可以在envp[]中傳遞當前進程所使用的環境變量。   使用的區別 可執行文件查找方式 參數表傳遞方式 環境變量的使用

    RFID設備管理軟件

    案例一execl   #include <stdio.h> #include <unistd.h>   int main(int argc,char *argv[]) { printf("start to execl.\n"); if(execl("/bin/ls","ls",NULL) < 0) { perror("Fail to execl"); return -1; } printf("end of execl.\n");   return 0; }   運行結果如下:

    RFID設備管理軟件


    案例二、execlp #include <stdio.h> #include <unistd.h>   int main(int argc,char *argv[]) { printf("start to execl.\n"); if(execlp("ls","ls","-l",NULL) < 0) { perror("Fail to execl"); return -1; } printf("end of execl.\n");   return 0; }   運行結果: RFID設備管理軟件

    案例三、execle   #include <stdio.h> #include <stdlib.h>   int main(int argc,char *argv[]) { if(getenv("B") == NULL) { printf("fail to getenv B.\n"); }else{ printf("env B = %s.\n",getenv("B")); }   if(getenv("C") == NULL) { printf("fail to getenv C.\n"); }else{ printf("env C = %s.\n",getenv("C")); }   if(getenv("PATH") == NULL) { printf("fail to getenv PATH.\n");   }else{ printf("env PATH = %s.\n",getenv("PATH")); } return 0; }   運行結果:

    RFID設備管理軟件

    #include <unistd.h>   int main(int argc,char *argv[]) { printf("start to execle.\n"); char * const envp[] = {"B=hello",NULL};   if(execle("./A.out","A.out",NULL,envp) < 0) { perror("Fail to execl"); return -1; }   printf("end of execl.\n");   return 0; }   運行結果:

    RFID設備管理軟件

    案例四:execv   #include <stdio.h> #include <unistd.h> #include <errno.h> #include <stdlib.h>   int main() { char * const arg[] = {"ps", "-ef", NULL}; //if (execl("/bin/ps", "ps", "-ef", NULL) < 0) if (execv("/bin/ps" ,arg) < 0) { perror("execl"); exit(-1); }   while (1);   return 0; }     五、進程的創建vfork()函數              vfork與fork一樣都創建一個子進程,但是它并不將父進程的地址空完全復制到子進程中,因為子進程會立即調用exec(或exit)于是也就不會存、訪該地址空間。不過在子進程調用exec或exit之前,它在父進程的空間中運行。          vfork和fork之間的另一個區別是:vfork保證子進程先運行,在它調用exec或exit之后 父進程才可能被調度運行。(如果在調用這兩個函數之前子進程依賴于父進程的進一步動作,則會導致死鎖)   探究1.vfork()

    RFID設備管理軟件

    編譯運行:
    RFID設備管理軟件

    因為我們知道vfork保證子進程先運行,子進程運行結束后,父進程才開始運行。所以,第一次打印的是子進程的打印的信息,可以看到var值變成了89。子進程結束后,父進程運行,父進程首先打印fork調用返回給他pid的值(就是子進程pid)。以上我們可以看出,vfork創建的子進程和父進程運行的地址空間相同(子進程改變了var 值,父進程中的var值也進行了改變)。   注意:如果子進程中執行的是exec函數,那就是典型的fork的copy-on-wirte。     五、wait和waitpid   wait函數:調用該函數使進程阻塞,直到任一個子進程結束或者是該進程接收到一個信號為止。如果該進程沒有子進程或者其子進程已經結束,wait函數會立即返回。   waitpid函數:功能和wait函數類似。可以指定等待某個子進程結束以及等待的方式(阻塞或非阻塞)。 wait函數 #include <sys/types.h> #include <sys/waith.h>   pid_t  wait(int  *status);   函數參數:   status是一個整型指針,指向的對象用來保存子進程退出時的狀態。   A.status若為空,表示忽略子進程退出時的狀態   B.status若不為空,表示保存子進程退出時的狀態   子進程的結束狀態可由Linux中一些特定的宏來測定。     案例一、   #include <stdio.h> #include <stdlib.h>   int main() { int pid;   if((pid = fork()) < 0) { perror("Fail  to fork"); return -1; }else if(pid == 0){ printf("child exit now.\n"); exit(0); }else{ while(1); }   exit(0); }   運行結果:

    RFID設備管理軟件

    從以上可以看出,子進程正常退出時,處于僵尸態。這個時候子進程的pid,以及內核棧資源并沒有釋放,這樣是不合理的,我們應該避免僵尸進程。如果父進程先退出呢,子進程又會怎樣?   #include <stdio.h> #include <stdlib.h>   int main() { int pid;   if((pid = fork()) < 0) { perror("Fail  to fork"); return -1; }else if(pid == 0){ printf("child running now - pid : %d.\n",getpid()); while(1); }else{ getchar(); printf("Father exit now - pid : %d.\n",getpid()); exit(0); }   }

    RFID設備管理軟件
    從上面可以看出,如果父進程先退出,則子進程的父進程的ID號變為1,也就是說當一個子進程的父進程退出時,這個子進程會被init進程自動收養。   案例二、利用wait等待回收處于僵尸態的子進程   #include <stdio.h> #include <stdlib.h>   int main() { int pid;   if((pid = fork()) < 0) { perror("Fail  to fork"); return -1; }else if(pid == 0){ printf("child runing now - pid : %d.\n",getpid()); getchar(); printf("child exiting now - pid : %d.\n",getpid()); exit(0); }else{ printf("Father wait zombie now - pid : %d.\n",getpid()); wait(NULL); printf("Father exiting now - pid : %d.\n",getpid()); exit(0); }   }   沒有輸入字符前: RFID設備管理軟件

    輸入字符后:

    RFID設備管理軟件

    此時我們沒有發現僵尸進程,當子進程退出時,父進程的wait回收了子進程未釋放的資源。   案例三、獲取進程退出時的狀態   #include <stdio.h> #include <stdlib.h>   int main() { int pid; int status;   if((pid = fork()) < 0) { perror("Fail to fork"); exit(-1); }else if(pid == 0){ printf("create child process : %d.\n",getpid()); printf("child process : %d calling exit(7).\n",getpid()); exit(7); }else{   if((pid = fork()) < 0 ){ perror("Fail to fork"); exit(-1); }else if(pid == 0){ printf("create child process : %d.\n",getpid()); while(1); }else{   while((pid = wait(&status)) != -1) { if(WIFEXITED(status)) { printf("child process %d is normal exit,the value is %d.\n",pid,WEXITSTATUS(status));   }else if(WIFSIGNALED(status)){ printf("child process %d is exit by signal,the signal num is %d.\n",pid,WTERMSIG(status)); }else{ printf("Not know.\n"); } } } }   printf("All child process is exit,father is exit.\n"); exit(0); }
    RFID設備管理軟件

    給進程15494發個信號

    RFID設備管理軟件

    程序運行結果:

    RFID設備管理軟件

    從以上探究可以知道,每當子進程結束后,wait函數就會返回哪個子進程結束的pid。如果沒有子進程存在,wait函數就返回-1。   函數返回值: 成功:子進程的進程號 失敗:-1 #include <sys/types.h> #include <sys/wait.h>   pid_t      waitpid(pid_t  pid,int *status,int options);   參數:   1.在父進程中創建兩個子進程(A   B) 2.A進程打印"child process %d exit",調用exit(2),結束 3.B進程一直運行   注意:父進程調用while(waitpid(-1,&status,WUNTRACED)   != -1 )

    RFID設備管理軟件

    status:同wait   options:   WNOHANG,若由pid指定的子進程并不立即可用,則waitpid不阻塞,此時返回值為0 WUNTRACED,若某實現支持作業控制,則由pid指定的任一子進程狀態已暫停,且其狀態自暫停以來還沒報告過,則返回其狀態。   0:同wait,阻塞父進程,等待子進程退出。   返回值 正常:結束的子進程的進程號 使用選項WNOHANG且沒有子進程結束時:0 調用出錯:-1   案例一、   #include <stdio.h> #include <stdlib.h>   int main() { int pid; int status;   if((pid = fork()) < 0) { perror("Fail to fork"); exit(-1); }else if(pid == 0){ printf("create child process : %d.\n",getpid()); printf("child process : %d calling exit(7).\n",getpid()); exit(7); }else{   if((pid = fork()) < 0 ){ perror("Fail to fork"); exit(-1); }else if(pid == 0){ printf("create child process : %d.\n",getpid()); while(1); }else{   while((pid = wait(&status)) != -1) { if(WIFEXITED(status)) { printf("child process %d is normal exit,the value is %d.\n",pid,WEXITSTATUS(status));   }else if(WIFSIGNALED(status)){ printf("child process %d is exit by signal,the signal num is %d.\n",pid,WTERMSIG(status)); }else{ printf("Not know.\n"); } } } }   printf("All child process is exit,father is exit.\n"); exit(0); }     程序運行結果:

    RFID設備管理軟件

    使用ps -aux結果

    RFID設備管理軟件 從以上可以看出,子進程15783退出時,父進程并沒有回收它的資源,此時可以看到它處于僵尸態。 由于父進程調用waitpid等待子進程15784退出,此時這個進程還沒退出,看可以看到父進程處于可中斷的睡眠狀態。   我們給子進程15784發個信號,在看看結果

    RFID設備管理軟件
    程序運行結果:

    RFID設備管理軟件

    可以看到當waitpid指定等待的進程退出時,waitpid立即返回,此時父進程退出。   案例二、   #include <stdio.h> #include <stdlib.h>   int main() { int pid; int status;   if((pid = fork()) < 0) { perror("Fail to fork"); exit(-1); }else if(pid == 0){ printf("create child process : %d.\n",getpid()); printf("child process : %d calling exit(7).\n",getpid()); exit(7); }else{   if((pid = fork()) < 0 ){ perror("Fail to fork"); exit(-1); }else if(pid == 0){ printf("create child process : %d.\n",getpid()); while(1); }else{ sleep(2); printf("Father wait child %d exit.\n",pid); while((pid = waitpid(pid,NULL,WNOHANG))) { printf("The process %d is exit.\n",pid); }   printf("The process %d is exit.\n",pid); } }   exit(0); }
    RFID設備管理軟件

    從上面探究我們可以看出,如果有子進程處于僵尸態,waitpid(pid,NULL,WNOHANG)立即處理后返回,如果沒有子進程處于僵尸態,此時waitpid(pid,NULL,WNOHANG)也會立即返回,而不阻塞,此時返回值為0。   案例探究三、   #include <stdio.h> #include <stdlib.h> #include <string.h>   int main(int argc,char *argv[]) { int pid; int status;   if((pid = fork()) < 0) { perror("Fail to fork"); exit(-1); }else if(pid == 0){ printf("create child process : %d.\n",getpid()); printf("child process in proces group %d.\n",getpgid(0)); printf("child process : %d calling exit(7).\n",getpid()); exit(7); }else{   if((pid = fork()) < 0 ){ perror("Fail to fork"); exit(-1); }else if(pid == 0){ sleep(3); printf("create child process : %d.\n",getpid()); setpgid(0,0); //讓子進程屬于以自己ID作為組的進程組 printf("child process in proces group %d.\n",getpgid(0)); printf("child process : %d calling exit(6).\n",getpid()); }else{ while(pid = waitpid(0,NULL,0)) { printf("Father wait the process %d is exit.\n",pid); } } }   exit(0); }   運行結果:

    RFID設備管理軟件

    當在父進中創建子進程時,父進程和子進程都在以父進程ID號為組的進程組。以上代碼中,有一個子進程改變了自己所在的進程組. 此時waitpid(0,NULL,0);只能處理以父進程ID為組的進程組中的進程,可以看到第一個子進程結束后waitpid函數回收了它未釋放的資源,而第二個子進程則處于僵尸態

    RFID設備管理軟件     from:http://blog.chinaunix.net/uid-26833883-id-3222794.htmlRFID管理系統集成商 RFID中間件 條碼系統中間層 物聯網軟件集成
    最近免费观看高清韩国日本大全