首页 > 运维 > linux运维 > 正文

Linux-进程控制

爱谁谁
发布: 2025-07-20 13:06:10
原创
245人浏览过

前言:

  进程控制不仅仅是管理程序的执行顺序,还涉及到资源的分配等问题,那么话不多说,开始我们今天的话题!

?进程退出函数✈️exit函数

  上次我们说到,进程退出时,都会返回一个退出码,用来表示进程退出的状态,而在更前面,我们曾经说过exit函数用来退出进程:

代码语言:javascript代码运行次数:0运行复制
<code class="javascript">  1 #include<stdio.h>  2 #include<stdlib.h>  3 #include<unistd.h>  4   5 int main()  6 {  7     while(1)  8     {  9         printf("I'm a process, pid=%d\n", getpid()); 10         sleep(1); 11  12         exit(3); 13     } 14     return 0; 15 }</code>
登录后复制
Linux-进程控制

  进程退出函数exit,函数参数可作为进程退出状态:

Linux-进程控制

eixt:退出进程 status:进程退出状态退出码。

✈️_exit函数
Linux-进程控制

  这是man手册里的_exit(),我们通过下面这段代码测试一下他们功能有何异同:

代码语言:javascript代码运行次数:0运行复制
<code class="javascript">#include<stdio.h>#include<string.h>#include<errno.h>#include<stdlib.h>#include<unistd.h>void Print(){    printf("hello Print\n");    _exit(4);}int main(){    while(1)    {        printf("I am a process: %d\n", getpid());        sleep(1);        Print(1);        //exit(3);放出另外一个_exit,反之则放出exit关闭_exit    }    return 0;}</code>
登录后复制
Linux-进程控制

  这是注释掉了exit(),调用Print函数的_exit()的结果。

Linux-进程控制

  这是注释掉Print函数内的_exit,使用exit()函数返回的结果。

  从此看来我们并没有发现什么不同之处,返回的错误码也没问题。他们的区别可以通过一下代码分析:

代码语言:javascript代码运行次数:0运行复制
<code class="javascript">#include<stdio.h>#include<string.h>#include<errno.h>#include<stdlib.h>#include<unistd.h>int main(){    printf("hello guy\n");    sleep(1);    exit(1);    return 0;}</code>
登录后复制
Linux-进程控制

  exit函数返回的结果符合我们的预期,如果把打印的\n删除:

Linux-进程控制

  也没什么问题,但是如果换成_exit函数:

Linux-进程控制

  从这里可以看到,当_exit函数遇到像printf打印却没有换行符的时候,就不能正确打印出自己想要的数据。

区别:   exit是用来终止进程的,exit(退出码),在我们进程的任何地方调用exit() 都表示进程退出,而_exit与exit的区别就是,exit() 会刷新缓冲区(以后会详谈)。

  实际上,_exit()函数是Linux下的一种系统调用,为什么要存在exit() 和 _exit() 两个不同的接口呢?exit() 函数是语言层面的封装,使用这种封装的好处就是语言具有 跨平台、可移植性。

  不同系统、平台可能给你提供的系统调用不相同,但是在语言层的封装却都是相同的,所以 C语言具有可移植性、跨平台性。


?进程等待

  我们之前说过,如果一个进程变僵尸那么使用kill -9也无能为力,造成僵尸的原因就是子进程退出了,父进程并没有将资源回收所导致的,所以系统提供了一些系统调用,通过父进程等待的方式获取子进程退出信息。

✈️wait接口

  在Linux中,为了防止进程变僵尸,系统系统了这样一个接口 wait():

Linux-进程控制

  wait接口是用来回收子进程资源的一个接口,我们看到wait接口的参数是一个指针,这其实是一个 输出型参数 ,我们现将其设置为 NULL。

接口功能:

  我们通过代码来认识一下它的功能:

代码语言:javascript代码运行次数:0运行复制
<code class="javascript">#include<stdio.h>#include<stdlib.h> #include<unistd.h>#include<sys/wait.h>#include<sys/types.h>int main(){    pid_t id = fork();    if(id == 0)    {        //child        int cnt = 5;        while(cnt)        {            printf("child is running, pid: %d, ppid: %d\n", getpid(), getppid());            sleep(1);            cnt--;        }        printf("child prepare for exit, will be ZM!\n");        exit(0);    }    printf("father is sleep...\n");    sleep(10);    printf("father start recycle\n");    //father    pid_t rid = wait(NULL);    if(rid > 0)    {        printf("wait sucess, rid: %d\n", rid);    }    printf("father get source sucess!\n");    sleep(3);    return 0;}</code>
登录后复制
Linux-进程控制

  图中画的红色框框表示僵尸了一段时间,而当父进程开始回收之后,子进程的僵尸状态就没有了。

  由此我们可以间接得出,fork之后,父进程一定要最后退出!


