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

来源:互联网 发布:双击屏幕唤醒软件 编辑:程序博客网 时间:2024/05/16 09:23


       这次实验要详细分析Linux内核创建新进程的过程。那么我们应该先明确,一个进程在Linux内核中是怎样的。那么就是要知道如下的几个知识。

       进程(Process)

               系统进行资源分配和调度的基本单位,一个进程是一个程序的运行实例。

      进程描述符(task_struct)

               用来描述进程的数据结构,可以理解为进程的属性。比如进程的状态、进程的标识(PID)等,都被封装在了进程描述符这个数据结构中,该数据结构被定义为task_struct

       进程控制块(PCB)

               是操作系统核心中一种数据结构,主要表示进程状态。在第一次实验就已经接触过了。

       进程状态

               开始、就绪、运行、阻塞、终止。



       众所周知,创建一个进程是通过fork()实行的。实际上在Linux中创建进程一共有三个函数:

              1.fork,创建子进程。

              2. vfork,与fork类似,但是父子进程共享地址空间,而且子进程先于父进程运行。

              3. clone,主要用于创建线程。这里值得注意的是,Linux中得线程是通过模拟进程实现的,较新的内核使用的线程库一般都是NPTL。


       那么我们现在来看看这三个函数在创建新进程时发生了。查看源代码,fork()通过触发0x80中断,陷入内核来提供调用。

       代码精简后如下:

 

       可以看出,无论是哪种方法创建新进程,最终都是通过do_fork()实现的!!!


      这次实验用到的是clone()函数。



开始实验

      还是对getpid系统调用,加入fork()到menuos中



      在clone处设置断点,开始跟踪




      先进入内核态SyS_clone



      然后执行do_fork



      接着执行copy_process



       在copy_process中又调用了许多函数进行各种初始化的工作,就不一一列出了,继续跟踪即可。基本上的流程结构清晰了,那我们就对着源代码来仔细分析一下。



开始分析

      查看do_fork()源代码



      可以看出,do_fork大概做了这么几件事情:

              1. 调用copy_process,将当期进程复制一份出来为子进程,并且为子进程设置相应地上下文信息。

              2.如果是vfork调用初始化vfork的完成处理信息。

              3. 调用wake_up_new_task,将子进程放入调度器的队列中,此时的子进程就可以被调度进程选中,得以运行。

              4.如果是vfork调用,需要阻塞父进程,知道子进程执行exec。


      查看copy_process源代码



      copy_process的大体流程

            1. 调用dup_task_struct复制当前的 task_struct。

            2.检查进程数是否超过限制。

            3. 初始化自旋锁、挂起信号、CPU 定时器等。

            4. 调用sched_fork初始化进程数据结构,并把进程状态设置为 TASK_RUNNING。

            5. 复制所有进程信息,包括文件系统、信号处理函数、信号、内存管理等。

            6. 调用copy_thread初始化子进程内核栈。

            7. 为新进程分配并设置新的 pid。

 

       查看dup_task_struct源代码



       dup_task_struct流程

           1. 调用alloc_task_struct_node分配一个 task_struct 节点。

           2. 调用alloc_thread_info_node分配一个 thread_info 节点,其实是分配了一个thread_union联合体,将栈底返回给 ti。

                                                                       

           3. 最后将栈底的值 ti 赋值给新节点的栈。


       查看sched_fork源代码



       sched_fork 大致完成了两项重要工作。

              1. 将子进程状态设置为 TASK_RUNNING。

              2. 是为其分配 CPU。


       查看copy_thread源代码


       copy_thread流程

              1. childregs->ax =0;//将子进程的 eax 赋值为0,因此子进程的pid开始是0。
              2. p->thread.ip = (unsigned long) ret_from_fork;//将子进程的 ip 设置为 ret_form_fork 的首地址,因此子进程是从 ret_from_fork 开始执行的。


总结

       新进程在copy_process中的具体创建过程执行如下:

       1. dup_task_struct中为其分配了新的堆栈。

       2. 调用了sched_fork,将其置为TASK_RUNNING。

       3. copy_thread中将父进程的寄存器上下文复制给子进程,保证了父子进程的堆栈信息是一致的。

       4. 将ret_from_fork的地址设置为eip寄存器的值。

       最终子进程从ret_from_fork开始执行。


       整个过程可大致表示为下图:

0 0