进程控制之进程创建与终止
进程创建
1 fork函数初识
在linux中fork函数是非常重要的函数,它从已存在进程中创建一个新进程。新进程为子进程,而原进程为父进程。
#include <unistd.h>
pid_t fork(void);
返回值:子进程中返回0,父进程返回子进程id,出错返回-1
父进程调用fork后,内核做:
• 分配新的内存块和内核数据结构给子进程
• 将父进程部分数据结构内容拷贝至子进程
• 添加子进程到系统进程列表当中
• fork返回,开始调度器调度
当一个进程调用fork之后,就有两个二进制代码相同的进程。而且它们都运行到相同的地方。但每个进程都将可以开始它们自己的旅程,看如下程序。
int main( void )
{pid_t pid;printf("Before: pid is %d\n", getpid());if ( (pid=fork()) == -1 )perror("fork()"),exit(1);printf("After:pid is %d, fork return %d\n", getpid(), pid);sleep(1);return 0;
}
运行结果:
[root@localhost linux]# ./a.out
Before: pid is 43676
After:pid is 43676, fork return 43677
After:pid is 43677, fork return 0
这里看到了三行输出,一行before,两行after。进程43676先打印before消息,然后它有打印after。另一个after消息有43677打印的。注意到进程43677没有打印before,为什么呢?如下图所示
所以,fork之前父进程独立执行,fork之后,父子两个执行流分别执行。注意,fork之后,谁先执行完全由调度器决定。
2 写时拷贝
通常,父子代码共享,父子不写入时,数据也是共享的,当任意一方试图写入,便以写时拷贝的方式各自一份副本。具体见下图:
当子进程尝试写入数据时,操作系统就会“报错”(不是传统意义上的报错,类似于触发了判断机制),操作系统要对当前情况进行判断分类
1 子进程想要写入数据的地址不属于它,而是一个未知的地址,即野指针,该情况是真的错误,终止进程
2 子进程需要在它的数据段中写入数据,则会触发写时拷贝,同时更改回权限(即将权限更改为之前的设置)
因为有写时拷贝技术的存在,所以父子进程得以彻底分离离!完成了进程独立性的技术保证!
写时拷贝,是一种延时申请技术,它的本质是“按需获取”可以提高整机内存的使用率
**扩展问题:**当你在C/C++上申请空间malloc/new的时候,需要在物理内存中开辟空间吗?不需要!
当你申请空间时开辟虚拟空间即可,只有当这个空间要被用到时,操作系统才会做内存级申请,而后再在虚拟空间和内存空间之间构建完整的映射关系,这是一种惰性空间开辟,同样可以提高内存利用率
这些操作对用户来说都是透明的,也就是说用户不知道也不需要知道
3 fork常规用法
• 父进程希望子进程复制自己,使父子进程同时执行同一个程序不同的代码段。例如,父进程等待客户端请求,生成子进程来处理请求。
• 父进程希望子进程要执行一个不同的程序。例如子进程从fork返回后,调用exec函数。
fork调用失败的原因:
• 系统中有太多的进程
• 实际用户的进程数超过了限制
进程终止
1 背景知识
进程终止的本质是释放系统资源,就是释放进程申请的相关内核数据结构和对应的数据和代码。
main函数最后会返回一个0,这个0是什么?
实际上,这个0是退出码,该退出码会被父进程拿到,表示进程的执行情况
为什么一定要有退出码?
因为父进程创建子进程是为了完成交给他的任务,任务完成的怎么样父进程要知道
0表示成功
!0表示失败,但失败总有原因,1、2、3…表示不同的失败原因
echo $?//查看最近一次进程结束的退出码 ?是变量,存储了退出码 $是显示变量中的内容
我们在命令行中运行的进程的父进程都是bash,因此bash可以拿到这些进程的退出码
2 退出码
退出码(退出状态)可以告诉我们最后一次执行的命令的状态。在命令结束以后,我们可以知道命令是成功完成的还是以错误结束的。其基本思想是,程序返回退出代码 0 时表示执行成功,没有问题。代码 1 或 0 以外的任何代码都被视为不成功。
Linux Shell 中的主要退出码:
• 可以使用strerror函数来获取退出码对应的描述。
3 进程退出场景
• 代码运行完毕,结果正确
• 代码运行完毕,结果不正确
• 代码异常终止
前两种场景均与退出码有关·,根据退出码可以判断是哪一种退出
最后一种场景中由于代码异常退出,退出码无意义,要看信号,如kill -9 杀掉进程
4 进程常见退出方法
正常终止(可以通过echo $? 查看进程退出码):
异常退出:
• ctrl + c,信号终止
main函数return n
return表示函数调用结束,main函数return表示进程退出
return是一种更常见的退出进程方法。执行return n等同于执行exit(n),因为调用main的运行时函数会将main的返回值当做 exit的参数。
exit(n)
exit表示的是进程结束,在你的代码中任何地方调用都会导致进程退出
_exit(n)
_exit同样表示的是进程结束,在你的代码中任何地方调用都会导致进程退出
exit vs _exit
1
我们可以看到,exit输出了退出语句,_exit没有输出退出语句,这是为什么呢?
这是因为exit终止进程时会主动刷新缓冲区,_exit终止进程不会刷新缓冲区
2
exit是C库函数,_exit是系统调用
exit最后也会调用_exit, 但在调用_exit之前,还做了其他工作
总结:
exit - 优雅的终止 _exit - 粗暴的终止
简单类比:
exit就像 礼貌地离开派对:你会感谢主人(调用清理函数),拿走你的杯子并洗干净(刷新缓冲区),然后才离开(调用内核)。
_exit 就像 突然离场:你一句话不说,直接开门就走。你的杯子还留在桌上,里面的酒(缓冲数据)也没人收拾。
终止进程的最佳实践是exit
到此,进程控制之进程创建与终止就讲完了,怎么样,是不是感觉大脑里面多了很多新知识。
如果觉得博主讲的还可以的话,就请大家多多支持博主,收藏加关注,追更不迷路
如果觉得博主哪里讲的不到位或是有疏漏,还请大家多多指出,博主一定会加以改正
博语小屋将持续为您推出文章