fork函数是由pthread_create、fork、vfork、kernel_thread等函数复用的,所以在分析的时候要注意各个函数的特点。fork做的事情一般是复制(直接赋值)一个当前结构到当前进程p,然后将当前工作目录、信号、信号量、信号处理函数、命名空间一一复制到当前进程p(因为它是一直复制比较耗时,fork使用了COW机制,在复制刚刚提到的重要结构之后,在复制其他非task_struct管理结构时,会使用它的映射表。并且相应的页表会被设置为只读,从而减少复制时间),最后通过copy_thread_tls设置新进程的寄存器、返回函数和返回值,返回到申请空间(因为新进程是直接加入到CPU的运行队列中,所以fork返回后,是先运行父进程还是先运行子进程,要看当时的运行情况)。注意:vfork保证子进程先运行,父进程调用exec或exit后才能被调度运行,即子进程调用exec或exit时,会触发wake_up($vfork)信号.所谓COW机制,就是上面说的:只是复制数据的页表,然后设置页表为只读。因此,当进程只读数据时,父子进程的内存空间仍然是共享的,但是如果一个进程产生了写动作,就会因为没有写权限而产生do_page_fault错误,do_page_falut函数就会resetitfor分配新的内存,建立新的映射关系,后面讲虚拟地址的时候再解释。注意:一个内核线程只有一个栈,内核栈;一个应用程序进程/线程有两个栈,内核栈和用户栈。毫无疑问,用户栈主要用于用户态(armv7的usr模式,armv8的EL0异常),内核态(armv7的svc模式,armv8的EL1异常)使用内核栈。至于userstack和kernelstack,后面会在架构中说明。1、子进程与父进程的关系(子进程可以通过task_struct->real_parent获取父进程的task_struct,注意:当涉及调试工具时,task_struct->parent指向调试进程,否则parent和real_parent都指向父进程),如下图:父进程可以通过task_struct->children列表找到所有的子进程。子进程可以通过task_struct->real_parent找到自己的父进程。2、进程与空闲进程的关系,如下图所示:整个系统的所有进程都可以在空闲进程的任务列表中找到。3、线程和线程组的关系,如下图所示:线程组组长实际上是一个进程,它的所有成员都是由它用pthread_create创建的。它的所有团队成员通过task_struct->group_leader找到它,它也可以通过task_struct->thread_group遍历到它所有的团队成员。在一个线程组中,所有线程的父进程与其组长线程的父进程相同,即task_struct->real_parent指向同一个进程。4、在线程共享信号中,线程的位置如下图所示:因为在Linux中,同一个线程组下的所有线程实际上是共享信号和信号处理函数的。这意味着什么?在Linux线程中,除了SIGSEGV(自身运行异常引起)、SIGBUS、SIGFPE、SIGILL等信号外,每个线程都可能收到这个信号(因为线程共享信号),但只要其中一个线程处理thisSignal(可以理解为:当信号到来时,程序遍历task_struct->signal->thread_node链表,寻找能够处理该信号的线程,如果找到,则break;如果没有找到,则查询下一个线程),其他线程将不再收到此信号。因此,如果我们pthread_create出来的线程不想处理这个信号,那么我们就需要做一些特殊的处理来屏蔽这个信号(每个线程都有自己的pendingsignalmask和blockingsignalmask)。如上图所示,每个线程的signal指向同一个task_struct->signal。遍历task_struct->signal->thread_head可以得到当前线程组下的所有线程。这些线程通过task_struct->thread_node挂接到signal->thread_head。注意:进程和线程都有不同的pid,同一个线程组下的所有线程都有一个且只有一个tgid,这个tgid的值就是线程组组长的pid值。5.进程在pid空间的结构,如下图:如上图可以看出,在fork函数中,进程首先从pid缓存内存中申请一个structpid结构体通过alloc_pid函数(这个结构会把pid添加到pid_hash[inalloc_pid]]表中),然后把这个pid结构赋值给task_struct->pids.pid,最后通过hlist_add_head_rcu把task_struct添加到pid指向的任务列表中function(这个链表分为三类:PID,PGID,SID),以后可以通过pid找到对应的task_struct,也可以通过task_struct找到对应的pid链。注意:pidhash是在linux内核初始化的时候初始化的,pidhash_init()和pidmap_init()存在于kernel/pid.c文件中。常用搜索函数:get_pid_task、find_get_pid、get_task_pid、find_task_by_vpid、find_task_by_pid_ns等。
