exec函数族以及进程的退出与同步

1.exec函数族

exec函数族包含六个函数:execl(),execlp(),execle(),execv(),execvp(),execve()。他们包含在系统库unistd.h中。
#include <unistd.h>
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[]);
int execve(const char *path, char * const argv[], char * const envp[]);
  • 参数说明:
    (1)当参数是path,传入的为路径名;当参数是file,传入的可执行文件名;
    (2)可以将exec函数族分为execl和execv两类:
    execl类:函数将以列举的形式传入参数,由于参数列表的长度不定,所以要用哨兵NULL表示列举结束;
    execv类:函数将以参数向量表传递参数,char * argv[]的形式传递文件执行时使用的参数,数组中最后一个参数为NULL;
    (3)如果没有参数char * const envp[],则采用默认环境变量;如果有,则用传入的参数替换默认环境变量;

     	在 Linux 系统中,环境变量是用来定义系统运行环境的一些参数,比如每个用户不同的家目录(HOME)、邮件存放位置(MAIL)等
    
  • 功能:根据指定的文件名或路径找到可执行文件,用该文件取代调用该函数的进程中的程序,再从该文件的main()函数开始执行文件的内容。

    调用exec函数族时不创建新进程,因此进程的pid不会改变。
    exec只是用新程序中的数据替换了进程中的代码段、数据段以及堆和栈中的数据。exec调用成功是没有返回值。

    exec函数族一般与fork()函数一起使用:使用forkI()函数创建进程,使用exec函数族修改进程任务。调用这两个函数之后,具体情况如下图:
    图片源自转载@HenrySmale

1.1.1 案例:在程序中创建一个子进程,之后使用父进程打印自己的pid信息,使子进程通过exec函数族获取系统命令文件,执行ls命令。

案例代码:

main.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main()
{
    pid_t tempPid;
    tempPid=fork();
    if(tempPid==-1){//of if
        perror("fork error");
        exit(1);
    }else if(tempPid>0){// parent
        printf("parent process:pid=%d\n",getpid());
    }else if(tempPid==0){// child
        printf("child procress:pid=%d\n",getpid());
        excel("/bin/ls","-a","-l","test_fork.c",NULL);
        perror("error exec\n");
        printf("child process:pid=%d\n",getpid());
    }
    return 0;
}//of main

案例打印结果:
在这里插入图片描述

2.进程退出

在Linux系统当中进程的退出通过exit()函数实现。exit()函数存在于系统函数库stdlib.h中,声明如下:

void exit(int status);
  • 功能:
    在进程中,exit()函数的功能其实就相当于“return 返回值”。

  • 参数说明:
    status:表示进程的退出状态(0表示正常退出,非0表示异常退出,一般使用-1或1 表示)。

为了增强可读性,在标准C中定义了两个宏:EXIT_SUCCESS和EXIT_FAILURE,分别表示正常退出和非正常退出。

扩展:_exit()函数,与exit()相似,也用于终止进程,定义在unistd.h库中

void _exit(int status);

两者区别:
_exit()函数:程序执行到该函数,系统会无条件停止操作,终止进程并清除进程所用内存空间以及进程在内核中的各种数据结构;
exit()函数对 _exit()函数进行了包装,在执行退出前还有若干道工序,最重要的就是他会在调用_exit()函数之前先检查文件的打开情况,将缓冲区的内容写回文件。相对而言,exit()函数比_exit()函数更加安全。

特殊进程:

  • 孤儿进程:父进程在子进程退出之前退出,子进程就会变成孤儿进程。此时子进程就会被init进程收养,之后init进程会代替其原来的父进程完成状态收集工作。

  • 僵尸进程:几乎放弃进程退出前占用的所有内存,既没有可执行代码,也不能被调度,只在进程列表中保留一个位置,记载进程的退出状态等信息供父进程收集。若父进程没有回收子进程的代码,子进程将会一直处于僵尸态。

3.进程同步

定义:异步环境下的一组并发进程因相互制约而互相发送信息、互相合作、互相等待,使得各个进程按照一定速度和顺序执行成为进程间的同步。

3.1 wait()函数

wait()存在于系统函数库sys/wait.h中,函数声明如下:

pid_t wait(int *status);
  • 参数说明:
    status是一个int *类型的指针,用来保存子进程退出时的状态信息。一般设置为NULL。

  • 功能:
    调用wait()函数的进程会被挂起,进入阻塞状态。
    出现子进程变为僵尸态:wait()函数捕获到该子进程的退出信息时才会转为运行态,回收子进程资源并返回;
    若没有变为僵尸态的子进程:wait()函数会让进程一直阻塞。
    若当前进程拥有多个子进程,只要捕获到一个变为僵尸态的子进程的信息,wait()函数就会返回并使进程恢复执行。

  • 返回值说明:
    调用成功:返回子进程的进程id
    调用失败:返回-1,errno被设置为ECHILD。

3.1.1 案例:若子进程pl是父进程p的先决条件,使用wait()函数使进程同步。

test6-4.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>
int main(){
    pid_t tempPid, tempW;
    tempPid = fork();
    if(tempPid == -1){
        perror("fork error");
        exit(1);
    }else if(tempPid == 0){//child
        sleep(3);
        printf("Child process, pid = %d, ppid = %d\n", getpid(), getppid());
    }else{//parent
        tempW = wait(NULL);
        printf("Catched a child process, pid = %d, ppid = %d\n", tempW, getppid());
    }//of if
    printf("......finish......\n");
    return 0;
}//of main

