张建帮 原创作品转载请注明出处 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000
这一次我们来聊聊系统调用中的
fork
,以便更深入地理解linux
中的调用流程与具体的实现(以内核linux-3.18.6
为例,源代码点这里)。在 Linux 操作系统中,每个进程被创建的时候,内核会给这个进程分配一个进程描述符结构,即PCB ,进程控制块。它保存了这个进程的状态,标识符,打开的文件,等待的信号,文件系统等待的资源信息。每个进程描述符都表示一个独立的进程。在操作系统中,每个进程的进程描述都被加入到一个双向链表的任务队列中,由操作系统决定哪个进程可以占用 CPU ,哪个进程应该让出 CPU 。在 Linux 中,一个进程的进程描述符结构如下图所示:
可以看到,它是一个很复杂的数据结构,我们将在下面进行更详细的分析。
关于linux中的系统调用的具体流程,我的前两篇博客已经讲得比较清楚了,因此这里只对
fork
系统调用做一些简单的介绍。fork 函数有着一次调用,两次返回的特点。在父进程中,fork 调用将会返回子进程的 PID ,而在子进程中,fork 调用返回的是0。
fork系统调用的调用过程简单描述如下:
首先是开始,父进程调用
fork
,因为这是一个系统调用,所以会导致 int 软中断,进入内核空间;内核根据系统调用号,调用
sys_clone
(有的版本可能是sys_fork
,无论是哪种,最后都是通过do_fork
函数实现的) 系统调用,而sys_clone
系统调用则会调用do_fork
函数来实现其功能。do_fork
这个函数是fork
的主要执行部分。在do_fork
中,首先做一些错误检查工作和准备复制父进程的初始化工作。然后do_fork
函数调用copy_process
函数完成相关功能
copy_process
函数是对父进程的内核状态和相关的资源进行复制的主要函数,它会调用copy_thread
函数,复制父进程的执行状态,包括相关寄存器的值,指令指针和建立相关的栈
copy_thread
函数还干了一件事,就是把0值写入到寄存器中,然后将其IP
指向一个汇编函数ret_from_fork
。所以在子进程运行的时候,虽然代码和父进程的代码是一致的,但是还是有些区别:在copy_thread
执行完毕后,子进程的第一条指令就被设置为了ret_from_fork
,进行一些清理工作,然后退出到用户空间。用户空间函数可以通过寄存器中的值得到 fork 系统调用的返回值为0。而在父进程中,
copy_process
执行完毕后,将会返回一个指向子进程的指针。然后回到 do_fork 函数,当copy_process
函数成功返回到do_fork
函数时,子进程在do_fork
中被唤醒,然后加入到进程调度队列中。此外,do_fork 还会返回子进程 的 PID正所谓“一图胜千言”,各位看官们如果对上面的大段文字不管兴趣的话,下面的流程图可能会让你一目了然:(箭头代表调用函数之间的调用)
基本上,这幅图像配合上面的文字说明,已经将
fork
的系统调用实现的主体部分或者说一些关键的地方已经讲得比较明白了,若是想更进一步的了解其中的细节,还是推荐去阅读linux的源代码,为了方便各位网友,我还是再把源代码的链接贴一遍:linux源码。另外,如果看想看上面这些函数的相关源码,可以参看下面的这篇文章,写的挺好的。
参考:
http://blog.tonychow.me/blog/2013/06/27/linuxzhong-forkxi-tong-diao-yong-fen-xi/