zoukankan      html  css  js  c++  java
  • 第六周——分析Linux内核创建一个新进程的过程

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

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

    实验部分

    跟踪调试

    1)更新menu

     rm menu -rf //强制删除
     git clone https://github.com/mengning/menu.git
    

    2)更新test.c

    cd menu
    mv test_fork.c test.c
    

    3)编译一下

    make rootfs //自动编译
    

    调用一下fork

    4)调试time

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

    之后设一票断点(重要的部分),但是最后运行时我的实验楼突然卡住了,没能运行出来,这里会用文字和一些老师的图片请见谅

    b sys_clone
    b dup_task_struct
    b copy_process
    b copy_thread
    b ret_form_fork
    

    总结部分

    内核三大功能:

    进程管理
    文件管理
    内核系统
    

    进程描述

    进程控制块PCB _ TASK _ STRUCT 进程描述符

    1)Linux进程的状态与操作系统原理中的描述的进程状态似乎有所不同,比如就绪状态和运行状态都是TASK_RUNNING

    2)我们来看一下task_struct的整体吧~

    所有进程链表struct list_head tasks;

    程序创建的进程具有父子关系,在编程时往往需要引用这样的父子关系。进程描述符中有几个域用来表示这样的关系

    进程创建

    1)kernel的两个线程

    a.用户态进程启动kernel_init
    b.启动kthreadd 所有内核线程的祖先
    

    0号进程(手写)->1号进程(复制0号进程的PCB,根据其修改pid加载init可执行程序)

    关于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) 
        {
            /* 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用户态子进程,else if和else都会被执行哦

    在fork后,两个进程,fork系统调用在父子进程个返回一次
    
    			1)子 pid=0
    			2)父 pid=子进程的id
    

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


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

    系统调用复习

    创建新进程是通过当前进程来实现的(不过有一些信息不能一样啦,pid啊,一部分的内核堆栈啊)

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

    err = arch_dup_task_struct(tsk, orig);//复制整个PCB
    

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

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

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

    从用户态的代码看fork();函数返回了两次,即在父子进程中各返回一次,父进程从系统调用中返回比较容易理解,子进程从系统调用中返回,那它在系统调用处理过程中的哪里开始执行的呢?这就涉及子进程的内核堆栈数据状态和task_struct中thread记录的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; //调度到子进程时的第一条指令地址(从这句子程序得到CPU时,开始执行)
    
    复制的时候只复制内核堆栈相关的一部分,内核堆栈最栈底
    

    1.阅读理解task _ struct数据结构http://codelab.shiyanlou.com/xref/linux-3.18.6/include/linux/sched.h#1235;
    2.分析fork函数对应的内核处理过程sys _ clone,理解创建一个新进程如何创建和修改task_ struct数据结构;

    请看总结。

    3.使用gdb跟踪分析一个fork系统调用内核处理函数sys_clone ,验证您对Linux系统创建一个新进程的理解,推荐在实验楼Linux虚拟机环境下完成实验。

    有实验截图~

    4.特别关注新进程是从哪里开始执行的?为什么从哪里能顺利执行下去?即执行起点与内核堆栈如何保证一致。

    ret _ from _ fork 因为得到了cpu,复制了相关的信息(init指令和SAVE_ALL压到内核站的内容)设置子进程调度Ip。

  • 相关阅读:
    Java的类演进过程
    P112、面试题16:反转链表
    P107、面试题15:链表中倒数第K个结点
    Java对象相关元素的初始化过程
    P102、面试题14:调整数组顺序使奇数位于偶数前面
    P99、面试题13:在o(1)时间删除链表结点
    面试常考的数据结构Java实现
    Linux命令面试常考的简单汇总
    操作系统与进程基础知识
    python--process
  • 原文地址:https://www.cnblogs.com/midori/p/5332958.html
Copyright © 2011-2022 走看看