索引
- fork+父子进程在理解
- 进程控制
- 1.进程终止
- 2.进程等待
- 3.获取进程状态Status
fork+父子进程在理解
fork之后,内核做了什么?
- 将分配新的内存块和内核数据结构给子进程
- 将父进程部分数据结构拷贝至子进程
- 添加子进程到系统列表中
- fork返回开始调度器调度
fork父子进程共享所有的代码 != 子进程执行的后续代码,子进程执行的代码只能从fork之后开始,cpu读取指令时一般直接向eip寄存器要,然后eip中存储的指令++,指向下一条执行指令,而fork之后eip寄存器此时存储的是下一条指令,子进程拷贝父进程的eip,因此便只能从fork之后的代码执行了
#include<stdio.h>
2 #include<unistd.h>
3 int main()
4 {
5 printf("我是一个进程:pid: %d\n",getpid());
6 fork();
7 printf("我依旧是一个进程:pid:%d\n",getpid());
8 return 0;
9
10 }
从上述看出,foek之后父子进程共享代码,但也是只从fork之后子进程才会执行代码,由于进程具有独立性,代码和数据都必须独立,所以此时父子进程共享的代码只能读取,那么如果有一个进程非要修改数据呢?
此时父子进程会发生写实拷贝解决问题,刚开始父子进程的代码和数据都是只读的,但是发生写实拷贝之后,代码还是只读,但是此时可以写入数据,写实拷贝只是拷贝被写入的数据,没有写入的数据父子进程照样共享
为什么要写实拷贝?直接在创建子进程的时候,将数据分开不行吗?
- 父进程的数据,子进程不一定全用,即便全用,也不一定全部写入 ----浪费资源
- 最理想的情况,只有会被父子修改的数据,进行分离拷贝,不需要修改共享 ------技术上实现复杂(因为代码要被编译才能知道哪些数据是被写入的,哪些数据是没有被写入的)
- 如果fork之后就无脑拷贝给子进程,会增加fork的成本
fork调用失败可能原因
- 系统中有太多的进程
- 实际用户的进程数超过了限制
进程控制
1.进程终止
常见进程退出有三种情况:
1.代码跑完,结果正确
2.代码跑完,结果不正确
3.代码没跑完,程序异常了
在C/C++中在main函数最后都会有return 0;其实无脑return 0是不正确的行为,应该是return X,其中X表示进程退出码表征进程退出信息,进程的退出信息在将来进程退出的时候给父进程读取的,可以参考一下C语言中默认的错误码描述
可以用echo $?
查看进程退出码
1 #include<stdio.h>
2 #include<unistd.h>
3 #include<stdlib.h>
4 #include<string.h>
5 int main()
6 {
7
8 for(int i = 0; i < 100; i++){
9 printf("%d: %s\n",i,strerror(i));
10 }
11 printf("\n");
12 return 123;
echo虽然是一个内置命令不会生成子进程,但还是将其看做一个运行成功的程序,图中第一个echo $?表示的是我们自己的程序,此时退出码是123,第二个echo $?表示的是上一个命令echo本身的退出情况。
进程正常退出时:
1.从main返回
2.调用exit(系统调用)
3_exit
#include <unistd.h>
void _exit(int status);
status:定义了进程的终止状态
其中父进程通过wiat来获取状态
其中,status虽然是int,但是仅仅是有低八位可以被父进程使用,
所以如果exit(-1),在终端执行$?发现返回的是255
10 printf("测试_exit函数\n");
11 _exit(321);
12 return 0;
还有一个函数与_exit相似,exit
#include <unistd.h>
void exit(int status);
exit最后也会调用_exit,只是在调用—_exit之前还做了其他工作
1.执行用户定义的清理函数
2.关闭所有打开的柳,所有的缓存数据均被写入
3.调用_exit
exit终止进程,刷新缓冲区,一秒钟之后消息被打印出来了,但是调用_exit的函数会直接终止进程,不会有任何刷新操作,所以一秒之后消息没有被刷新出来
关于进程终止,内核做了什么?
进程状态是Z的时候,父进程读取退出码信息后,将状态设置成X后释放代码和数据,其中操作系统会自己形成一个链表,将废弃的内核结构放在链表之中,当有新的进程形成内核数据结构时节省了开辟空间所需要的时间,直接将内核数据结构初始化即可。
2.进程等待
什么是进程等待?
进程等待是指一个进程暂停自己的执行,等待另一个进程执行完毕或发送信号给它,然后再继续执行,通常情况下,父进程会等待子进程完成,以确保子进程的结果能够被正确处理
为什么要进行进程等待?
- 子进程退出,如果父进程如果没有及时获取退出状态信息,该子进程的文件描述符和其他资源可能一直保留,经常造成内存泄漏
- 父进程再给子进程派发任务时,我们需要知道子进程运行完成后结果是对还是不对,是否是正常退出
- 父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息
如何进行进程等待
wait方法
#include<sys/types.h>
#include<sys/wait.h>
pid_t wait(int*status);
参数:输出型参数,获取子进程的退出状态,不关心则可以设置成null
返回值:成功则返回被等待进程的pid,失败返回-1
16 while(1)
17 {
18
19 printf("我是子进程,我正在运行,我的pid: %d\n",getpid() );
20 sleep(1);
21 flag++;
22 if(flag == 5){
23 exit(0);
24 }
25
26 }
27 }
28 else
29 {
30 printf("我是父进程pid: %d,,准备等待子进程!\n",getpid()) ;
31 sleep(10);
32 pid_t ret = wait(NULL);
33 if(ret < 0) {
34 printf("等待失败!\n");
35 }else {
36 printf("等待成功:result: %d\n",ret);
37 }
38 sleep(20);
39
40 }
41 return 0;
42 }
此时等待成功,原本父子进程的状态都是S,然后子进程变成Z,子进程退出之后就被回收,然后父进程接着运行
如果子进程不退出的话,那么父进程的代码会一直堵在wait()函数部分,因为wait默认是阻塞式等待,只要wait没有等到进程退出,就会一直卡在那。
waitpid方法
pid_ t waitpid(pid_t pid, int *status, int options);
参数:
1.pid:
1、pid = -1,等待任意一个子进程,与wait等效
2、pid > 0,等其进程ID与pid相等的子进程
2.status:
输出型参数,由操作系统填充
如果传递NULL表示不关心子进程的退出状态
3.options:
1、WNOHANG:若pid指定的子进程没有结束,
则waitpid()函数返回0,不予等待,
2、若正常结束,则返回该子进程的id
(如果加了这个参数就不是阻塞式等待了,就是非阻塞式等待)
返回值:
1.当正常返回的时候waitpid返回收集到的子进程的进程id
2.如果设置了选项WNOHANG,而调用中waitpid发现没有退
出的子进程可以收集,则返回0、
3.如果调用中出错,则返回-1,此时errno会被设置成相应的值以指示错误
8 int main()
9 {
10
11 pid_t id = fork();
12 if(id == 0)
13 {
14 int flag = 1;
15 //child
16 while(1)
17 {
18
19 printf("我是子进程,我正在运行,我的pid: %d\n",getpid() );
20 sleep(1);
21 flag++;
22 if(flag == 5){
23 break;
24 }
25
26 }
27 exit(12);
28 }
29 else
30 {
31 int status = 0;
32
33 printf("我是父进程pid: %d,,准备等待子进程!\n",getpid()) ;
34 sleep(1);
35 // pid_t ret = wait(NULL);
36 // pid_t ret = waitpid(id,&status,0);
37 pid_t ret = waitpid(id,&status,WNOHANG);
38 if(ret < 0) {
39 printf("等待失败!\n");
40 }else {
41 printf("等待成功:result: %d\n",ret);
42 }
43
44 sleep(2);
45 }
46 return 0;
}
此时是非阻塞式等待,父进程运行到waitpid()时,子进程还未退出,当父进程运行完自己代码的时候,直接退出,子进程变成了孤儿进程,此时子进程会托管给1号进程也就是bash。
3.获取进程状态Status
先了解一下什么是信号
在Linux中,信号是一种用于进程间通信的机制,信号可以被一个进程发送给另一个进程,用于通知或中断目标进程的执行。
在Linux中kill命令用于向指定进程发送信号,从而控制进程的行为和状态
语法格式:
kill [signal] PID
signal:信号编号
PID:进程的PID
wait/waitpid都有一个status参数,但是其不能当做一个简单的整形看待,当做位图看待,(只研究status低16比特位的情况);
8 int main()
9 {
10
11 pid_t id = fork();
12 if(id == 0)
13 {
14 int flag = 1;
15 //child
16 while(1)
17 {
18
19 printf("我是子进程,我正在运行,我的pid: %d\n",getpid() );
20 sleep(1);
21 flag++;
22 /* if(flag == 5){
23 break;
24 }*/
25
26 }
27 // exit(12);
28 }
29 else
30 {
31 int status = 0;
32
33 printf("我是父进程pid: %d,,准备等待子进程!\n",getpid()) ;
34
35 // pid_t ret = wait(NULL);
36 // pid_t ret = waitpid(id,&status,0);
37 pid_t ret = waitpid(id,&status,0);
38 if(ret > 0){
39 printf("wait success,ret : %d,我所等待的子进程的退出> 码 :%d,退出信号是 :%d\n",\
40 ret,(status>>8)&0xFF,status&0xFF);
41 }
49 }
50 return 0;
51 }
上述代码子进程一直循环,父进程阻塞式等待,用kill-9杀死子进程,此时父进程收到的退出码为0,退出信号为9
LInux中还可以用宏来表示status输出型参数
Linux中还也可以用 WEXITSTATUS(status)宏表示进程的退出码
WIFEXITED(status):表示进程是否正常退出
WIFSIGNALED(status):非0表示被信号终止
WTERMSIG(status):获取终止信号的编号
总结:
退出码表示的是有没有跑完,结果对不对
退出信号表示的进程是否异常,进程一旦异常,我们只需要关心退出信号即可,关心退出码没有任何意义