在此讨论各个copy_xxx函数的概述其作用。
kernel/fork.c
如果CLONE_SYSVSEM置位,则copy_semundo使用父进程的System V信号量。
如果CLONE_FILES置位,则copy_files使用父进程的文件描述符;否则创建新的files结构,其中包含的信息与父进程相同。该信息的修改可以独立于原结构。
如果CLONE_FS置位,则copy_fs使用父进程的文件系统上下文(task_struct->fs)。这是一个fs_struct类型的结构,包含了诸如根目录、进程的当前工作目录之类的信息。
如果CLONE_SIGHAND或CLONE_THREAD置位,则copy_sighand使用父进程的信号处理程序。
如果CLONE_THREAD置位,则copy_signal与父进程共同使用信号处理中不特定于处理程序的部分。
如果COPY_MM置位,则copy_mm让父进程和子进程共享同一地址空间。在这种情况下,两个进程使用同一个mm_struct实例,task_struct->mm指针即指向该实例。
如果COPY_MM没有置位,并不意味着需要复制父进程的整个地址空间。内核确实会创建页表的一份副本,但并不复制页的实际内容。这是使用Copy-On-Write(写时复制)机制完成的,仅当其中一个进程将数据写入页时,才会进行实际复制。
copy_namespaces有特别的调用语义。它用于建立子进程的命名空间。如果没有指定CLONE_NEWxyz,则与父进程共享相应的命名空间,否则创建一个新的命名空间。copy_namespaces相当于调度程序,对每个可能的命名空间,分别执行对应的复制例程。因为本质上就是复制数据或通过引用计数的管理来共享现存的实例。
copy_thread是一个特定于体系结构的函数,用于复制进程中特定于线程的数据。备注:这里的特定于线程并不是指某个CLONE标志,也不是指操作对线程而非整个进程执行。其语义是指复制执行上下文中特定于体系结构的所有数据。
重要的是填充task_struct->thread的各个成员。这是一个thread_struct类型的结构,其定义是体系结构相关的。它包含了所有寄存器(和其他信息),内核在进程之间切换时需要保存和恢复进程的内容,该结构可用于此。
为理解各个thread_struct结构的布局,需要深入了解各种CPU的相关知识。
回到对copy_process的讨论,内核必须填好task_struct中对父子进程不同的各个成员。包含下列一些:
task_struct中包含的各个链表元素,例如sibling和children;
间隔定时器成员cpu_timers;
待决信号列表( pending);
在用之前描述的机制为进程分配一个新的pid实例之后,则保存在task_struct中。对于线程,线程组ID与分支进程(即调用fork/clone的进程)相同:
kernel/fork.c
pid_nr函数对给定的pid实例计算全局数值PID。
对普通进程,父进程是分支进程。对于线程来说有些不同:由于线程被视为分支进程内部的第二(或第三、第四,等等)个执行序列,其父进程应是分支进程的父进程。
kernel/fork.c
非线程的普通进程可通过设置CLONE_PARENT触发同样的行为。对线程来说还需要另一个校正,即普通进程的线程组组长是进程本身。对线程来说,其组长是当前进程的组长:
kernel/fork.c
新进程接下来必须通过children链表与父进程连接起来。这是通过辅助宏add_parent处理的。
kernel/fork.c
thread_group_leader只检查新进程的pid和tgid是否相同。倘若如此,则该进程是线程组的组长。在这种情况下,还需要完成更多必要的工作。
回想一下,在非全局命名空间的进程命名空间中,各个进程有特定于该命名空间的init进程。如果通过置位CLONE_NEWPID创建一个新的PID命名空间,那么init进程的角色必须由调用clone的进程承担。
新进程必须被加到当前进程组和会话。
最后, PID本身被加到ID数据结构的体系中。创建新进程的工作就此完成!
5、创建线程时的特别问题
用户空间线程库使用clone系统调用来生成新线程。该调用支持(上文讨论之外的)标志,对copy_process(及其调用的函数)具有某些特殊影响。但有一点应该记住,在Linux内核中,线程和一般进程经常用作同义词(如前所述, 线程也经常用于指进程的体系结构相关部分)。在本节中,重点讲解用户线程库用于实现多线程功能的标志。
CLONE_PARENT_SETTID将生成线程的PID复制到clone调用指定的用户空间中的某个地址( parent_tidptr,传递到clone的指针) :
kernel/fork.c
复制操作在do_fork中执行,此时新线程的task_struct尚未初始化,copy操作尚未创建新线程的数据。
CLONE_CHILD_SETTID首先会将另一个传递到clone的用户空间指针(child_tidptr) 保存在新进程的task_struct中。
kernel/fork.c
p->set_child_tid = (clone_flags & CLONE_CHILD_SETTID) ? child_tidptr : NULL;
在新进程第一次执行时,内核会调用schedule_tail函数将当前PID复制到该地址。
kernel/sched.c
CLONE_CHILD_CLEARTID首先会在copy_process中将用户空间指针child_tidptr保存在task_struct中,这次是另一个不同的成员。
kernel/fork.c
p->clear_child_tid = (clone_flags & CLONE_CHILD_CLEARTID) ? child_tidptr: NULL;
在进程终止时, 将0写入clear_child_tid指定的地址。
kernel/fork.c
sys_futex,一个快速的用户空间互斥量,用于唤醒等待线程结束事件的进程。
上述标志可用于从用户空间检测内核中线程的产生和销毁。CLONE_CHILD_SETTID和CLONE_PARENT_SETTID用于检测线程的生成。CLONE_CHILD_CLEARTID用于在线程结束时从内核向用户空间传递信息。在多处理器系统上这些检测可以真正地并行执行。