zoukankan      html  css  js  c++  java
  • 20135239益西拉姆 Linux内核分析 进程的描述和进程的创建

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

    第六周 进程的描述和进程的创建

    一、 进程的描述

    • 进程控制块PCB——task_struct

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

    • struct task_struct数据结构很庞大
    • Linux进程的状态与操作系统原理中的描述的进程状态似乎有所不同,比如就绪状态和运行状态都是TASK_RUNNING,为什么呢?
    • 进程的标示pid
    • 所有进程链表struct list_head tasks;
      • 内核的双向循环链表的实现方法 - 一个更简略的双向循环链表
    • 程序创建的进程具有父子关系,在编程时往往需要引用这样的父子关系。进程描述符中有几个域用来表示这样的关系
    • Linux为每个进程分配一个8KB大小的内存区域,用于存放该进程两个不同的数据结构:Thread_info和进程的内核堆栈
      • 进程处于内核态时使用, 不同于用户态堆栈,即PCB中指定了内核栈,那为什么PCB中没有用户态堆栈?用户态堆栈是怎么设定的?
      • 内核控制路径所用的堆栈 很少,因此对栈和Thread_info 来说,8KB足够了
    • struct thread_struct thread; //CPU-specific state of this task
    • 文件系统和文件描述符
    • 内存管理——进程的地址空间

    进程的创建

    • 进程的创建概览及fork一个进程的源代码
    • 回顾:
      • startkernel创建了cpuidle,也就是0号进程。而0号进程又创建了两个线程,一个是kernel_init,也就是1号进程,这个进程最终启动了用户态;
      • 另一个是kthreadd。这就是“道生一,一生二”。0号进程是固定的代码;
      • 1号进程是通过复制0号进程PCB之后在此基础上做修改得到的。
    • iret与int 0x80指令对应,一个是弹出寄存器值,一个是压入寄存器的值
    • 如果将系统调用类比于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!
    ");
          }  
    }
    

    `

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

    1. fork、vfork和clone三个系统调用都可以创建一个新进程,而且都是通过调用do_fork来实现进程的创建;
    2. Linux通过复制父进程来创建一个新进程,那么这就给我们理解这一个过程提供一个想象的框架:

      • 复制一个PCB——task_struct
      • err = arch_dup_task_struct(tsk, orig);
      • 要给新进程分配一个新的内核堆栈
      • `ti = allocthreadinfo_node(tsk, node);

        tsk->stack = ti;

        setupthreadstack(tsk, orig); //这里只是复制,而非复制内核堆`

    3. 要修改复制过来的进程数据,比如pid、进程链表等等都要改改吧,见copy_process内部。
    4. 从用户态的代码看fork();函数返回了两次,即在父子进程中各返回一次,父进程从系统调用中返回比较容易理解,子进程从系统调用中返回,那它在系统调用处理过程中的哪里开始执行的呢?这就涉及子进程的内核堆栈数据状态和taskstruct中thread记录的sp和ip的一致性问题,这是在哪里设定的?copythread in copy_process
    5. *childregs = *current_pt_regs(); //复制内核堆栈
    6. childregs->ax = 0; //为什么子进程的fork返回0,这里就是原因!
    7. p->thread.sp = (unsigned long) childregs; //调度到子进程时的内核栈顶
    8. p->thread.ip = (unsigned long) ret_from_fork; //调度到子进程时的第一条指令地址

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

    • 更新menu内核,然后删除test_fork.c以及test.c(以减少对之后实验的影响
    • 编译内核,可以看到fork命令
    • 启动gdb调试,并对主要的函数设置断点
    • 在MenuOS中执行fork,就会发现fork函数停在了父进程中
    • 继续执行之后,停在了dofork的位置。然后n单步执行,依次进入copyprocess、duptaskstruct。按s进入该函数,可以看到dst = src(也就是复制父进程的struct)
    • 在copythread中,可以看到把taskpg_regs(p)也就是内核堆栈特定的地址找到并初始化
    • 到了159、160行的代码就是把压入的代码再放到子进程中:

       `*children = *current_pt_regs();
      
        childregs->ax = 0;`
      
    • 164行,是确定返回地址 p->thread.ip = (unsigned long) ret_from_fork;
    • 最后输入finish运行完毕。

    总结

    本周主要就是课本的进程一章的拓展,通过实践来更加的运用完整,很有趣。

  • 相关阅读:
    事件
    DOM中对象的获得
    C# 字符串 相关操作
    两个listbox 复制
    C#窗体控件简介ListBox
    store procedure
    view_baseInfo
    不走弯路,就是捷径
    inherit
    Excel 版本对应
  • 原文地址:https://www.cnblogs.com/20135239-yxlm/p/5349566.html
Copyright © 2011-2022 走看看