進程的創建(二)

一 fork函數

#include <sys/types.h>
#include <unistd.h>

pid_t fork(void);

  返回值:成功:父進程:返回子進程的PID,子進程:返回0;失敗:返回-1。
  fork()函數通過系統調用創建一個與原來進程(父進程)幾乎完全相同的進程(子進程是父進程的副本,它將獲得父進程數據空間、堆、棧等資源的副本。注意,子進程持有的是上述存儲空間的“副本”,這意味着父子進程間不共享這些存儲空間。linux將複製父進程的地址空間內容給子進程,因此,子進程有了獨立的地址空間。),也就是這兩個進程做完全相同的事。

二 exec系列函數

1 函數接口

  fork後的子進程中使用exec函數族,可以裝入和運行其它程序(子進程替換原有進程,和父進程做不同的事)。
  fork創建一個新的進程就產生了一個新的PID,exec啓動一個新程序,替換原有的進程,因此這個新的被 exec 執行的進程的PID不會改變(和調用exec的進程的PID一樣)。

#include <unistd.h>

extern char **environ;

int execl(const char *path,const char *arg, ...);
int execlp(const char *file,const char *arg, ...);
int execle(const char *path,const char *arg,...,char * const envp[]);
int execv(const char *path,char *const argv[]);
int execvp(const char *file,char *const argv[]);
//execve是系統調用
int execve(const char *file,char *const argv[],char *const envp[]);

  參數:path參數表示你要啓動程序的名稱包括路徑名,arg參數表示啓動程序所帶的參數,一般第一個參數爲要執行命令名,不是帶路徑且arg必須以NULL結束。
  返回值:成功返回0,失敗返回-1。
  exec系列函數底層都是通過execve系統調用實現:

#include <unistd.h>
int execve(const char *filename, char *const argv[],char *const envp[]);
/*
DESCRIPTION:
       execve() executes the program pointed to by filename.  filename must be
       either a binary executable, or a script starting with  a  line  of  the form
*/

2 帶“p”的函數

  對於帶字母p的函數:如果參數中包含/,則將其視爲路徑名;否則視爲不帶路徑的程序名,在PATH環境變量的目錄列表中搜索這個程序。

3 不帶“p”的函數

  不帶字母p(表示path)的exec函數第一個參數必須是程序的相對路徑或絕對路徑,例如"/bin/ls"或"./a.out",而不能是"ls"或"a.out"。

4 帶“v”的函數

  對於帶有字母v(表示vector)的函數,則應該先構造一個指向各參數的指針數組,然後將該數組的首地址當作參數傳給它,數組中的最後一個指針也應該是NULL,就像main函數的argv參數或者環境變量表一樣。

5 帶“e”的函數

  e(表示environment)結尾的exec函數,可以把一份新的環境變量表傳給它,其他exec函數仍使用當前的環境變量表執行新程序。

6 帶“l”的函數

  l的exec函數:execl,execlp,execle,帶有字母l(表示list)的exec函數要求將新程序的每個命令行參數都當作一個參數傳給它,命令行參數的個數是可變的,因此函數原型中有…,…中的最後一個可變參數應該是NULL,起sentinel的作用。

#include <iostream>
#include <unistd.h>
#include <cstdlib>
#include <sys/types.h>
#include <sys/wait.h>
using namespace std;
 
int main(int argc, char *argv[])
{
    pid_t pid;
    char *arg[] = {"ls", "-l", NULL}; 
 
    cout << "This is main process, PID is " << getpid() << endl;
    pid = fork();
    if (pid < 0)
    {
        cout << "fork error..." << endl;
        exit(-1);
    }
    else if (pid == 0)
    {//This is the child process
       cout << "This is child process, PID is " << getpid() << endl;
       //execl("/bin/ls", "ls", "-l", NULL); 
       //execlp("ls", "ls", "-l", NULL);
       //execle("/bin/ls", "ls", "-l", NULL, NULL);
       //execv("/bin/ls", arg);
       //execvp("ls", arg);
       execve("/bin/ls", arg, NULL);//上面的六個函數的運行結果都是一樣的
       exit(11);//將子進程的退出碼設置爲11
    }
    else
    {//This is the main process
        cout << "This is main process waiting for the exit of child process." << endl;
        int child_status;
        pid = wait(&child_status);
        cout << "This is main process. The child status is " << child_status << ", and child pid is " << pid << ", WIFEXITED(child_status) is " << WIFEXITED(child_status) << ", WEXITSTATUS(child_status) is " << WEXITSTATUS(child_status) << endl;
    }
 
    exit(0);
}

