当前位置: 首页 > news >正文

【Linux】进程控制

 🌈个人主页:秦jh__https://blog.csdn.net/qinjh_?spm=1010.2135.3001.5343
🔥 系列专栏:https://blog.csdn.net/qinjh_/category_12625432.html

9efbcbc3d25747719da38c01b3fa9b4f.gif

目录

fork函数初识

fork 函数返回值 

写时拷贝

 fork调用失败的原因

进程终止 

 进程退出场景

 进程常见退出方法

 进程等待

进程等待必要性

进程等待的方法

wait方法

 waitpid方法

 获取子进程status

非阻塞等待


前言

    💬 hello! 各位铁子们大家好哇。

             今日更新了Linux的进程控制的内容
    🎉 欢迎大家关注🔍点赞👍收藏⭐️留言📝

fork函数初识

在linux中fork函数时非常重要的函数,它从已存在进程中创建一个新进程。新进程为子进程,而原进程为父进程。

#include <unistd.h>
pid_t fork(void);
//返回值:子进程中返回0,父进程返回子进程id,出错返回-1

进程调用fork,当控制转移到内核中的fork代码后,内核做:

  • 分配新的内存块和内核数据结构给子进程
  • 将父进程部分数据结构内容拷贝至子进程
  • 添加子进程到系统进程列表当中 fork返回,开始调度器调度

4222d2554a3e4c3482974e91c4c48a30.png

当一个进程调用fork之后,就有两个二进制代码相同的进程。而且它们都运行到相同的地方。但每个进程都将可以 开始它们自己的旅程。 

fork 函数返回值 

  • 子进程返回0,
  • 父进程返回的是子进程的pid。 

为什么父进程返回的是子进程的pid?

为了让父进程方便对子进程进行标识,进而进行管理。 

写时拷贝

通常,父子代码共享,父子不写入时,数据也是共享的,当任意一方试图写入,便以写时拷贝的方式各自一份副本。 (进程的独立性)

 fork调用失败的原因

  • 系统中有太多的进程
  • 实际用户的进程数超过了限制 

进程终止 

进程终止做的事:

  • 释放曾经的代码和数据所占据的空间
  • 释放内核数据结构

内核数据结构中,PCB会被延期处理,因为有一种状态是僵尸状态。

 #include<stdio.h>2 #include<unistd.h>3 4 int main()5 {6   printf("I am process, pid:%d, ppid:%d\n",getpid(),getppid());7   sleep(2);8   return 100;                                                                                                                                            9 }

319ed017acbb4221a7e20a2b62f493c0.png

任何命令行启动的进程,它的父进程都是bash,所以ppid都一样。 

echo是内建命令,打印的都是bash内部的变量数据。是一个变量名。

echo $?表示的是父进程获取到的,最近一个子进程退出的退出码。

main函数的返回值叫做进程的退出码。

退出码:

  • 为0,标识成功
  • 不为0,表示失败

a681cdc819a542419cecb86022a7e7d7.png

第一个echo $?返回./myprocess 的退出码,第二个echo $?返回上一个echo $?的退出码

虽然echo $?没有创建子进程,但它是由父进程执行的,所以他也会影响?的值。 

024160f61dac4cfc85cbc3b89226a2bf.png

 #include<stdio.h>  2 #include<unistd.h>  3 #include<string.h>  4   5 int main()  6 {  7   for(int errcode=0;errcode<=255;errcode++)  8   {  9     printf("%d:%s\n",errcode,strerror(errcode));                                                                                                         10   }                                                                                                                       11   printf("I am process, pid:%d, ppid:%d\n",getpid(),getppid());                                                           12   sleep(2);                                                                                                               13   return 0;                                                                                                               14 }   

退出码不为0表示失败。不同的非0值,一方面表示失败,另一方面表示失败的原因。

strerror函数会将错误码转成对应的错误描述,如下图;2f7eae6278334a21bd3c64ad768dd2ca.png

父进程为什么要得到子进程的退出码呢?

因为要知道子进程的退出情况。(成功还是失败,失败的原因是什么),然后展现给用户看。

