zoukankan      html  css  js  c++  java
  • 通过fork函数创建进程的跟踪,分析linux内核进程的创建

    作者:吴乐 山东师范大学

    《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000

    一、实验过程

    1.打开gdb,设置断点

    2.跟踪到do_fork处

    3.跟踪到copy_process断点处。

    4.跟踪到ret_from_fork子进程创建完成。

    二、代码部分分析

    Fork的系统调用代码在linux/arch/i386/kernel/process.c中:

          asmlinkage int sys_fork(struct pt_regs regs) 

    return do_fork(SIGCHLD, regs.esp, ®s, 0, NULL, NULL); 
    }

    Sys_fork系统调用通过 do_fork()函数实现,通过对do_fork()函数传递不同的clone_flags来实现fork,clone,vfork。

    Syn_clone和syn_vfork的系统调用代码如下:

          asmlinkage int sys_clone(struct pt_regs regs) 

    unsigned long clone_flags; 
    unsigned long newsp; 
    int __user *parent_tidptr, *child_tidptr; 
    clone_flags = regs.ebx; 
    newsp = regs.ecx; 
    parent_tidptr = (int __user *)regs.edx; 
    child_tidptr = (int __user *)regs.edi; 
    if (!newsp) 
    newsp = regs.esp; 
    return do_fork(clone_flags, newsp, ®s, 0, parent_tidptr, child_tidptr); 

    asmlinkage int sys_vfork(struct pt_regs regs) 

    return do_fork(CLONE_VFORK | CLONE_VM | SIGCHLD, regs.esp, ®s, 0, NULL, NULL); 
    }

    其中clone_flas在includelinuxsched.h中定义

          /* 
    * cloning flags: 
    */ 
    #define CSIGNAL 0x000000ff /* 进程退出时需要传递的信号*/ 
    #define CLONE_VM 0x00000100 /* 父子进程共享地址空间 */ 
    #define CLONE_FS 0x00000200 /* 父子进程共享文件系统信息 */ 
    #define CLONE_FILES 0x00000400 /* 父子进程共享已打开的文件 */ 
    #define CLONE_SIGHAND 0x00000800 /* 父子进程共享信号处理 */ 
    #define CLONE_PTRACE 0x00002000 /* 继续调试子进程 */ 
    #define CLONE_VFORK 0x00004000 /* 调用vfork(),父进程休眠*/ 
    #define CLONE_PARENT 0x00008000 /* 设置一个共有的父进程 */ 
    #define CLONE_THREAD 0x00010000 /* 父子进程在同一个线程组 */ 
    #define CLONE_NEWNS 0x00020000 /* 为子进程创建一个新的命名空间 */ 
    #define CLONE_SYSVSEM 0x00040000 /* 父子进程共享system V SEM_UNDO */ 
    #define CLONE_SETTLS 0x00080000 /* 为子进程创建新的TLS */ 
    #define CLONE_PARENT_SETTID 0x00100000 /* 设置父进程TID */ 
    #define CLONE_CHILD_CLEARTID 0x00200000 /* 清除子进程TID */ 
    #define CLONE_DETACHED 0x00400000 /* Unused, ignored */ 
    #define CLONE_UNTRACED 0x00800000 /* 不允许调试子进程 */ 
    #define CLONE_CHILD_SETTID 0x01000000 /* 设置子进程TID */ 
    #define CLONE_STOPPED 0x02000000 /* 设置进程停止状态 */ 
    #define CLONE_NEWUTS 0x04000000 /* 创建新的utsname组 */ 
    #define CLONE_NEWIPC 0x08000000 /* 创建新的IPC */

    Do_fork()在kernel/fork.c中定义,代码如下:

          /* 
    * Ok, this is the main fork-routine

    * It copies the process, and if successful kick-starts 
    * it and waits for it to finish using the VM if required. 
    */ 
    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) 

    struct task_struct *p; 
    int trace = 0; 
    struct pid *pid = alloc_pid(); 
    long nr; 
    if (!pid) 
    return -EAGAIN; 
    nr = pid->nr; 
    if (unlikely(current->ptrace)) { 
    trace = fork_traceflag (clone_flags); 
    if (trace) 
    clone_flags |= CLONE_PTRACE; 

    p = copy_process(clone_flags, stack_start, regs, stack_size, parent_tidptr, child_tidptr, pid); 
    /* 
    * 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; 
    if (clone_flags & CLONE_VFORK) { 
    p->vfork_done = &vfork; 
    init_completion(&vfork); 

    if ((p->ptrace & PT_PTRACED) || (clone_flags & CLONE_STOPPED)) { 
    /* 
    * We'll start up with an immediate SIGSTOP. 
    */ 
    sigaddset(&p->pending.signal, SIGSTOP); 
    set_tsk_thread_flag(p, TIF_SIGPENDING); 

    if (!(clone_flags & CLONE_STOPPED)) 
    wake_up_new_task(p, clone_flags); 
    else 
    p->state = TASK_STOPPED; 
    if (unlikely (trace)) { 
    current->ptrace_message = nr; 
    ptrace_notify ((trace << 8) | SIGTRAP); 

    if (clone_flags & CLONE_VFORK) { 
    freezer_do_not_count(); 
    wait_for_completion(&vfork); 
    freezer_count(); 
    if (unlikely (current->ptrace & PT_TRACE_VFORK_DONE)) { 
    current->ptrace_message = nr; 
    ptrace_notify ((PTRACE_EVENT_VFORK_DONE << 8) | SIGTRAP); 


    } else { 
    free_pid(pid); 
    nr = PTR_ERR(p); 

    return nr; 
    }

    Do_fork()函数的核心是copy_process()函数,该函数完成了进程创建的绝大部分工作并且也在fork.c定义,copy_process函数较长,逐段往下看:

          static struct task_struct *copy_process(unsigned long clone_flags, 
    unsigned long stack_start, 
    struct pt_regs *regs, 
    unsigned long stack_size, 
    int __user *parent_tidptr, 
    int __user *child_tidptr, 
    struct pid *pid) 

    int retval; 
    struct task_struct *p = NULL; 
    if ((clone_flags & (CLONE_NEWNS|CLONE_FS)) == (CLONE_NEWNS|CLONE_FS)) 
    return ERR_PTR(-EINVAL); 
    /* 
    * Thread groups must share signals as well, and detached threads 
    * can only be started up within the thread group. 
    */ 
    if ((clone_flags & CLONE_THREAD) && !(clone_flags & CLONE_SIGHAND)) 
    return ERR_PTR(-EINVAL); 
    /* 
    * Shared signal handlers imply shared VM. By way of the above, 
    * thread groups also imply shared VM. Blocking this case allows 
    * for various simplifications in other code. 
    */ 
    if ((clone_flags & CLONE_SIGHAND) && !(clone_flags & CLONE_VM)) 
    return ERR_PTR(-EINVAL); 
    retval = security_task_create(clone_flags); 
    if (retval) 
    goto fork_out; 
    retval = -ENOMEM; 
    p = dup_task_struct(current); 
    if (!p) 
    goto fork_out; 
    rt_mutex_init_task(p); 
    #ifdef CONFIG_TRACE_IRQFLAGS 
    DEBUG_LOCKS_WARN_ON(!p->hardirqs_enabled); 
    DEBUG_LOCKS_WARN_ON(!p->softirqs_enabled); 
    #endif

    这段代码首先对传入的clone_flag进行检查,接着调用了dup_task_struct()函数,该函数的主要作用是:为子进程创建一个新的内核栈,复制task_struct结构和thread_info结构,这里只是对结构完整的复制,所以子进程的进程描述符跟父进程完全一样。跟进dup_task_struct()函数看代码:

          static struct task_struct *dup_task_struct(struct task_struct *orig) 

    struct task_struct *tsk; 
    struct thread_info *ti; 
    prepare_to_copy(orig); 
    tsk = alloc_task_struct(); 
    if (!tsk) 
    return NULL; 
    ti = alloc_thread_info(tsk); 
    if (!ti) { 
    free_task_struct(tsk); 
    return NULL; 

    *tsk = *orig; 
    tsk->stack = ti; 
    setup_thread_stack(tsk, orig); 
    #ifdef CONFIG_CC_STACKPROTECTOR 
    tsk->stack_canary = get_random_int(); 
    #endif 
    /* One for us, one for whoever does the "release_task()" (usually parent) */ 
    atomic_set(&tsk->usage,2); 
    atomic_set(&tsk->fs_excl, 0); 
    #ifdef CONFIG_BLK_DEV_IO_TRACE 
    tsk->btrace_seq = 0; 
    #endif 
    tsk->splice_pipe = NULL; 
    return tsk; 
    }

    三、总结linux创建新进程的过程

      系统允许一个进程创建新进程,新进程即为子进程,子进程还可以创建新的子进程,形成进程树结构模型。整个linux系统的所有进程也是一个树形结构。树根是系统自动构造的,即在内核态下执行的0号进程,它是所有进程的祖先。由0号进程创建1号进程(内核态),1号负责执行内核的部分初始化工作及进行系统配置,并创建若干个用于高速缓存和虚拟主存管理的内核线程。随后,1号进程调用execve()运行可执行程序init,并演变成用户态1号进程,即init进程。它按照配置文件/etc/initab的要求,完成系统启动工作,创建编号为1号、2号...的若干终端注册进程getty。每个getty进程设置其进程组标识号,并监视配置到系统终端的接口线路。当检测到来自终端的连接信号时,getty进程将通过函数execve()执行注册程序login,此时用户就可输入注册名和密码进入登录过程,如果成功,由login程序再通过函数execv()执行shell,该shell进程接收getty进程的pid,取代原来的getty进程。再由shell直接或间接地产生其他进程。

      上述过程可描述为:0号进程->1号内核进程->1号内核线程->1号用户进程(init进程)->getty进程->shell进程

  • 相关阅读:
    02类的介绍
    Java图解
    String s=new String("abc")创建了几个对象?
    神经网络(13)--具体实现:random initialization
    神经网络(12)--具体实现:如何对back propagation的正确性进行验证
    神经网络(11)--具体实现:unrolling parameters
    神经网络(10)--有助于对神经网络Backpropagation算法的理解
    神经网络(9)--如何求参数: backpropagation algorithm(反向传播算法)
    神经网络(8)---如何求神经网络的参数:cost function的表达
    神经网络(7)---多分类问题
  • 原文地址:https://www.cnblogs.com/wule/p/4404504.html
Copyright © 2011-2022 走看看