系统调用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计算得的杂凑队列。
结束。