前言
程堆栈、打开的文件描述符、信号控制设定、 进程优先级、进程组号等。例如,要是父进程打开了五个文件,那么子进程也有五个打开的文件,而且这些文件的当前读写指针也停在相同的地方。
所以,这一步所做的是复制。这样得到的子进程独立于父进程,具有良好的并发性,但是二者之间的通讯需要通过专门的通讯机制。子进程所独有的只有它的进程号、计时器等。使用fork函数的代价是很大的,这些开销并不是所有的情况下都是必须的。比如某进程fork出一个子进程后,其子进程仅仅是为了调用exec执行另一个可执行文件,那么在fork过程中对于虚存空间的复制将是一个多余的过程。
但由于现在Linux中是采取了copy-on-write(COW写时复制)技术,为了降低开销,fork最初并不会真的产生两个不同的拷贝,因为在那个时候,大量的数据其实完全是一样的。
写时复制是在推迟真正的数据拷贝。若后来确实发生了写入,那意味着parent和child的数据不一致了,于是产生复制动作,每个进程拿到属于自己的那一份,这样就可以降低系统调用的开销。所以有了写时复制后,vfork其实现意义就不大了。
- 在fork之后,子进程和父进程都会继续执行fork调用之后的指令。子进程是父进程的副本。它将获得父进程的数据空间,堆和栈的副本,这些都是副本,父子进程并不共享这部分的内存。也就是说,子进程对父进程中的同名变量进行修改并不会影响其在父进程中的值。但是父子进程又共享一些东西,简单说来就是程序的正文段。正文段存放着由cpu执行的机器指令,通常是read-only的。
- 值得注意的是,子进程也是继承父进程的缓冲区的(全缓冲,在填满标准I/O缓冲区后,才进行实际的I/O操作;行缓冲,在遇到换行符时,标准I/O库进行实际I/O 操作。),所以打印输出时在不进行特殊处理的情况下会出现交叉打印。
- 有些打印函数,如printf,在输出到屏幕上时是行缓冲,但是重定向输出到文件中却变成了全缓冲。比如程序a.out中使用了printf进行输出,如果a.out > log,则printf是按照全缓冲处理的。
vfork函数
定义:pid_t vfork(void);
vfork和fork的区别:
- - vfork保证子进程先运行,父进程会被阻塞直到子进程调用exec或exit之后,父进程才可能被调度运行。
- - vfork和fork一样都创建一个子进程,但它并不将父进程的地址空间完全复制到子进程中,因为子进程会立即调用exec(或者exit),于是也就不访问该地址空间。 相反,在子进程调用exec和exit之前,它在父进程的地址空间中运行,在exec之后子进程会有自己的进程地址空间。
注意: 在fork中用return语句是允许的,因为子进程是复制了一份数据。然而,在vfork中用return语句,因为父子进程共享地址空间,子进程return会引起弹栈,导致栈的破坏,也就是父进程不能够继续执行下去了。因此,在vfork中需要用exit()函数或者_exit()函数exec族函数。
clone函数
定义:int clone(int (*fn)(void *), void *child_stack, int flags, void *arg);
系统调用fork()和vfork()是无参数的,而clone()则带有参数。fork()是全部复制,vfork()是共享内存,而clone() 是则可以将父进程资源有选择地复制给子进程,而没有复制的数据结构则通过指针的复制让子进程共享,具体要复制哪些资源给子进程,由参数列表中的 clone_flags来决定。另外,clone()返回的是子进程的pid。
CLONE_PARENT 创建的子进程的父进程是调用者的父进程,新进程与创建它的进程成了“兄弟”而不是“父子”
- CLONE_FS 子进程与父进程共享相同的文件系统,包括root、当前目录、umask
- CLONE_FILES 子进程与父进程共享相同的文件描述符(file descriptor)表
- CLONE_NEWNS 在新的namespace启动子进程,namespace描述了进程的文件hierarchy