Linux系统编程——控制进程

文章目录

进程创建

fork

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

  • 分配新的内存块核心数据结构给子进程
    (进程PCB进程地址空间页表构建映射关系)
  • 将父进程部分数据结构内容拷贝至子进程
  • 添加子进程到系统列表当中
  • fork返回后,开始调度器调度

写时拷贝

在这里插入图片描述
页表中需要有2^32个地址需要映射,如果页表每一部分6字节
则一共需要24GB

缺页中断:父子当中突然改变了数据,OS就发生了中断,但是之前那个空间是共享的,OS就开辟了一个空间,把老的空间给拷过来,OS重新修改了页表,映射关系,把只读选项给去掉了,保持进程的独立性
有了写时拷贝,就能保证父子进程数据的独立性

进程终止

把进程开辟的东西都释放掉

进程退出场景

  1. 代码运行完毕,结果是正确的
  2. 代码运行完毕,结果是错误的
  3. 代码异常中止
1
return 0
  • 为什么main函数要return 0,意义在哪里?

进程的退出码,衡量代码跑完了,是对还是不对,
return 0代表success
return 100
echo $?查看退出码
在这里插入图片描述
退出码会被父进程继承
echo $?
会输出最近一次进程退出时的退出码
在这里插入图片描述
而第二次输出0
是echo命令的退出码

错误的退出码

每个退出码都有其对应的错误原因

1
2
3
4
5
6
7
8
9
10
11
12
13
 1 #include<stdio.h>
2 #include<string.h>
3 int main()
4 {
5 //printf("hello\n");
6 int i=0;
7 for(i=0;i<100;i++)
8 {
9 printf("%d: %s \n",i,strerror(i));//返回错误信息,;里面对应的i就是返回的错误码,返回的信息就是对应的错误信息
10 }
11
12 return 123;
13 }

在这里插入图片描述
错误码与错误描述

代码异常终止

运行终止的退出码,我们并不关系

进程常见退出方法

1.main函数return,代表进程退出!!
而非main函数

2.exit(退出码)进程退出程序
在任意地方使用都是让进程退出
参数是退出码

都可以达到进程退出的目的

3._exit(退出码)
强制终止进程,不要进行进程的后续收尾工作,如刷新缓冲区,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
int fun()
{
printf("fun test");
return 1;
}
int main()
{
//fun();
//printf("hello\n");
//int a=10;
//a/=0;//跑到一半异常终止
//在vs中一般叫程序崩溃
//

// int i=0;
// for(i=0;i<140;i++)
// {
// printf("%d: %s \n",i,strerror(i));//返回错误信息,;里面对应的i就是返回的错误码,返回的信息就是对应的错误信息
// }

printf("hello");//此时数据被暂时保存在输入缓冲区中
sleep(3);
_exit(12);
// exit(EXIT_SUCCESS);
//exit或return 本身就是会要求系统进行缓冲区刷新
//return 123;
}

用户级缓冲区
在这里插入图片描述
进程退出在操作系统层面做了什么?

系统场面上少了一个进程,free PCB,free 页表,mm_struct,页表和各种映射关系,代码+数据申请的空间也要被释放掉

进程等待

进程等待是什么?

fork():创建子进程(为了帮助父进程某种任务),父进程
让我们的父进程fork之后,需要通过wait/waitpid来等待子进程退出

为什么要让父进程等待呢

  1. 通过获取子进程退出的信息,能够得知子进程执行结果
  2. 可以保证,时序问题,子进程先退出,父进程后退出
  3. 进程退出的时候,会先进入僵尸状态,会造成内存泄漏的问题,需要通过父进程wait,来释放该子进程占用的空间(子进程的僵尸资源)

进程进入了僵尸进程,kill -9就没有办法杀掉该进程

进程等待的方法

wait

作用是等待任意一个子进程
头文件

1 #include <stdio.h>
2 #include<sys/types.h>
3 #include<sys/wait.h>

