一、进程创建
fork函数初识
在linux中fork函数时非常重要的函数,它从已存在进程中创建一个新进程。新进程为子进程,而原进程为父进程。
fork()创建进程是以父进程为模板的
进程调用fork,当控制转移到内核中的fork代码后,内核做:
- 分配新的内存块和内核数据结构给子进程
- 将父进程部分数据结构内容拷贝至子进程
- 添加子进程到系统进程列表当中
- fork返回,开始调度器调度
fork()之前为一个进程,而之后为两个进程,
当一个进程调用fork之后,就有两个二进制代码相同的进程。而且它们都运行到相同的地方。但每个进程都将可以
开始它们自己的旅程,看如下程序。
#include<iostream>
#include<stdlib.h>
#include<unistd.h>
using namespace std;
int main(){
cout<<"我是父进程 pid为:"<<getpid()<<endl;
pid_t id=fork();
if(id==0){
cout<<"我是子进程 pid为:"<<getpid()<<endl;
sleep(1);
}else if(id>0){
cout<<"我是父进程 pid为:"<<getpid()<<endl;
sleep(1);
}
return 0;
}
fork()之后子进程为什么会执行fork()之后的为什么没有从fork()之前执行?
所以,fork之前父进程独立执行,fork之后,父子两个执行流分别执行。注意,fork之后,谁先执行完全由调度器
决定。
子进程在创建时将父进程部分数据结构内容拷贝至子进程,而父进程也将程序计数器拷贝给了子进程,所以父子进程是从同一个地方开始执行的。
fork函数返回值
子进程返回0,
父进程返回的是子进程的pid。
为什么fork()有两个返回值?
cout<<"我是父进程 pid为:"<<getpid()<<endl;
pid_t id=fork();
if(id==0){
cout<<"我是子进程 pid为:"<<getpid()<<endl;
sleep(1);
}else if(id>0){
cout<<"我是父进程 pid为:"<<getpid()<<endl;
当执行fork()时进入内核 fork()前边的代码由内核实现,而retrun语句有进程执行,当前边的有内核执行完进程已经创建完毕,已经开始执行了,而return语句分别有父子进程执行当父进程执行时返回的是子进程的pid,子进程执行时返回的是0。
写时拷贝
默认情况下。父子进程共享代码,但是数据各自是有一份!
代码共享:所有代码共享,不过一般都是从fork()之后开始执行,为什么代码是共享的?代码是不可修改的,所以各自私有一份浪费空间
数据为什么是私有的?因为进程之间具有独立性。数据是很多的,不是所有的数据都立马要使用,且不是所有的数据都需要拷贝。但是如果立马要独立,就需要将数据全部拷贝下来,把本来可以可以后边拷贝,甚至不需要的数据,都拷贝下来
就浪费时间和空间。这就有了写时拷贝
通常,父子代码共享,父子再不写入时,数据也是共享的,当任意一方试图写入,便以写时拷贝的方式各自一份副
本。具体见下图:
创建子进程时将父进程的数据结构拷贝给子进程,所以父子进程页表指向的物理内存地址也一样。
当子进程五王数据段写入数据时,因为父子进程的数据段对应的物理内存地址时共享的,为了不影响父进程会重新开辟一段空间,调整映射关系。写时拷贝是由os完成
问题:
- 1 写时拷贝什么时候用
- 原理是当父进程创建子进程时 页表将对应的代码段和数据段上设置为只读状态,当写入数据时。
- 2 如何理解子进程创建?如何理解fork()?
本质就是多了一个进程,子进程要以父进程为模板
二、进程终止
代码运行完毕,结果正确
代码运行完毕,结果不正确
代码异常终止
进程常见退出方法
正常终止(可以通过echo $? 查看进程退出码):
- 从main返回
- 调用exit
- _exit
异常退出:
- ctrl + c,信号终止
_exit函数
说明:虽然status是int,但是仅有低8位可以被父进程所用。所以_exit(-1)时,在终端执行$?发现返回值
是255。
exit函数
exit最后也会调用exit, 但在调用exit之前,还做了其他工作
1 执行用户通过 atexit或on_exit定义的清理函数。
2. 关闭所有打开的流,所有的缓存数据均被写入
3. 调用_exit
return退出
return是一种更常见的退出进程方法。执行return n等同于执行exit(n),因为调用main的运行时函数会将main的返
回值当做 exit的参数。