2021-2022-1 20191315《信息安全系统设计与实现(上)》学习笔记6

第三章 Unix/Linux进程管理

阐述了多任务处理原则;介绍了进程概念;并以一个编程示例来说明多任务处理、上下文切换和进程处理的各种原则和方法。多任务处理系统支持动态进程创建、进程终止,以及通过休眠与唤醒实现进程同步、进程关系,以及以二叉树的形式实现进程家族树,从而允许父进程等待子进程终止;提供了一个具体示例来阐释进程管理函数在操作系统内核中是如何工作的;然后,解释了Unix/Linux中各进程的来源,包括系统启动期间的初始进程、INIT进程、守护进程、登录进程以及可供用户执行命令的sh进程;接着,对进程的执行模式进行了讲解,以及如何通过中断、异常和系统调用从用户模式转换到内核模式;再接着,描述了用于进程管理的 Unix/Linux 系统调用,包括 fork、wait、exec 和 exit ;阐明了父进程与子进程之间的关系,包括进程终止和父进程等待操作之间关系的详细描述;解释了如何通过INIT进程处理孤儿进程,包括当前 Linux 中的 subreaper 进程,并通过示例演示了 subreaper 进程;接着,详细介绍了如何通过 exec 更改进程执行映像,包括 execve 系统调用、命令行参数和环境变量;解释了 I/O 重定向和管道的原则及方法,并通过示例展示了管道编程的方法;读者可借助本章的编程项目整合进程管理的各种概念和方法,实现用于执行命令的 sh 模拟器。sh 模拟器的功能与标准 sh 完全相同。

多任务处理

多任务处理指的是同时进行几项独立活动的能力。在计算机技术中,多任务处理指的是同时执行几个独立的任务。在单处理器(单CPU)系统中,一次只能执行一个任务。多任务处理是通过在不同任务之间多路复用CPU的执行时间来实现的,即将CPU执行操作从一个任务切换到另一个任务。不同任务之间的执行切换机制称为上下文切换,将一个任务的执行环境更改为另一个任务的执行环境。如果切换速度足够快,就会给人一种同时执行所有任务的错觉。这种逻辑并行性称为“并发”。在有多个 CPU 或处理器内核的多处理器系统中,可在不同CPU上实时、并行执行多项任务。此外,每个处理器也可以通过同时执行不同的任务来实现多任务处理。多任务处理是所有操作系统的基础。总体上说,它也是并行编程的基础。

进程的概念

  • 在操作系统中,任务也成为进程。
  • 进程是对映像的执行。
  • 操作系统内核将一系列执行视为使用系统资源的单一实体。系统资源包括内存空间、I/O设备以及最重要的CPU时间。PROC结构体包含了某个进程的所有信息。

多任务处理系统

type.h文件

定义了系统常熟和表示进程的简单PROC结构体

ts.s文件

在32位GCC汇编代码中可实现进程上下文切换

queue.c文件

可实现队列和链表操作函数

  • enqueue()函数按优先级将PROC输入队列中。在优先级队列中,具有相同优先级的进程按照FIFO的顺序排序。
  • dequeue()函数可返回从队列或链表中删除的第一个元素。
  • printList()函数可打印链表元素。

t.c文件

定义MT系统数据结构、系统初始化代码和进程管理函数

进程同步

一个操作系统包含许多并发进程,这些进程可以彼此交互。进程同步是指控制和协调进程交互以确保其正确执行所需要的各项规则和机制。最简单的进程同步工具是休眠和唤醒。

睡眠模式

当某进程需要某些当前没有的东西时,例如申请独占一个存储区域、等待用户通过标准输入来输入字符等,它就会在某个事件值上进入休眠状态,该事件值表示休眠的原因。

唤醒模式

多个进程可能会进入休眠状态等待同一个事件,这是很自然的,因为这些进程可能都需要同一个资源,例如一台当前正处于繁忙状态的打印机。在这种情况下,所以这些进程都将休眠等待同一个事件值。

