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进程

  • 相关阅读:
    flash中网页跳转总结
    as3自定义事件
    mouseChildren启示
    flash拖动条移出flash无法拖动
    需要一个策略文件,但在加载此媒体时未设置checkPolicyFile标志
    Teach Yourself SQL in 10 Minutes
    电子书本地转换软件 Calibre
    Teach Yourself SQL in 10 Minutes
    Teach Yourself SQL in 10 Minutes
    Teach Yourself SQL in 10 Minutes – Page 31 练习
  • 原文地址:https://www.cnblogs.com/wule/p/4404504.html
Copyright © 2011-2022 走看看