美文网首页
分析Linux内核创建一个新进程的过程

分析Linux内核创建一个新进程的过程

作者: 梅花小筑 | 来源:发表于2016-04-03 16:52 被阅读229次

    Linux对系统中的每个进程都用一个独立的 task_struct 结构进行表示和管理.其中 task_struct 结构体如下所示:

    struct task_struct { 
     volatile long state;进程状态/* -1 unrunnable, 0 runnable, >0 stopped */
     void *stack; 堆栈
     pid_t pid; 进程标识符
     unsigned int rt_priority;实时优先级
     unsigned int policy;调度策略
     struct files_struct *files;系统打开文件
     ...
    }
    

    全部代码见:
    task_sruct

    进程(Process)是系统进行资源分配和调度的基本单位,一个进程是一个程序的运行实例。而在Linux中,可以使用一个进程来创建另外一个进程。这样的话,Linux的进程的组织结构其实有点像Linux目录树,是个层次结构的,可以使用 pstree命令来查看。在最上面是init程序的执行进程。它是所有进程的老祖宗。

    进程的创建概览及fork一个进程的用户态代码

    (1)进程的起源再回顾

    道生一(start_kernel...cpu_idle)
    一生二(kernel_init和kthreadd)
    二生三(即前面的0、1、2三个进程)
    三生万物(1号进程是所有用户态进程的祖先,2号进程是所有内核线程的祖先)

    创建一个新进程在内核中的执行过程

    fork、vfork和clone三个系统调用都可以创建一个新进程,而且都是通过调用do_fork来实现进程的创建;
    Linux通过复制父进程来创建一个新进程,那么这就给我们理解这一个过程提供一个想象的框架:
    复制一个PCB:

    task_struct  err = arch_dup_task_struct(tsk, orig);
    

    要给新进程分配一个新的内核堆栈

    ti = alloc_thread_info_node(tsk, node);
    tsk->stack = ti;
    setup_thread_stack(tsk, orig); //这里只是复制thread_info,而非复制内核堆栈
    

    要修改复制过来的进程数据,比如pid、进程链表等等都要改改吧,见copy_process内部。
    从用户态的代码看fork();函数返回了两次,即在父子进程中各返回一次,父进程从系统调用中返回比较容易理解,子进程从系统调用中返回,那它在系统调用处理过程中的哪里开始执行的呢?这就涉及子进程的内核堆栈数据状态和task_struct中thread记录的sp和ip的一致性问题,这是在哪里设定的?copy_thread in copy_process

    *childregs = *current_pt_regs(); //复制内核堆栈
    childregs->ax = 0; //为什么子进程的fork返回0,这里就是原因!
    p->thread.sp = (unsigned long) childregs; //调度到子进程时的内核栈顶
    p->thread.ip = (unsigned long) ret_from_fork; //调度到子进程时的第一条指令地址
    

    浏览进程创建过程相关的关键代码

    (1)系统调用内核处理函数sys_fork、sys_clone、sys_vfork

    系统调用 最终都是执行do_fork() do_fork()里的复制进程的函数 复制 pcb 复制PCB的具体函数 分配内核栈 初始化新进程 复制pt_regs 设置子进程的返回地址

    如上图,子进程被调到时是从 ret_from_fork处开始执行的.

    ret_from_fork处的代码

    如上图,可以看到在 ret_from_fork 中调转到了 syscall_exit,故而可以在子进程中返回到用户态代码.
    将 test_fork.c改名为test.c后,进入 emu'调试模式,用 gdb 调试并下断点.如图:

    断点 首先中断在 sys_clone处 do_fork 复制 task_struct并分配内核堆栈 copy_thread 最终回到ret_from_fork

    do_fork 系统内核调用;
    copy_process 复制父进程的所有信息给子进程;
    dup_task_struct 中为子进程分配了新的堆栈;
    调用 sched_fork,并把新进程设置为 TASK_RUNNING;
    copy_thread 中把父进程的寄存器上下文复制给子进程;
    设置 ret_from_fork 的地址为 eip 寄存器的值。

    我对 Linux 创建新进程的理解:
    首先复制父进程的 task_struct,并为父进程分配新的内核堆栈并在新的内核堆栈里复制父进程内核堆栈中关于返回到用户态的返回信息,并相应地修改返回地址与返回值(0)接着对子进程最初始化操作,包括清空信号,关闭应当关闭的文件描述符,设置用户 Id等等.最终子进程从该调用退出,返回到被修改后的系统调用总控程序中的特定地址( ret_from_fork), 进而返回到用户态,就执行.
    而对于父进程来说并不会感受到这一切,只相当于调用了一个普通的系统调用,并返回了子进程的 pid.

    相关文章

      网友评论

          本文标题:分析Linux内核创建一个新进程的过程

          本文链接:https://www.haomeiwen.com/subject/dpdulttx.html