进程终止

  • 正常终止:进程调用exit(value),发出_exit(value)系统调用来执行在操作系统内核中的kexit(value)
  • 异常终止:进程因某个信号而异常终止
    在这两种情况下,当进程终止时,最终都会在操作系统内核中调用 kexi()。

Unix/Linux中的进程

进程来源

操作系统内核会强行创建PID=0初始进程,然后,系统执行初始进程P0,然后,挂载一个跟文件系统,最后,P0复刻出一个子进程P1。

INIT和守护进程

P1运行时,将执行映像更改为init程序,P1通常被称为init进程,P1的大部分子进程都是用来提供系统服务的,称为守护进程。

登录进程

sh编程

当用户成功登录时,LOGIN 进程会获取用户的 gid 和 uid,从而成为用户的进程。它将目录更改为用户的主目录并执行列出的程序,通常是命令解释程序 sh。现在,用户进程执行sh,因此用户进程通常称为 sh 进程。它提示用户执行命令。一些特殊命令,如cd(更改目录)、退出、注销等,由sh 自己直接执行。其他大多数命令是各种 bin 目录(如/bin、/sbin、/usr/bin、/usr/local/bin 等)中的可执行文件。对于每个(可执行文件)命令,sh 会复刻一个子进程,并等待子进程终止。子进程将其执行映像更改为命令文件并执行命令程序。子进程在终止时会唤醒父进程 sh,父进程会收集子进程终止状态、释放子进程 PROC 结构体并提示执行另一个命令等。除简单的命令之外,sh 还支持 I/O 重定向和通过管道连接的多个命令。

进程的执行模式

  • 中断:中断是外部设备发送给 CPU的信号,请求 CPU 服务。当在 Umode 下执行时,CPU 中断是启用的,因此它将响应任何中断。在中断发生时,CPU 将进入 Kmode 来处理中断,这将导致进程进人 Kmode。
  • 陷阱:陷阱是错误条件,例如无效地址、非法指令、除以0等,这些错误条件被 CPU识别为异常,使得 CPU 进入 Kmode 来处理错误。在 Unix/Linux 中,内核陷阱处理程序将陷阱原因转换为信号编号,并将信号传递给进程。对于大多数信号,进程的默认操作是终止。
  • 系统调用:系统调用(简称syscall)是一种允许 Umode 进程进入 Kmode 以执行内核函数的机制。当某进程执行完内核函数后,它将期望结果和一个返回值返回到 Umode,该值通常为0(表示成功)或-1(表示错误)。如果发生错误,外部全局变量errno(在 errno.h中)会包含一个 ERROR 代码,用于标识错误。

进程管理的系统调用

fork()

int pid = fork()
fork()创建子进程并返回子进程的pid,如果fork()失败则返回-1。

进程执行顺序

在folk()完成后,子进程与父进程和系统中所有其他进程竞争CPU运行时间。接下来运行哪个进程取决于他们的调度优先级,优先级呈动态变化。

进程终止

  • 正常终止:当内核中的某个进程终止时,他会将_exit(value)系统调用中的值记录为进程PROC结构体中的退出状态。并通知他的二父进程并使该进程成为僵尸进程。父进程课通过系统调用找到僵尸子进程,获得其pid和退出状态
    pid=wait(int *status)
    它还会清空僵尸子进程PROC结构体,使该结构可被另一个进程重用。
  • 异常终止:当某进程遇到异常时,他会陷入操作系统内核。内核的异常处理程序将陷阱错位类型转换为一个幻数,称为信号,将信号传递给进程,时进程终止。用户可以使用命令
    kill -s signal_numeber pid向通过pid识别的目标发送信号。

等待子进程终止

在任何时候,一个进程都可以使用
int pid = wait(int *status);
系统调用,等待僵尸子进程。如果成功,则wait()会返回僵尸子进程的PID,而且status包含僵尸子进程的exitCode。此外,wait()还会释放僵尸子进程,以供重新使用。

subreaper进程

exec():更改进程执行映像

