💬 hello! 各位铁子们大家好哇。 今日更新了Linux的进程控制的内容 🎉 欢迎大家关注🔍点赞👍收藏⭐️留言📝
在linux中fork函数时非常重要的函数,它从已存在进程中创建一个新进程。新进程为子进程,而原进程为父进程。
#include <unistd.h>
pid_t fork(void);
//返回值:子进程中返回0,父进程返回子进程id,出错返回-1
进程调用fork,当控制转移到内核中的fork代码后,内核做:
当一个进程调用fork之后,就有两个二进制代码相同的进程。而且它们都运行到相同的地方。但每个进程都将可以 开始它们自己的旅程。
为什么父进程返回的是子进程的pid? 为了让父进程方便对子进程进行标识,进而进行管理。
通常,父子代码共享,父子不写入时,数据也是共享的,当任意一方试图写入,便以写时拷贝的方式各自一份副本。 (进程的独立性)
进程终止做的事:
内核数据结构中,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 }
任何命令行启动的进程,它的父进程都是bash,所以ppid都一样。 echo是内建命令,打印的都是bash内部的变量数据。?是一个变量名。 echo $?表示的是父进程获取到的,最近一个子进程退出的退出码。 main函数的返回值叫做进程的退出码。 退出码:
第一个echo ?返回./myprocess 的退出码,第二个echo ?返回上一个echo 虽然echo $?没有创建子进程,但它是由父进程执行的,所以他也会影响?的值。
#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函数会将错误码转成对应的错误描述,如下图;
父进程为什么要得到子进程的退出码呢? 因为要知道子进程的退出情况。(成功还是失败,失败的原因是什么),然后展现给用户看。 退出码可以使用系统默认的,也可以自定义。
进程终止的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 }
当外面运行上面代码后,会报段错误。 OS就会提前终止进程。
我们把代码里的野指针注释掉,此时代码正常运行,一直循环。此时我们给该进程发11号信号,该进程即使没有错误,收到信号后,也会进行对应的报错。 所以说进程出异常,本质是因为进程收到了OS发给进程的信号。
所以如果进程异常了,我们可以通过退出信号,就可以判断进程为什么异常了,此时的退出码是无意义的。 在用户层面上,要确定进程是什么情况:
衡量一个进程退出,只需要两个数字:退出码和退出信号。
进程的PCB里面有退出信号和退出码,当进程退出时,会释放代码和数据,但是PCB会保存一段时间,该进程变成Z(僵尸)状态。父进程就可以从子进程的PCB中拿到退出信息。
正常终止:
下面是exit的使用举例:
_exit和exit在使用上没什么区别,只有一个细微的差别,如下例子:
上图是带\n的。结果打印并且换行了。
上面是不带\n的。结果打印了但没换行。
上面是不带\n的_exit的使用。结果什么也没打印。
结论:exit会在进程退出的时候,冲刷缓冲区,_exit不会。
exit在底层是调用_exit的。 由上面的结论可得,缓冲区在_exit之上,不然_exit也会冲刷缓冲区。
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 // child
27 ChildRun();
28 printf("child quit ...\n");
29 exit(0);
30 }
31 // fahter
32 pid_t rid = wait(NULL);
33 if(rid > 0)
34 {
35 printf("wait success, rid: %d\n", rid);
36 }
37 }
作用:等待任何一个子进程退出 返回值:等待成功时,返回子进程的pid。失败返回-1。 参数: 输出型参数,获取子进程退出状态,不关心则可以设置成为NULL
运行上面的代码,结果如下图:
上面代码if后面不需要else就表示是父进程的代码了。因为if里面即子进程里面用exit退出了,所以后面的都是父进程的。
下面做出修改:
运行结果:
修改后的代码先让父进程休眠十秒。子进程运行五秒后退出,此时由于父进程还在休眠无法回收,所以子进程就变成Z状态,再过五秒后,子进程就被父进程回收了。
如果我们把sleep(10)注释掉,此时父进程开始就马上进入等待,等待期间父进程不会被调度。如果子进程没有退出,父进程其实一直在阻塞等待。
pid_ t waitpid(pid_t pid, int *status, int options); 返回值:
参数:
waitpid有三个参数,当pid,即第一个参数为-1时,等待任意一个子进程,与wait等效。 当第一个参数pid>0时,就会等待其进程ID与pid相等的子进程 。 如下图,此时等待上方父进程的子进程。
等待失败例子:
当我们把pid给一个错误的,此时进程就是等待失败。
第二个参数status代表的是子进程的退出信息。 退出信息=退出码+退出信号
最低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 }
上面是通过status退出信息来获取退出码和退出信号的代码。 status右移8位,然后与0xFF即二进制的8个1进行按位与,获取退出码。 status按位与0x7F即二进制的7个1来获取退出信号。 结果表明,前面所说都是正确的。
实际上我们不使用位操作符处理status,而是使用两个宏,WIFEXITED和WEXITSTATUS。
我们用的大部分接口都是阻塞等待接口,在阻塞等待时,父进程干不了别的事,一直在等待子进程退出。下面介绍非阻塞等待。
这里也需要用到一个宏: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 // child
27 ChildRun();
28 printf("child quit ...\n");
29 exit(123);
30 }
31
32 //father
33 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 else
50 {
51 printf("child quit unnormal!\n");
52 }
53 break;
54 }
55 else
56 {
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 :检测是成功的,只不过子进程还没退出,需要下一次进行重复等待。