第三章Unix/Linux进程管理—学习笔记六

发布时间 2023-10-22 19:12:08作者: 20211315邹雪梅

〇.本章思维导图

1.最近刚好操作系统也学习到进程相关部分,因此再看这些知识更易懂了
2.思维导图

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

一、基本概念

1. 多任务处理

一般来说,多任务处理指的是同时进行几项独立活动的能力。比如,我们经常看到有人一边开车一边打电话。从某种意义上说,这些人正在进行多任务处理,尽管这样非常不好。在计算机技术中.多任务处理指的是同时执行几个独立的任务。在单处理器(单CPU)系统中,一次只能执行一个任务。多任务处理是通过在不同任务之间多路复用CPU的执行时间来实现的,即将CPU执行操作从一个任务切换到另一个任务。不同任务之间的执行切换机制称为上下文切换,将—个任务的执行环境更改为另个任务的执行环境。如果切换速度足够快,就会给人一种同时执行所有任务的错觉。这种逻辑并行性称为"并发"。在有多个CPU或处理器内核的多处理器系统中。可在不同CPU上实时、并行执行多项任务。此外,每个处理器也可以通过同时执行不同的任务来实现多任务处理。多任务处理是所有操作系统的基础。总体上说,它也是并行编程的基础。

2. 进程的概念

操作系统是一个多任务处理系统。在操作系统中,任务也称为进程。在实际应用中,任务和进程这两个术语可以互换使用。在第2章中,我们把执行映像定义为包含执行代码、数据和堆栈的存储区。进程的正式定义∶进程是对映像的执行。
操作系统内核将一系列执行视为使用系统资源的单一实体。系统资源包括内存空间、I/O设备以及最重要的 CPU时间。在操作系统内核中,每个进程用一个独特的数据结构表示,叫作进程控制块(PCB)或任务控制块(TCB)等。在本书中,我们直接称它为PROC 结构体。与包含某个人所有信息的个人记录一样,PROC结构体包含某个进程的所有信息。在实际操作系统中,PROC结构体可能包含许多字段,而且数量可能很庞大。

多任务处理系统

多任务处理(MT)系统,说明多任务处理、上下文切换和进程处理原则。下面的程序实现了一个模拟操作系统内核模式各项操作的多任务环境,由以下几个部分组成。

1. type.h 文件

type.h 文件定义了系统常数和表示进程的简单 PROC结构体。
`/*********** type.h file ************/

define NPROC 9 // number of PROCs

define SSIZE 1024 // gtack size = 4KB

// PROC status

define FREE 0

define READY 1

define SLEEP 2

define ZOMBIE 3

typedef struct proc{
struct proc *next; // next proc pointer
int *ksp; // saved stack pointer
int pid; // pid = 0 to NPROC-1
int ppid; // parent pid
int status; // PROC status
int priority; // scheduling priority
int kstack[SSIE] // process stack
}PROC;
`

2. ts.s文件

ts.s是汇编代码,在32位GCC汇编代码中可实现进程上下文切换。
`#------------- ts.s file file -----------------
.globl running, scheduler, tswitch
tswitch:
SAVE: pushl %eax
pushl %ebx
pushl %ecx
pushl %edx
pushl %ebp
pushl %esi
pushl %edi
pushfl
movl running, %ebx # ebx -> PROC
movl %esp, 4(%ebx) # PORC.save_sp = esp
FIND: call scheduler
RESUME:movl running, %ebx # ebx -> PROC
movl 4(%ebx), %esp #esp = PROC.saved_sp
popf1
popl %edi
popl %esi
popl %ebp
popl %edx
popl %ecx
popl %ebx
popl %eax
ret

stack contents = |retpc|eax|ebx|ecx|edx|ebp|esi|edi|eflag|

-1 -2 -3 -4 -5 -6 -7 -8 -9

`

3. queue.c文件