加载子进程的可执行文件。
#include <unistd.h>
int execl(const char *path, const char *arg, ...);
path:可执行文件的路径
arg:第一个main函数的参数,最后一次必须以NULL结尾。
int execlp(const char *file, const char *arg,...);
file:可执行文件的名字,会从PATH环境变量的路径中查找可执行文件并执行。
arg:第一个main函数的参数,最后一次必须以NULL结尾。
int execle(const char *path, const char *arg,..., char * const envp[]);
path:可执行文件的路径
arg:第一个main函数的参数,最后一次必须以NULL结尾。
envp:父进程的环境变量表,传递给子进程。
int execv(const char *path, char *const argv[]);
path:可执行文件的路径
argv:命令行参数
int execvp(const char *file, char *const argv[]);
file:可执行文件的名字,会从PATH环境变量的路径中查找可执行文件并执行。
argv:命令行参数
int execvpe(const char *file, char *const argv[],char *const envp[]);
file:可执行文件的名字,会从PATH环境变量的路径中查找可执行文件并执行。
argv:命令行参数
envp:父进程的环境变量表,传递给子进程。

环境变量

环境变量是为当前sh定义的变量,由子sh或进程继承。他们定义了后续程序的执行环境。各环境变量定义为:关键字=字符串
在sh会话中,用户可使用env或printenv命令查看环境变量。重要环境变量:

SHELL=/bin/bash
TERM=xterm
USER=kcw
PATH=/usr/1oca1/bin:/usr/bin:/bin:/usr/local/games:/usr/games:./
HOME= / home /kcw
  • SHELL:指定将解释任何用户命令的sh。
  • TERM:指定运行sh时要模拟的终端类型。
  • USER:当前登录用户。
  • PATH:系统在查找命令时将检查的目录列表。
  • HOME:用户的主目录。在 Linux 中,所有用户主目录都在/home中。
    在sh会话中,可以将环境变量设置为新的(字符串)值,如:
    HOME= / home / newhome
    可通过EXPORT命令传递给后代sh,如:expoert HOME

I/O重定向

文件描述符

文件描述符是从0开始到9的结束的整数,指明了与进程相关的特定数据流的源。当Linux系统启动一个进程(该进程可能用于执行shell命令)时,将自动为该进程打开三个文件:标准输入(文件标识符为0)、标准输出(1标识)和标准错误输出(2标识),若要打开其他的输入或输出文件则从整数3开始标识。默认情况下,标准输入与键盘输入相关联,标准输出与标准错误输出与显示器相关联。Shell从标准输入读取输入数据,将输出送到标准输出,如果该命令在执行过程中发生错误,则将错误信息输出到标准错误输出。tee命令将shell的输出从标准输出复制一份到文件中,tee命令加-a表示追加到文件的末尾。

管道

管道是用于进程交换数据的单向进程件通信通道。管道有一个读取端和一个写入端。

管道命令处理

在Unix/Linux中,命令行cmd1 | cmd2,sh将通过一个进程运行cmd1,并通过另一个进程运行cmd2,他们通过一个管道连接在一起,因此cmd1的输出变为cmd2的输入

命令管道

命令管道又叫FIFO

  • 在sh中,通过mknod命令创建一个命令管道:
    mknod mypipe p
  • 或在c语言中发出mknod()系统调用:
    int r = mknod("mypipe",s_IFIFP,0);
    进程可像访问普通文件一样发个文命名管道。

问题与解决

进程组是什么及其相关操作?

进程组是由一个或多个进程的集合,每个进程除有一个进程ID还有一个进程组ID,进程组中的进程归属同一个作业控制(负责完成同一个任务)。
同一进程组的进程,会统一接收到终端的信号,由fork创建的子进程,默认就加入了父进程的进程组。
每个进程组都有一个组长,组长的进程ID就是组ID。
#include <unistd.h> int setpgid(pid_t pid, pid_t pgid);
功能:获取某个pid进程的进程组ID
pid_t getpgid(pid_t pid);
功能:设置进程pid进程的进程组ID,就相当于加入pgid进程组。pgid就是它的组长

原文地址:https://www.cnblogs.com/harperhjl/p/15451385.html