成功返回被等待进程的pid,失败就返回-1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
#include <stdio.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<unistd.h>
#include<stdlib.h>
int main()
{
pid_t id=fork();
if(id==0)
{
//child
int cnt=5;
while(cnt){
printf("child[%d] is running : cnt is :%d \n",getpid() ,cnt);
cnt--;
sleep(1);
//子进程会持续5秒之后就exit,但是父进程;立刻就退出了
//子进程就会变成一个孤儿进程,被系统领养
}
exit(0);//我们执行完子进程之后直接退出,不执行父进程的代码
//进程退出
}
sleep(10);//先让父进程等待10秒
//前5秒是正常运行的
//后5秒子进程处于z状态
printf("father wait begin\n");
//因为父进程和子进程是同时执行的
//parent
//为了不让子进程变成一个孤儿进程,父进程必须在那里等待
pid_t ret=wait(NULL);//成功的话就返回其
if(ret>0)
printf("father wait %d\n ",ret);
else
printf("father wait fail\n");
//到这里僵尸状态就没了
sleep(10);
//回收完毕之后父进程继续活上10秒
return 0;
}

在这里插入图片描述

一开始前5秒都共同运行着,5秒之后,子进程退出,进入z状态,因为父进程还在sleep,没有去wait,再过5秒,父进程开始执行wait,wait成功,僵尸进程就没了,再过了10秒钟,父进程就退出了

wait完全可以回收僵尸进程

waitpid

成功了就会返回其等待的子进程的pid,错了就会返回-1

1
2
3
4
5
6
#include <sys/types.h>
#include <sys/wait.h>

pid_t wait(int *wstatus);

pid_t waitpid(pid_t pid, int *wstatus, int options);

waitpid就是一个系统调用,
status就是一个输出形参数
最终一定要让父进程,通过status,得到子进程执行的结果
正常退出代码
在这里插入图片描述

异常终止代码
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

bash是命令行启动的所有进程的父进程|
bash一定是通过wait 方式得到子进程的退出结果,所以我们能看到echo$?
查看到子进程的退出码

status:
WIFEXITED(status):若进程正常退出,就为真
WEXITSTATUS(status):若WIFEXITED非0,就可以提取子进程的退出码
(就不要进行麻烦的位操作)

在这里插入图片描述
option
0为默认行为,阻塞等待
掉wait的时候,父进程啥也不干,就在哪里等子进程,等待子进程退出,
WNOHANG:设置等待方式为非阻塞等待

阻塞等待

啥也不干,就在那里静静的等待,就等子进程退出才会返回

阻塞的本质
就是将进程的PCB被放入到等待队列当中,并将进程的状态改为S状态,
返回的本质,进程的PCB 从等待队列中拿到R 队列,从而被CPU 调度,

在这里插入图片描述

非阻塞等待

检测子进程的运行状态,等待的过程中,可能需要多次检测:
基于非阻塞等待的轮询方案,

调用一个接口,CPU立马返回,CPU不断重复的调度父进程,就是重复调度waitpid的过程

无论是阻塞还是非阻塞,都是等待的一种方式,
谁等(父进程)
等(子进程)
等待子进程退出(条件/事件)

我们看到吗某些运用或者OS本身,长时间不动,是应用或程序hang住了
WNOHANG:非阻塞

  1. 返回值结果,子进程改变就没有退出,
    
  2. 子进程退出,waitpid(调用成功or失败),
    
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
#include <stdio.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<unistd.h>
#include<stdlib.h>
int main()
{
pid_t id=fork();
if(id==0)
{
//child
int cnt=5;
while(cnt){
printf("child[%d] is running : cnt is :%d \n",getpid() ,cnt);
cnt--;
sleep(1);
//子进程会持续5秒之后就exit,但是父进程;立刻就退出了
//子进程就会变成一个孤儿进程,被系统领养
}
exit(0);//我们执行完子进程之后直接退出,不执行父进程的代码
}
int status=0;

//pid_t ret=waitpid(id,&status,0);//阻塞等待
while(1)//非阻塞的轮询方案
{
pid_t ret=waitpid(id,&status,WNOHANG);//非阻塞等待
if(ret==0){

//检测子进程没有退出,但是waitpid等待是成功的,需要父进程继续进行等
printf("do father thing\n");//得知还没好,就可以做一下自己的事情

}
else if(ret>0)
{

//子进程退出了,waitpid也成功了,获取到了对应的结果
printf("exit code: %d\n,",WEXITSTATUS(status));
break;
}
else
{
//ret<0
//等待失败
perror("waitpid");
break;
}
sleep(1);
}
return 0;

}

Linux系统编程——控制进程
http://example.com/2022/03/31/Linux系统编程——控制进程/
作者
Zevin
发布于
2022年3月31日
许可协议