• "Linux内核分析"第六周实验报告


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

    1、进程的描述

    进程控制块PCB——task_struct

    为了管理进程,内核必须对每个进程进行清晰的描述,进程描述符(即task_struct)提供了内核所需了解的进程信息。

    父子进程关系管理:

     

    这是CPU状态,在进程上下文切换的过程中,起到关键作用:

    2、进程的创建

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

     

    以fork一个子进程的代码为例:

    1. #include <stdio.h>
    2. #include <stdlib.h>
    3. #include <unistd.h>
    4. int main(int argc, char * argv[])
    5. {
    6.     int pid;
    7.     /* fork another process */
    8.     pid = fork();
    9.     if (pid < 0) 
    10.     
    11.         /* error occurred */
    12.         fprintf(stderr,"Fork Failed!");
    13.         exit(-1);
    14.     
    15.     else if (pid == 0) 
    16.     {
    17.         /* child process */
    18.         printf("This is Child Process! ");
    19.     
    20.     else 
    21.     
    22.         /* parent process  */
    23.         printf("This is Parent Process! ");
    24.         /* parent will wait for the child to complete*/
    25.         wait(NULL);
    26.         printf("Child Complete! ");
    27.     }
    28. }

    可用fork系统调用在父进程和子进程各返回一次。在fork之后,这段代码实际上就变成了两个进程。

    理解进程创建过程复杂代码的方法

    系统调用回顾:

     

     

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

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

    • Linux通过复制父进程来创建一个新进程,那么这就给我们理解这一个过程提供一个想象的框架:

      • 复制一个PCB——task_struct

        1. err = arch_dup_task_struct(tsk, orig);
      • 要给新进程分配一个新的内核堆栈

        1. ti = alloc_thread_info_node(tsk, node);
        2. tsk->stack = ti;
        3. setup_thread_stack(tsk, orig); //这里只是复制thread_info,而非复制内核堆栈
      • 要修改复制过来的进程数据,比如pid、进程链表等等都要改改吧,见copy_process内部。

    • 从用户态的代码看fork();函数返回了两次,即在父子进程中各返回一次,父进程从系统调用中返回比较容易理解,子进程从系统调用中返回,那它在系统调用处理过程中的哪里开始执行的呢?

    这就涉及子进程的内核堆栈数据状态和task_struct中thread记录的sp和ip的一致性问题,这是在哪里设定的?copy_thread in copy_process

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

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

    以上为复制数据结构部分代码,dup_task_struct。这段代码相对简单,就是将src其中内容赋值给dst。

    以上为alloc调用,它功能就是生成了两个页面。

    重点还是看copy_thread的功能,pt_regs获取了pid指向的堆栈内信息,比如栈底等内容。

    这是父进程对已有堆栈数据的copy,拷贝内核堆栈数据和指定新进程的第一条指令地址。

    因为子进程的返回值为0,所以copy时还需要修改一下内核栈内压入的。

     子进程是从哪里开始执行的?

    子进程是从这个位置开始执行的,就是fork返回值被得到的时候。

    对pt_regs内容进行查看,可以看到其中包含int指令和SAVE_ALL压到内核栈的内容。只复制了i32的相关内容。

    从entry_32中,可以找到ret_from_fork的函数代码部分。

    会跳到syscall_exit部分继续执行。正常返回到用户态。

    使用gdb跟踪创建新进程的过程

     如果用实验楼环境,首先需要rm menu -rf 删除原先menu 然后clone一份新的。

    然后进入menu后,mv test_fork.c test.c,把test.c覆盖掉。

    然后make rootfs,编译出来。

    可以发现fork被进入到menu里。

    然后使用gdb调试,使用-s -S。

    然后设置断点:b sys_clone 和 b do_fork 和 b dup_task_struct 和 b copy_process 和 b copy_thread 和 b ret_from_fork

    然后进行n单步调试。

    可以看到子进程执行的起点。

    3、总结

    linux 系统创建进程都是用 fork() 系统调用创建子进程。

    由 fork() 系统调用创建的新进程被称为子进程。

    该函数被调用一次,但返回两次。

    如果 fork()进程调用成功,两次返回的区别是子进程的返回值是0,而父进程的返回值则是新子进程的进程号。

  • 相关阅读:
    冒泡排序
    最长回文子串
    两个排序数组的中位数
    Manacher算法解析
    绕过校园网WEB认证_iodine实现
    绕过校园网WEB认证_dns2tcp实现
    ajax跨域请求
    Vue实例生命周期
    组件化应用构建
    表单输入绑定
  • 原文地址:https://www.cnblogs.com/20135121conan/p/5330414.html
Copyright © 2020-2023  润新知