《Linux操作系统编程》 第六章 Linux中的进程监控: fork函数的使用,以及父子进程间的关系,掌握exec系列函数

在这里插入图片描述

在这里插入图片描述

博主 libin9iOak带您 Go to New World.✨
个人主页——libin9iOak的博客
《面试题大全》 文章图文并茂生动形象简单易学!欢迎大家来踩踩~
《IDEA开发秘籍》学会IDEA常用操作,工作效率翻倍~
希望本文能够给您带来一定的帮助文章粗浅,敬请批评指正!

在这里插入图片描述

文章目录

  • 第六章 Linux中的进程监控
    • 学习目的
    • 学习要求
    • 学习方法
    • 概念和原理
      • 6.1 Linux的进程控制块
        • 6.1.1 task_struct结构包含的信息
        • 6.1.2 task_struct:进程标识符
        • 6.1.3 task_struct:进程状态
      • 6.2 Linux中进程的创建
        • 6.2.1 Linux中进程的创建
      • 6.3 Linux中进程的执行
        • 6.3.1 Linux中进程的执行
      • 6.4 Linux中进程的终止
        • 6.4.1 Linux中进程的终止
      • 6.5 Linux中进程的等待
        • 6.5.1 Linux中进程的等待
      • 6.6 Linux中进程的监控
    • 重点
    • 难点
    • 习题
  • 原创声明

第六章 Linux中的进程监控

学习目的

使学生理解Linux中进程控制块的数据结构,Linux进程的创建、执行、终止、等待以及监控方法。并重点掌握fork函数的使用以及exec系列函数。

学习要求

了解:Linux进程控制块的数据结构以及进程的状态,进程的内存空间布局,特殊进程。

理解:Linux进程创建时环境变量、命令行参数的设置,理解父进程等待子进程结束和获得子进程返回值的原理;

掌握:fork函数的使用,以及父子进程间的关系,掌握exec系列函数。

学习方法

通过对进程运行与监控的相关知识点的编程学习和锻炼,培养学生们对进程相关实例问题进行分析与解决的能力

概念和原理

6.1 Linux的进程控制块

进程在内核中的表现形式:进程控制块(PCB)

Linux系统中的每个进程都有一个名为task_struct的数据结构,它相当于“进程控制块”。 task_struct是Linux内核的一种数据结构,它会被装载到内存中并且包含着进程的信息。

指向task_struct数据结构的指针形成了一个task数组。当建立新进程的时候,Linux为新进程分配一个task_struct结构,然后将指针保存在task数组中。调度程序一直维护着一个current指针,它指向当前正在运行的进程。

6.1.1 task_struct结构包含的信息

▪ 标识符 :描述本进程的唯一标识符,用来区别其他进程。

▪ 优先级 :相对于其他进程的优先级。

▪ 程序计数器:程序中即将被执行的下一条指令的地址。

▪ 内存指针:包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针。

▪ 上下文数据:进程执行时处理器的寄存器中的数据。

▪ I/O状态信息:包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表。

▪ 记账信息:可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等

6.1.2 task_struct:进程标识符

​ pid_t pid; //进程的唯一标识

pid_t tgid; // 线程组中领头线程的pid值

每个进程都必须拥有它自己的进程描述符pid。

pid是32位无符号整型数据,最大值取32767。

进程标识符是内核提供给用户程序的接口,用户程序通过pid操作程序。

6.1.3 task_struct:进程状态

​ volatile long state

​ 进程状态切换

《Linux操作系统编程》 第六章 Linux中的进程监控: fork函数的使用,以及父子进程间的关系,掌握exec系列函数_第1张图片

图6-1 进程状态切换

6.2 Linux中进程的创建

6.2.1 Linux中进程的创建

(1) 创建进程

▪ Linux中创建进程的方式:

- 在shell中执行命令或可执行文件

- 在代码中(已经存在的进程中)调用函数创建子进程

(2) 创建子进程-fork函数

▪ 函数原型:pid_t fork(void);

▪ 返回值:

- fork函数被正确调用后,可能会在子进程中或父进程中分别返回

- 在子进程中返回值为0(不合法的PID,提示当前运行在子进程中)

- 在父进程中返回值为子进程ID(让父进程掌握所创建子进程的ID号)

- 出错返回-1

(3) fork函数工作流程

▪ 子进程是父进程的副本

- 子进程复制/拷贝父进程的PCB、用户空间(数据段、堆和栈)

- 父子进程共享正文段(只读)

▪ 父进程继续执行fork函数调用之后的代码,子进程也从fork函数调用之后的代码开始执行

