文章目录
- 一、进程创建
- fork函数初识
- fork之后,操作系统做了什么?
- 写时拷贝
- fork的常规用法
- fork调用失败的原因
- 二、进程终止
- 关于终止的正确认识
- 关于终止的常见做法
- _exit函数
- 关于终止,内核做了什么?
正文开始!
一、进程创建
fork函数初识
在Linux中fork函数是非常重要的函数,他从已存在进程中创建一个新进程。新进程称为子进程,而原进程为父进程。
返回值:子进程中返回0,父进程中返回子进程的id,出错返回-1。
进程调用fork,当控制转移到内核中的fork代码后,内核做:
- 分配新的内存块和内核数据结构给子进程
- 将父进程部分数据结构内容拷贝至子进程
- 添加子进程到系统进程列表当中
- fork返回,开始调度器调度
当一个进程调用fork之后,就有两个二进制代码相同的进程。而且他们都运行到相同的地方。但每个进程都将可以开始他们自己的执行。
看下面代码理解:
#include<stdio.h>
2 #include<unistd.h>
3
4 int main()
5 {
6 printf("我是一个进程,pid:%d\n",getpid());
7 fork();
8 printf("我依旧是一个进程:pid:%d\n",getpid());
9 sleep(1);
return 0;
}
这里我们看到三行输出,一行before,两行after。进程5034先打印before消息,然后他又打印after。另一个after消息由5035打印的。注意到进程5035没有打印before,为什么呢?如下图所示
所以,fork之前父进程独立执行,fork之后,父子两个执行流分别执行。注意fork之后,谁先执行完全由调度器决定。
那么fork之后,是否只有fork之后代码是被父子进程共享的??
一般情况下,fork之后父子进程共享所有的代码!!
子进程执行的后续代码!=共享的所有代码,只不过子进程只能从这里开始执行!!
那么这是为什么呢?
因为CPU中有一个eip,会保存正在执行指令的下一条指令!eip程序计数器会拷贝给子进程,子进程便从该eip所指向的代码处开始执行!!!
fork之后,操作系统做了什么?
进程=内核的进程的数据结构+进程的代码和数据
创建子进程的内核数据结构(struct task_struct+struct mm_struct+页表)+代码继承父进程,数据以写时拷贝的方式,来进行共享或者独立!
写时拷贝
通常情况下,父子代码共享,父子在不写入时,数据也是共享的,当任意一方试图写入,便以写时拷贝的方式各自有一份副本。具体如下图:
写时拷贝本身就是由OS的内存管理模块完成的!
那么为什么要写时拷贝呢?
创建子进程的时候,就把数据分开不行吗???
1.父进程的数据,子进程不一定全用,即便使用,也不一定全部写入。—会有浪费空间的嫌疑
2.最理想的情况,只有会被父子进程修改的数据,进行分离拷贝。不需要修改的共享即可—但是从技术角度实现复杂
3.如果fork的时候,就无脑拷贝数据给子进程,会增加fork的成本(内存和时间)
所以最终我们采用写时拷贝!!!(只会拷贝父子修改的,变相的就是拷贝数据的最小成本)
写时拷贝本质上是一个延迟拷贝策略。只有真正使用的时候,才给你!
就意味着你想要,但是不立马使用的空间,先不给你,那么也就意味着可以先给别人!
综上,就是变相的提高了内存的使用率!
fork的常规用法
- 一个父进程希望复制自己,使得父子进程同时执行不同的代码段。例如,父进程等待客户端请求,生成子进程来处理请求。
- 一个进程要执行一个不同的程序。例如子进程从fork返回后,调用exec函数。
fork调用失败的原因
1.系统中有太多的进程了
2.实际用户的进程数超过了限制
二、进程终止
关于终止的正确认识
我们使用C/C++的时候,main使我们的入口函数,函数的结束我们经常会写return 0;语句!
a.return 0,给谁return
根据下面的论述我们可以得出return是给父进程的!
b.为何是0?其他值可以吗?
答案是:进程代码跑完,结果是否正确
0:表示success
非零:表示失败
但是当我们失败的时候,我们最想知道的是失败的原因是什么?
所以我们用非零标识不同的原因!
首先我们先来了解常见的进程退出:
1.代码跑完,结果正确
2.代码跑完,结果不正确
3.代码没跑完,程序异常了
我们将return X;X称为进程退出码!表征进程退出的信息!得出的信息是要让父进程读取的!
先来看代码!
1 #include<stdio.h>
2 #include<unistd.h>
3
4 int main()
5 {
6
7 return 123;
8
9 }
10
我们可以看到程序只有一行代码,直接运行完成就退出了!
根据我们之前的学习,我们可以直到命令行的父进程就是bash进程
我们可以用当前指令查看退出码
echo $?
$?代表在bash中,最近依次执行完毕时,对应进程的退出码!
一般而言,失败的非零值我该如何设置呢??以及默认表达的含义?
1 #include<stdio.h>
2 #include<unistd.h>
3 #include<string.h>
4
5 int main()
6 {
7 for(int i=0;i<100;i++)
8 {
9 printf("%d:%s\n",i,strerror(i));
10 }
11 return 0;
12}
错误码退出码可以对应不同的错误原因,方便定位问题!
关于终止的常见做法
1.在main函数中return。为什么其它函数不行呢?
2.在自己的代码任意地点中,调用exit()函数!
man 3 exit
1 #include<stdio.h>
2 #include<unistd.h>
3 #include<string.h>
4
5 void func()
6 {
7
8 printf("hello func\n");
E> 9 exit(111);
10
11
12 }
13 int main()
14 {
15
16 func();
17 return 10;
18
19}
_exit函数
exit终止进程会刷新缓冲区
_exit直接终止进程,不会有任何刷新操作!
其实exit底层也是在调_exit函数。
关于终止,内核做了什么?
进程= 内核数据结构 + 进程代码和数据