zoukankan      html  css  js  c++  java
  • Linux第六周学习总结——进程额管理和进程的创建

    Linux第六周学习总结——进程额管理和进程的创建

    作者:刘浩晨

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

    一、 进程的描述

    操作系统内核三大功能:进程管理(核心)、内存管理和文件系统。

    1. 进程控制快PCB——进程描述符task_struct数据结构

      进程状态(五种状态)转化:

      注意:就绪状态和运行状态都是TASK_RUNNING,具体是就绪还是执行要看系统当前的资源分配情况。

    2. 进程标识符PID
      进程标识符pid_t pid唯一地标识进程

    3. 所有进程链表struct list_ head tasks;
      双向循环链表链接起了所有的进程,也表示了父子、兄弟等进程关系程序创建的进程具有父子关系,在编程时往往需要引用这样的父子关系。

    4. Linux为每个进程分配一个8KB大小的内存区域,用于存放该进程两个不同的数据结构:Thread_ info和进程的内核堆栈。内核控制路径所用的堆栈很少,因此对栈和Thread_ info来说,8KB足够了。

    5. 进程处于内核态时使用,不同于用户态堆栈,即PCB中指定了内核栈。

    6. struct thread_ struct thread; //与当前任务CPU状态相关,对进程上下文切换有关键性作用

    7. struct mm_struct *mm, *active_mm; //内存管理进程的地址空间

    8. struct files_struct *files; //打开文件描述符列表

    二、 进程的创建

    1. 进程的创建概览及fork一个进程的源代码

    进程的起源回顾:

    start_ kernel创建了cpu_ idle,即0号进程。0号进程又创建了两个线程,一个是kernel_ init,即1号进程,这个进程最终启动了用户态;另一个是kthreadd。0号进程是固定的代码,1号进程是通过复制0号进程PCB之后在此基础上做修改得到的。

    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. 系统调用回顾



    • iret与int 0x80指令对应,一个是弹出寄存器值,一个是压入寄存器的值。
    • 如果将系统调用类比于fork();那么就相当于系统调用创建了一个子进程,然后子进程返回之后将在内核态运行,而返回到父进程后仍然在用户态运行。

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

    fork、vork和clone三个系统调用都可以创建一个新进程,都通过调用do_fork()实现进程创建。

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

    • 复制一个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,而非复制内核堆栈

    • 从用户态的代码看fork(),函数返回了两次,即在父子进程中各返回一次。这就涉及子进程的内核堆栈数据状态和task_struct中thread记录的sp和ip的一致性问题,这是在哪里设定的——copy_thread in copy_process

    *childregs = *current_pt_regs(); //复制内核堆栈,并不是全部,只是regs结构体(内核堆栈栈底的程序)
    childregs->ax = 0; //为什么子进程的fork返回0,这里就是原因!

    p->thread.sp = (unsigned long) childregs; //调度到子进程时的内核栈顶
    p->thread.ip = (unsigned long) ret_from_fork; //调度到子进程时的第一条指令地址,也就是说返回的就是子进程的空间了

    5.创建的新进程从哪里开始执行?

    一个新创建的子进程,获得CPU之后,从哪一行代码进程执行:

    • 与之前写过的my_ kernel相比较,kernel中是可以指定新进程开始的位置(也就是通过eip寄存器指定代码行)。fork中也有相似的机制

    • 这涉及子进程的内核堆栈数据状态和task_ struct中thread记录的sp和ip的一致性问题,这是在copy_ thread in copy_ process设定的

    *childregs = *current_pt_regs(); //复制内核堆栈,并不是全部,只是regs结构体(内核堆栈栈底的程序)
    childregs->ax = 0; //为什么子进程的fork返回0,这里就是原因!

    p->thread.sp = (unsigned long) childregs; //调度到子进程时的内核栈顶
    p->thread.ip = (unsigned long) ret_from_fork; //调度到子进程时的第一条指令地址,也就是说返回的就是子进程的空间了

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

    1.更新menu内核,删除test_fork.c以及test.cc,并重新执行make rootfs

    2.比原先多出fork命令,编译内核查看:

    3.启动gdb跟踪调试内核,在一些重要函数处设置断点:

    4.在MenuOS中执行fork,停在父进程中。继续执行后,停在do_fork的位置:

    5.n命令进行单步执行,依次进入copy_process、dup_task_struct。此时父进程的PCB(task_struct数据结构)已经复制过来。s命令进入函数,可以看到dst = src:

    6.copy_thread函数中,把task_pg_regs(p)也就是内核堆栈特定的地址找到并初始化

    7.当前进程的内核堆栈寄存器中的值复制到子进程中

    8.164行:p->thread.ip = (unsigned long) ret_from_fork; //确定返回地址

    9.当程序跳转到syscall_exit,就不能再继续gdb跟踪调试,输入finish使得进程运行完。

    总结:

    1.Linux通过复制父进程来创建一个新进程,通过调用do_fork来实现。

    2.Linux为每个新创建的进程动态地分配一个task_struct结构。

    3.为了把内核中的所有进程组织起来,Linux提供了几种组织方式,其中哈希表和双向循环链表方式是针对系统中的所有进程(包括内核线程),而运行队列和等待队列是把处于同一状态的进程组织起来。

    4.fork()函数被调用一次,但返回两次。

  • 相关阅读:
    第14章 位图和位块传输_14.1-14.3 位图基础
    第13章 使用打印机_13.2 打印图形和文字
    第13章 使用打印机_13.1 打印基础
    第12章 剪贴板_12.3 实现一个剪贴板查看器
    第12章 剪贴板_12.2 剪贴板的高级用法
    第12章 剪贴板_12.1 剪贴板的简单用法
    第11章 对话框_11.3 通用对话框
    第11章 对话框_11.2 非模态对话框
    第11章 对话框_11.1 模态对话框
    第5章 作业和进程池(2)
  • 原文地址:https://www.cnblogs.com/lhc-java/p/5340414.html
Copyright © 2011-2022 走看看