▪ 为了提高效率,fork后,子进程并不立即复制父进程数据段、堆和栈,采用了写时复制机制(Copy-On-Write)

(4) fork函数执行后父子进程的主要异同

▪ 相同

- 真实用户ID,真实组ID

- 有效用户ID,有效组ID

- 环境变量

- 堆

- 栈

- 打开的文件

▪ 不同

- fork的返回值

- 子进程ID及父进程ID

- 子进程的

- tms_utime,

- tms_stime,

- tms_cutime,

- tms_ustime

(5) fork函数的用法1

▪ 父进程希望子进程复制自己(共享代码,复制数据空间),但父子进程执行相同代码中的不同分支。

▪ 例如:网络并发服务器中,父进程等待客户端的服务请求。当请求达到,父进程调用fork创建子进程处理该请求,而父进程继续等待下一个服务请求

(6) fork函数的用法2

▪ 父子进程执行不同的可执行文件(父子进程具有完全不同的代码段和数据空间)

(7) 创建子进程-vfork函数

▪ vfork用于创建新进程,而该新进程的目的是执行另外一个可执行文件

- 子进程在调用exec或exit之前,在父进程的地址空间中运行

- vfork函数保证子进程先执行,在它调用exec或者exit之后,父进程才会继续被调度执行(父进程处于TASK_UNINTERRUPTIBLE状态)

(8) 创建进程-clone函数

▪ clone():有选择的复制父进程的资源。(带参数)

▪ int clone(int (*fn)(void *), void *child_stack, int flags, void *arg);

- fn为函数指针,此指针指向一个函数体;

- child_stack为给子进程分配系统堆栈的指针(在linux下系统堆栈空间是2页面,就是8K的内存,其中在这块内存中,低地址上放入了值,这个值就是进程控制块task_struct的值);

- arg 就是传给子进程的参数,一般为(0);

- flags 为要复制资源的标志,描述需要从父进程继承那些资源。

(9) 创建进程流程

《Linux操作系统编程》 第六章 Linux中的进程监控: fork函数的使用,以及父子进程间的关系,掌握exec系列函数_第2张图片

图6-2 创建进程流程

(10) Linux进程空间的获取

▪ 申请空白PCB详细阐述:

- do_fork() à alloc_task_struct(void)

(11) 区别fork(),vfork(),clone()

▪ 拷贝内容

- fork():子进程拷贝父进程的数据段,代码段 。

- vfork():子进程与父进程共享数据段 ;

- clone(): 有选择的复制父进程的资源。(带参数)

- 通过参数clone_flags的设置来决定哪些资源共享,哪些资源拷贝

- CLONE_VM( 共 享 地 址 空间)

- CLONE_FS|CLONE_FILES(共享文件描述符集)

- CLONE_SIGCHLD(共享信号)

▪ 访问次序控制

- fork():父子进程的执行次序不确定 。

- vfork():保证子进程先运行,在调用execve()或exit()之前,与父进程数据是共享的。

- clone():由标志CLONE_VFORK 来决定子进程在执行时父进程是阻塞还是运行,若没有设置该标志,则父子进程同时运行; 若设置了该标志,则父进程挂起,直到子进程结束为止。

6.3 Linux中进程的执行

6.3.1 Linux中进程的执行

(1) exec系列函数

fork、vfork和clone三个函数主要是Linux用来创建新的进程(线程)而设计的,exec系列函数则是用来用指定的程序替换当前进程的所有内容。exec系列函数经常在前三个函数使用之后调用,来创建一个全新的程序运行环境。exec函数簇提供了一个在进程中启动另一个程序执行的方法。

进程调用exec系列函数在进程中加载执行另外一个可执行文件

▪ execl execv execle execve execlp execvp

▪ 六个函数开头均为exec,所以称为exec系列函数

- l:表示list,每个命令行参数都说明为一个单独的参数

- v:表示vector,命令行参数放在数组中

- e:表示由函数调用者提供环境变量表

- p:表示通过环境变量PATH来指定路径,查找可执行文件

▪ PATH环境变量说明

- PATH 环境变量包含了一张目录表,系统通过 PATH 环境变量定义的路径搜索执行码

- PATH 环境变量定义时目录之间要用“:”分隔,以“.”号表示结束。

- PATH 环境变量定义在用户的.profile或.bash_profile 中

(2) execl函数

▪ 函数原型

- 头文件:unistd.h

- int execl(const char *pathname, const char *arg0, …,NULL);

▪ 参数

- pathname:要执行程序的绝对路径名

- 可变参数:要执行程序的命令行参数,以空指针结束

▪ 返回值

- 出错返回-1

- 成功该函数不返回!

