do_fork 源码分析



 对do_fork的源码进行了分析,看了好多遍,昨晚又看了一遍,现在能够自己顺下来大致执行过程,比之前理解更深刻。

先解释一下ptr_err,这在下面会用到。因为在内核中,有些函数,比如kmalloc是返回指针的,kmalloc是分配内存的,如果分配不到,就返回null指针。有些函数错误时,我们在知道它错了的基础上还要获得错误码,在用户空间,提供了error变量,获得错误码,在内核中就相应的有ptr_err,很明显,也是用来获得错误码的。在do_fork中,copy_process错误时,错误码保存在pid中。

do_fork函数的主要作用就是复制原来的进程使其成为一个新的进程,它完成了整个进程创建中的大部分工作。

Fork、vfork和clone三个系统调用所对应的系统调用服务例程都调用了do_fork()。只是所传递的参数不同,导致父进程和子进程在共享资源的程度上不同。fork是全部复制父进程,传给子进程,所以fork不带参数。而clone复制部分父进程资源,也就是说复制是有选择的。vfork准确来说,它创建的是线程而不是进程。并且vfork创建的子进程先于父进程执行。只有子进程执行结束或退出,父进程才被唤醒。

 

接下来分析do_fork()。

long do_fork(unsigned long clone_flags,
          unsigned long stack_start,
          struct pt_regs *regs,
          unsigned long stack_size,
          int __user *parent_tidptr,
          int __user *child_tidptr)

首先解释以上几个参数,

clone_flags: 通过clone标志可以有选择的对父进程的资源进行复制;fork,vfork和clone就是因为flag标志不同才加以区分的。

statck_start:子进程用户态堆栈的地址;

regs:指向pt_regs结构体的指针。当系统发生系统调用,即用户进程从用户态切换到内核态时,该结构体保存通用寄存器中的值,并被存放于内核态的堆栈中;

stack_size:未被使用,通常被赋值为0;

parent_tidptr:父进程在用户态下pid的地址,只有CLONE_PARENT_SETTID标志被设定时才有意义;

child_tidptr:子进程在用户下pid的地址,和上面的一样,也是在CLONE_CHILD_SETTID标志被设定时有意义;

大体执行步骤如下:

1.定义一个task_struct类型的指针p struct task_struct *p;),作用:接收子进程所分配的进程描述符(这儿是用copy_process实现的)。为新进程分配一个pid。分配完毕后,判断pid是否分配成功,判断语句如下:

if (pid < 0)

return -EAGAIN;

2. 检查当前进程是否被跟踪


   跟踪与否是看ptrace的值。父进程非0则未被跟踪。如果父被跟踪,就得判断子进程是否被跟踪。

如果trace为1,那么就将跟踪标志CLONE_PTRACE加入标志变量clone_flags中。

if (unlikely(current->ptrace)) {

trace = fork_traceflag (clone_flags);

if (trace)

clone_flags |= CLONE_PTRACE;

}

3. 通过copy_process()创建子进程的描述符,并创建子进程执行时所需的其他数据结构,最终则会返回这个创建好的进程描述符。

 

p = copy_process(clone_flags, stack_start, regs, stack_size, parent_tidptr, child_tidptr, pid);

4. copy_process执行不成功,则先释放已分配的pid,根据ptr_err得到错误码,保存在pid中。

 如果成功,则执行如下代码:
首先定义了一个完成量vfork,如果clone_flags包含CLONE_VFORK标志,那么将进程描述符中的vfork_done字段指向这个完成量,之后再对vfork完成量进行初始化。

if (!IS_ERR(p)) {

struct completion vfork;

if (clone_flags & CLONE_VFORK)

{

p->vfork_done = &vfork;

init_completion(&vfork);

}

5. 如果子进程被跟踪(在父进程被跟踪的前提下,一般来说父进程是很少被跟踪的)或者设置了CLONE_STOPPED标志,就为子进程增加挂起信号。

具体操作是,将SIGSTOP信号所对应的那一位置1。

if ((p->ptrace & PT_PTRACED) || (clone_flags & CLONE_STOPPED)) {

sigaddset(&p->pending.signal, SIGSTOP);

set_tsk_thread_flag(p, TIF_SIGPENDING);

}

6. 如果子进程未设置CLONE_STOPPED标志,那么通过wake_up_new_task函数使得父子进程之一优先运行;否则,将子进程的状态设置为TASK_STOPPED。

if (!(clone_flags & CLONE_STOPPED))

wake_up_new_task(p, clone_flags);

else

p->state = TASK_STOPPED;

7. 如果父进程被跟踪,则将子进程的pid赋值给父进程,存于进程描述符的pstrace_message字段。使得当前进程停止运行,并向父进程的父进程发送SIGCHLD信号。

if (unlikely (trace)) {

current->ptrace_message = pid;

ptrace_notify ((trace << 8) | SIGTRAP);

}

8. 如果CLONE_VFORK标志被设置,则通过wait操作将父进程阻塞,直至子进程调用exec函数或者退出。(这儿显示的就是vfork的作用)

if (clone_flags & CLONE_VFORK) {

wait_for_completion(&vfork);

if (unlikely (current->ptrace & PT_TRACE_VFORK_DONE))

ptrace_notify ((PTRACE_EVENT_VFORK_DONE << 8) | SIGTRAP);

}

最后: 返回pid。

这也fork系统调用时父进程会返回子进程pid的原因。

看完do_fork首先明白了父进程为什么返回子进程的pid,之前只是被动接受父进程是返回子进程的pid,现在能够理解这样的原因。在这之前也只是直接调用,就像之前编fork程序,就几行程序。看了fork的源代码,才知道操作系统做了多少,远远比想象中多,虽然只是明白了do_fork的大致流程,有些细节,以及这之中调用的函数,如copy_process函数,还没看明白,但是每一次看都会比之前理解的更深刻。


 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章