20201317 LYX Unix/Linux进程管理 第六周学习
第三章 Unix/Linux进程管理
本章讨论了Unix/inux 中的进程管理;阐述了多任务处理原则;介绍了进程概念;并以一个编程示例来说明多任务处理、上下文切换和进程处理的各种原则和方法。
多任务处理
一般来说,多任务处理指的是同时进行几项独立活动的能力。
比如,我们经常看到有人一边开车一边打电话。从某种意义上说,这些人正在进行多任务处理,尽管这样非常不好。在计算机技术中.多任务处理指的是同时执行几个独立的任务。在单处理器(单CPU)系统中,一次只能执行一个任务。
多任务处理是通过在不同任务之间多路复用CPU的执行时间来实现的,即将CPU执行操作从一个任务切换到另一个任务。不同任务之间的执行切换机制称为上下文切换,将—个任务的执行环境更改为另个任务的执行环境。如果切换速度足够快,就会给人一种同时执行所有任务的错觉。这种逻辑并行性称为"并发"。在有多个CPU或处理器内核的多处理器系统中。可在不同CPU上实时、并行执行多项任务。此外,每个处理器也可以通过同时执行不同的任务来实现多任务处理。
多任务处理是所有操作系统的基础。总体上说,它也是并行编程的基础。
进程
操作系统是一个多任务处理系统。在操作系统中,任务也称为进程。在实际应用中,任务和进程这两个术语可以互换使用。在中,我们把执行映像定义为包含执行代码、数据和堆栈的存储区。进程的正式定义∶进程是对映像的执行。
操作系统内核将一系列执行视为使用系统资源的单一实体。系统资源包括内存空间、I/O设备以及最重要的 CPU时间。在操作系统内核中,每个进程用一个独特的数据结构表示,叫作进程控制块(PCB)或任务控制块(TCB)等。在本书中,我们直接称它为PROC 结构体。与包含某个人所有信息的个人记录一样,PROC结构体包含某个进程的所有信息。在实际操作系统中,PROC结构体可能包含许多字段,而且数量可能很庞大。
首先,我们来定义一个非常简单的PROC结构体来表示进程。
typedef etruct proc(
struct proc *next; // next proc pointer
int *ksp; // saved sp: at byte offset 4
int pid; // process ID
int ppid; // parent proces pid
int statuS; // PROC status=FREE|READY, etc.
int priority; // scheduling priority
int kstack[1024]; // process execution stack
)PROC;
- ksp 保存堆栈指针,以便进程恢复。当进程放弃CPU时,会将上下文保存在堆栈中。
- pdi 进程id编号
- ppid 父进程id编号
- status 进程当前状态
- priority 进程调度优先级
- kstack[1024]进程执行时的堆栈
在PROC结构体中,next是指向下一个PROC结构体的指针,用于在各种动态数据结构(如链表和队列)中维护PROC结构体。ksp字段是保存的堆栈指针。当某进程放弃使用CPU时,它会将执行上下文保存在堆栈中,并将堆栈指针保存在PROC.ksp中,以便以后恢复。在PROC结构体的其他字段中,pid是标识一个进程的进程ID编号,ppid是父进程 ID 编号,status是进程的当前状态,priority是进程调度优先级,kstack 是进程执行时的堆栈。操作系统内核通常会在其数据区中定义有限数量的 PROC结构体,表示为∶
PROC proc [NPROC]; // NPROC a constant,e.g.64
用来表示系统中的进程。在一个单 CPU系统中,一次只能执行一个进程。操作系统内核通常会使用正在运行的或当前的全局变量PROC指针,指向当前正在执行的 PROC。在有多个CPU的多处理器操作系统中,可在不同CPU上实时、并行执行多个进程。因此,在一个多处理器系统中正在运行的[NCPU]可能是一个指针数组,每个指针指向一个正在特定CPU上运行的进程。
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");
进程管理中的系统调用
系统调用是什么?
简单来说,系统调用是接口,它把应用程序的请求传给内核,调用相应的内核函数完成所需的处理,再将处理结果返回给应用程序。
fork()是什么?
是创造进程的一种系统调用。
fork返回值为什么有两个?
首先fork的返回值有三种情况
返回值>0;在父进程中, fork返回新创建子进程的进程ID(进程标识符) ;
返回值=0;在子进程中, fork返回0 ;
返回值<0;如果出现错误, fork返回一个负值;
其次为什么有两个呢,是因为在执行代码:“pid = fork();”时,同时产生了一个子进程,与当前C程序完全一样的子进程,当然了,子进程中的代码段不再执行“pid = fork();”这一行了,所以这也就是为什么有两个返回值了,因为父进程和子进程各有一个返回值赋值给pid,
在执行函数fork()时,创建了一个子进程,此时是两个进程同时运行。fork()返回两次,子进程返回值为0,所以执行 printf("child pid: %d\n", getpid()); 父进程返回子进程id(pid>0),所有执行printf("pid: %d\n", pid);printf("father pid: %d\n", getpid());。两个进程执行顺序不定。
fork()的两种用法:
- 一个父进程希望复制自己,使父子进程同时执行不同的代码段。
比如在网络服务程序中,父进程等待客户端的服务请求。当请求到达时,父进程调用fork()使子进程处理此请求;而父进程继续等待下一个请求。
- 一个进程要执行一个不同的程序。
这个在shell下比较常见,这种情况下,fork()之后一般立即接exec函数。
I/O重定向
所谓的I/O重定向也就是让已创建的FD指向其他文件。比如,下面是对STDOUT重定向到testfile.txt前后内核文件描述符表变化的示意图
在I/O重定向的过程中,不变的是FD 0/1/2代表STDIN/STDOUT/STDERR,变化的是文件描述符表中FD 0/1/2对应的具体文件,应用程序只关心前者。本质上这和接口的原理是相通的,通过一个间接层把功能的使用者和提供者解耦。
下面我们通过strace命令跟踪一下echo命令的系统调用:
管道编程
匿名管道:一般用于父子进程
FIFO管道/命名管道:一般用于两个独立的进程
通过命令的方式实现管道:
发送文件:send.cpp
接收文件:recv.cpp
结果:
通过命令行的输入实现管道的通信作用,send程序的输出变成了recv程序的输入
匿名管道:
单工管道
程序进程与Shell命令行进程单项通信
先需要编写好发送/接收文件
先通过编码的方式实现管道
读取程序
执行结果:
写入程序
结果:
父子进程管道图解:
文件打开图解
问题与解决方案
- 首先是C++语言,由于是我前几次编写,没有太多经验,执行方式也遇到了困难。
解决方法:首先,我通过菜鸟教程学习了C++的简单编程,又通过其他编程语言的程序学习,成功理解了较复杂程序。
- 其次如何理解管道编程?
解决方法:通过查阅资料,我能很好的理解了管道的含义和理解,并通过实践进行了解决。