三 wait函數

1 wait

  wait函數是在父進程中使用,用來獲取子進程的狀態。

#include <sys/types.h>
#include <sys/wait.h>
 
pid_t wait(int *status);
pid_t waitpid(pid_t pid, int *status, int options);
int waitid(idtype_t idtype, id_t id, siginfo_t *infop, int options);

  wait系統調用會使父進程暫停執行,直到它的等待的子進程結束爲止。也就是說wait是阻塞的。
  wait可以返回兩個信息,直接返回子進程的PID,還有status(注意這個值不是在子進程中調用exit函數中的退出碼,下面有專門的宏使用該status),子進程的退出狀態信息存放在wait的參數status指向的內存中。

  • WIFEXITED(status)//如果子進程正常退出,那麼返回1;見實例輸出結果;
  • WEXITSTATUS(status)//返回子進程的退出碼;如果退出碼的值很大,那麼它只會返回退出碼的低八位。
WIFEXITED(status)
              returns true if the child terminated normally, that is, by calling exit(3) or _exit(2), or by returning from main().
 WEXITSTATUS(status)
        returns  the  exit status of the child.  This consists of the least significant 8 bits of the status argument that the child specified in a
        call to exit(3) or _exit(2) or as the argument for a return statement in main().  This macro should be employed only if WIFEXITED  returned
        true.
 WIFSIGNALED(status)
        returns true if the child process was terminated by a signal.
 WTERMSIG(status)
        returns  the  number  of the signal that caused the child process to terminate.  This macro should be employed only if WIFSIGNALED returned
        true.
 WCOREDUMP(status)
        returns true if the child produced a core dump.  This macro should be employed only if WIFSIGNALED returned true.  This macro is not speci‐
        fied  in POSIX.1-2001 and is not available on some UNIX implementations (e.g., AIX, SunOS).  Only use this enclosed in #ifdef WCOREDUMP ...
        #endif.
 WIFSTOPPED(status)
        returns true if the child process was stopped by delivery of a signal; this is possible only if the call was done using WUNTRACED  or  when
        the child is being traced (see ptrace(2)).
 WSTOPSIG(status)
        returns the number of the signal which caused the child to stop.  This macro should be employed only if WIFSTOPPED returned true.
 WIFCONTINUED(status)
        (since Linux 2.6.10) returns true if the child process was resumed by delivery of SIGCONT.
#include <iostream>
#include <unistd.h>
#include <cstdlib>
#include <sys/types.h>
#include <sys/wait.h>
using namespace std;

int main(int argc, char *argv[])
{
    pid_t pid;
 
    cout << "This is main process, PID is " << getpid() << endl;
    
    pid = fork();
    if (pid < 0){
        cout << "fork error..." << endl;
        exit(-1);
    }
    else if (pid == 0){//This is the child process
       cout << "This is child process, PID is " << getpid() << endl;
       sleep(3);//子進程休眠3秒,這樣就可以看到wait函數阻塞了父進程,因爲三秒之後,wait語句下面的語句纔開始執行
       exit(11);//將子進程的退出碼設置爲11
    }
    else{//This is the main process
        cout << "This is main process waiting for the exit of child process." << endl;
        int child_status;
        pid = wait(&child_status);
        cout << "This is main process. The child status is " << child_status << ", and child pid is " << pid << ", WIFEXITED(child_status) is " << WIFEXITED(child_status) << ", WEXITSTATUS(child_status) is " << WEXITSTATUS(child_status) << endl;
    }
    exit(0);
}

