第24章 进程的创建
简介
- fork:创建一新进程,拷贝父进程的数据段、堆、栈和执行文本段
- exit(status):库函数位于系统调用_exit之上,会释放进程占有资源,status表示退出状态,父进程可通过wait获取该状态
- wait(status):若子进程未终止,会挂起父进程直至子进程终止,status表示子进程的终止状态
- execve(path, argv, envp):加载一个新程序到当前进程的内存,将丢失现有程序的文本段,并为新程序创建堆、栈和数据段
创建新进程:fork
```include
pid_t fork(void); // 返回两次:子进程返回0,父进程返回子进程ID,出错返回-1 // 失败原因:进程数量超过了系统对此真实用户在进程数量的限制(RLIMIT_NPROC),或是触及系统创建进程数量的系统级上限 // fork调用一次,返回两次。将子进程ID返回给父进程的理由是子进程可以有多个,而没有接口可以全部获取,而子进程返回0的理由是一个子进程的ID不可能是0 // 子进程获取到父进程的数据空间、堆和栈副本,共享正文段 // fork之后父进程还是子进程先执行是不确定的,取决于内核调度算法, 如果需要父进程和子进程之间同步,需要某种形式的进程间通信
父进程和子进程每个相同的打开描述符共享一个文件表项和文件偏移量,如果没有同步,它们的输出就会相互混合,但这不是常见的操作模式,fork之后处理文件描述符有两种常见情况:
- 父进程等待子进程完成,shell就是这样做
- 父进程和子进程各自执行不同的程序段,否则就必须进行进程间同步
如果不需要这种对文件描述符的共享方式,需注意两点:
- 父子进程使用不同的文件描述符
- 如果执行exec,打开close-on-exec标志
fork两种常见用法:
- 网络常见用法:父进程和子进程同时执行不同的代码段,父进程等待客户端的请求,请求到达时,fork子进程处理此请求,父进程继续等待下一个服务请求
- shell常见用法:调用exec执行另一个程序
除了打开文件,父进程的很多属性也有子进程继承:
- 实际用户ID、实际组ID、有效用户ID、有效组ID,附属组ID
- 进程组ID、会话ID
- 控制终端
- 设置用户ID标志和设置组ID标志
- 当前工作目录
- 根目录
- 文件模式创建屏蔽字
- 信号屏蔽和安排
- 对任意打开文件描述符的执行时关闭标志
- 环境
- 存储映像
- 资源限制
父进程和子进程的区别:
- fork的返回值
- 进程ID
- 父进程ID
- 子进程不继承父进程设置的文件锁
- 子进程的未处理闹钟被清除
- 子进程的未处理信号集设置为空集
- 子进程的tms_utime、tms_stime、tms_cutime和tms_ustime设置为0
##### 系统调用vfork
include
pid_t vfork(void); // 返回两次:子进程返回0,父进程返回子进程ID,出错返回-1
``` 用于创建一个新进程,而该新进程的目的是exec一个新程序,它与fork的区别:
- vfork不会把父进程的地址空间完全复制到子进程,因为子进程会马上exec,不会引用该地址空间
- vfork保证子进程先运行,在它调用exec或exit后父进程才可能被调度
vfork的怪异语义将导致一些难以觉察的缺陷,除非能给性能带来重大提升(概率很小),否则应当避免使用这个调用
fork之后的竞争条件
fork之后,无法确定父子进程谁将率先访问CPU,不过根据实验得出结论:在Linux上99%以上的概率会先执行父进程,而先执行子进程的原因是父进程在有机会执行前其CPU时间片到期了;Linux专有文件/proc/sys/kernel/sched_child_runs_first可以修改默认值
同步信号以规避竞争条件
若需要保证某一特定的执行次序,必须采用某种同步技术,如信号量、文件锁、管道,或者信号等