zoukankan      html  css  js  c++  java
  • Linux内核分析——进程的描述和进程的创建

                  进程的描述和进程的创建

    一、 进程的描述

    (一)进程控制块PCB——task_struct

    1、操作系统的三大管理功能包括:

      (1)进程管理

      (2)内存管理

      (3)文件系统

    2、PCB task_struct中包含:

      (1)进程状态

      (2)进程打开的文件

      (3)进程优先级信息

    3、通过唯一的进程标识PID来区别每个进程。

    4、进程控制块PCB——task_struct

     

    5、进程状态

     

      (1)创建新进程后实际的状态是TASK_RUNNING,就绪但是没有运行,调度器选择一个task之后进入运行态,也叫TASK_RUNNING。

      (2)当进程是TASK_RUNNING时,代表这个进程是可运行的,至于它有没有真的在运行,取决于它有没有获得cpu的控制权,即有没有在cpu上实际的运行。

      (3)一个正在进行的进程调用do_exit(),进入TASK_ZOMBIE,进程被终止,“僵尸进程”。

      (4)等待特定时间或者资源的时候,进入阻塞态,如果条件满足就进入就绪态,被选择后进入运行态。

    6、理解task_struct数据结构

     

      (1)进程的标示pid

      (2)为了对给定类型的进程进行有效的搜索,内核维护了几个进程链表。

      (3)所有进程链表struct list_head tasks

     

     

    7、CPU相关的状态

     

    二、 进程的创建

    (一)进程的创建概览及fork一个进程的用户态代码

     

    1、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()系统调用从内核返回两次:一次回到父进程,另一次回到新产生的子进程。

      (2)fork()实际上是由clone()系统调用实现的。

    (二)理解进程创建过程复杂代码的方法

    1、调用fork的过程:

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

    • fork、vfork和clone三个系统调用都可以创建一个新进程,而且都是通过调用do_fork来实现进程的创建;
    • Linux通过复制父进程来创建一个新进程,那么这就给我们理解这一个过程提供一个想象的框架:
    • 复制一个PCB——task_struct
      1. err = arch_dup_task_struct(tsk, orig);
    • 要给新进程分配一个新的内核堆栈
      1. ti = alloc_thread_info_node(tsk, node);
      2. tsk->stack = ti;
      3. setup_thread_stack(tsk, orig); //这里只是复制thread_info,而非复制内核堆栈
      • 要修改复制过来的进程数据,比如pid、进程链表等等都要改改吧,见copy_process内部。
    • 从用户态的代码看fork();函数返回了两次,即在父子进程中各返回一次,父进程从系统调用中返回比较容易理解,子进程从系统调用中返回,那它在系统调用处理过程中的哪里开始执行的呢?这就涉及子进程的内核堆栈数据状态和task_struct中thread记录的sp和ip的一致性问题,这是在哪里设定的?copy_thread in copy_process
      1. *childregs = *current_pt_regs(); //复制内核堆栈
      2. childregs->ax = 0; //为什么子进程的fork返回0,这里就是原因!
      3. p->thread.sp = (unsigned long) childregs; //调度到子进程时的内核栈顶
      4. p->thread.ip = (unsigned long) ret_from_fork; //调度到子进程时的第一条指令地址

    新进程执行起点:return_from_fork,只复制了内核堆栈一部分,int指令和save_all压到内核栈的内容,参数、系统调用号等都进行压栈。

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

    1、准备工作:

      rm menu -rf

      git clone http://github.com/mengning/menu.git   //更新Menu

      cd menu

      mv test_fork.c test.c   //把test.c覆盖掉

      make rootfs

     

    2、进行gdb调试:

      qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img -s -S

      gdb

      file linux-3.18.6/vmlinux

      target remote:1234

      // 设置断点

      b sys_clone  //因为fork实际上是执行的clone

      b do_fork

      b dup_task_struct

      b copy_process

      b copy_thread

      b ret_from_fork

      c

      n

     

      tsk->stack = ti;    //把内核堆栈的地址赋给它

      sturct pt_regs *childregs = task_pg_regs(p);  //把内核堆栈压栈的空间地址找到,初始化。

      *childregs = *current_pt_regs(); 

      childregs->ax = 0;  //把当前进程的内核堆栈的压的寄存器赋值到子进程中来。

      p->thread.ip = (unsigned long) ret_from_fork; //设置子进程被调度的ip,即子进程的起点

      jmp syscall_exit;   //这之后就跟踪不到了。

    三、总结

      进程就是处于执行期的程序;进程就是正在执行的程序代码的实时结果;进程是处于执行期的程序以及相关的资源的总称。

    进程在创建它的时刻开始存活。在Linux系统中,这通常是调用fork()系统的结果。fork()系统调用从内核返回两次:一次回到父进程,另一次回到新产生的子进程。它实际上是由clone()系统调用实现的。

  • 相关阅读:
    (Oracle)取当前日期的最近工作日
    (Oracle)误删oracle表结构恢复
    (Oracle)DDL及其数据泵导入导出(impdp/expdp)
    kettle 连接oracle12c问题解决办法:
    js日历算法
    js设置cookies
    js获取下拉框的value值
    js记住密码
    checkBox的全选与全不选
    select下拉框选中其中一个值
  • 原文地址:https://www.cnblogs.com/20135235my/p/5349690.html
Copyright © 2011-2022 走看看