退出码可以使用系统默认的,也可以自定义。 

 进程退出场景

 进程终止的3中情况:

  1. 代码跑完,结果正确
  2. 代码跑完,结果不正确
  3. 代码异常终止

代码跑完,结果不正确的原因可以通过退出码确定。

一旦出现异常,退出码就没有意义了。

进程出异常,本质是因为进程收到了OS发给进程的信号。

 int main()  6 {  7   int *p=NULL;  8   while(1)9   {10     printf("I am a process,pid:%d\n",getpid());11     sleep(1);12     *p=100;//野指针                                                                                                                                      13   }                                                                                      19   sleep(2);                                                                                                                    20   return 0;                                                                                                      21 }         

c79e444ac1c14d4691619137518b0917.png

当外面运行上面代码后,会报段错误。 OS就会提前终止进程。

f5bd024b28a74553aa72b45b4af936c3.png

 我们把代码里的野指针注释掉,此时代码正常运行,一直循环。此时我们给该进程发11号信号,该进程即使没有错误,收到信号后,也会进行对应的报错。 所以说进程出异常,本质是因为进程收到了OS发给进程的信号。

所以如果进程异常了,我们可以通过退出信号,就可以判断进程为什么异常了,此时的退出码是无意义的。

在用户层面上,要确定进程是什么情况:

  1. 先确认是否异常
  2. 如果不是异常,就一定是代码跑完了,看退出码即可。

衡量一个进程退出,只需要两个数字:退出码和退出信号。

58d7e42eb536436d8d30361cd37dee9b.png

 进程的PCB里面有退出信号和退出码,当进程退出时,会释放代码和数据,但是PCB会保存一段时间,该进程变成Z(僵尸)状态。父进程就可以从子进程的PCB中拿到退出信息。

 进程常见退出方法

正常终止:

  1. main函数return,表示进程终止(非main函数的return,都只是表示函数结束)
  2. 调用exit函数  注意:在代码的任意位置调用exit,都表示进程退出
  3. _exit (系统调用)

下面是exit的使用举例: 

46d0faa80bd24f759e5c9572f12af158.png9c106357391140a581b5d83ed16b10b3.png


_exit和exit在使用上没什么区别,只有一个细微的差别,如下例子: 

65bd46c902a64a94b00829c6cf6698bc.pngc51ee9f6ed84403d8b4dee798ddc2d84.png

 上图是带\n的。结果打印并且换行了。

 ca30f4665c2a40039a65bfc4fd127b33.png0a40b2280e9741efb41f326a66f0d1eb.png

 上面是不带\n的。结果打印了但没换行。

6742125addad46eaa4ea95fbf9ae6d67.png 8a5cbfd368f143749dce3ce8a544cd7e.png

上面是不带\n的_exit的使用。结果什么也没打印。 

 结论:exit会在进程退出的时候,冲刷缓冲区,_exit不会。

73846c51dafc4242aa9ddbf97785c5f6.png

exit在底层是调用_exit的。 由上面的结论可得,缓冲区在_exit之上,不然_exit也会冲刷缓冲区。

 进程等待

进程等待必要性

  • 子进程退出,父进程如果不管不顾,就可能造成‘僵尸进程’的问题,进而造成内存泄漏。
  • 进程一旦变成僵尸状态,那就刀枪不入,“杀人不眨眼”的kill -9 也无能为力,因为谁也没有办法 杀死一个已经死去的进程。
  • 父进程派给子进程的任务完成的如何,我们需要知道。如,子进程运行完成,结果对还是不对, 或者是否正常退出。
  • 父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息

进程等待的方法

wait方法

  1 #include <stdio.h>                                                             2 #include <unistd.h>3 #include <string.h>4 #include <stdlib.h>5 #include <sys/types.h>6 #include <sys/wait.h>7 8 void ChildRun()9 {10     int cnt = 5;11     while(cnt)12     {13         printf("I am child process, pid: %d, ppid:%d, cnt: %d\n", getpid(), get    ppid(), cnt);14         sleep(1);15         cnt--;16     }17 }18 19 int main()20 {21     printf("I am father, pid: %d, ppid:%d\n", getpid(), getppid());22 23     pid_t id = fork();24     if(id == 0)25     {26         // child27         ChildRun();28         printf("child quit ...\n");29         exit(0);30     }31     // fahter32     pid_t rid = wait(NULL);33     if(rid > 0)34     {35         printf("wait success, rid: %d\n", rid);36     }37 }

1bac1ec205b14d62a97797e7dfcbe407.png

作用:等待任何一个子进程退出

返回值:等待成功时,返回子进程的pid。失败返回-1。 

参数: 输出型参数,获取子进程退出状态,不关心则可以设置成为NULL

运行上面的代码,结果如下图:

4f55d1b963d740bf9c21cd334c853893.png07a8e754be6149718b8baafab6e08c0c.png

 上面代码if后面不需要else就表示是父进程的代码了。因为if里面即子进程里面用exit退出了,所以后面的都是父进程的。

 下面做出修改:

2040a5225160487bae54789443ffac91.png

运行结果:

f3ef0f84845e444faad7a920a0897341.png

395d944bbd75468b9936c06fb78454ed.png

 修改后的代码先让父进程休眠十秒。子进程运行五秒后退出,此时由于父进程还在休眠无法回收,所以子进程就变成Z状态,再过五秒后,子进程就被父进程回收了。

21b2b53882e04cebbcdcea678b9f4761.png

 如果我们把sleep(10)注释掉,此时父进程开始就马上进入等待,等待期间父进程不会被调度。如果子进程没有退出,父进程其实一直在阻塞等待。

 waitpid方法

44b52cc9c6234dfe8b37ffe4fc075db1.png

pid_ t waitpid(pid_t pid, int *status, int options);

返回值:

  • 当正常返回的时候waitpid返回收集到的子进程的进程ID;
  • 如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则返回0;
  • 如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在;

参数:

  • pid:
    • Pid=-1,等待任一个子进程。与wait等效。
    • Pid>0.等待其进程ID与pid相等的子进程。
  •   status:
    • WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)
    • WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)
  • options:
    • WNOHANG: 若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则返回该子进程的ID。 

