fork
#include<unistd.h>
pid_t fork(void);
完成对fork调用后将会存在两个进程,每个进程都会从fork()返回处继续执行。
创建成功,父进程会返回子进程的pid,子进程返回0。
执行fork()时,子进程会获得父进程所有文件描述符的副本。即父子进程对应的描述符指向相同的打开文件句柄。包括:当前文件偏移量、文件状态标志。如果子进程更改了文件偏移量,父进程也会受到影响。
copy-on-write
传统的fork()调用直接将父进程虚拟内存页拷贝到新的子进程,如果在fork()之后立即执行exec()的话,会带来浪费。在现代Linux系统采用两种技术来避免浪费:
1.内核将每一进程代码段标为只读。这样父子进程可以共享同一代码段。
2.对于父进程数据段、堆段、和栈段中各页,采用写时复制技术。最初,内核做了一些设置,令这些段的页表指向与父进程相同的物理页内存,并将这些页表标志为只读。调用fork()之后,内核会捕获所有父子进程针对这些页面的修改企图,并为将要修改的页面创建拷贝。系统将新的页面拷贝分配给遭内核捕获的进程,还会对子进程的相应页表做调整。
fork() 的实际开销就是复制父进程的页表以及给子进程创建唯一的进程描述符。
vfork
产生的子进程将使用父进程内存,直至其调用exec()或退出。与此同时会挂起父进程。
Termination
通常,进程的终止有两种方式。一是异常终止。二是使用_exit()系统调用正常终止。
_exit和exit
#include<unistd.h>
void _exit(int status);
statu参数定义了终止状态,父进程可使用wait获取。
成与一般会调用库函数exit()
#include<stdlib.h>
void exit(int status)
exit()退出会执行如下动作:
调用退出处理程序
刷新stdio流缓冲区
调用_exit()
main()函数中的return(n) 等于exit(n)
无论进程是否正常终止都会发生如下动作
关闭所有打开的文件描述符
释放文件锁
关闭POSIX消息队列
关闭POSIX有名信号量
取消mmap创建的内存映射
…
exec()
#incude<unistd.h>
int execle(const char* pathname, const char* arg, ...);
int execlp(const char* filename, const char* arg, ...);
int execvp(const char* filename, char *const argv[]);
int execv(const char* pathname, char *const argv[]);
int execl(const char* pathname, const char* arg, ...);
以上函数都是通过
#include<unistd.h>
int execve(const char* pathname, char* const argv[], char *const envp[]);
进行封装的库函数。
函数 | 对程序文件的描述 | 对参数的描述 | 环境变量来源 |
---|---|---|---|
execve() | 路径名 | 数组 | envp参数 |
execle() | 路径名 | 列表 | envp参数 |
execlp() | 文件名+PATH | 列表 | 调用者的environ |
exevp() | 文件名+PATH | 数组 | 调用者的environ |
execv() | 路径名 | 数组 | 调用者的environ |
execl() | 路径名 | 列表 | 调用者的environ |