(3) execv函数

▪ 函数原型

- 头文件:unistd.h

- int execv(const char *pathname, char *const argv[]);

▪ 参数

- pathname:要执行程序的绝对路径名

- argv:数组指针维护的程序命令行参数列表,该数组的最后一个成员必须为空指针

▪ 返回值

- 出错返回-1

- 成功该函数不返回

(4) execle函数

▪ 函数原型

- 头文件:unistd.h

- int execle(const char *pathname, const char *arg0,… NULL, char *const envp[]);

▪ 参数

- pathname:要执行程序的绝对路径名

- 可变参数:要执行程序的命令行参数,以空指针结束

- envp::指向环境字符串数组的指针,该数组的最后一个成员必须为空指针

▪ 返回值

- 出错返回-1

- 成功该函数不返回

(5) 其他exec函数

▪ execve函数

- int execve(const char *pathname,char *const argv[], char *const envp[]);

▪ execlp函数

- int execlp(const char *filename,const char *arg0, …,NULL);

- filename参数可以是相对路径(路径信息从环境变量PATH中获取)

- 例如默认环境变量中包含的PATH=/bin:/usr/bin:/usr/local/bin/

▪ execvp函数

- int execvp(const char *filename,char *const argv[]);

6.4 Linux中进程的终止

6.4.1 Linux中进程的终止

(1) 进程的启动与退出

▪ 进程启动

子进程和父进程共享代码段,从fork函数执行之后的代码处开始执行;exec类函数会让进程从可执行文件的main函数开始重新执行

▪ 进程退出

当一个进程结束了运行或在半途终止了运行,那么内核就需要回收该进程除进程控制块之外所占用的系统资源(包括进程运行时打开的文件,申请的内存等),并让其他进程从进程控制块中收集有关信息

(2) Linux中进程的退出方式

▪ 正常退出

- 在main函数中执行return返回

- 在任意代码中调用exit函数或_exit函数

▪ 异常退出

- 在任意代码中调用abort函数

- 进程接收到终止信号

(3) exit和return的区别

▪ exit和_exit是函数,有参数。 exit 和_exit执行完后把控制权交给内核。

▪ return是函数执行完后的返回。renturn执行完后把控制权交给调用函数。

(4) 进程正常退出函数

▪ 头文件stdlib.h ,函数定义:void exit( int status )

▪ 头文件unistd.h,函数定义:void _exit (int status )

▪ 调用这两个函数均会正常地终止一个进程

▪ 调用_exit 函数将会立即返回内核

▪ 调用exit 函数:

- 执行一些预先注册的终止处理函数

- 执行文件I/O操作的善后工作,使得所有缓冲的输出数据被更新到相应的设备

- 返回内核

(5) 缓冲I/O方式

Linux的标准函数库中,有一种被称作 “缓冲I/O”的操作,其特征就是对应每一个打开的文件,在内存中都有一片缓冲区。每次读文件时,会连续地读出若干条记录,这样在下次读文件时就可以直接从内存的缓冲区读取;同样,每次写文件的时候也仅仅是写入内存的缓冲区,等满足了一定的条件(如达到了一定数量或遇到特定字符等),再将缓冲区中的内容一次性写入文件。

6.5 Linux中进程的等待

6.5.1 Linux中进程的等待

(1) wait函数

▪ 功能:获取任意子进程的状态改变信息(如果是终止状态则对子进程进行善后处理)

▪ 函数原型

- 头文件:sys/wait.h

- pid_t wait(int *statloc);

▪ 参数与返回值

- statloc:用于存储子进程的状态改变信息

- 返回值:若成功返回状态信息改变的子进程ID,出错返回-1

(2) waitpid函数

▪ 功能:等待某个特定子进程状态改变

▪ 函数原型

- 头文件:sys/wait.h

- pid_t waitpid(pid_t pid, int *statloc, int options);

▪ 返回值

- 成功返回终止子进程ID,失败返回-1

▪ 参数

- pid

- pid == -1: 等待任意子进程状态改变(同wait)

- pid > 0: 等待进程ID为pid的子进程状态改变

- pid == 0: 等待其组ID等于调用进程组ID的任意子进程

- pid < -1: 等待其组ID等于pid绝对值的任意子进程

- statloc:用于存储子进程的状态改变信息

- options:可以为0,也可以是以下常量

- WNOHANG:如果没有任何已经终止的子进程则马上返回, 函数不等待,此时返回值为0

- WUNTRACED:用于跟踪调试

▪ waitpid特有的功能

- waitpid可等待一个特定的进程的状态改变信息

