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

作者:李嘉

原创作品转载请注明出处 + 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000 

 

1、内核三大特征

       进程管理  内存管理 文件系统 核心为进程管理

    进程控制块pcb结构 task_struct  400多行代码 

 

Shell执行命令并并打入镜像

cd LinuxKernel

rm -rf menu

git clone https://github.com/mengning/menu.git

cd menu

mv test_fork.c test.c

make rootfs

启动后如图

 

 

 

查看test.c对应的Fork命令

 

 

运行fork

 

关闭并开启调式模式跟踪 S s 已经通过gdb进行调试 来分析

sys_clone

do_fork

dup_task_struct

copy_process

copy_thread

ret_from_fork

 

6个函数

 

 

运行c继续

 

 

 

如图所示 do_fork调用copy_process,copy_process调用的copy_thread,

 

借鉴了一位朋友的堆栈分析图

1、分析 http://codelab.shiyanlou.com/xref/linux-3.18.6/include/linux/sched.h#1235

1235  struct task_struct {

1236    volatile long state; /* -1 unrunnable, 0 runnable, >0 stopped */

1237    void *stack;

1238    atomic_t usage;

1239    unsigned int flags;  /* per process flags, defined below */

1240    unsigned int ptrace;

task_struct 的主要内容如下

    state 进程运行状态

       stack 进程的内核运行堆栈

       flag 标识

       CONFIG_SMP 多处理器使用到(条件编译)

 

       进程调度相关

       int on_rq;  运行队列

       prio 优先级

       进程链表 (双向)

       struct list_head tasks

       进程管理 内存地址空间相关(物理地址及逻辑地址的转换 内存管理单元mmu)

       struct mm_struct *mm,*active_mm;

      

       pid 进程标识

 

       进程父子关系 兄弟 父子 都是通过双向链表相连

       struct list_head ptraced 调式用的

      

       utime stime 时间相关

       thread struct  cpu 相关 用于进程上下文切换

   struct fs_struct *fs 文件系统相关数据结构

       struct file_struct *files 打开的文件描述符列表

    signal 信号处理相关

   mutex 互斥锁

       pipe  管道相关

 

当前进程复制进程描述符(写死)

1号复制0号的PCB 修改PID 等 加载INIT 可执行程序

1号进程是所有用户态进程的父进程

 

fork 出现两个进程 父进程和子进程 父进程返回子进程的ID,子进程返回0

 

在 Linux 内核中,供用户创建进程的系统调用fork()函数的响应函数是

sys_fork

sys_vfork

sys_clone

这3个函数这三个函数都是通过调用内核函数 do_fork() 来实现的。根据
调用时所使用的 clone_flags 参数不同,do_fork() 函数完成的工作也各异。

简要分析do_fork()是怎么工作的,代码以如下:

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;
          /*
          * 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;
          }
          //创建进程描述符以及子进程所需要的其他所有数据结构
          p = copy_process(clone_flags, stack_start, stack_size,child_tidptr, NULL, trace);
          /*
          * 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;
                    struct pid *pid;
                    trace_sched_process_fork(current, p);
          pid = get_task_pid(p, PIDTYPE_PID);
          nr = pid_vnr(pid);
 
          if (clone_flags & CLONE_PARENT_SETTID)
                put_user(nr, parent_tidptr);
          
                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_pid(trace, pid);
          
                  if (clone_flags & CLONE_VFORK) {
                    if (!wait_for_vfork_done(p, &vfork))
                          ptrace_event_pid(PTRACE_EVENT_VFORK_DONE, pid);
                  }
          
                  put_pid(pid);
             } else {
                    //出错处理
                 nr = PTR_ERR(p);      
          }
          return nr;
}

 

使用系统调用fork系统调用创建一个新进程,而且都是通过调用do_fork来实现进程创建,Linux通过复制父进程PCB即task_struct来创建一个新进程,要给新进程分配一个新的内核堆栈;.要修改复制过来的进程数据,比如pid、进程链表等等执行copy_process和copy_thread然后通过.p->thread.sp = (unsigned long) childregs;来调度子进程时的内核堆栈,而p->thread.ip = (unsigned long) ret_from_fork; 为调度到子进程时的第一条指令地址

 

posted @ 2015-04-12 20:26  李嘉小鱼  阅读(140)  评论(0)    收藏  举报