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

来源:互联网 发布:natapp内网穿透域名 编辑:程序博客网 时间:2024/04/29 16:03

1、阅读理解task_struct数据结构

        为了管理进程,内核必须对每个进程进行清晰的描述,进程描述符提供了内核所需要了解的进程信息。Linux内核通过一个被称为进程描述符的task_struct结构体来管理进程,图为task_struct结构体包含了一个进程所需的所有部分信息:

                              

结构体task_struct中部分成员的用法进行简要说明分析:

行号           成员                                             分析1237       void *stack;                           //stack should points to a threadinfo struct1239       unsigned int flags;                    //进程状态的信息1244       int on_cpu;                            //当前进程在哪个CPU上运行1253       int prio, static_prio, normal_prio;    //优先级信息1254       unsigned int rt_priority;              //实时任务的优先级 1255       const struct sched_class *sched_class; //与调度相关的函数1256       struct sched_entity se;                //调度实体1257       struct sched_rt_entity rt;             //实时任务调度实体1272       unsigned int policy;                   //调度策略1292       struct sched_info sched_info;          //调度相关的信息(CPU上运行时间,队列等待时间等。)1295       struct list_head tasks;                //任务队列 1302       struct mm_struct *mm, *active_mm;      //mm是进程的内存管理信息1312       int exit_state;                        //进程退出时的状态1313       int exit_code, exit_signal;            //进程退出时发出的信号1330       pid_t pid;                             //进程ID1331       pid_t tgid;                            //线程组ID1361       struct list_head thread_group;         //该进程所有线程的链表1381       u64 start_time;                        //线程启动时间1384       u64 real_start_time;                   //线程启动时间1401       int link_count, total_link_count;      //文件系统信息计数1412       struct thread_struct thread;           //该进程在CPU下的状态1414       struct fs_struct *fs;                  //文件系统信息结构体1481       int lockdep_depth;                     //锁的深度1488       void *journal_info;                    //文件系统日志信息1491       struct bio_list *bio_list;             //IO设备表1624       unsigned long timer_slack_ns;          //松弛时间值1625       unsigned long default_timer_slack_ns;  //松弛时间值
         Linux进程的状态与操作系统原理中的描述的进程状态有所不同,比如就绪状态和运行状态都是TASK_TUNNING,一个正在运行的进程,我们调用do_exit(),就进入了TASK_ZOMBIE(进程被终止)。程序创建的进程具有父子关系,在编程时往往需要引用这样的父子关系。进程描述符中有几个域用来表示这样的关系。

2、分析fork函数对应的内核处理过程sys_clone,理解创建一个新进程如何创建和修改task_struct数据结构

2.1 fork()函数的理解
         
一个进程调用fork()函数创建子进程时,系统先给新的进程分配资源,例如存储数据和代码的空间。然后把原来的进程的所有值都复制到新的新进程中,只有少数值与原来的进程的值不同。相当于克隆了一个自己。
          fork()函数通过系统调用创建一个与原来进程几乎完全相同的进程,也就是两个进程可以做完全相同的事,但如果初始参数或者传入的变量不同,两个进程也可以做不同的事。

                                        


        fork()是在用户态用于创建一个子系统的系统调用。Fork()系统调用在父进程和子进程各返回一次。即:
        fork调用的一个奇妙之处就是它仅仅被调用一次,却能够返回两次,它可能有三种不同的返回值:

    1. 在父进程中,fork返回新创建子进程的进程ID;
    2. 在子进程中,fork返回0;
    3. 如果出现错误,fork返回一个负值;
                

         在执行fork函数后,如果新进程创建成功,则返回两个进程,一个是父进程,一个是子进程。在父进程中,fork返回新创建子进程的进程ID;在子进程中,fork函数返回0。我们可以通过输出fork返回的值来判断当前进程是子进程还是父进程。
        fork出错可能有两种原因:

  1. 当前的进程数已经达到了系统规定的上限,这时errno的值被设置为EAGAIN;
  2. 系统内存不足,这时errno的值被设置为ENOMEM。

         所谓道生一(start_kernel......cpu_idle),一生二(kernel_init和kthreadd),二生三(即前面0,1和2三个进程),三生万物(1号进程是所有用户态进程的祖先,2号进程是所有内核进程的祖先)。概述了系统启动,创建了第一个进程后,再分别创建用户态进程和内核态进程的过程。
        在系统启动的时候,0号进程是我们手工写进入的,把它的系统描述符的数据结构用代码写死的。1号进程的创建相当于复制了一份0号进程的PCB,然后根据1号进程的需要,把PID等参数修改为1号进程的相应值。

2.2 fork()函数的底层实现
        Linux通过复制父进程来创建一个新进程,那么这就给我们理解这一个过程提供一个想象的框架:

1.  复制一个PCB——task_struct

err = arch_dup_task_struct(tsk, orig);

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

ti = alloc_thread_info_node(tsk, node);

tsk->stack = ti;

setup_thread_stack(tsk, orig); //这里只是复制thread_info,而非复制内核堆栈

3.  要修改复制过来的进程数据,比如pid、进程链表等等都要改改吧,见copy_process内部。

        Linux通过clone()系统调用实现fork()。这个调用通过一系列的参数标志来指明父,子进程需要共享的资源。fork(),vfork()和__clone()库函数都根据各自需要的参数标志去调用clone()。然后由clone()去调用do_fork()。do_frok完成了创建中的大部分工作,它的定义在ker/frok.c文件中。该函数调用copy_process()的函数,然后让进程开始运行。copy_process()做扫尾工作并返回一个指向子进程的指针。再回到do_fork()函数,如果copy_process()函数返回成功,新创建的子进程被唤醒并让其投入运行。

3、使用gdb跟踪分析一个fork系统调用内核处理函数sys_clone 

        3.1 使用实验楼环境进行试验,首先进入“LinuxKernel”文件夹,删除“menu”文件,重新下载“menu”文件,如下图所示:

                     

        3.2 进入“menu”文件中,利用mv命令将test.c替换为test_fork.c文件,如下图所示:

                    

         3.3 执行qemu命令,运行该程序:

                      

             3.4 进入gdb环境中,分别在sys_clone,do_fork,dup_task_struct,copy_process,copy_thread,ret_from_fork处设置断点,然后跟踪调试test_fork.c程序:

                     

            3.5单条指令开始执行,逐步跟踪do_fork的运行情况:

                    

4、总结

        Linux中,fork()系统调用产生的子进程在系统调用处理过程中从ret_from_fork处开始执行。

        Linux进程的创建过程就是内存中进程相关资源产生的过程,就是clone的过程。具体分析过程如下图:

              

         分析:fork()函数创建一份子进程。首先是fork()函数执行系统调用,调用system_call(),system_call进而根据进程调用号找到sys_fork()函数所在的位置,进而执行sys_fork()函数;之后,sys_fork()函数开始调用do_fork()函数,do_frok()函数位于fork.c文件中,它完成了子进程创建的大部分工作;最后do_fork()函数调用copy_process()函数,copy_process()函数完成了很多工作,比如,为新进程分配内核堆栈,检查进程数目是否超限,区分父、子进程,获取子进程PID等操作。


参考资料:

    1. http://blog.chinaunix.net/uid-24227137-id-3344607.html
    2. http://www.51develop.net/forum.php?mod=viewthread&tid=8963
    3. http://blog.csdn.net/yunsongice/article/details/5508242
    4. http://blog.chinaunix.net/uid-21783276-id-2689028.html

0 0
原创粉丝点击