b539af90b6fc4f2d930fb6f54774eef9.png

waitpid有三个参数,当pid,即第一个参数为-1时,等待任意一个子进程,与wait等效。

当第一个参数pid>0时,就会等待其进程ID与pid相等的子进程 。

如下图,此时等待上方父进程的子进程。

 e1b3369a4d09403a83fc39f93abea3ed.png

等待失败例子:

267ce05c9408456eac8ccb2e3ba03255.png

当我们把pid给一个错误的,此时进程就是等待失败。 

 获取子进程status

9561a3f9229b4f85bd2f60442eb20512.png267e939dbf8e482abbf940c6c85eb65b.png

 第二个参数status代表的是子进程的退出信息。

退出信息=退出码+退出信号

fdeb1eae4f2d4a638ec006d69b6901db.png

  • wait和waitpid,都有一个status参数,该参数是一个输出型参数。
  • 如果传递NULL,表示不关心子进程的退出状态信息。
  • 否则,操作系统会根据该参数,将子进程的退出信息反馈给父进程。
  • status不能简单的当作整形来看待,可以当作位图来看待(只研究status低16比特 位):

 最低7位表示终止信号,9到16位表示退出码。所以退出码的范围是0~255。退出信号的范围是0~125。这两个范围足以表示退出码的退出信号的情况了。

 19 int main()                  20 {                           21     printf("I am father, pid: %d, ppid:%d\n", getpid(), getppid());22                             23     pid_t id = fork();      24     if(id == 0)             25     {                       26         // child            27         ChildRun();         28         printf("child quit ...\n");29         exit(1);            30     }                       31     sleep(7);               32     // fahter               33     // pid_t rid = wait(NULL);34     int status=0;           35     pid_t rid=waitpid(id,&status,0);36     if(rid > 0)             37     {                       38         printf("wait success, rid: %d\n", rid);39     }                       40     else                    41     {                       42       printf("wait failed !\n");43     }                       44     sleep(3);               45     printf("father quit, status:%d, child quit code : %d,child quit signal: %    d\n",status,(status>>8)&0xFF,status&0x7F);                                   46 } 

b968fef8bab94d0ea2eb1cf604e4da1d.png

上面是通过status退出信息来获取退出码和退出信号的代码。

status右移8位,然后与0xFF即二进制的8个1进行按位与,获取退出码。

status按位与0x7F即二进制的7个1来获取退出信号。

结果表明,前面所说都是正确的。

 实际上我们不使用位操作符处理status,而是使用两个宏,WIFEXITED和WEXITSTATUS。

0c3b4634e6fb4ebdb32d5fb1ccc41d80.png

0886a08b7f2740959fed79bad71261af.png

  • WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)
  • WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)