2 waitpid

  waitpid用來等待某個特定進程的結束,可以指定等待子進程的PID。
  參數options:允許改變waitpid的行爲,最有用的一個選項是WNOHANG,它的作用是防止waitpid把調用者掛起,也就是說父進程不會暫停執行,waitpid此時是非阻塞的;如果pid指定的目標子進程還沒有結束或意外終止,則waitpid立即返回0;如果目標子進程確實正常退出了,在返回該子進程的pid。waitpid調用失敗時返回-1並設置errno。如果waitpid函數中的pid爲-1,那麼它就和wait函數一樣,即等待任意一個子進程結束。
  參數的status的含義是一樣的。

#include <iostream>
#include <unistd.h>
#include <cstdlib>
#include <sys/types.h>
#include <sys/wait.h>
using namespace std;
 
int main(int argc, char *argv[])
{
    pid_t pid;
    int i = 0;
    cout << "This is main process, PID is " << getpid() << endl;
   
    for (i = 0; i < 2; ++i)
    {        
	    pid = fork();
	    if (pid < 0){
	        cout << "fork error..." << endl;
	        exit(-1);
	    }
	    else if (pid == 0){//This is the child process
	       cout << "This is child process, PID is " << getpid() << endl;
	       exit(11);//將子進程的退出碼設置爲11
	    }
	    else{//This is the main process
	        cout << "This is main process waiting for the exit of child process." << endl;
	    }
    }
    int child_status;
    pid_t child_pid2;
    sleep(1);//一定要等待,因爲waitpid設爲了無阻塞的,如果不等待,當執行完waitpid下面的語句的時候子進程2可能還沒有退出,那麼就得不到它的退出碼了
    child_pid2 = waitpid(pid, &child_status, WNOHANG);
    cout << "This is main process. The second child status is " << child_status << ", and child pid is " << child_pid2 << ", WIFEXITED(child_status) is " << WIFEXITED(child_status) << ", WEXITSTATUS(child_status) is " << WEXITSTATUS(child_status) << endl;	
    exit(0);
}

四 子進程傳遞信號

  我們知道在事件已經發生的情況下執行非阻塞的調用才能提高程序的效率。對於waitpid函數而言,我們最好在某個子進程退出之後再調用它。那麼父進程怎麼知道子進程退出了呢?這個可以靠信號SIGCHLD來解決。當一個進程結束時,它將給其父進程發送一個SIGCHLD信號。我們可以在父進程中捕獲SIGCHLD信號,並在信號處理函數中調用非阻塞的waitpid以徹底結束一個子進程。

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <signal.h>
 
void sigchld_handler(int signum)
{
    int status;
    pid_t pid;
 
    if((pid = waitpid(-1, &status, WNOHANG)) < 0)
    {
        printf("waitpid error\n");
        return;
    }
    printf("The signal is %d, child:%d exit status is %d\n", signum, pid, WEXITSTATUS(status));
}
 
//不可被信號中斷的睡眠函數
void unbreak_sleep(int sec)
{
    while(sec > 0)
    {
        sec = sleep(sec);
    }
}
 
int main()
{
    signal(SIGCHLD, sigchld_handler);
    
    pid_t pid = fork();
    if(pid < 0){
        printf("fork error\n");
        return -1;
    }
    else if(pid == 0){//子進程
        printf("子進程:%d....等待3秒之後退出,退出碼是100\n", getpid());
        sleep(3);
        printf("子進程退出\n");
        exit(100);
    }
    else if(pid > 0){//父進程
        printf("父進程:%d,創建的子進程的pid = %d, 父進程等待7秒\n", getpid(), pid);        
        //sleep(7);
        unbreak_sleep(7);
    }
 
    printf("父進程退出\n");
    exit(0);
}

五 system函數

  system執行參數中的可執行文件;也是運行起來另外一個進程;調用該函數會創建一個shell來執行系統命令或可執行文件;父進程被掛起,直到子進程結束且system()調用返回;

#include <stdlib.h>
int system(const char *command);

六 posix_spawn()

  
  

#include <spawn.h>
int posix_spawn(pid_t *restrict pid, const char *restrict path,
        const posix_spawn_file_actions_t *file_actions,
        const posix_spawnattr_t *restrict attrp,
        char *const argv[restrict], char *const envp[restrict]);

  

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章