✈️waitpid接口

  Linux也提供了wait方式来获取子进程退出信息的接口 waitpid():

Linux-进程控制

  其中waitpid返回值与wait的返回值含义相同,第一个参数的pid有很多种表示方法,但是常见的表示有这两种:

pid参数意义:

  status同样是一个输出型参数。options有两种传值,一种是0,一种是非阻塞,我们目前将其设置为0表示阻塞状态。

  通过下面代码感受getpid():

代码语言:javascript代码运行次数:0运行复制
<code class="javascript">#include<stdio.h>#include<stdlib.h> #include<unistd.h>#include<sys/wait.h>#include<sys/types.h>int main(){    pid_t id = fork();    if(id == 0)    {        //child        int cnt = 5;        while(cnt)        {            printf("child is running, pid: %d, ppid: %d\n", getpid(), getppid());            sleep(1);            cnt--;        }        exit(1);    }    //father    int status = 0;    pid_t rid = waitpid(id, &status, 0);//阻塞等待    if(rid > 0)    {        printf("wait sucess, rid: %d  status: %d\n", rid, status);//查看rid和status    }    return 0;}</code>
登录后复制
Linux-进程控制

  我们给status的初始值为0,但是这里却变成了256。这里的status并不是指单纯的整数,与进程的 退出码 和 退出信号 有关,而其存储形式类似于位图的存储:

Linux-进程控制在这里插入图片描述

  我们只看比特位的后十六位:

Linux-进程控制

  中间有一个名为core dump的比特位我们在信号部分在详谈。

  所以我们status为什么是256其实就很简单了:

✈️导出错误码

  上面说了status的最后16位比特位有效,并且这十六位由退出码和信号编号所组成,所以我们可以使用位运算的方式将退出码和退出信号提取出来:

代码语言:javascript代码运行次数:0运行复制
<code class="javascript">#include<stdio.h>#include<stdlib.h> #include<unistd.h>#include<sys/wait.h>#include<sys/types.h>int main(){    pid_t id = fork();    if(id == 0)    {        //child        int cnt = 50;        while(cnt)        {            printf("child is running, pid: %d, ppid: %d\n", getpid(), getppid());            cnt--;            sleep(1);        }        exit(0);    }    //father    int status = 0;    pid_t rid = waitpid(id, &status, 0);    if(rid > 0)    {      //status & 0x7F: 0000 0000 0000 0000 0000 0000 0111 1111        printf("wait sucess, rid: %d  status: %d  exit signo%d  exit code: %d\n", rid, status, status&0x7F, (status>>8)&0xFF);    }    return 0;}</code>
登录后复制
Linux-进程控制

  使用了kill -9得出的退出信号就是9,进程被杀死退出码为0。如果在代码中出现了逻辑错误,比如除零错误:

Linux-进程控制

  还有类似空指针等情况,有兴趣可以自己尝试,这里就不在试了。

豆包AI编程
豆包AI编程

豆包推出的AI编程助手

豆包AI编程 483
查看详情 豆包AI编程

  实际上,Linux给我们提供了两个常见的宏定义:

  有了这些宏,我们就不用那么麻烦直接使用位操作了,我们可以直接使用对应的宏:

代码语言:javascript代码运行次数:0运行复制
<code class="javascript">#include<stdio.h>#include<stdlib.h> #include<unistd.h>#include<sys/wait.h>#include<sys/types.h>int main(){    pid_t id = fork();    if(id == 0)    {        //child        int cnt = 5;        while(cnt)        {            printf("child is running, pid: %d, ppid: %d\n", getpid(), getppid());            cnt--;            sleep(1);        }        exit(123);    }    //father    int status = 0;    pid_t rid = waitpid(id, &status, 0);    if(rid > 0)    {      //status & 0x7F: 0000 0000 0000 0000 0000 0000 0111 1111       // printf("wait sucess, rid: %d  status: %d  exit signo%d  exit code: %d\n", rid, status, status&0x7F, (status>>8)&0xFF);        if(WIFEXITED(status))//使用宏定义        {             printf("wait sucess, rid: %d  status: %d  exit code: %d\n", rid, status, WEXITSTATUS(status));        }        else         {            printf("child process error!\n");        }    }    return 0;}</code>
登录后复制
Linux-进程控制

  源码中的status:

