• <Linux>(极简关键、省时省力)《Linux操作系统原理分析之Linux 进程管理 4》(8)


    4 Linux 进程管理

    4.4 Linux 进程的创建和撤销

    4.4.1 Linux 进程的族亲关系

    1、进程树
    在这里插入图片描述
    在系统加电启动后,系统只有一个进程,它就是由系统创建的初始进程,又称 init 进程

    Init 进程的任务结构体名字为 init_task。Init 进程是系统中所有进程的祖先进程,进程标识号 PID 为 1。

    进程之间的父子关系
    进程之间的兄弟关系:按照创建时间确定,先者为兄,后者为弟;

    2、 Linux 进程的族亲关系
    在每个进程的任务结构体中设置了 5 个成员项指针:

    Struct task_struct *p_opptr /*指向祖先进程任务结构体指针*/
    Struct task_struct *p_pptr /*指向父进程任务结构体指针*/
    Struct task_struct *p_cptr /*指向子进程任务结构体指针*/
    Struct task_struct *p_ysptr /*指向弟进程任务结构体指针*/
    Struct task_struct *p_osptr /*指向兄进程任务结构体指针*/
    
    • 1
    • 2
    • 3
    • 4
    • 5

    在任务结构体中只有一个指向子进程指针,它指向该进程子进程中最年轻的一个,其他子进程通过兄弟关系指针与父进程的关系。

    4.4.2 Linux 进程的创建

    Linux 中,除 init 进程是启动时由系统创建的,其他进程都是由当前进程使用系统调用 fork()创建的。

    👉子进程被创建后,通常要继承(共享)父进程所有的资源。包括虚拟存储空间的内容、打开的文件、专用的信号处理程序等。
    👉写时拷贝(copy on write)技术:子进程和父进程共享同一个虚拟空间,只是这两个进程中某个进程需要虚拟内存写入时,这时才建立属于该进程的那部分虚拟空间,并把原虚拟空间的那部分内容拷贝到新建的虚拟内存区域中,然后在新建的属于自己的虚拟空间区域中写入信息。 这样既可以使子进程继承父进程的资源,又可以在需要时建立自己的存储空间,避免子进程存储空间的无谓浪费,有效地节省了系统时间。
    👉 父进程和子进程在“分裂”后,运行时都在继续执行 fork()程序的代码。但是为了区分两个进程:父进程执行完 fork()时返回值是子进程的 PID 值;而子进程执行 fork()的返回值是 0。

    #include <sys/typex.h>
    #include <unisd.h>
    Main()
    {
    Pid_t val;
    Printf(PID before fork():%d\n”,(int) getpid());
    Val=fork();
    If(val!=0)
    Printf(“parent process PID:%d\n”,(int) getpid());
    Else
    Printf(“child process PID:%d\n”,(int) getpid());
    }
    程序执行结果:
    PID before fork(): “此时父进程的 PID”
    parent process PID: “此时父进程的 PID”
    child process PID: “此时子进程的 PID
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    fork()进程的“分裂”过程
    在这里插入图片描述

    4.4.3 Linux 进程创建的过程

    Linux 中进程都是由当前进程使用系统调用 fork()创建的,而实际上是 fork()中进一步调用内核函数 do_fork()来完成的。Fork()的源代码定义在/kernel/fork.c 中。下面给出 do_fork()内核函数的主要功能:

    1)在内存空间为新进程(子进程)分配任务结构体使用的空间,然后把当前进程(父进程)的任务结构体的所有内容拷贝到子进程的任务结构体中。
    2)为新进程在其虚拟内存建立内核堆栈。虽然子进程共享父进程的虚拟空间,但是两个进程在进入核心态后必须有自己独立的内核堆栈。
    3)对子进程任务结构体中的部分内容进行初始化设置,例如进程的链接关系(族亲关系)等,主要是与父进程不同的那些数据。
    4)把父进程的资源管理信息拷贝给子进程,如虚拟内存信息、文件信息、信号信息等,前面说过,这里的拷贝是建立两个进程对这些资源的共享关系。
    5)把子进程加入可运行队列中,由调度进程在适当的时机调度运行,也就是在这个时候,当前进程分裂为两个进程。
    6)无论哪个进程使用 CPU 运行,都继续执行 do_fork()函数的代码,执行结束后返回它们各自的返回值。

    从以上过程可以看出,do_fork()函数的功能首先是建立子进程的任务结构体和对其进行初始化,然后继承父进程资源,最后设置进程的时间片并把它加入可运行队列。

    4.4.4 Linux 进程的执行

    1、 系统调用 系统调用 exec()
    在系统中创建一个进程的目的是需要该进程完成一定的任务,通过执行它实现所需的功能。在 Linux系统中,使进程执行的唯一方法是使用系统调用 exec()。
    Exec()由多种使用形式,它们只是在参数上不同,而功能相同。下面介绍一种最常用的形式:

    int execl(path,arg0,…,argn,(char*0char *path,*arg0,…,*argn;
    
    • 1
    • 2

    👉Path: 指出要执行的程序文件的完整路径名。
    👉arg0: 指出要执行的程序文件的文件名。
    👉Arg1,…,argn:程序的参数,这些参数的个数可以不同。
    👉该系统调用将引起另一个程序的执行,故它成功调用后不需返回,只有在调用失败时,返回值 -1;

    例:在程序运行中要执行 linux 命令“ls –l”,程序编制如下:
    #include <stdio.h>
    Main()
    {
    Execl(/bin/ls”,”ls”,-l”,0); /*调用成功,则执行 ls 命令*/
    Printf(“can only get here on error/n”); /*出错时,返回该程序,显示错误信息*/
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    2.子进程执行新的程序
    一个进程使用 fork()建立子进程后,让子进程执行另外一个程序的方法也是通过使用 exec()系统调用。程序的一般形式为:

    Main()
    {
    Pid_t val;
    Val =fork();
    If(val!=0)
    {
    …….. /*执行父进程的语句*/
    }
    Else
    {
    exec(…..); /*执行程序*/
    }
    ……..
    Exit1);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    Linux 采用“写时拷贝”技术,所以子进程在创建时是共享父进程的正文段和数据段,但是在执行 exec()时,子进程将建立自己的虚拟空间,并把参数 arg0 制定的可执行程序映像装入子进程的虚拟空间,这是就形成了子进程自己的正文段和数据段。

    4.4.5 Linux 进程的终止和撤销

    进程终止的两种情况:

    👉进程完成自身的任务而自动终止:方法 1,使用系统调用 exit()显示终止进程;方法 2,执行到程序的 main()的结尾而隐式地终止进程。
    👉进程被内核强制终止:如进程运行出现致命错误,或者收到不能处理的信号时。

    1、进程的终止 exit()
    Linux 中终止进程是通过调用内核函数 do_exit()实现的,该函数定义在 kernel/exit.c 中。下面结合do_exit()函数的部分源代码对进程终止和撤销的过程说明如下:
    1)函数首先设定当前进程的标志,即任务结构体的 flags 成员项设定为 PF_EXITING,表示该进程将要终止

    Current->flags = PF_EXITING;
    
    • 1
    1. 释放系统中该进程在各个管理队列中的任务结构体。
    Del_timer(&current->real_timer);/*从动态计时器系列中释放该进程的任务结构体*/
    Sem_exit(); /*从 IPC 进程间通信的信号量机制中释放该进程的任务结构体*/
    
    • 1
    • 2

    3)释放进程使用的各种资源。

    Kerneld_exit(); /*释放内核*/
    _exit_mm(current); /*释放虚拟空间*/
    _exit_files(current); /*释放打开的文件*/
    _exit_fs(current); /*释放 fs 结构体*/
    _exit_sighand(current); /*释放信号*/
    Exit_thread(); /*释放线程*/
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    4)把进程的状态设置为僵死状态。

    Current->state = TASK_ZOMBIE
    • 1

    5)把退出码置入任务结构体。

    Current->exit_code =code;/*正常退出时,退出码为 0;异常退出时,为非 0 值,通常是错误编号*/
    
    • 1

    6)变更进程族亲关系。

    Exit_notify();/*把要撤销的进程的子进程的父进程赋予 init 进程*/
    
    • 1

    7)执行进程调度,选择下一个使用 CPU 的进程。

    Schedule();
    
    • 1
  • 相关阅读:
    QCustomPlot实现曲线拖拽
    关于软件<PDF文档管理系统V1.0>的介绍
    idea实现Docker 远程部署项目
    基于STC15单片机-LM35-DS8B20温度测量-DS1302计时-proteus仿真-源程序
    Python实现k-近邻算法案例学习
    解密Spring Cloud LoadBalancer:实现高效负载均衡的魔法密卷(二)
    11.20顺序表查找,质数查找,折半查找,任意折查找
    Linux下yum源配置实战 2
    更新电脑显卡驱动的操作方法有哪些?
    delete 与 truncate 命令的区别
  • 原文地址:https://blog.csdn.net/tangcoolcole/article/details/134463943