送给大家一句话: 我并不期待人生可以一直过得很顺利,但我希望碰到人生难关的时候,自己可以是它的对手。—— 加缪
通过对进程的学习,我们对虚拟地址,页表,物理地址有了大概认识。我们平时使用的地址都是虚拟地址,通过页表可以访问物理地址(统一的视角进行控制,保证数据安全)。也认识到写时拷贝
。
也认识O(1)调度算法,通过两个队列(活跃队列,过期队列)完成进程的分时控制,通过优先级来放入不同位置,以时间复杂度O(1)快速寻找进程。
在linux中fork函数时非常重要的函数,它从已存在进程中创建一个新进程。新进程为子进程,而原进程为父进程。fork函数具有两个返回值,通过对返回值的判断(if else )可以进行父进程和子进程的不同书写。 注意:进程调用fork,当控制转移到内核中的fork代码后,内核做以下工作:
这里是为了保证父进程和子进程的独立性。
那为什么父进程返回子进程PID ,给子进程返回0呢??? 很好理解:就像现实生活中,父母有了孩子,会给他或她起一个名字,父母知道了名字,就可以很好管理孩子。父进程与子进程同理,父进程为了便于管理子进程,所以fork函数会返回对应子进程的pid。
通过图解可以很好理解写时拷贝。
在创建子进程的时候,子进程的页表映射与父进程一致(默认继承的),一旦子进程要进行修改数据,为了保证进程的独立性(保证父进程安全运行),不得不开辟一个新空间,并修改子进程页表的映射(虚拟地址不变!)。
一般使用if else 分开书写,也可以通过系统调用打开新的进程。
进程终止会进行:
但是task_struct会延期处理,因为终止的进程处于Z状态(僵尸进程)
我们的main函数常常会有一个返回值 0 ,那为什么要返回零呢???
1 #include<stdio.h>
2 #include<unistd.h>
3 #include<string.h>
4
5
6 int main()
7 {
8
9 printf("I am parent process,pid:%d,ppid:%d\n",getpid(),getppid());
10 sleep(2);
11
12 return 100;
13 }
来看我们返回100(为了效果明显)时
echo 打印的是bash 的环境变量,这个100 就是刚才进程返回到父进程(bash)的退出码(环境变量 ?
表示最近一个进程的退出码),一般0表示正常运行,非零表示有问题。
父进程关心子进程的信息,想要知道子进程是否正常运行。不同的退出码表示不一样的失败原因,我们来获取一下:
1 #include<stdio.h>
2 #include<unistd.h>
3 #include<string.h>
4
5 int g_val = 10000;
6
7 int main()
8 {
9
10 for(int errcode = 0 ;errcode <=255; errcode++)
11 {
12 printf("%d: %s\n",errcode,strerror(errcode));
13
14 }
15 printf("I am parent process,pid:%d,ppid:%d\n",getpid(),getppid());
16 sleep(1);
17
18 return 0;
19 }
这样就可以获取所有的退出码和对应的退出信息了:
通过退出码就能获取对应退出信息,来告知用户为什么退出了。其底层实现也好理解,通过数字与字符串的一一对应来做到。 常见进程退出场景:
就像:VS编程运行的时候,如果崩溃了 — 操作系统发现你的进程做了不应该做的事情,OS就杀死进程!!! 一旦出现异常,退出码就没有意义了!!! 为什么出异常才是最重要的!!! 那为什么会出现异常呢??? 原因是:进程出现异常的本质是进程收到来自OS发给进程的信号!(kill -9 就是一个信号)
注意:
正常终止(可以通过 echo $? 查看进程退出码):
来看手册中如何描述的:
调用exit 函数试试:
1 #include<stdio.h>
2 #include<unistd.h>
3 #include<string.h>
4 #include<stdlib.h>
5 int g_val = 10000;
6
7 int main()
8 {
9
10 printf("I am parent process,pid:%d,ppid:%d\n",getpid(),getppid());
11 sleep(1);
12 exit(123);
13
14 }
15
运行后是这样的效果:
exit比return 直接,调用一次就可以完全退出!
_exit 是一个系统调用(system call),参数与exit一致,使用与exit几乎一模一样。
**但是exit会冲刷缓冲区,而_exit 不会,**因为缓冲区在系统调用之上 ,而exit 是一个C语言库函数。图解:
exit最后也会调用_exit, 但在调用_exit之前,还做了其他工作:
也就是说,任何进程在退出时都要被父进程进行等待,不然子进程处于僵尸进程就会造成内存泄漏!!!
#include<sys/types.h>
#include<sys/wait.h>
pid_t wait(int*status);
返回值:
成功返回被等待进程pid,失败返回-1。
参数:
输出型参数,获取子进程退出状态,不关心则可以设置成为NULL
我们测试一下:
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 ,pid:%d,ppid:%d,cnt:%d\n",getpid(),getppid(),cnt);
14 sleep(1);
15 cnt--;
16 }
17 }
18
19 int main()
20 {
21
22 printf("I am father ,pid:%d,ppid:%d\n",getpid(),getppid());
23
24 pid_t id = fork();
25 if(id == 0)
26 {
27 //child
28 Childrun();
29 printf("child quit...\n");
30 exit(0);
31 }
32 //father
33 sleep(10);
34 pid_t rid = wait(NULL);
35 if(rid > 0)
36 {
37 printf("wait success,rid:%d\n",rid);
38 }
39 sleep(3);
40 return 0;
41 }
这个程序会在子进程运行结束前等待子进程,并且会存在一段时间的窗口期,此时子进程处于僵尸进程:
在这个父进程等待的过程中,父进程一直在等待子进程的退出,处于阻塞等待状态。父进程本质是等待某种软件条件就绪,那么如何理解阻塞等待子进程呢??? 就是把自己列入等待队列,把状态列入不运行状态,等待子进程(类似scanf 的阻塞)。
#include<sys/types.h>
#include<sys/wait.h>
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
pid_t rid = waitpid(-1,NULL,0);
与刚才的wait等价!!!如果id不为-1,是一个对应的id,那么就会等待指定进程!!!,如果id错误(不存在该进程),就会发生等待错误!!!
status
是一个输出型参数,需要我们传入一个指针来获取。来测试一下(子进程退出码设置为1 )
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 ,pid:%d,ppid:%d,cnt:%d\n",getpid(),getppid(),cnt);
14 sleep(1);
15 cnt--;
16 }
17 }
18
19 int main()
20 {
21
22 printf("I am father ,pid:%d,ppid:%d\n",getpid(),getppid());
23
24 pid_t id = fork();
25 if(id == 0)
26 {
27 //child
28 Childrun();
29 printf("child quit..\n");
30 exit(1); //退出码设置为1
31 }
32 //father
33 sleep(10);
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 printf("father quit...,status:%d\n",status);
41 sleep(3);
42 return 0;
43 }
这就成功获取了status! 我们需要的是 退出码 和 退出信号,那么我们如何通过status获取这两个数据呢???
也就通过位运算就可以成功获取了。
![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/2650b18879684c50981074b8e6bbdf7d.png)
这样就可以了:
通过两个信息就可以判断进程是否正常运行,如果异常,也能知道异常原因了。
当然,如果使用位运算就有点那啥了,我们可以使用宏:
对于第三个参数,就可以让父进程在等待的刚才中区做其他事情。也就是进行非阻塞等待:WNOHANG: 若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则返回该子进程的ID
下面写入了一段非阻塞轮询等待的代码,这样就能保证父进程在等待的过程中,可以去做其他事情!
34 while(1)
35 {
36 int status = 0 ;
37 pid_t rid = waitpid(id , &status , 0);
38 if(rid == 0)
39 {
40 sleep(1);
41 printf("child is running ,father check next time!\n");
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 }
来看运行效果(父进程一直在查):
这样就完成了。