• 20135220谈愈敏Blog6_进程的描述和创建


    进程的描述和创建

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

    进程的描述

    操作系统的三大管理功能:

    • 进程管理(最重要的)
    • 内存管理
    • 文件系统

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

    进程控制块PCB task_struct:

    • 进程状态
    • 进程打开的文件
    • 进程优先级信息

    task_struct总体数据结构的抽象:

    tty:控制台
    fs:文件系统
    files:文件描述符
    mm:内存管理
    signal:信号描述
    

    进程状态:不同于操作系统(就绪、运行、阻塞),如Linux中就绪状态和运行状态都是TASK_RUNNING

    进程的标示:pid

    进程描述符task_struct数据结构:

    进程的创建

    进程起源回顾:

    道生一(start_kernel....cpu_idle)
    一生二(kernel_init和kthreadd)
    二生三(即前面0、1和2三个进程)
    三生万物(1号进程是所有用户态进程的祖先,2号进程是所有内核线程的祖先)
    

    0号进程,是代码写死的,1号进程复制0号进程PCB,再修改,再加载可执行程序。

    fork创建一个子进程

    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时返回,子进程是在内核中返回
    {
        /* 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!
    ");
    }
    }
    

    fork在父进程和子进程各返回一次

    子进程中返回的是0,父进程中返回值是子进程的id。
    

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

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

    Linux通过复制父进程来创建一个新进程:

    do_fork主要是复制了父进程的task_struct,然后修改必要的信息,从而得到子进程的task_struct。
    

    理解这一个过程的一个想象的框架:

    复制一个PCB——task_struct

    err = arch_dup_task_struct(tsk, orig);
    

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

    ti = alloc_thread_info_node(tsk, node);
    tsk->stack = ti;
    setup_thread_stack(tsk, orig); //这里只是复制thread_info,而非复制内核堆栈
    

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

    新进程从哪里开始执行?

    从用户态的代码看fork();函数返回了两次,即在父子进程中各返回一次:

    • 父进程从系统调用中返回比较容易理解
    • 子进程从系统调用中返回,那它在系统调用处理过程中的哪里开始执行的呢?

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

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

    fork出来的子进程是从ret_from_fork开始执行的,然后跳转到syscall_exit,从系统调用中返回。

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

    启动MenuOS

    cd LinuxKernel   
    rm menu -rf
    git clone https://github.com/mengning/menu.git
    cd menu
    mv test_fork.c test.c
    make rootfs
    

    gdb调试fork命令

    qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img -s -S
    
    gdb
    file linux-3.18.6/vmlinux
    target remote:1234
    

    设置断点:

    b sys_clone
    b do_fork
    b dup_task_struct
    b copy_process
    b copy_thread
    b ret_from_fork
    

    跟踪调试:

    总结

    首先了解了进程控制块PCB,进程描述符task_struct,先从整体抽象概念讲,然后看具体的实现代码,虽然task_struct数据结构很庞大很复杂,我们也要理解一些重点,如进程运行状态、内核堆栈,还有当前CPU相关的一些,文件系统列表,打开的文件描述符等。

    关于进程的创建:0号进程,是代码写死的,1号进程复制0号进程PCB,Linux是通过复制父进程来创建一个新进程,然后修改必要的信息,从而得到子进程的task_struct

    fork创建的新的子进程是从ret_from_fork开始执行的,然后跳转到syscall_exit,从系统调用中返回。新进程如何开始执行的关键:

    p->thread.sp = (unsigned long) childregs; //调度到子进程时的内核栈顶
    p->thread.ip = (unsigned long) ret_from_fork; //调度到子进程时的第一条指令地址
  • 相关阅读:
    html5结构标签
    video标签、audio标签
    a标签、img图片、iframe、表单元素、div
    pre,html转义,abbr缩写,表格table
    html 列表标签
    p,br,hn,b,i,u,s,sup,sub标签
    网站开发准备学习
    交互原型设计工具
    为什么要重写hashCode()方法和equals()方法及如何重写
    Java接口回调
  • 原文地址:https://www.cnblogs.com/tymjava/p/5340523.html
Copyright © 2020-2023  润新知