非阻塞等待

 我们用的大部分接口都是阻塞等待接口,在阻塞等待时,父进程干不了别的事,一直在等待子进程退出。下面介绍非阻塞等待。

这里也需要用到一个宏:WNOHANG

1 #include <stdio.h>2 #include <unistd.h>3 #include <string.h>4 #include <stdlib.h>5 #include <sys/types.h>6 #include <sys/wait.h>7 8 void ChildRun()9 {10     int cnt = 5;11     while(cnt)12     {13         printf("I am child process, pid: %d, ppid:%d, cnt: %d\n", getpid(), getppid    (), cnt);14         sleep(1);15         cnt--;16     }17 }18 19 int main()20 {21     printf("I am father, pid: %d, ppid:%d\n", getpid(), getppid());22 23     pid_t id = fork();24     if(id == 0)25     {26         // child27         ChildRun();28         printf("child quit ...\n");29         exit(123);30     }31   32     //father33     while(1)34     {                                                                              35         int status=0;36         pid_t rid=waitpid(id,&status,WNOHANG); //非阻塞等待37         if(rid==0)38         {39           sleep(1);40           printf("child is running, father check next time!\n");41           //DoOtherThing();42         }                                                                          43         else if(rid>0)44         {45           if(WIFEXITED(status))46           {     47             printf("child quit success, child exit code:%d\n",WEXITSTATUS(status));48           }49           else50           {51             printf("child quit unnormal!\n");52           }53           break;54         } 55         else56         {57           printf("waitpid failed!\n");58           break;59         } 60     }

使用WNOHANG的时候,需要使用循环结构。因为WNOHANG只会查看一次子进程是否结束,使用循环结构就可以到最后判断子进程是什么情况了。即非阻塞等待的时候+循环=非阻塞轮询。

在非阻塞等待时,父进程可以在每次查看子进程的间隙做其他事情。

pid_ t waitpid(pid_t pid, int *status, int options);

pid_t >0 :等待成功,子进程退出了,并且父进程回收成功

pid_t <0 :等待失败了

pid_t =0 :检测是成功的,只不过子进程还没退出,需要下一次进行重复等待。


http://www.mrgr.cn/news/34043.html

相关文章:

  • 转行要趁早!网络安全岗人才稀缺,前景广阔,零基础入门到精通,收藏这篇就够了
  • 亲测好用,ChatGPT 3.5/4.0新手使用手册,最好论文指令手册~
  • 刚刚更新| Stable diffusion 4.9.7 升级版终于来了!(Ai绘画无需部署,解压即用)
  • C++学习笔记----7、使用类与对象获得高性能(二)---- 理解对象生命周期(8)
  • 数据结构与算法——Java实现 11.习题——有序链表去重
  • [笔记]23年度展会信息— 吊钩 起升机构
  • ElasticSearch分页查询性能及封装实现
  • 数据结构之图论初识
  • 五类ip地址的区别是什么
  • MiniMind环境搭建训练推理测试
  • HBASE_题库详解
  • 一篇讲完HTML核心内容
  • 面试官:Vue.observable你有了解过吗?说说看
  • 时序建模基础——RevIN
  • 适合新手小白挖掘的高危逻辑漏洞
  • 中欧美三方,理解《人工智能安全治理框架》的特点
  • numpy.dot example
  • 一位架构师的自述:在尚未踏入的世界成为你自己
  • 打印机问题故障处理_十大打印机故障大全及处理方法
  • 干耳屎硬掏不出来怎么办?双十一好用的可视挖耳勺推荐