1.进程创建
1.1fork函数初识
在linux中fork函数是⾮常重要的函数,它从已存在进程中创建⼀个新进程。新进程为⼦进程,⽽原进 程为⽗进程。
#include <unistd.h>
pid_t fork(void);
返回值:⾃进程中返回0,⽗进程返回⼦进程id,出错返回-1
进程调⽤fork,当控制转移到内核中的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之后,谁先执⾏完 全由调度器决定。
1.2 fork的返回值
fork()
是 Unix/Linux 系统创建新进程的系统调用,其特殊之处在于 "一次调用,两次返回":
-
在父进程中返回 子进程的 PID(>0)
-
在子进程中返回 0
-
出错时返回 -1
1.3写时拷贝
通常,⽗⼦代码共享,⽗⼦再不写⼊时,数据也是共享的,当任意⼀⽅试图写⼊,便以写时拷⻉的⽅ 式各⾃⼀份副本。具体⻅下图
核心概念:
写时拷贝是一种 延迟内存复制 的优化策略,父子进程 共享同一份物理内存 ,直到某个进程尝试 修改内存页 时,操作系统才会进行实际复制
关于触发写实拷贝的条件是:
-
CPU 触发 页错误(Page Fault)
-
内核检查错误原因是否为 COW
-
分配新的物理页,复制原页内容
-
修改故障进程的页表,指向新物理页
-
恢复进程执行
1。4 fork调⽤失败的原因
系统中有太多的进程
实际⽤⼾的进程数超过了限制
2.终止进程
进程终⽌的本质是释放系统资源,就是释放进程申请的相关内核数据结构和对应的数据和代码
2-1 进程退出场景
• 代码运⾏完毕,结果正确
• 代码运⾏完毕,结果不正确
• 代码异常终⽌
2-2 进程常⻅退出⽅法
正常终⽌(可以通过 echo $? 查看进程退出码):
- 从main返回
- 调⽤exit
- _exit
2-2-1 退出码
退出码(退出状态)可以告诉我们最后⼀次执⾏的命令的状态。在命令结束以后,我们可以知道命令
是成功完成的还是以错误结束的。其基本思想是,程序返回退出代码 0 时表⽰执⾏成功,没有问题。
代码 1 或 0 以外的任何代码都被视为不成功。
Linux Shell 中的主要退出码
2.3 exit函数
1. 标准库函数 vs 系统调用
函数原型 | 所属标准 | 关键差异 |
---|---|---|
void exit(int status) |
C标准库 | 执行完整清理:刷新缓冲区、调用atexit注册函数 |
void _exit(int status) |
POSIX系统调用 | 直接终止进程,不执行I/O缓冲清理 |
资源回收关键机制
资源类型 | 处理方式 | 例外情况 |
---|---|---|
文件描述符 | 全部关闭 | 无 |
内存泄漏 | 用户态内存不会自动释放 | 需依赖OS内核回收 |
临时文件 | 未删除的临时文件保留 | 需手动unlink |
共享内存 | 引用计数减1 | 计数为0时才释放 |
注意事项:
1. 多线程环境
-
调用
exit()
会终止 整个进程(所有线程) -
替代方案:
-
pthread_exit()
终止当前线程 -
主线程使用
return
退出
-
2. 信号处理冲突
-
在信号处理函数中调用
exit()
是安全的 -
避免在
SIGKILL
/SIGSTOP
处理中操作(信号不可捕获)
2-3-1 return退出
return是⼀种更常⻅的退出进程⽅法。执⾏return n等同于执⾏exit(n),因为调⽤main的运⾏时函数会 将main的返回值当做 exit的参数。