20135239益西拉姆 Linux内核分析 进程的描述和进程的建立

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

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

1、 进程的描述

  • 进程控制块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!\n");
      } 
      else 
      {  
           /* parent process  */
           printf("This is Parent Process!\n");
           /* parent will wait for the child to complete*/
           wait(NULL);
           printf("Child Complete!\n");
      }  
}

`框架

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

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

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

      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)也就是内核堆栈特定的地址找到并初始化
  • 到了15九、160行的代码就是把压入的代码再放到子进程中:3d

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

总结

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

相关文章
相关标签/搜索