创建进程 fork
fork 的定义
在 Linux 中,我们使用 fork
来创建一个子进程
fork 的返回值
fork 函数有些特殊,成功它返回 2 次,失败返回 -1,利用这个特性可以判断当前的进程是子进程还是父进程:
1. 在子进程中返回 0
2. 在父进程中返回子进程的进程 ID
fork 的写时复制技术
通过执行 fork,子进程得到父进程的一个副本,例如子进程获得父进程的数据空间,堆和栈的副本,但是它们并不共享存储空间,它们只共享代码段。但是在现在的系统实现中,并不执行拷贝父进程的副本,作为替代方案,而是使用写时复制(Copy - On - Write)技术。
写时复制:在 fork 之后,这些区域由父子进程共享,而且内核将它们的访问权限改变为只读,如果父子进程中的任何一个试图修改这些区域,内核只为修改区域的那片内存制作一个副本给子进程。
不管是哪种技术实现,最后父子进程的数据都是独立的,不会相互影响
子进程的执行位置
fork 还有一个特点:子进程不是从 main
函数开始执行的,而是从 fork 返回的地方开始
创建进程 vfork
还有一个创建进程的系统调用 vfork
,它跟 fork 很相似,但是也有几点不同:
1. vfork 的目的是创建一个子进程来运行一个程序
2. vfork 并不复制父进程地址空间,子进程在父进程地址空间中运行,并阻塞父进程直到子进程返回
3. vfork 保证子进程先运行
4. 子进程需要调用 exec 或 exit 函数退出,否则会带来未知结果。
exec
fork 函数里面最后也是调用 exec 等函数来执行程序的
exec 有很多变种函数,例如 execlp
,execle
,等等,但基本的用法都是差不多的
运行结果就相当与 shell 命令:ps - ef
,其他的变种函数可以通过 man exec
来查看。
进程等待 wait
父进程可以使用 wait 系统调用主动等待子进程或者指定进程结束,并获得子进程的结束信息
这个系统调用的过程如下:
1. wait 暂停调用它的进程直到子进程结束
2. wait 调用成功返回子进程的 PID
3. wstatus 存储子进程的返回信息(正常退出,异常退出,被信号杀死),以此来知道子进程是如何结束的
大致的流程如下:
- 1
- 2
- 3
- 4
- 5
如果子进程调用 exit 退出,那么内核将 exit 的退出状态码放在 status 中
如果进程被杀死,内核将信号序列放在 status 中
实际使用时,wait 提供了相关的宏来判断 status 的状态
进程结束
既然能够创建进程,那肯定能够结束进程,在 Linux 中进程退出又分为正常和异常退出,分别来了解了解。
正常退出
有 5 种正常退出进程的方法:
1. 在 main 内执行 return
,等价于调用 exit
2. 调用 exit
3. 调用 _exit
或 _Exit
4. 进程的最后一个线程在其启动例程中执行 return 语句
5. 进程的最后一个线程调用 pthread_exit
函数
异常终止
有 3 种异常终止的方法:
1. 调用 abort
,产生 SIGABRT
信号
2. 当进程接受到某些信号时
3. 最后一个线程对「取消」请求作出响应
不管是哪种终止情况,我们都可以使用 wait
或者 waitpid
来得到子进程的退出状态。