《Linux内核分析》 第六周

本文详细解读Linux内核中进程的描述与管理,包括进程控制块、进程转化图、任务运行状态及僵尸进程的概念,以及如何通过task_struct数据结构进行进程描述。进一步探讨了Linux下进程的创建过程,从start_kernel到fork系统调用,再到新进程的初始化与内核堆栈的分配。并通过实验使用gdb跟踪创建新进程的详细步骤,最后总结了进程生命周期的前半部分,即从创建到基本描述。

《Linux内核分析》 第6周

一、进程的描述

1.进程控制块PCB

2.linux下的进程转化图

  • 744616-20160328213943801-429247020.png

  • TASK_RUNNING可以是就绪态或者执行态,具体取决于系统调用
  • TASK_ZOMBIE僵尸进程(终止的进程)

3.进程描述符task_struct

    • task_struct是一个数据结构,数据结构庞大;
    • pid:进程标识值,一个int型数值
  1. 进程链表struct list_head tasks,数据结构如下:
  • 744616-20160328214007332-1999439315.png

  • 它是一个双向链表,用来把当前所有进程用链表连起来

  1. 进程描述符中有几个域表示父子关系
  • 744616-20160328214022019-1333008799.png

  • 程序创建的进程具有父子关系在编程时经常需要用到这样的父子关系。进程描述符中有几个域用来表示这样的关系。
  • 每个进程都有一个父进程,每一个父进程都有0个或多个子进程

  1. struct thread_struct thread
  • 744616-20160328214035254-935448214.png

  • 与CPU相关

  1. struct *file表示打开的文件链表
  2. Linux为每个进程分配一个8k大小的内存区域,用于存放该进程两个不同的数据结构:Thread_info和进程的内核堆栈

二、进程的创建

1.复习:“道生一,一生二”

  • start_kernel创建了cpu_idle,即0号进程
  • 0号进程又创建了两个线程kernel_init和kthreadd
  • kernel_init即1号进程,这个进程最终启动了用户态
  • 2.系统调用回顾

  1. 系统调用
  • 用户态int 0x80指令实现软中断,过程详细如图:
  • 744616-20160328214050957-1178785889.png

  • 744616-20160328214104754-975855443.png

  1. fork与系统调用相关,如图:
    • 744616-20160328214120379-641440981.png

3.创建新进程的过程理解

  1. 创建一个新进程的框架
  • dup——thread复制父进程的pcb
  • 744616-20160328214136144-264898866.png

  • copy_process修改复制的pcb以适应子进程的特点,也就是子进程的初始化
  • 744616-20160328214149441-1203567039.png

  • 分配一个新的内核堆栈(用于存放子进程数据)

     - 内核堆栈的一部分也要从父进程中拷贝
     - ![](http://images2015.cnblogs.com/blog/744616/201603/744616-20160328214201832-60467643.png)
     - ![](http://images2015.cnblogs.com/blog/744616/201603/744616-20160328214217316-1809241664.png)
     - 根据拷贝的内核堆栈情况设置eip,esp寄存器的值
  1. 一个新进程(子进程)从哪一行代码开始执行?
  • 与之前写过的my_kernel相比较,kernel中可以指定新进程开始的位置(通过eip)。fork中也有相似的机制。
  • copy_thread in copy_process
1.*childregs = *current_pt_regs(); //复制内核堆栈,并不是全部,只是regs结构体(内核堆栈栈底的程序)
2.childregs->ax = 0; //为什么子进程的fork返回0,这里就是原因!
3. 
4.p->thread.sp = (unsigned long) childregs; //调度到子进程时的内核栈顶
5.p->thread.ip = (unsigned long) ret_from_fork; //调度到子进程时的第一条指令地址,也就是说返回的就是子进程的空间了

4.fork代码

        1.#include <stdio.h>
        2.#include <stdlib.h>
        3.#include <unistd.h>
        4.int main(int argc, char * argv[])
        5.{
        6.int pid;
        7./* fork another process */
        8.pid = fork();
        9.if (pid < 0) 
        10.{ 
        11./* error occurred */
        12.fprintf(stderr,"Fork Failed!");
        13.exit(-1);
        14.} 
        15.else if (pid == 0) //pid == 0和下面的else都会被执行到(一个是在父进程中即pid ==0的情况,一个是在子进程中,即pid不等于0)
        16.{
        17./* child process */
        18.printf("This is Child Process!\n");
        19.} 
        20.else 
        21.{  
        22./* parent process  */
        23.printf("This is Parent Process!\n");
        24./* parent will wait for the child to complete*/
        25.wait(NULL);
        26.printf("Child Complete!\n");
        27.}
        28.}

三、实验 使用gdb跟踪创建新进程的过程

1.在实验楼环境下的操作步骤

  1. 更新menu内核,然后删除test_fork.c以及test.c(以减少对之后实验的影响)
    • 744616-20160328214324910-461197814.png
  2. 编译内核,可以看到fork命令
    • 744616-20160328214338691-1110951019.png
  3. 启动gdb调试,并对主要的函数设置断点
    • 744616-20160328214355910-534313164.png
  4. 在MenuOS中执行fork,就会发现fork函数停在了父进程中
    • 744616-20160328214415926-238574247.png
  5. 继续执行之后,停在了do_fork的位置。然后n单步执行,依次进入copy_process、dup_task_struct。按s进入该函数,可以看到dst = src(也就是复制父进程的struct)
    • 744616-20160328214435144-1307157711.png

    • 744616-20160328214450707-1341282738.png

  6. 在copy_thread中,把task_pg_regs(p)也就是内核堆栈特定的地址找到并初始化
    • 744616-20160328214510504-1417482147.png

四、总结

本周的课堂主要讲述的是进程的前半部分——关于基本的描述,以及新进程生命周期的开始(进程创建)。

关于进程描述,可以看到的是task_struct是进程描述的关键;其中含有一个进程从创建到终结的全部信息。关于进程创建,这里以fork函数为例进行了讲解;因为调用了fork函数之后就会创建新进程,创建的过程从代码和gdb调试两个方向进行了分析。

转载于:https://www.cnblogs.com/shadow135211/p/5330762.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值