• Linux内核设计第六周 ——进程的描述和创建


    Linux内核设计第六周

    ——进程的描述和创建

    第一部分 知识点总结

    一、进程描述符task_struct数据结构

    1、操作系统的三大功能:

    进程管理、内存管理、文件系统

    2、进程的作用:

    将信号、进程间通信、内存管理和文件系统联系起来

    3、进程控制块PCB——task_struct数据结构

    提供了内核需要了解的信息

    4、task_struct结构庞大,有400多行代码。包含了进程状态、内核堆栈等相关信息的定义。 可以从下面的图示中,清晰的看出大体的框架。

    5、Linux的进程和操作系统原理中描述的进程状态(就绪状态、运行状态、阻塞状态)有所不同,实际内核中,就绪和运行状态都用TASK_RUNNING表示。

    6、进程标志符pid/tpid——用于标识进程

    7、总体浏览task_struct结构

    二、进程的创建概览及fork一个进程的用户态代码

    1、进程创建的关键信息:状态、内核堆栈、CPU上下文切换、链接、文件系统、信号、内存等。

    2、进程的起源

    3、进程创建步骤:

    • 复制进程描述符
    • 修改子进程的PCB信息

    4、shell命令如何创建子进程

    • 使用fork函数在用户态创建子进程
    • fork系统调用在父进程和子进程各返回一次
    • 在子进程中,返回0,在父进程中,返回子进程ID

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

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

    • 先根据对功能的理解,自己预想可以怎样实现;
    • 再从源代码中,找到和预想相似的证据。

    1、系统调用复习

    • 从系统内核理解

    • 从用户程序理解

    2、fork代码

    #include <stdio.h>
    #include <stdlib.h>
    #include <unistd.h>
    int main(int argc, char * argv[])
    {
        int pid;
        /* fork another process */
        pid = fork();
        if (pid < 0) 
        { 
            /* error occurred */
            fprintf(stderr,"Fork Failed!");
            exit(-1);
        } 
        else if (pid == 0) //pid == 0和下面的else都会被执行到(一个是在父进程中即pid ==0的情况,一个是在子进程中,即pid不等于0)
        {
            /* child process */
            printf("This is Child Process!
    ");
        } 
        else 
        {  
            /* parent process  */
            printf("This is Parent Process!
    ");
            /* parent will wait for the child to complete*/
            wait(NULL);
            printf("Child Complete!
    ");
        }
    }

    3、fork、vork、clone都可以创建一个子进程,它们都调用了do_fork()实现进程创建。

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

    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,而非复制内核堆栈
    setup_thread_stack(tsk,orig);

    3复制copy_process

    即内核堆栈中的thread.sp的内容

    4、fork执行过程分析

    5、子进程是从哪里执行的? 
    代码部分为

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

    ret_from_fork就是子函数开始执行的地方,我们查看ret_from_fork在系统文件entry32.h中的定义可以发现,是执行了如下图所示的过程。

    第二部分 实验部分

    1、更新menu,删除test_fork.c和test.c文件,重新执行make rootfs

    2、重新make rootfs之后,可以看到内核被启动

    3、像之前的实验一样,启动gdb调试

    4、在fork系统调用的关键代码处,设置断点。

    5、在Menu系统中输入fork指令,可以看到只输出了fork功能的描述,没有之后的部分,说明该过程在断点处停止了。

    6、单步调试,可以看到程序停在了copy_process函数处

    7、继续单步执行,程序再次停在了dup_task_struct函数处

    8、进入duptaskstruct函数,继续单步执行,程序再次停在了duptaskstruct函数处

    9、在copy_thread函数中,继续单步执行,可以看到,内核空间压栈地址被初始化了。

    10、继续执行单步调试,如图所示,当前内核堆栈寄存器中的值复制到子进程中

    11、如图所示,标记代码的作用是,设置子进程被调度的起点

    12、如图所示,我们可以看到程序停止在了ret_from_fork处

    13、对ret_from_fork继续执行单步调试,我们可以看,当前系统执行的是汇编代码

    14、当程序跳转到syscall_exit处后,就不能再继续gdb跟踪调试了

    总结:

    子进程是从ret_ from_ fork开始执行的。子进程在进入sys_ call之前与父进程的堆栈状态相同,ret_ from_ fork中跳转到了syscall_ exit,继续执行上周我们分析过的部分,注意:当子进程获得CPU控制权的时候,它的ret_ from_ fork可以把后面堆栈从iret返回到用户态,这里的用户态是子进程的用户态

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

  • 相关阅读:
    C# SendKeys用法
    Winform的高DPI问题
    CefSharp在高DPI的屏幕上出现黑边(winform)
    CefSharp支持flash
    CeSharp支持MP4
    C#加密解密总览
    Eclipse 调试Bug之使用断点的七大技巧
    详解Eclipse断点
    怎样编写高质量的java代码
    Quartz任务调度基本使用
  • 原文地址:https://www.cnblogs.com/java-stx/p/5333556.html
Copyright © 2020-2023  润新知