执行结果如图:
在这里插入图片描述

宏函数:WIFEXITED(status)和 WEXITSTATUS(status),用于判断进程退出状态的宏函数。位于sys/wait.h库中

  • int WIFEXITED(int status):用于判断子进程是否正常退出,若是,返回非0值;否则返回0.
  • int WEXITSTATUS(int status):通常与WIFEXITED()结合使用,若WIFEXITED()返回非0值(即正常退出),则使用该宏可以提取子进程的返回值。

3.1.2 案例:使用wait()函数同步进程,并使用宏获取子进程的返回值

test6-5.c
#include <stdio.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <unistd.h>
int main(){
    int tempStatus;
    pid_t tempPid, tempW;
    tempPid = fork();
    if(tempPid == -1){
        perror("fork error");
        exit(1);
    } else if(tempPid == 0){//子
        sleep(3);
        printf("Child process: pid=%d\n",getpid());
        exit(5);
     }  else{//父
        tempW = wait(&tempStatus);
        if(WIFEXITED(tempStatus)){
            printf("Child process pid=%d exit normally.\n", tempW );
            printf("Return Code:%d\n",WEXITSTATUS(tempStatus));
        } else {
            printf("Child process pid=%d exit abnormally.\n", tempW);
        }//of if
    }//of if
    return 0;
}//of main

执行结果:
在这里插入图片描述

3.2 waitpid()函数:位于系统函数库sys/wait.h库中,函数声明如下:

pid_t waitpid(pid_t pid,int *status,int options);
  • 参数说明:

  • pid:一般是进程的pid,也有可能是其他取值。进一步说明如下:
    – pid > 0:等待子进程(编号为pid)退出,若退出,函数返回;若未结束,则一直等待;
    – pid = 0:等待同一进程组的所有子进程退出,若某子进程加入了其他进程组,则waitpid不再关心它的状态;
    – pid = -1:waitpid函数退化为wait函数,阻塞等待并回收一个子进程;
    – pid < -1:等待指定进程组中的任何子进程,进程组的id等于pid的绝对值。
    -

  • options: 提供控制选项,可以是一个常量,也可以是|连接的两个常量,选项如下:
    – WNOHANG:如果子进程没有终止,waitpid不会阻塞父进程,会立即返回;
    – WUNTRACED:如果子进程暂停执行,waitpid立即返回;
    – 0:不使用选项。

  • 返回值说明:
    成功:返回捕捉到的子进程的pid
    0:options的值为WNOHANG,调用waitpid()时发现没有已退出的子进程可收集
    -1:调用过程出错。errno被设置成相应的值以指示错误位置

waitpid()函数可以等待指定的子进程,也可以在父进程不阻塞的情况下获取子进程状态。相对于wait()来说,waitpid()的使用更加灵活。

案例:使父进程等待进程组中某个指定的进程,若该进程不退出,则让父进程一直阻塞

test6-6.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>
int main(){
    pid_t tempPid, tempP, tempW;
    tempPid= fork();                            //创建第一个子进程
    if (tempPid == -1){
        perror("fork1 error");
        exit(1);
    } else if (tempPid == 0){                        //子进程沉睡
        sleep(5);
        printf("First child process:pid=%d\n", getpid());
    } else {                        //父进程继续创建进程
        int i;
        tempP = tempPid;
        for (i = 0; i < 3; i++){                    //由父进程创建3个子进程
            if ((tempPid = fork()) == 0){
                break;
            }//of if
        }//of for i
        if (tempPid == -1){                        //出错
            perror("fork error");
            exit(2);
        } else if (tempPid == 0){                    //子进程
            printf("Child process:pid=%d\n", getpid());
            exit(0);
        } else {                    //父进程
            tempW = waitpid(tempP, NULL, 0);            //等待第一个子进程执行
            if (tempW == tempP){
                printf("Catch a child Process: pid=%d\n", tempW);
            }else{
                printf("waitpid error\n");
            }//of if
        }//of if
    }//of if
    return 0;
}//of main

执行结果:
在这里插入图片描述

3.2.1 案例:使用waitpid()函数不断获取某进程中子进程的状态。

test6-7.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <unistd.h>
int main() {
    pid_t tempPid, tempW;
    tempPid = fork();
    if (tempPid == -1){
        perror("fork error");
        exit(1);
    } else if (tempPid == 0){
        sleep(3);
        printf("Child process:pid=%d\n", getpid());
        exit(0);
    } else {
        do{
            tempW = waitpid(tempPid, NULL, WNOHANG);
            if (tempW == 0){
                printf("No child exited\n");
                sleep(1);
            }//of if
        } while (tempW == 0);
        if (tempW == tempPid){
            printf("Catch a Child process:pid=%d\n", tempW);
        }else{
            printf("waitpid error\n");
        }//of if
    }//of if
    return 0;
}//of main

执行结果:
在这里插入图片描述

4.特殊进程的危害

  • 僵尸进程不能再次被运行,会占用一定的内存空间,并占据进程编号,当僵尸进程较多时,将会消耗系统内存,新进程可能因内存不足或无法获取pid而无法创建;
  • 父进程通过wait()和waitpid()函数可以有效防止僵尸进程的产生,对于已存在的僵尸进程,则可通过杀死其父进程的方法解决;
  • 当僵尸进程的父进程被终止后,僵尸进程将作为孤儿进程被init进程接收,init进程会不断调用wait()函数获取子进程状态,对已处于僵尸态的进程进行处理;
  • 孤儿进程永远不会成为僵尸进程。
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值