Linux多进程编程二——进程创建与死亡


一、进程的创建与相关函数

1.进程创建

  系统允许一个进程创建新的进程,新进程为原进程的子进程,子进程还可以创建新的子进程,形成进程树结构模型,其相关函数为pid_t fork(void);具体使用方式如下:

/*
        #include 
        #include 

        pid_t fork(void);
        作用:用于复制创建子进程
        返回值:
            fork()返回值会返回两次,一次在父进程一次在子进程
            在父进程中:返回子进程id
            在子进程中:返回0
            通过返回值区分父子进程
            失败:返回-1(在父进程中),并设置errno,可能是进程数上线,或者是系统内存不足


        父子进程之间的关系:
        区别:
            1.fork()函数返回值不同
                父进程>0,为子进程id
                子进程==0
            2.pcb中的数据:
                当前的id, pid
                当前的进程的父进程的id,和ppid

        共同点:
            某些状态下,子进程刚刚被创建,还没有进行数据的写入,此时,父子进程共享
                -用户区数据
                -文件描述符表
            一开始变量共享,若修改了数据,则不共享变量
*/
#include 
#include 
#include
int main(){
    int num =10;
    //创建子进程
    pid_t pid = fork();
    //判断父进程还是子进程
    if (pid>0)
    {
        printf("pid:%d\n",pid);
        //如果>0,返回子进程进程号,当前是父进程
        printf("i am parent process, pid:%d, ppid:%d\n", getpid(), getppid());
        printf("parent_num:%d\n", num);
        num+=10;
        printf("parent_num+=10:%d\n", num);

    }
    else if (pid==0)
    {
        //当前是子进程
        printf("i am child process, pid:%d, ppid:%d\n", getpid(), getppid());
        printf("child_num:%d\n", num);
        num+=100;//子进程num与父进程num无关
        printf("child_num+=100:%d\n", num);
    }
    else{
        perror("fork\n");
        return -1;
    }
    //for循环
    for (int i = 0; i < 3; ++i)
    {
        printf("i: %d, pid: %d\n",i , getpid());
        sleep(1);
    }
    return 0;
}

  父子进程共享内核区,但是内核区中pid不同。用户区互不相关。

 2.exec函数族

  exec函数族的作用是根据指定的文件名找到可执行文件,并用它来取代调用进程的内容,换句话说,就是在调用进程内部执行一个可执行文件。exec函数族的函数执行成功后不会返回,因为调用进程的实体,包括代码段,数据段和堆栈等都已经被新的内容取代,只留下进程ID 等一些表面上的信息仍保持原样。看上去还是旧的躯壳,却已经注入了新的灵魂。只有调用失败了,它们才会返回 -1,从原程序的调用点接着往下执行。常用函数有int execl(const char *pathname, const char *arg); int execlp(const char *file, const char *arg);

/*

        #include 

        int execl(const char *pathname, const char *arg);
        参数:
            -pathname:需要指定可执行的文件路径的名称(建议绝对路径)
            -arg:字符串,即可执行文件需要的参数,被main的arg[]数组获取
                第一个参数一般写可执行程序的名称,没有实际作用
                第二个及以后的参数为可执行程序需要的参数列表
                参数最后需要以NULL结束(称为哨兵)
        返回值:
            只有出错的时候才有返回值,为-1,并设置errno,成功了没有返回值(已经不执行原文件了)
*/

/*

        #include 

        int execlp(const char *file, const char *arg);
        作用:会到环境变量中去寻找可执行文件,找到了则执行
        参数:
            -file:环境变量中文件的名称
            -arg:字符串,即可执行文件需要的参数,被main的arg[]数组获取
                第一个参数一般写可执行程序的名称,没有实际作用
                第二个及以后的参数为可执行程序需要的参数列表
                参数最后需要以NULL结束(称为哨兵)
        返回值:
            只有出错的时候才有返回值,为-1,并设置errno,成功了没有返回值(已经不执行原文件了)
*/

#include
#include


int main(){
    //创建一个子进程,在子进程中执行exec中函数
    pid_t pid = fork();
    if(pid>0){
        printf("i am parent process, pid: %d\n", getpid());
        sleep(1);
    }else if(pid==0){
        //子进程替换
        execl("/usr/bin/ps","ps","aux",NULL);
        printf("i am child process, pid: %d\n",getpid());
    }
    for (int i = 0; i < 3; i++)
    {
        printf("i :%d, pid = %d\n", i, getpid());
    }
    return 0;
}

二、进程的消亡

1. 进程正常退出

/*

        #include 

        void exit(int status);


        #include 

        void _exit(int status);


        status参数:是进程退出时的一个状态信息,父进程回收子进程资源时可以获取到。

*/
#include 
#include 
#include
int main(){

    printf("hello\n");
    printf("world");

    //_exit(0); Linux系统函数不刷新io缓冲
    exit(0);//标准c库会刷新io缓冲,使用更多
    return 0;
}

2.孤儿进程

  父进程死亡,但是子进程还未结束运行。此时孤儿进程会被系统init进程领养。孤儿进程并没有什么危害。

 3.僵尸进程

  每个进程结束后,都会释放自己地址空间中的用户区数据,但是内核区PCB必须要由父进程去释放,当进程终止时,父进程未回收,子进程PCB残留在内核中,变成僵尸进程。以下代码会创建出僵尸进程,即父进程未结束,并且未回收子进程所占用的信息。僵尸进程如其名,不能被kill -9 命令杀死,只能通过强制结束父进程来完成回收,如果产生大量僵尸进程,会导致有限的系统进程号被大量占用,导致不能产生新的进程,会造成不可估计的损失,应当避免。

