• 【Linux】exec函数族及进程控制相关


    目录

    1. exec函数族介绍

     2. 进程控制

    2.1 进程退出

    2.2 孤儿进程

    2.3 僵尸进程

    2.4 进程回收

    2.5 退出信息相关宏函数


    1. exec函数族介绍

    exec函数族的作用是根据指定的文件名或目录找到可执行文件,并用它来取代调用进程的内容,换句话说,就是在调用进程内部执行一个可执行文件。

    通常的做法是使用fork函数创建一个子进程,然后在子进程中执行exec函数。

    exec函数族的函数执行成功后不会返回,因为调用进程的实体,包括代码段、数据段和堆栈等都已经被新的内容取代,只留下进程ID等一些表面上的信息保持原样。调用失败,会返回-1,从原程序的调用点接着往下执行。

    如下函数就是exec函数族包含的函数,前6个函数是标准C库的函数,最后一个函数是Linux/unix的函数

    1. int execl(const char *path, const char *arg,.../* (char*)NULL*/);
    2. int execlp(const char *file, const char *arg,.../* (char*)NULL*/);
    3. int execle(const char *path, const char *arg,.../* (char*)NULL, char *const envp[]*/);
    4. int execv(const char *path, char *const argv[]);
    5. int execvp(const char *file, char *const argv[]);
    6. int execvpe(const char *file, char *const argv[], char *const envp[]);
    7. int execve(const char *path, char *const argv[], char *const envp[]);

    函数名中的l/v/p/e的含义:

    • l(list):表示参数地址列表,以空指针结尾;
    • v(vector):表示存有个参数地址的指针数组的地址;
    • p(path):按PATH环境变量指定的目录搜索可执行文件
    • e(environment):给程序设置新的环境变量;

    以上函数都在头文件 #include 中声明。

    参数说明:

    参数path表示可执行文件的的完整路径;

    参数file表示可执行文件的文件名,如果file中包含/,将其视为路径名,否则按照PATH环境变量查找指定的可执行文件;

    execl()/execlp()execle()函数参数中的 arg,...表示执行可执行文件所需要的参数列表,第一个参数为了方便,一般设置为可执行文件的名字,从第二个参数开始,是程序执行需要的参数列表,参数最后以空指针NULL结尾。

    execv()/execvp()/execvpe()/execve()函数中的 argv 表示执行可执行文件需要的参数列表的数组,以空指针NULL结尾,如 char *argv[] = {"test", "ps","aux", NULL};   

    execle()/execvpe()/execve()函数中的参数 envp 是自己指定的环境变量的参数列表,以空指针NULL结尾,如: char *envp[] = {"/home/Linux", "/home/Linux/lesson19", NULL};  

    函数返回值:只有当调用失败时,才会有返回值,返回-1,并设置errno;如果调用成功,没有返回值。

     更详细信息:linux进程---exec族函数(execl, execlp, execle, execv, execvp, execvpe)_云英的博客-CSDN博客_execle

    execl()使用示例,当前路径下有可执行文件hello,功能:打印hello world。

    1. #include
    2. #include
    3. #include
    4. int main()
    5. {
    6. // 创建一个子进程,在子进程中执行exec函数族中的函数
    7. pid_t pid = fork();
    8. if (pid > 0)
    9. {
    10. // 父进程
    11. printf("parent process, pid : %d\n", getpid());
    12. // sleep(1);
    13. }
    14. else if (pid == 0)
    15. {
    16. // 子进程,执行execl()函数
    17. const char *path = "hello";
    18. execl(path, path,"ps", "aux",NULL); // 也可以执行shell命令
    19. perror("execl");
    20. // 如果execl执行成功,下面这一行不会执行
    21. printf("chils process, pid : %d\n", getpid());
    22. }
    23. for (int i = 0; i < 3; i++)
    24. {
    25. printf("pid = %d, i = %d\n", getpid(), i);
    26. }
    27. return 0;
    28. }

    运行显示结果,可以看到正常运行的情况下,"chils process, pid : %d\n\n"  没有被打印,因为在子进程中可执行文件hello取代了原来的子进程的内容。

     execlp()函数使用示例:

    1. #include
    2. #include
    3. #include
    4. int main()
    5. {
    6. // 创建一个子进程,在子进程中执行exec函数族中的函数
    7. pid_t pid = fork();
    8. if (pid > 0)
    9. {
    10. // 父进程
    11. printf("parent process, pid : %d\n", getpid());
    12. }
    13. else if (pid == 0)
    14. {
    15. // 子进程,执行execlp()函数,执行命令 ls -l
    16. const char *path = "ls";
    17. execlp("ls", "ls","-l", NULL); // 执行shell命令
    18. //execlp("/home/zoya/Linux/chapter2/lesson6/hello", "hello", NULL); // 执行shell命令
    19. // 如果execl执行成功,下面这一行不会执行
    20. printf("chils process, pid : %d\n\n", getpid());
    21. }
    22. return 0;
    23. }

    显示结果, 执行了 ls -l,命令

     如果代码改成如下,那么会执行hello

    1. //execlp("ls", "ls","-l", NULL); // 执行shell命令
    2. execlp("/home/zoya/Linux/chapter2/lesson6/hello", "hello", NULL); // 执行shell命令

    execve()函数使用示例:

    1. #include
    2. #include
    3. #include
    4. int main()
    5. {
    6. pid_t pid = fork();
    7. if (pid > 0)
    8. {
    9. printf("parent process, pid : %d\n", getpid());
    10. }
    11. else if (pid == 0)
    12. {
    13. char *argv[] = {"hello", NULL};
    14. char *envp[] = {"/home/zoya/Linux/chapter2/lesson6", NULL};
    15. if(-1 ==execve(argv[0],argv,envp))
    16. {
    17. perror("execve");
    18. }
    19. printf("chils process, pid : %d\n", getpid());
    20. }
    21. else{
    22. printf("create process failed!\n");
    23. }
    24. for(int i=0;i<5;i++)
    25. {
    26. printf("i=%d, pid : %d\n",i,getpid());
    27. }
    28. return 0;
    29. }

    执行结果,execve会在参数envp指定的路径中查找指定的可执行文件

     2. 进程控制

    2.1 进程退出

    控制进程退出函数有:

    1. #include
    2. void exit(int status);
    3. #include
    4. void _exit(int status);

    参数status表示退出的状态信息,父进程回收子进程资源时可以获取该信息;

    exit()和_exit()的区别:

    • exit()在退出前会调用退出处理函数,刷新I/O缓冲,关闭文件描述符等操作,然后调用系统函数_exit()终止进程。

    2.2 孤儿进程

    父进程运行结束,但是子进程还在运行(未运行结束),这样的子进程称为孤儿进程(Orphan Process)。

    当出现一个孤儿进程时,内核就会把该孤儿进程的父进程设置为init(pid=1的进程),而init进程会循环等待它的已经退出的子进程。当孤儿进程结束其生命周期时,init进程会处理善后工作。

    孤儿进程没有什么危害。

    示例:

    1. #include
    2. #include
    3. #include
    4. int main()
    5. {
    6. pid_t pid = fork();
    7. if(pid > 0)
    8. {
    9. printf("parent process,pid : %d, ppid : %d\n",getpid(),getppid());
    10. }
    11. else if(pid == 0)
    12. {
    13. sleep(1);
    14. printf("child process, pid : %d, ppid : %d\n",getpid(),getppid());
    15. }
    16. else{
    17. printf("create process failed!\n");
    18. }
    19. for (int i=0;i<3;i++)
    20. {
    21. printf("i=%d, pid : %d\n",i,getpid());
    22. }
    23. return 0;
    24. }

    显示结果,当父进程结束后,子进程会被托管给init进程(pid=1) ,init进程会对子进程的资源进行回收。

    2.3 僵尸进程

    每个子进程结束之后,都会释放自己地址空间中的用户区数据,但是内核区的PCB没有办法自己释放掉,需要父进程释放。

    若子进程终止时,父进程尚未回收,子进程残留资源存放于内核中,变成僵尸进程(Zombie)。

    僵尸进程不能被 kill -9杀死。会导致一个问题:如果父进程不调用wait()或waitpid()的话,子进程保留的信息就不会被释放,其进程号一直被占用,但是系统所能使用的进程号是有限的,如果系统中有大量的僵尸进程,将因为没有可用的进程号导致系统不能产生新的进程。所以僵尸进程是有危害的,应当避免产生僵尸进程。

    避免僵尸进程:父进程有义务释放子进程的资源,可以使用wait/waitpid函数对内核区释放。

    2.4 进程回收

    每个进程退出时,内核释放该进程的所有资源、包括打开的文件、占用的内存等。但仍然为其保留一定的信息,这些信息主要指进程控制块PCB的信息(包括进程号、退出状态、运行时间等)。

    父进程可以通过调用wait或waitpid得到它的退出状态同时彻底清除掉这个进程。

    wait和waitpid函数功能一样,区别在于wait函数会阻塞,waitpid函数默认是阻塞,但是可以设置不阻塞,waitpid函数还可以指定等待哪个子进程结束。

    注意:一次wait或waitpid调用只能清理一个子进程,清理多个子进程应该使用循环。

    wait()/waitpid()函数声明:

    1. pid_t wait(int *wstatus);
    2. pid_t waitpid(pid_t pid, int *wstatus, int options);

    这两个函数都可以释放与子进程相关的资源;

    参数说明:

    • wstatus:进程退出时的状态信息;
    • pid:
    pid指说明
    >0表示回收进程id为pid的子进程
    =0表示回收当前进程组的任意子进程,比较常用
    =-1表示回收任意子进程,包括其它组的子进程,相当于wait(),最常用
    <-1表示回收进程组的组ID=|pid|的子进程
    • options:设置阻塞或非阻塞状态,0:表示阻塞状态,WNOHANG设置非阻塞状态,表示没有子进程退出就立即返回。

    返回值:

    • >0 表示被回收的子进程的ID;
    • =-1 表示函数调用失败,或者没有子进程了;
    • waitpid()函数返回0 表示是在非阻塞状态,还有子进程没有退出;

    2.5 退出信息相关宏函数

    1. WIFEXITED(status) 非0 ,表示进程正常退出
    2. WEXITSTATUS(status) 如果宏为真,获取进程退出的状态
    3. WIFSIGNALED(status) 非0,进程异常终止
    4. WTERMSIG(status) 如果宏为真,获取使进程终止的信号编号
    5. WIFSTOPPED(status) 非0,表示进程处于暂停状态
    6. WSTOPSIG(status) 如果宏为真,获取使进程暂停的信号的编号
    7. WIFCONTINUED(status) 非0,进程暂停后已继续运行

    使用示例:

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. int main()
    7. {
    8. // 父进程创建5个子进程
    9. pid_t pid;
    10. // 创建5个子进程
    11. for(int i=0;i<5; i++){
    12. pid = fork();
    13. if(pid == 0) // 子进程
    14. {
    15. break;
    16. }
    17. }
    18. if(pid > 0){
    19. // 父进程
    20. while (1)
    21. {
    22. printf("parent process, pid : %d, ppid : %d\n",getpid(),getppid());
    23. sleep(1);
    24. // 调用wait()/waitpid()进行子进程资源回收
    25. int st;
    26. //int ret = wait(&st); // wait():父进程在这里被阻塞
    27. pid_t id=-1; // -1表示回收任意的子进程,包括其它组的子进程
    28. int options=0; // 0表示阻塞
    29. options = WNOHANG; // WNOHANG表示非阻塞
    30. int ret = waitpid(-1,&st,options); // 父进程调用waitpid()等待子进程退出,并回收子进程资源
    31. if(ret == -1) // 函数调用失败,或者没有子进程了
    32. {
    33. break;
    34. }
    35. else if(ret == 0) // 说明还有子进程存在
    36. {
    37. continue;
    38. }else if(ret > 0) // 有子进程被回收,ret表示被回收的子进程的ID
    39. {
    40. // 输出 子进程退出状态信息
    41. if(WIFEXITED(st)){ // WIFEXITED() 宏 非0表示进程正常退出
    42. printf("退出的状态码:%d\n",WEXITSTATUS(st)); // WEXITSTATUS() 宏 获取进程退出状态,就是进程中使用exit()的参数值
    43. }
    44. if(WIFSIGNALED(st)){ // WIFSIGNALED() 宏 非0 表示进程异常终止
    45. printf("被哪个信号干掉: %d\n",WTERMSIG(st)); // WTERMSIG() 宏 获取使进程异常终止的信号编号
    46. }
    47. printf("child end, pid=%d\n",ret); // 打印终止的子进程pid
    48. }
    49. sleep(1);
    50. }
    51. }
    52. else if(pid == 0){
    53. // 子进程
    54. //while(1)
    55. {
    56. printf("child process, pid : %d, ppid : %d\n",getpid(),getppid());
    57. sleep(1);
    58. }
    59. exit(0); // 0表示正常退出
    60. }
    61. else{
    62. perror("fork");
    63. }
    64. return 0;
    65. }

    结果:正常退出,打印退出的状态码。

  • 相关阅读:
    [MATLAB]结构化程式与自定义函数
    fabric.js的使用
    哈工大李治军老师操作系统笔记【23】:内存换出(Learning OS Concepts By Coding Them !)
    表单识别(三)
    关于安卓SVGA浅尝(一)svgaplayer库的使用
    如何有效记忆大量文字
    Pytorch:model.train()和model.eval()用法和区别,以及model.eval()和torch.no_grad()的区别
    Android---打开相机拍照
    第 2 章 线性表 ( 具有实用意义的线性链表(带头结点)实现)
    RAW、RGB 、YUV三种图像格式理解
  • 原文地址:https://blog.csdn.net/sinat_41752325/article/details/126711089