操作系统修炼秘籍(28):Linux进程的创建
Linux进程的创建
在Linux中,除了pid=1的init进程外,所有进程都有父进程,父子进程以树型结构的方式存在,父进程创建出来的多个子进程之间称为兄弟进程。
在Linux中,使用fork()系统调用来创建一个新进程,调用fork()的进程是父进程,新创建出来的进程是子进程。注意:只有操作系统有权限创建进程,其它程序要想创建进程都只能通过发送fork()系统调用请求内核帮忙创建。
也许,通过代码来演示创建子进程的过程是最直观的。这里使用shell伪代码来演示,以便没有任何编程基础的人也能看懂这个过程。但是注意,shell并没有提供fork命令,所以这里的fork是假设它存在的。
1 | pid=$(fork) # 从此开始,有两个进程分支 |
这段伪代码的流程如图。
fork()创建子进程时会复制父进程,因此它会从父进程处继承非常非常多的东西,只有每个进程应该独立的东西才不会继承,例如进程的PID属性是每个进程唯一的,子进程肯定不会去继承父进程,代表父进程的PPID属性的值也是不同的。而像内存中的数据(即虚拟内存),基本上会复制,比如父子进程的堆、栈空间在创建出来时是完全相同的。
因为创建进程需要复制非常多的东西,所以如果全都复制的话,效率会非常低也非常消耗内存资源。所以,在真正创建进程的时候采用的是一种称为写时复制(copy-on-write,COW)的技术来降低这种复制开销。内核在创建子进程的时候,内核会将父子进程的虚拟地址空间的数据页指向同一物理内存的数据页,并且将其标记为只读,这样就不会复制这些数据,当父或子进程要读取这些数据的时候,可以直接从物理内存中读取,而当父或子进程需要修改这些数据的时候,内核才将这个数据页拷贝到父或子进程的地址空间,这样修改的那一页数据只会影响各进程自身,而不会影响其它进程。这就是写时复制技术。
另外需要注意的是,刚创建子进程的时候,虽然父子的虚拟内存是一致的,并在有需要的时候复制相关数据页,但父子进程的代码段是共享的(内核会将代码段设置为只读),也就是说,父子进程拥有完全相同的代码,所以父子进程都能执行从发起fork()系统调用开始后面所有的代码。
Shell下执行的所有命令,都是通过fork+exec+wait让命令运行起来的,这里暂时先不管wait。仍然以shell伪代码来简单演示:
1 | pid=$(fork) # 从此开始,有两个进程分支 |