- waitpid可以实现非阻塞的等待操作,有时希望取得子进程的状态改变信息,但不希望阻塞父进程等待子进程状态改变

- waitpid支持作业控制(进程组控制)

(3) 获知子进程状态改变

▪ 主动获取

- 调用wait或waitpid函数等待子进程状态信息改变,并获取其状态信息

▪ 异步通知

- 当一个进程发生特定的状态变化(进程终止、暂停以及恢复)时,内核向其父进程发送SIGCHLD信号

- 父进程可以选择忽略该信号,也可以对信号进行处理(默认处理方式为忽略该信号)

(4) 僵尸进程

对于已经终止但父进程尚未对其调用wait或waitpid函数的子进程称为僵尸进程(TASK_ZOMBIE状态)。

(5) 孤儿进程

如果父进程在子进程终止之前终止,则子进程的父进程将变为init进程,保证每个进程都有父进程,由init进程调用wait函数进行善后

6.6 Linux中进程的监控

(1) 进程及进程状态

▪ UNIX/Linux系统中的“进程”是可运行程序在内存中的一次运行实例。

▪ 进程和程序的主要区别是:

- 进程是动态的, 它有自己的生命周期和不同状态; 而程序是静态的, 通常存放在某种介质(如磁盘或纸张等)上。

- 进程具有运行控制结构和作用数据区;程序没有。

- 一个程序可以同时在内存中有多个运行实例, 即同时作为多个进程的组成部分。

▪ 生命周期大致分为三种状态:

- 运行态:进程正占用CPU和其它资源进行运算.

- 就绪态:进程已做好一切准备, 等待获得CPU投入运行.

- 睡眠态:进程因等待输入输出或其它系统资源, 而让出CPU资源, 等待运行条件满足。

(2) 获取进程状态信息: ps 命令

▪ 不带参数的ps命令运行时, 显示该用户当前活动进程的基本信息:

- PID 进程标识号. 系统每个进程在其生命周期都有一个唯一的

- TTY 启动该进程的终端号

- TIME 进程累计占用CPU的时间

- COMMAND 产生该进程的命令

▪ ps命令的常用任选项 -e (或-a) 显示系统中所有活动进程的信息,而不只是本用户的进程。

▪ -f 任选项:显示每个进程的完整信息,包括完整的命令行

(3) 暂停进程运行: sleep 命令

sleep time

sleep命令使运行它的进程暂停time指定的秒数.

(4) 终止进程运行: kill 命令

用户发出 kill 命令, 强行终止后台进程或键盘锁住了的前台进程的运行.

kill 命令的三种常用格式为:

  1. kill PID

正常结束进程, 自动完成所有善后工作, 作用类似于按 Del 键.

  1. kill -1 PID

先挂起该进程, 终止子进程, 完成善后工作, 再终止该进程.

  1. kill -9 PID

立即强行终止该进程, 不作任何善后工作. 可能出现资源浪费和"孤儿"进程.

重点

进程创建、执行程序和等待进程退出。

难点

​ 进程创建过程。

这部分过于抽象难以理解,通过代码导读能够更加形象一些,但是学生可能并不熟悉Linux源代码,因此需要在Linux入门章节增加代码结构介绍和阅读指导,然后布置和鼓励学生们自行阅读源代码,从而在讲解时更加方便,也更能激发学生的学习意愿。

习题

  1. 在Linux中创建进程主要有哪几种方式?

答:(1)在shell中执行命令或可执行文件。(2)在代码中(已经存在的进程中)调用函数创建子进程。

  1. 用fork()创建新进程时,子进程从父进程继承了哪些资源?

答:子进程复制/拷贝父进程的PCB、用户空间(数据段、堆和栈),父子进程共享正文段(只读)。

  1. waitpid函数的pid参数怎样设置表示等待任一子进程终止?

答:pid设置为 -1代表等待任意子进程状态改变(同wait)。

4.在Linux中什么情况下使用exec函数簇?

答:exec函数簇是用来用指定的程序替换当前进程的所有内容。exec系列函数经常在fork、vfork和clone三个函数使用之后调用,来创建一个全新的程序运行环境。exec函数簇提供了一个在进程中启动另一个程序执行的方法。

原创声明

=======

作者: [ libin9iOak ]


本文为原创文章,版权归作者所有。未经许可,禁止转载、复制或引用。

作者保证信息真实可靠,但不对准确性和完整性承担责任。

未经许可,禁止商业用途。

如有疑问或建议,请联系作者。

感谢您的支持与尊重。

点击下方名片,加入IT技术核心学习团队。一起探索科技的未来,共同成长。

你可能感兴趣的:(Linux,linux,java,面试)