#include 
#include 
#include
int main(){
    int num =10;
    //创建子进程
    pid_t pid = fork();
    //判断父进程还是子进程
    if (pid>0)
    {
        //父进程一直循环
        while(1){
            printf("i am parent process, pid:%d, ppid:%d\n", getpid(), getppid());
            sleep(1);
        }
        
    }
    else if (pid==0)
    {
        //当前是子进程,直接打印并结束
        printf("i am child process, pid:%d, ppid:%d\n", getpid(), getppid());
    }
    else{
        perror("fork\n");
        return -1;
    }
    //for循环
    for (int i = 0; i < 3; ++i)
    {
        printf("i: %d, pid: %d\n",i , getpid());
        sleep(1);
    }
    return 0;
}

4.避免僵尸进程的方法——进程回收命令

  在每个进程退出的时候,内核释放该进程所有的资源、包括打开的文件、占用的内存等。但是仍然为其保留一定的信息,这些信息主要主要指进程控制块PCB的信息(包括进程号、退出状态、运行时间等)。父进程可以通过调用wait或waitpid得到它的退出状态同时彻底清除掉这个进程。wait ()和waitpid()函数的功能一样,区别在于, wait()函数会阻塞,waitpid ()可以设置不阻塞, waitpid()还可以指定等待哪个子进程结束。注意:一次wait或waitpid调用只能清理一个子进程,清理多个子进程应使用循环。

pid_t wait(int *wstatus);
/*
        #include 
        #include 
        pid_t wait(int *wstatus);
        -功能:等待任意一个子进程结束,回收状态变化信息,释放相关资源
        -参数:
            int *wstatus:进程退出时的状态信息,传入的是一个Int类型的地址,传出参数。
        -返回值:
            -成功返回被回收子进程id
            -失败返回-1(所有子进程结束)
        ----调用wait函数时,此进程会被挂起,直到子进程退出或者受到一个不能被忽略的信号,则被唤醒
        若没有子进程或者子进程都结束了,返回-1
*/
#include 
#include 
#include wait.h>
#include 
#include
int main(){
    //有一个父进程,要创建5个子进程
    pid_t pid;
    for (int i = 0; i < 5; i++){
        pid = fork();
        if(pid==0)break;//避免子进程嵌套
    }
    if(pid>0){
        //父进程
        while(1){
            printf("parent , pid = %d\n", getpid());

            int st;
            int ret = wait(&st);
            if(ret == -1){
                break;
            }
            if(WIFEXITED(st)){
                //是不是正常退出
                printf("退出的状态码:%d\n",WEXITSTATUS(st));

            }
            if(WIFSIGNALED(st)){
                //是不是异常终止
                printf("被什么信号终止:%d\n",WTERMSIG(st));
            }
            printf("child die, pid = %d\n", ret);
            sleep(1);
        }
    
    }else if (pid==0)
    {
        /* 子进程 */
        //while(1){
            printf("child, pid = %d, ppid = %d\n", getpid(), getppid());
            sleep(1);
        //}
        exit(1);
        
    }
    
    

    return 0;
}
pid_t waitpid(pid_t pid, int *wstatus, int options);
/*
        #include 
        #include 
        pid_t waitpid(pid_t pid, int *wstatus, int options);
        功能:回收指定进程号的子进程,但是可以设置非阻塞,即不等待子进程终止
        参数:
            -pid_t pid:
                pid>0:某个子进程pid
                pid==0:回收当前进程组所有的子进程
                pid==-1:表示回收所有的子进程,相当于调用wait
                pid<-1:回收某个进程组子进程(组号为此参数的绝对值)
            -int *wstatus:回收信息
            -int options:设置阻塞或者非阻塞
                0:阻塞
                WNOHANG:非阻塞
        返回值:
            >0:返回子进程的id
            ==0:options=WNOHANG,表示还有子进程活着
            ==-1:错误,或者没有子进程了
*/


#include 
#include 
#include wait.h>
#include 
#include
int main(){
    //有一个父进程,要创建5个子进程
    pid_t pid;
    for (int i = 0; i < 5; i++){
        pid = fork();
        if(pid==0)break;//避免子进程嵌套
    }
    if(pid>0){
        //父进程
        while(1){
            sleep(1);
            printf("parent , pid = %d\n", getpid());
            int st;
            int ret = waitpid(-1, &st, WNOHANG);
            if(ret == -1){
                break;
            }
            if(ret==0){
                continue;
            } else if (ret > 0){
                if(WIFEXITED(st)){
                //是不是正常退出
                printf("退出的状态码:%d\n",WEXITSTATUS(st));

            }
                if(WIFSIGNALED(st)){
                //是不是异常终止
                    printf("被什么信号终止:%d\n",WTERMSIG(st));
            }
                printf("child die, pid = %d\n", ret);
            }
        }
    }else if (pid==0)
    {
        /* 子进程 */
        while(1){
            printf("child, pid = %d\n", getpid());
            sleep(1);
        }
        exit(0);
    }
    return 0;
}

  退出信息宏如下(代码中st):

相关