zoukankan      html  css  js  c++  java
  • 2019-2020-1 20199305《Linux内核原理与分析》第七周作业

    进程的描述与创建

    (一)进程的描述

    (1)操作系统内核实现操作系统的三大管理功能(进程管理最为核心)

    • 进程管理

    • 内存管理

    • 文件系统

    (2)在Linux内中用一个数据结构struct task_struct来描述进程,以下是其数据结构的一部分

    struct task_struct { 
     volatile long state;        //进程状态
     void *stack;                // 指定进程内核堆栈
     pid_t pid;                  //进程标识符
     unsigned int rt_priority;   //实时优先级
     unsigned int policy;        //调度策略
     struct files_struct *files; //系统打开文件
     ...
    }
    

    (3)Linux内核管理的进程状态

    当使用fork()系统调用来创建一个新进程时,新进程的状态TASK_RUNNING(就绪态,但是没有运行)。当调度器选择这个新创建的进程运行时,新创建的进程就切换到运行态,它也是TASK_RUNNING。在Linux内核中,当进程是TASK_RUNNING状态时,它是可运行的,也就是就绪态,是否在运行取决于它有没有获得CPU的控制权,也就是说这个进程有没有在CPU中去实际执行,如果实际执行了,那进程状态就是运行态;如果被内核调度出去了,在等待队列里就是就绪态。对于一个正在运行的进程,调用用户态库函数exit()会陷入内核执行该内核函数do_exit(),也就是终止进程,那么会进入TASK_ZOMBIE状态,即进程的终止状态。TASK_ZOMBIE状态一般叫作僵尸进程,Linux内核会在适当的时候把僵尸进程给处理掉,处理掉之后进程描述符被释放了,该进程才从Linux中消失。一个正在运行的进程在等待特定的事件或资源时会进入阻塞态,阻塞态有两种:TASK_INTERRUPTIBLE和TASK_UNINTERRUPTIBLE,前者可以被信号和wake_up()唤醒的,后者只能被wake_up()唤醒。

    (二)进程的创建

    (1)0号进程的初始化

    init_task为第一个进程(0号进程)的进程描述符结构体变量,它的初始化是通过听编码方式固定下来的。除此之外,所有的其他进程的初始化都是通过do_fork复制父进程的方式初始化的。

    (2)用户态创建进程的方法

    具体进程的创建大概就是把当前进程的描述符等相关进程资源复制一份,从而产生一个子进程,并根子进程的需要对复制的进程描述符做一定的修改,然后把创建好的子进程放入运行队列(操作系统原理中的就绪队列)。在进程调度时,新创建的子进程处于就绪状态有机会被调度执行。

     #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!
    ");
            }
        }
    

    上面的代码中,else if(pid == 0)和else两段代码都被执行了,原因是因为fork系统调用把当前进程复制了一个子进程,即一个进程变成两个进程,两个进程执行相同的一段代码,但由于父进程和子进程的返回值可能不同,所以输出的信息可能不同,父进程并没有打破if else的条件分支的结构,且父子进程的执行顺序并不确定。

    (三)实验跟踪分析进程创建过程

    (1)首先在MenuOS中添加fork命令,编译结果如下

    (2)执行fork结果如下图所示,可以看到返回了父进程和子进程

    (3)接下来是设置断点,如下图所示

    (4)执行一个fork发下只输出了一条就停住了,继续执行,就会看到停在的do_fork()的位置上

    (5)接下来单步执行,往下执行就看到了copy_process

    (6)继续执行进入dup_task_struct

    (7)执行到copy_thread,继续执行就已经跟踪不到ret_from_fork,因为是汇编代码,不一定能跟踪到

    (四)总结

    创建一个进程是复制当前进程的信息,就是fork一个进程,这样就创建了一个新进程,因为父进程和子进程的绝大部分信息是完全一致的,但是有些信息是不能一样的,你如pid的值和内核堆栈。还有将新进程连接到各种链表中,要保存进程执行到哪个位置,有一个thread数据结构记录ip和sp等信息也不能一样,否则将会发生问题。父进程创建一个子进程,应该会有一个地方复制了父进程的进程描述符Task_struct结构体变量,并有很多地方来修改复制出来的进程描述符结构体变量。因为父子进程各自都有很多自己独立的个性,子进程应该有很多地方修改内核堆栈里的信息,因为内核兑换赞里的很多数据是从父进程复制来的,而fork系统调用在父子进程中分别返回到用户态,父子进程的内核堆栈中可能某些信息也不完全一样。还有thread,根据子进程复制的父进程的内核堆栈的状况,肯定要设定好EIP和ESP寄存器,即设定好子进程开始执行的位置,复制父进程的资源不需要修改进程资源,父子进程是共享内存存储空间的。

  • 相关阅读:
    Docker安装
    Shell编程
    Java:字节流和字符流(输入流和输出流)
    spring中的组合模式
    MySQL:ALTER COLUMN、MODIFY COLUMN 和 CHANGE COLUMN
    springboot项目打包docker镜像maven插件
    OGNL表达式
    项目中redisTemplate设置的key,redis客户端上查询不到的问题
    spring-session-data-redis反序列化问题
    深入理解SPI机制
  • 原文地址:https://www.cnblogs.com/20199305yizihan/p/11776045.html
Copyright © 2011-2022 走看看