do_fork情景分析

内核的版本是3.10.9. 我自己的分析使用棕色

long do_fork(unsigned long clone_flags,

          unsigned long stack_start,
          unsigned long stack_size,
          int __user *parent_tidptr,
          int __user *child_tidptr)
{
    struct task_struct *p;
    int trace = 0;
    long nr;

    /*
     * Do some preliminary argument and permissions checking before we
     * actually start allocating stuff
     */
    if (clone_flags & (CLONE_NEWUSER | CLONE_NEWPID)) {
        if (clone_flags & (CLONE_THREAD|CLONE_PARENT))
            return -EINVAL;
    }

++++++++++++++++++++++++++++++++++++++

CLONE_NEWUSER这个flag是和user namespace相关的标识,user namespace可以用来进行权限设定。在通过clone函数fork进程的时候,我们可以选择clone之前的user namespace,当然也可以通过传递该标识来创建新的user namespace。

CLONE_NEWPID这个flag是和PID namespace相关的标识。思路同上。

这两个flag是和虚拟化技术相关的。虚拟化技术就需要资源隔离,也就是说,不同的虚拟主机(实际上在一台物理主机上)资源是不可见的。因此,linux kernel增加了若干个name space,例如user name space、PID namespace、IPC namespace、uts namespace、network namespace等。以PID namespace为例,原来的linux kernel中,PID唯一的标识了一个process,在引入PID namespace之后,不同的namespace可以拥有同样的ID,也就是说,标识一个进程的是PID namespace + PID。

CLONE_PARENT这个flag表示新fork的进程想要和创建该进程的cloner拥有同样的父进程。

CLONE_THREAD这个flag表示新的fork的进程与cloner是否在一个线程组中。

在了解了上述的内容之后,我们就可以进行后续的分析了。

一旦cloner设定了CLONE_NEWPID的flag,其本意就是和过去的PID namespace say goodbye,和cloner的PID namespace做切割,CLONE_PARENT和CLONE_THREAD这两个flag都是设定新进程的PID和旧的PID有藕断丝连的关系,这样矛盾的设定当要坚决打击。

CLONE_NEWUSER和CLONE_PARENT和CLONE_THREAD排他性设定 TODO

CLONE_NEWUSER设定的时候,就会为fork的进程创建一个新的user namespace,以便隔离USER ID。linux 系统内的一个进程和某个user namespace内的uid和gid相关。user namespace被实现成树状结构,新的user namespace中第一个进程的uid就是0,也就是root用户。这个进程在这个新的user namespace中有超级权限,但是,在其父user namespace中只是一个普通用户。

++++++++++++++++++++++++++++++++++++++



    /*
     * Determine whether and which event to report to ptracer.  When
     * called from kernel_thread or CLONE_UNTRACED is explicitly
     * requested, no event is reported; otherwise, report if the event
     * for the type of forking is enabled.
     */
    if (!(clone_flags & CLONE_UNTRACED)) {
        if (clone_flags & CLONE_VFORK)
            trace = PTRACE_EVENT_VFORK;
        else if ((clone_flags & CSIGNAL) != SIGCHLD)
            trace = PTRACE_EVENT_CLONE;
        else
            trace = PTRACE_EVENT_FORK;

        if (likely(!ptrace_event_enabled(current, trace)))
            trace = 0;
    }

++++++++++++++++++++++++++++++++++++++

Linux的内核提供了ptrace这样的系统调用,通过它,一个进程(我们称之tracer,例如strace、gdb)可以观测和控制另外一个进程(被trace的进程,我们称之tracee)的执行。一旦Tracer和tracee建立了跟踪关系,那么所有发送给tracee的信号(除SIGKILL)都会汇报给Tracer,以便Tracer可以控制或者观测tracee的执行。例如断点的操作。Tracer程序一般会提供界面,以便用户可以设定一个断点(当tracee运行到断点时,会停下来)。当用户设定了断点后,tracer就会保存该位置的指令,然后向该位置写入SWI __ARM_NR_breakpoint(这种断点是soft break point,可以设定无限多个,对于hard break point是和CPU体系结构相关,一般支持2个)。当执行到断点位置的时候,发生软中断,内核会给tracee进程发出SIGTRAP信号,当然这个信号会被tracer捕获。对于tracee,当收到信号的时候,无论是什么信号,甚至是ignor的信号,tracee进程都会停止运行。Tracer进程可以对tracee进行各种操作,例如观察tracer的寄存器,观察变量等等。
在了解完上述的背景之后,再来看代码就比较简单了。这个代码块控制创建进程是否向tracer上报信号,如果需要上报,那么要上报哪些信号。如果用户进程在创建的时候有携带CLONE_UNTRACED的flag,那么该进程则不能被trace。对于内核线程,在创建的时候都会携带该flag,这也就意味着,内核线程是无法被traced,也就不需要上报event给tracer。

++++++++++++++++++++++++++++++++++++++


    p = copy_process(clone_flags, stack_start, stack_size,
             child_tidptr, NULL, trace);

++++++++++++++++++++++++++++++++++++++

这是fork的主要内容,我会独立一篇文章出来详细分析

++++++++++++++++++++++++++++++++++++++   


/*

     * Do this prior waking up the new thread - the thread pointer
     * might get invalid after that point, if the thread exits quickly.
     */
    if (!IS_ERR(p)) {
        struct completion vfork;

        trace_sched_process_fork(current, p);

        nr = task_pid_vnr(p);

        if (clone_flags & CLONE_PARENT_SETTID)
            put_user(nr, parent_tidptr);

++++++++++++++++++++++++++++++++++++++

如果设置了CLONE_PARENT_SETTID这个flag,父进程需要知道子进程的thread ID,因此,父进程在调用clone的时候,会将地址传递给内核,内核将创建的子进程的thread ID写入其中。

++++++++++++++++++++++++++++++++++++++


        if (clone_flags & CLONE_VFORK) {
            p->vfork_done = &vfork;
            init_completion(&vfork);
            get_task_struct(p);
        }

        wake_up_new_task(p);

        /* forking complete and child started to run, tell ptracer */
        if (unlikely(trace))
            ptrace_event(trace, nr);

        if (clone_flags & CLONE_VFORK) {
            if (!wait_for_vfork_done(p, &vfork))
                ptrace_event(PTRACE_EVENT_VFORK_DONE, nr);
        }
    } else {
        nr = PTR_ERR(p);

    }

++++++++++++++++++++++++++++++++++++++

Vfork和fork是类似的,除了下面两点:
1. 阻塞父进程
2. 不复制父进程的页表
之所以会有vfork这样的创建进程的方法,其主要原因是由于很可能父进程在fork之后会调用exec函数,如果这样,fork所做的努力都白费了。在引入了COW技术后,fork开销已经不大,因此vfork的意义也不是非常的明显。
之所以vfork要阻塞父进程是因为vfork后父子进程使用的是完全相同的mm, 也就是说使用的是完全相同的虚拟内存空间, 包括栈也相同。所以两个进程不能同时运行, 否则栈就乱掉了。所以vfork后, 父进程是阻塞的,直到调用了exec系列函数或者exit函数后。这时候,子进程的mm(old_mm)需要释放掉,不再与父进程共用了,这时候就可以解除父进程的阻塞状态。

++++++++++++++++++++++++++++++++++++++


    return nr;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值