Linux-进程控制
✈️阻塞与非阻塞

  我们前面代码所采用的等待方式均为阻塞等待,即在回收子进程的资源之前,父进程什么事情可不干,可以用grep 查询:

代码语言:javascript代码运行次数:0运行复制
<code class="javascript">ps ajx | grep -i mybin#查询当前进程状态</code>
登录后复制
Linux-进程控制

  既然有阻塞,也必然有非阻塞:

表1 阻塞与非阻塞区别

阻塞

非阻塞

方式

当waitpid函数以阻塞模式调用时,父进程等待子进程退出,这期间父进程不做任何事情

当waitpid函数以非阻塞调用时,父进程以轮询的方式每段时间检测子进程是否退出,没退出就返回做父进程的事情

参数

0

WNOHANG

  我们以下面这段代码来理解非阻塞:

代码语言:javascript代码运行次数:0运行复制
<code class="javascript">#include<stdio.h>#include<stdlib.h> #include<unistd.h>#include<sys/wait.h>#include<sys/types.h>int main(){    pid_t id = fork();    if(id == 0)    {        //child        int cnt = 5;        while(cnt)        {            printf("child is running, pid: %d, ppid: %d\n", getpid(), getppid());            cnt--;            sleep(1);        }        exit(123);    }    //father    int status = 0;    while(1)    {        pid_t rid = waitpid(id, &status, WNOHANG);//非阻塞调用        if(rid > 0)        {            printf("wait sucess, rid: %d, status: %d, exit code: %d\n", rid, status, WEXITSTATUS(status));            break;         }        else if(rid == 0)        {            printf("father say: child is running, do other things\n");        }        else         {            perror("waitpid");            break;        }        sleep(1);    }    return 0;}</code>
登录后复制
Linux-进程控制

  父进程在等待子进程资源回收的时候自己也在不断地执行,最后也可以成功回收子进程。

  为了更直观看到现象,我们不妨模拟进程等待的环境:

代码语言:javascript代码运行次数:0运行复制
<code class="javascript">#include<stdio.h>#include<stdlib.h> #include<unistd.h>#include<sys/wait.h>#include<sys/types.h>#define NUM 5typedef void(*fun_t)();fun_t tasks[NUM];void printLog(){    printf("this is a log print task\n");}void printNet(){    printf("this is  a net\n");}void printNPC(){    printf("this is a flush NPC\n");}void initTask(){    tasks[0] = printLog;    tasks[1] = printNet;    tasks[2] = printNPC;    tasks[3] = NULL;}void executeTask(){    for(int i = 0; tasks[i]; ++i) tasks[i]();}int main(){    initTask();    pid_t id = fork();    if(id == 0)    {        //child        int cnt = 5;        while(cnt)        {            printf("child is running, pid: %d, ppid: %d\n", getpid(), getppid());            cnt--;            sleep(1);        }        exit(123);    }    //father    int status = 0;    while(1)    {        pid_t rid = waitpid(id, &status, WNOHANG);        if(rid > 0)        {            printf("wait sucess, rid: %d, status: %d, exit code: %d\n", rid, status, WEXITSTATUS(status));            break;         }        else if(rid == 0)        {            printf("father say: child is running, do other things\n");            printf("##############task begin############\n");            executeTask();            printf("##############task end##############\n");        }        else         {            perror("waitpid");            break;        }        sleep(1);    }    return 0;}</code>
登录后复制

?✏️总结 在Linux下,进程退出提供了两个接口,exit() 和 _exit(),他们的区别就是 _exit()函数没有刷新缓冲区这一功能。 一个创建子进程的父进程有必要进行进程等待,用来防止进程变为僵尸进程从而危害系统。其中进程等待有两个接口 wait() 和 waitpid()。wait和waitpid:

区别

wait

waitpid

参数

*status

id, *status, options

状态

等待 任意 子进程退出,并返回终止子进程id

等待 指定 子进程退出,并返回终止子进程id

阻塞

无(默认阻塞调用)

0 和 WNOHANG(用来支持阻塞非阻塞的宏)

 根据你所期望进程执行的行为从而选择对应的接口。

创作不易,如果对您有帮助的话,还望留下一个小小的赞~~

以上就是Linux-进程控制的详细内容,更多请关注php中文网其它相关文章!

最佳 Windows 性能的顶级免费优化软件
最佳 Windows 性能的顶级免费优化软件

每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。

下载
来源:php中文网
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
最新问题
开源免费商场系统广告
热门教程
更多>
最新下载
更多>
网站特效
网站源码
网站素材
前端模板
关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新 English
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送
PHP中文网APP
随时随地碎片化学习

Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号