• 分析进程创建的过程---linux内核学习笔记(六)


    内容一:实验报告相关说明

     

    所学课程:《Linux内核分析》MOOC课程  

    链接:http://mooc.study.163.com/course/USTC-1000029000

     

    内容二:进程控制块简析

      为了管理进程,内核必须对每个进程进行清晰的描述,内核所需了解的进程信息都记录在结构体 task_struct 中,所以进程控制块PCB

    又被成为进程描述符。

          2.1    部分变量解释

    1237    void *stack;                     //进程堆栈
    1239    unsigned int flags;              /* per process flags, defined below */    
    1242    #ifdef CONFIG_SMP              //代表多核情况
    1251    int on_rq;                    //运行队列
    1253    int prio, static_prio, normal_prio; //优先级
    1255    const struct sched_class *sched_class;//进程调度相关
    1413   /* 文件系统信息 */
    1414	struct fs_struct *fs;
    1415   /*打开的文件描述符列表 */
    1416	struct files_struct *files;
    
    
    1419    /*信号处理相关*/
    1420	struct signal_struct *signal;
    1421	struct sighand_struct *sighand;
     

          2.2  进程链表

    1295    struct list_head tasks;    
    //结构声明如下
    23    struct list_head {
    24          struct list_head *next, *prev;
    25    };

    作用,构建双向链表:

      2.3  

    //进程内存相关
    1301    struct mm_struct *mm, *active_mm;
    
    //记录进程的PID,以及PID的哈希表
    1330    pid_t pid;
    1331    pid_t tgid;
    1360	struct pid_link pids[PIDTYPE_MAX];

      2.4  进程的父子关系描述部分

    1338     * pointers to (original) parent process, youngest child, younger sibling,
    1339     * older sibling, respectively.  (p->father can be replaced with
    1340     * p->real_parent->pid)
    1341     */
    1342    struct task_struct __rcu *real_parent; /* real parent process */
    1343    struct task_struct __rcu *parent; /* recipient of SIGCHLD, wait4() reports */
    1344    /*
    1345     * children/sibling forms the list of my natural children
    1346     */
    1347    struct list_head children;    /* list of my children */
    1348    struct list_head sibling;    /* linkage in my parent's children list */
    1349    struct task_struct *group_leader;    /* threadgroup leader */

    其关系描述可以简化为如下:

      2.5  进程与cpu相关状态

    1412    struct thread_struct thread;
    //结构实现
    468     struct thread_struct {   
    470            struct desc_struct    tls_array[GDT_ENTRY_TLS_ENTRIES];
    471            unsigned long        sp0;
    472            unsigned long        sp;
    ......
    483         unsigned long        ip;
    ......
    525    };                         

    类似与之前所学的mypcb中记录的IP和SP


    内容三:进程的创建

      linux系统允许任何一个用户创建一个子进程,创建之后,子进程存于系统之中,并且独立于父进程。该子进程可以接受调度,可以分配得到系统资源。系统中,除了0号进程以外(0号进程是由系统创建的),任何一个进程都是由其他进程创建的。所以说 Linux中,1号进程是所有用户态进程的祖先,0号进程是所有内核线程的祖先。

    • fork、vfork和clone三个系统调用都可以创建一个新进程,而且都是通过调用do_fork来实现进程的创建;

      Linux是通过复制父进程来创建一个新进程,进程创建的大致框架就是:复制PCB,对复制的PCB进行修改、分配新的内核堆栈...

      3.1  fork函数

     

    //函数原型
    pid_t fork( void);

     

    fork函数被调用一次但返回两次。两次返回的唯一区别是子进程中返回0值而父进程中返回子进程ID。

    这个fork函数的最后的落脚点是do_fork

    1703  SYSCALL_DEFINE0(fork)
    1704  {
    1705  #ifdef CONFIG_MMU
    1706      return do_fork(SIGCHLD, 0, 0, NULL, NULL);
    1707  #else
    1708      /* can not support in nommu mode */
    1709      return -EINVAL;
    1710  #endif
    1711  }

      3.2  clone过程分析

      clone如果不管条件编译的内容,实际上也是执行了do_fork

    1746    return do_fork(clone_flags, newsp, 0, parent_tidptr, child_tidptr);

      所以分析从do_fork开始:按照老师所说的主要框架来找相关代码进行确认。

        3.2.1  复制:

    1651    p = copy_process(clone_flags, stack_start, stack_size, child_tidptr, NULL, trace);

      copy_process中dup_task_struct函数复制了整个PCB。

    1240    p = dup_task_struct(current);

        3.2.2  对PCB进行修改

      copy_process函数中,从dup_task_struct函数后面的都是对复制的PCB进行修改,包括初始化内存、文件系统、信号等等,其中理解的关键是:

    1396    retval = copy_thread(clone_flags, stack_start, stack_size, p);
    135    struct pt_regs *childregs = task_pt_regs(p);    //SAVE_ALL地址
        //修改SP
    139    p->thread.sp = (unsigned long) childregs;
    140    p->thread.sp0 = (unsigned long) (childregs+1);
        //还处于父进程中,将父进程SAVE_ALL的内容拷贝过来
    159    *childregs = *current_pt_regs();
        //修改IP,所以产生的子进程在系统调用处理过程中从ret_from_fork处开始执行。
    164    p->thread.ip = (unsigned long) ret_from_fork;

      3.2.3  ret_from_fork

      程序接下来跳转到ret_from_fork。从此执行时内核堆栈只有之前存的一点点内容,中间的代码作用是怎么样的呢?

    290  ENTRY(ret_from_fork)
    291    CFI_STARTPROC
    292    pushl_cfi %eax
    293    call schedule_tail
    294    GET_THREAD_INFO(%ebp)
    295    popl_cfi %eax
    296    pushl_cfi $0x0202        # Reset kernel eflags
    297    popfl_cfi
    298    jmp syscall_exit
    299    CFI_ENDPROC
    300  END(ret_from_fork)

    可以查看 jmp syscall_exit,到syscall_exit时候,堆栈状态与系统调用前的是一样的。所以可以推断前面的292~297就是填充堆栈内容。

    505  syscall_exit:
    506    LOCKDEP_SYS_EXIT
    507    DISABLE_INTERRUPTS(CLBR_ANY)    # make sure we don't miss an interrupt
    508                    # setting need_resched or sigpending
    509                    # between sampling and the iret
    510    TRACE_IRQS_OFF
    511    movl TI_flags(%ebp), %ecx
    512    testl $_TIF_ALLWORK_MASK, %ecx    # current->work
    513    jne syscall_exit_work

      所以新创建的子进程获得cpu使用权时是从ret_from_fork开始执行,再返回到用户态,对应的是子进程的用户空间。

     

     内容四:GBD验证分析过程

      自己搭建的系统上调试。

      3.1:按老师要求更改menu,并make rootfs

     

       3.2:程序运行效果

      3.3 GDB调试(图太多了,选了其中两张)

        设置断点,并运行至第一个断点

      运行到第二个断点,后面跟踪不到了。

     

    内容五:小结

      通过本次课的学习,自己掌握了如下的知识:

      1:了解了进程的描述符,结构体 task_struct。对其中比较重要的声明有了一定的了解。

      2:初步的学习了进程启动的流程。

        2.1:通过fork创建一个新的进程,fork函数的特点是一次调用,两次返回。而其最终的落脚点是do_fork.

        2.2:  在进程创建的过程中,通过copy_process中的dup_task_struct复制父进程的PCB,然后紧接着修改需要修改的内容。

        2.3: 修改的内容有很多,其中很重要的一点是在copy_thread中,将子进程的IP设置为ret_from_fork。

        2.4: 所以当子进程获得CPU的使用权时,子进程是从ret_from_fork这个标号处开始执行,执行一系列堆栈内容填充指令后,跳转到syscall_exit,最后切换

    到用户态,此时则处于子进程的用户空间中。

      3: 学习方法:linux的具体实现代码很多,细节也很多,如果直接看代码,很容易忽略主干,所以老师说,应该在看代码之前,思考并找出代码实现功能的基本框架,

    然后在代码中找证据。

      

  • 相关阅读:
    格式布局
    tp框架之文件上传
    tp框架之验证码
    tp框架之自动验证表单
    tp框架之留言板练习
    tp框架之session
    tp框架之登录验证
    tp框架之函数调用
    tp框架之分页与第三方类的应用
    tp框架之AJAX
  • 原文地址:https://www.cnblogs.com/esxingzhe/p/4420406.html
Copyright © 2020-2023  润新知