queue.c文件可实现队列和链表操作函数。enqueue()函数按优先级将PROC输入队列中。在优先级队列中,具有相同优先级的进程按先进先出(FIFO)的顺序排序。dequeue()函数可返回从队列或链表中删除的第一个元素。printList()函数可打印链表元素。
/******************************* queue.c file *******************************/ int enqueue(PROC **queue,PROC *p) { PROC *q = *queue; if(q == 0 || p->priority> q->priority){ *queue = p; p->next = q; } else{ while(g->next && p->priority <= q->next->priority) q = q->next; p->next = q->next; q->next = p; } } PROC *dequeue (PROC **queue) { PROC *p = *queue; if (p) *queue =(*queue)->next; return p; } int printList(char *name,PROC *p) { printf("%s = ",name); while(p){ printf("[8d %d]->",p->pid,p->priority); p = p->next; } printf("NULL\n"); }

4. t.c文件

t.c文件定义MT系统数据结构、系统初始化代码和进程管理函数。

进程同步

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

1. 睡眠模式

当某进程需要某些当前没有的东西时,它就会在某个事件值上进入休眠状态,该事件值表示休眠的原因。为实现休眠操作,我们可在PROC结构体中添加一个event字段,并实现 ksleep(int event)函数,使进程进入休眠状态。

2. 唤醒操作

多个进程可能会进入休眠状态等待同一个事件。在这种情况下,所有这些进程都将休眠等待同一个事件值。当某个等待时间发生时,另一个执行实体(可能是某个进程或中断处理程序)将会调用kwakeup(event),唤醒正处于休眠状态等待该事件值的所有程序。如果没有任何程序休眠等待该程序,kwakeup()就不工作,即不执行任何操作。

进程终止

在以下种情况下,当进程终止时,最终都会在操作系统内核中调用 kexit() 。

1. 正常终止

进程调用 exit(value), 发出exit(value)系统调用来执行在操作系统内核中的kexit(value)

2. 异常终止

进程因某个信号而异常终止。

Unix/Linux中的进程

INIT进程P1

①它是除了P0之外所有进程的祖先,所有登录进程都是它的子进程。
②它管理所有没有父进程的进程。(他就像孤儿院的院长,所有孤儿都叫他爸爸)。
③它不停寻找僵尸进程,并终止他们(埋葬它们死亡的空壳)。

操作系统启动时,内核会强行创建PID=0的初始进程,然后系统执行P0。系统挂载文件,然后初始化完成后,复刻出子进程P1。
P1运行时,执行映像更改为INIT程序,复刻出更多子进程,用于提供系统服务,这样的进程成为守护进程。

进程的执行模式

中断:中断是外部设备发送给 CPU的信号,请求CPU服务。
陷阱:陷阱是错误条件,例如无效地址、非法指令、除以0等、这些错误条件被CPU识别为异常,使得CPU进入Kmode来处理错误。
系统调用:系统调用(简称syscall)是一种允许Umode 进程进入Kmode 以执行内核函数的机制。如果发生错误,外部全局变量 errno(在errno.h中)会包含一个ERROR代码,用于标识错误。用户可使用库函数perror( "error message");

I/O重定向

在Unix系统中,每个进程都有STDIN、STDOUT和STDERR这3种标准I/O,它们是程序最通用的输入输出方式。C语言可以通过scanf("%s", str);从终端输入字符串,通过printf("%s\n", str);向终端输出字符串。
理解I/O重定向的原理需要从Linux内核为进程所维护的关键数据结构入手。对Linux进程来讲,每个打开的文件都是通过文件描述符(FD)来标识的,内核为每个进程维护了一个文件描述符表,这个表以FD为索引,再进一步指向文件的详细信息。在进程创建时,内核为进程默认创建了0、1、2三个特殊的FD,这就是STDIN、STDOUT和STDERR。
在I/O重定向的过程中,不变的是FD 0/1/2代表STDIN/STDOUT/STDERR,变化的是文件描述符表中FD 0/1/2对应的具体文件,应用程序只关心前者。本质上这和接口的原理是相通的,通过一个间接层把功能的使用者和提供者解耦。

