系统调用fork()、vfork()与clone()

  • Post author:
  • Post category:其他



系统调用clone()的主要用途是创建一个线程,可以根据参数选择性复制进程的资源。fork(),全面复制父进程的资源。vfork(),与父进程共享用户空间,当创建了子进程,子进程先运行,等子进程退出后父进程再运行(与execve()配合使用)。


clone、fork、vfork都调用do_fork()。


do_fork()中的标志位参数:

 // cloning flags:
#define CSIGNAL		0x000000ff	/* signal mask to be sent at exit */
 共享内存描述符和所有页表
#define CLONE_VM	0x00000100	/* set if VM shared between processes */
/**
 * 共享根目录和当前工作目录所在的表,以及用于屏蔽新文件初始许可权的位掩码值
 */
#define CLONE_FS	0x00000200	/* set if fs info shared between processes */
 * 共享打开文件表
 */
#define CLONE_FILES	0x00000400	/* set if open files shared between processes */
/**
 * 共享信号处理程序的表、阻塞信号表和挂起信号表
 * 如果该标志为1,那么一定要设置CLONE_VM
 */
#define CLONE_SIGHAND	0x00000800	/* set if signal handlers and blocked signals shared */
/**
 * 如果父进程被跟踪,那么,子进程也被跟踪。
 * 尤其是,debugger程序可能希望以自己作为父进程来跟踪子进程。在这种情况下,内核把该标志强设为1
 */
#define CLONE_PTRACE	0x00002000	/* set if we want to let tracing continue on the child too */
/**
 * 在发出vfork系统调用时设置
 */
#define CLONE_VFORK	0x00004000	/* set if the parent wants the child to wake it up on mm_release */
/**
 * 设置子进程的父进程为调用进程的父进程。
 */
#define CLONE_PARENT	0x00008000	/* set if we want to have the same parent as the cloner */
/**
 * 把子进程插入到父进程的同一线程组中。并使子进程共享父进程的信号描述符。因此也设置子进程的tgid字段和group_leader字段。
 * 如果这个标志位为1,则必须设置CLONE_SIGHAND标志。
 */
#define CLONE_THREAD	0x00010000	/* Same thread group? */


sys_fork() > do_fork() > alloc_task_struct( )


为子进程分配两个连续的物理页面。低端用作子进程的task_struct结构,高端用作其系统空间堆栈。



sys_fork() > do_fork() >get_pid( )


根据clone_flags中的标志位CLONE_PID的值,或返回父进程(当前进程)的pid,或返回一个新的pid放在子进程的task_struct中。


进程号常数PID_MAX定义为0x8000。进程号的最大值是0x7fff,即32767.进程号0~299是为系统进程(包括内核线程)保留的。主要用于各种“保护神”进程。



sys_fork() > do_fork() >

copy_files()


有条件的复制已打开文件的控制结构。当参数CLONE_FILES标志位为1,就只能通过atomic_inc()递增当前进程的files_struct结构中的共享计数。如果

参数CLONE_FILES标志位为0,那就要复制,首先通过kmem_cache_alloc()为子进程分配一个files_struct数据结构作为newf,然后将oldf把内容复制给newf。




sys_fork() > do_fork() >


copy_fs()






与文件系统有关,




当CLONE_FS为0时才复制,task_struct结构指针中的指针指向一个fs_struct数据结构,结构中记录的是进程的根目录root、当前工作目录pwd、一个用于操作权限管理的umask,还有一个计数器。



sys_fork() > do_fork() >


copy_sighand()




关于对信号的处理,是否复制父进程对信号的处理是由标志位CLONE_SIGHAND控制的。如果一个进程设置了信号处理程序,其task_struct结构中的指针sig就指向一个signal_struct数据结构。




sys_fork() > do_fork() >


copy_mm()






关于进程用户空间的处理。当CLONE_VM标志位为0时才真正进行。其中最重要的vm_area_struct数据结构和页面映射表,由dup_mmap()复制。






sys_fork() > do_fork() >


copy_fs() >dup_mmap()>copy_page_range( )





逐级处理页面目录项和页面表项。


(1)表项的内容为全0,表明页面未映射,不要处理。


(2)表项不为0,P位为0;说明映射已经建立,但是该页面目前不在内存中。已经被调出到交换设备上。通过swap_duplicate()递增它的共享计数。然后就转到cont_copy_pte_range将此表项复制到子进程的页面表中。


(3)需要从父进程复制的可写页面。采用“copy_on_write”技术(写时拷贝),先通过复制页面表项暂时共享这个页面,到子进程(或父进程)真的要写这个页面时再来分配页面和复制。只要一个虚存区间的性质时可写(VM_MAYWRITE为1)而又不是共享(VM_SHARED为0),就属于copy_on_write区间。再通过复制页面表项暂时共享一个页面表项时要做两件重要的事。1、父进程的页面表项改成写保护,然后把已经改成写保护的表项设置到子进程的页面表中。相应的页面在两个进程中都变成了“只读”。当父进程或是子进程企图写入该页面时,都会引起一次页面异常,异常处理程序对此的反应则是另行分配一个物理页面,并将内容真正地“复制”到新的物理页面中。让父、子进程各自拥有自己的物理页面。然后将两个表中相应的表项改成可写。


(4)父进程的只读页面。复制页面表项共享物理页面。



sys_fork() > do_fork() >


copy_thread()



复制父进程的系统空间堆栈。将该结构的eax置成0(fork()返回值)。将task_struct结构中esp置成这里的参数esp,他决定了进程在用户空间的堆栈位置。




sys_fork() > do_fork()


task_struct结构中counter字段的值就是进程的运行时间配额,这里将父进程的事件配额分成两半,让父、子进程各有原值的一半。如果创建的是线程,则还要通过task_struct结构中的队列头thread_group与父进程链接起来,形成一个“线程组”。接着就让子进程进入他的关系网。先通过SET_LINKS(p)将子进程的task_struct结构链入内核的进程队列,然后通过hash_pid()将其链入按其pid计算得的杂凑队列。


结束。







版权声明:本文为Rebirth__Man原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。