文件重定向常用方法
` COMMAND_OUTPUT >
# 重定向stdout到一个文件.
# 如果没有这个文件就创建, 否则就覆盖.

 ls -lR > dir-tree.list
 # 创建一个包含目录树列表的文件.

: > filename
# > 会把文件"filename"截断为0长度.
# 如果文件不存在, 那么就创建一个0长度的文件(与'touch'的效果相同).
# : 是一个占位符, 不产生任何输出.

filename

 # > 会把文件"filename"截断为0长度.
 # 如果文件不存在, 那么就创建一个0长度的文件(与'touch'的效果相同).
 # (与上边的": >"效果相同, 但是在某些shell下可能不能工作.)

COMMAND_OUTPUT >>
# 重定向stdout到一个文件.
# 如果文件不存在, 那么就创建它, 如果存在, 那么就追加到文件后边.

 # 单行重定向命令(只会影响它们所在的行):
 # ---------------------------------------------------------------

1>filename
# 重定向stdout到文件"filename".
1>>filename
# 重定向并追加stdout到文件"filename".
2>filename
# 重定向stderr到文件"filename".
2>>filename
# 重定向并追加stderr到文件"filename".
&>filename
# 将stdout和stderr都重定向到文件"filename".

 =====================================================================
 # 重定向stdout, 一次一行.
 LOGFILE=script.log

 echo "This statement is sent to the log file, \"$LOGFILE\"." GFILE
 echo "This statement is appended to \"$LOGFILE\"." 1>>$LOGFILE
 echo "This statement is also appended to \"$LOGFILE\"." OGFILE
 echo "This statement is echoed to stdout, and will not appear $LOGFILE\"."
 # 每行过后, 这些重定向命令会自动"reset".



 # 重定向stderr, 一次一行.
 ERRORFILE=script.errors

 bad_command1 2>$ERRORFILE       #  错误消息发到$ERRORFILE中.
 bad_command2 2>>$ERRORFILE      #  错误消息添加到$ERRORFILE中.
 bad_command3                    #  错误消息echo到stderr,
                                 #+ 并且不出现在$ERRORFILE中.
 # 每行过后, 这些重定向命令也会自动"reset".
 =====================================================================

2>&1
# 重定向stderr到stdout.
# 得到的错误消息与stdout一样, 发送到一个地方.

i>&j
# 重定向文件描述符i 到 j.
# 指向i文件的所有输出都发送到j中去.

&j

 # 默认的, 重定向文件描述符1(stdout)到 j.
 # 所有传递到stdout的输出都送到j中去.

0< FILENAME
< FILENAME
# 从文件中接受输入.
# 与">"是成对命令, 并且通常都是结合使用.
#
# grep search-word <filename

[j]<>filename
# 为了读写"filename", 把文件"filename"打开, 并且分配文件描述符"j"
# 如果文件"filename"不存在, 那么就创建它.
# 如果文件描述符"j"没指定, 那默认是fd 0, stdin.
#
# 这种应用通常是为了写到一个文件中指定的地方.
echo 1234567890 > File # 写字符串到"File".
exec 3<> File # 打开"File"并且给它分配fd 3.
read -n 4 <&3 # 只读4个字符.
echo -n . >&3 # 写一个小数点.
exec 3>&- # 关闭fd 3.
cat File # ==> 1234.67890
# 随机存储.

|
# 管道.
# 通用目的的处理和命令链工具.
# 与">"很相似, 但是实际上更通用.
# 对于想将命令, 脚本, 文件和程序串连起来的时候很有用.
cat *.txt | sort | uniq > result-file
# 对所有的.txt文件的输出进行排序, 并且删除重复行,
# 最后将结果保存到"result-file"中.

`