参考教材:《操作系统概念(原书第九版)》
二、进程与线程
进程
进程的概念与特征
进程没有严格的定义,但可以通过不同的角度去描述:
➢一个正在执行的程序(Program)
➢计算机中正在运行的程序的一个实例(Instance)
➢可以分配给处理器并由处理器执行的一个实体(Entity)
➢由一个顺序执行的代码段、一个当前状态和一组相关系统资源所刻画的活动单元进程提供给应用程序两个关键抽象
➢逻辑控制流 (Logical control flow)
✓每个程序似乎独占地使用CPU
✓通过OS内核的上下文切换机制提供
➢私有地址空间 (Private address space)
✓每个程序似乎独占地使用内存系统
✓OS内核的虚拟内存机制提供
进程的5个基本特征
◼ 动态性 动态特性表现在它因创建而产生,由调度而执行,因得不到资源而暂停执行,最后因完成或撤销而消亡(进程生命周期)
◼ 并发性 引入进程的目的就是为了使多个程序并发执行,提高资源利用率(主要CPU)
◼ 独立性 进程是一个能独立运行的基本单位,也是系统进行资源分配和调度的基本单位
◼ 异步性 进程以各自独立的、不可预知的速度向前推进
◼ 结构性 进程 = 程序 + 数据 + 进程控制块(PCB)进程是受限直接执行的
- 什么是受限直接执行?
- “直接执行”:只需要在CPU上运行
- “受限”:进程的操作收到操作系统的限制,比如:向磁盘发出I/O请求或者获得更多的系统资源(比如:CPU或者内存)
- 受限直接执行的原因?
- “直接执行”:为了让程序尽可能快的执行
- “受限”:避免进程执行一些危险的操作
- 如何实现受限直接执行?
- 实现“受限”
- (1)用户模式(user mode):用户模式下,运行的代码会受到限制。比如:在用户模式下运行,进程不能发出I/O请求。
- (2)内核模式(kernel mode):和用户模式相对,操作系统(或内核)都是在这种模式下运行
- (3)系统调用:用户希望执行某种特权操作(比如:从硬盘读取数据),硬件为用户程序提供了执行系统调用的能力,允许内核小心地向用户程序暴露某些关键功能,例如:访问文件系统,创建和销毁进程、和其它进程通信,以及分配更多的内存)
- (4)陷阱(trap)指令:要执行系统调用就必须执行陷阱指令,该指令在被操作系统加载到内核的同时将用户模式提升为内核模式。完成后,操作系统会调用一个从陷阱返回指令(return-from-trap),回到用户模式
- (5)陷阱表(trap table):计算机启动时,操作系统就会初始化陷阱表,并且CPU会记住它的位置。当执行陷阱指令时,CPU就会根据陷阱表找到需要运行的指令。
- 实现直接执行——实现进程的切换
- (1)操作系统获取CPU的控制权
- a. 协作方式(等待系统调用):操作系统等待进程进行系统调用或者某种非法操作发生时,从而获得CPU控制权
- b. 非协作方式(操作系统进行控制):通过时钟中断(timer interrupt),时钟设备可以每隔几秒钟产生一次中断,产生中断时,正在运行的程序停止,操作系统中预先配置的中断处理程序(iterrput handler)会运行,此时操作系统会重新获得CPU控制权
- (2)保存和恢复上下文
- a. 调度程序(scheduler):决定继续运行当前正在运行的程序还是切换到另一个进程
- b. 上下文切换:为当前正在运行的进程保存一些信息,并为即将执行的进程恢复一些信息(借助PCB实现)
- c. 进行上下文切换时,操作系统会执行一些底层汇编代码,来保存通用寄存器(GR)、程序计数器(PC),以及当前正在运行进程的内核栈指针,然后恢复寄存器、程序计数器,并切换内核栈,供即将运行的进程使用。
- 实现“受限”
- 什么是受限直接执行?
进程的状态、转换和控制
- 进程的三种基本状态
◼ (1)就绪状态
➢进程分配到必要的资源,等待获得CPU执行的状态。 组织成一个或多个就绪队列。即进程获得了除了CPU外所需的一切资源。
◼ (2)运行状态
➢进程分配到必要的资源和CPU,在CPU上执行时的状态。
◼ (3)阻塞状态
➢又称等待态。程序正在等待某一事件而暂停运行,放弃CPU而处于暂停状态。即使CPU空闲,但是并未等到所需资源时,该进程也不能运行。
五状态:
◼ 新建(new) – 至少建立PCB,但进程相关的其他内容可能未调入主存
◼ 终止(terminated) – 进程已经终止,但资源等待父进程或系统回收
七状态
进程控制块PCB
➢描述进程与其他进程、系统资源的关系以及进程在各个不同时期所处的状态的数据结构,称为进程控制块 PCB (process control block)进程的组成
➢PCB:进程的动态特征,该进程与其他进程和系统资源的关系
➢程序与数据:描述进程本身所应完成的功能进程控制块内容
包括四个主要部分:进程描述信息,进程控制和管理信息,资源分配清单,处理器相关信息
进程的上下文
- 进程的物理实体(代码和数据等)和支持进程运行的环境合称为进程的上下文。
◼ 由进程的程序块、数据块、运行时的堆和用户栈(两者通称为用户堆栈)等组成的用户空间信息被称为用户级上下文;
◼ 由进程标识信息、进程现场信息、进程控制信息和系统内核栈等组成的内核空间信息被称为系统级上下文;
◼ 处理器中各寄存器的内容被称为寄存器上下文(也称硬件上下文),即进程的现场信息。
◼ 用户级上下文地址空间和系统级上下文地址空间一起构成了一个进程的整个存储器映像
在进行进程上下文切换时,操作系统把换下进程的寄存器上下文保存到系统级上下文中的现场信息位置。
进程的控制
- 进程的创建:
➢ 允许一个进程创建另一个进程。创建者为父进程,被创建者为子进程。子进程可以继承父进程所拥有的资源,当子进程被撤销时,应将其从父进程那里获得的资源归还给父进程。- 父进程通过调用fork函数创建一个新的运行状态的子进程
➢ int fork(void)
✓ 返回值: 返回0给子进程, 返回子进程的PID给父进程, 出错的话返回-1
✓ 子进程与父进程的PID不同:
➢ fork:调用一次,返回两次值
✓ 调用者(父进程): 返回子进程的PID
✓ 新建的子进程: 返回0 - 例子
- 父进程通过调用fork函数创建一个新的运行状态的子进程
- 进程的终止:
- 引起进程终止的事件有:
✓正常结束
✓异常结束
✓外界干预:OS、父进程终止、父进程请求 - void exit (int status)
✓终止程序
✓约定: 正常结束时返回 0 , 错误时返回非0值
✓另一种显式设置退出状态的方法是从主程序返回一个整数值 - Exit只会被调用一次,并且不会再次返回。
- 引起进程终止的事件有:
- 进程的阻塞与唤醒
- 正在执行的进程,由于期待的事情未发生,会使自己由运行态变为阻塞态。阻塞是进程自身的一种主动行为。
- 当阻塞等待的条件被满足时,进程会被唤醒进入就绪态。唤醒操作一般是由另一个和被唤醒进程相关的合作的进程实现的。
- 可能引起进程阻塞的事件
✓ 请求系统服务
✓ 等待I/O操作
✓ 等待数据到达
✓ 无新工作可做:服务进程
- 进程的切换
➢进程切换是指处理机从一个进程的运行转到另一个进程上运行,在这个过程中,进程的运行环境发生了实质性的变化。进程切换的过程如下:
✓(1)保存处理机上下文,包括程序计数器和其他寄存器
✓(2)更新PCB信息
✓(3)把进程的PCB移入相应的队列,如就绪、在某事件阻塞等队列
✓(4)选择另一个进程执行,并更新其PCB
✓(5)更新内存管理的数据结构
✓(6)恢复处理机上下文
进程的一些其他操作
- 回收子进程
➢原因
✓进程终止后仍然消耗着系统资源:如: 返回标志, 变量表等
✓僵尸进程:终止了但还未被回收
➢回收( Reaping )
✓父进程执行回收(using wait or waitpid函数):收到子进程退出状态
✓内核删除僵尸子进程
➢如果父进程不执行回收工作? ✓如果父进程没有回收子进程并终止,则孤儿进程将会由Init进程(PID=1)进行回收
- 僵尸进程:一个进程使用fork创建子进程,如果子进程退出,而父进程并没有调用wait或waitpid获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中。这种进程称之为僵尸进程。
- 孤儿进程:一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作。
wait与子进程同步
乱序回收,父进程回收子进程、父进程输出的子进程返回的信息是随机顺序的
有序回收,父进程回收子进程、父进程输出的子进程返回的信息是从pid[N-1]到pid[0]的进程休眠
➢Sleep 函数:
✓阻塞进程一段时间
✓如果请求的时间量已经到了: return 0
✓当被信号中断时,返回剩下的休眠时间秒数
➢Pause 函数:
✓使调用函数进入睡眠状态,直到进程接收到信号为止execve:加载并运行新程序
- int execve(char *filename, char *argv[], char *envp[])
- 装载并在当前进程中运行:
✓可执行文件 filename:可以是目标文件或脚本文件
✓参数列表 argv:通常 argv[0]==filename - 环境变量列表 envp
– “name=value” 字符串 (e.g., USER=droh)
– getenv, putenv, printenv - 覆写 code,data和stack区域
✓保存PID,打开文件和上下文信号 - 调用一次并且永不返回,发生错误则抛出异常
进程图
异常控制流
CPU的控制流
- 异常(Exception)是响应某些事件(即处理器状态更改)进而将控制权转移到OS内核 (kernel)
➢内核是OS的内存驻留部分
➢事件举例: 零除指令, 算术溢出, 缺页, I/O请求, 用户输入中断指令等
异常的类型
由处理器外部的事件产生
➢通过设置cpu的中断引脚来触发 ➢中断处理程序返回的位置是下一条指令- 例如:
- 时钟中断
- 每过一段时间(如几毫秒),外部定时器芯片触发中断
- 被内核用来从用户程序取回控制权
- I/O中断
- 在键盘中输入Ctrl-C
- 从网络传输收到包
- 硬盘中的数据到达
- 时钟中断
- 例如:
由执行当前指令的结果的触发
陷阱( Traps )
- 指令在被操作系统加载到内核的同时将用户模式提升为内核模式。完成后,操作系统会调用一个从陷阱返回指令(return-from-trap),回到用户模式
- 有意的异常,如:系统调用( system calls), 断点
- 将控制流转移到下一条指令
- 陷阱表(trap table):计算机启动时,操作系统就会初始化陷阱表,并且CPU会记住它的位置。当执行陷阱指令时,CPU就会根据陷阱表找到需要运行的指令。
故障( Faults )
- 无意的但是可能是可恢复的
- 如: 缺页 (可恢复), 保护故障(protection faults,不可恢复), 浮点异常
- 重新执行当前指令或者终止
终止( Aborts )
- 无意的且不可恢复的,如: 不合法的指令, 奇偶校验错误, 机器检查
- 终止当前程序
进程间通信
共享内存系统 (Shared-Memory System)
- 通过共享某些数据结构或共享存储器实现进程之间的信息交换。
- 通过共享某些数据结构或共享存储器实现进程之间的信息交换。
消息传递系统 (Message passing System)
- 进程间的数据交换是以消息(message,在计算机网络中又称报文)为单位。程序员直 接利用系统提供的一组通讯命令(原语)来实现通讯。
- 在消息通信中,接收方和发送方之间有明确的协议和消息格式 。
消息缓冲队列机制(直接通信)
- 发送进程直接将消息发送给接收进程,并将它挂在接收进程的消息缓冲队列上。接收进程从消息缓冲队列中取得消息。
- 发送进程直接将消息发送给接收进程,并将它挂在接收进程的消息缓冲队列上。接收进程从消息缓冲队列中取得消息。
信箱通信方式(间接通信)
- 发送进程将消息发送到某个中间实体(一般称为信箱)中,接收进程从中取得消息,所以称为信箱通讯方式,相应地系统称为电子邮件系统
- 信箱通信中,需要定义信箱结构,还包括消息发送和接收功能模块,提供发送原语和接收原语。
- 邮箱头:邮箱名称、邮箱大小、拥有该邮箱的进程名
- 邮箱体:存放消息
管道
- 管道用于连接一个读进程和一个写进程,以实现它们之间通信的共享文件(pipe文件,又称为FIFO文件,严格遵循先进先出 ,不支持文件定位操作)。
- 为了协调双方的通信,管道通信机制必须提供以下三方面的协调能力:
- 互斥:一个进程正在对pipe进行读/写操作时,另一进程必须等待。
- 同步:当写(输入)进程把一定数量的数据写入pipe后,便去睡眠等待,直到读(输出)进程取走数据将其唤醒;当读进程读一空pipe,也应睡眠等待,直至写进程将数据写入管道,才将其唤醒。
- 对方是否存在:只有确定对方已存在时,才能进行管道通信,否则会造成因对方不存在而无限期等待
- 管道用于连接一个读进程和一个写进程,以实现它们之间通信的共享文件(pipe文件,又称为FIFO文件,严格遵循先进先出 ,不支持文件定位操作)。
线程
线程的基本概念
线程的定义:线程是进程内一个相对独立的可调度的执行单元
线程:轻量级进程。
- 基本的CPU执行单元
- 并不单独拥有系统资源,只拥有其在运行中必不可少的资源,如线程ID、程序计数器、寄存器集合和堆栈
- 但同一进程下的不同线程共享进程拥有的全部资源
- 有就绪、阻塞、运行三种状态
- 进程作为除CPU外的系统资源的分配单元,线程作为CPU的 分配单元
为什么要区分线程和进程的概念?
- 进程:为了保护独立的地址空间
- 线程:为了更好的支持并发
线程内存模型
- 概念模型:
- 一组并发线程运行在一个进程的上下文中
- 每个线程都有它独立的线程上下文,包括线程ID、栈、栈指 针、程序计数器、条件码和通用目的寄存器值。
- 所有线程共享进程上下文的剩余部分
- 代码、数据、堆以及共享库代码和数据区域
- 打开文件和已安装的信号处理程序
- 从实际操作的角度来说,这个模型不是那么严格的:
- 寄存器值其实是不被共享的,但虚拟内存总是共享的
- 一个线程可以读写另一个线程的栈
- 概念模型和实际操作模型之间的区别是困惑和错误的来源之一
多线程模型
- 有两种不同方法来提供线程支持
- 用户层的用户级线程
- 用户级线程位于内核之上,它的管理无需内核支持;
- 内核层的内核级线程
- 内核级线程由操作系统来直接支持与管理
- 用户层的用户级线程
- 用户级线程和内核级线程之间关系
多对一模型
一对一模型
多对多模型
Posix(可移植操作系统接口Portable Operating System Interface of UNIX)线程
Posix Threads Interface(Pthreads 接口)
- Pthreads 约60个函数的标准接口,这些函数可操纵C程序中的线程
- Creating threads(创建线程)
- pthread_create()
- Terminating threads(终止线程)
- pthread_cancel()
- pthread_exit()
- Joining and Detaching threads(回收和分离线程)
- pthread_join()
- pthread_detach()
- Creating threads(创建线程)
- Pthreads 约60个函数的标准接口,这些函数可操纵C程序中的线程
创建线程
- int pthread_create(pthread_t *tid, pthread_attr_t *attr, func *f, void *arg)
- tid :返回创建的新线程的pid
- attr(属性):设置线程的属性
- f :创建线程后将执行的程序
- arg(参数):一个可以传递给开始程序的参数。 它必须作为空类型的指针强制转换通过引用传递 。 如果不传递任何参数,则可以使用NULL
- 线程属性:
- 默认情况下,线程在创建时会带有某些属性
- 程序员可以通过线程属性对象来更改其中的一些属性
- pthread_attr_init 和 pthread_attr_destroy 可以初始化/销毁线程属性对象
- int pthread_create(pthread_t *tid, pthread_attr_t *attr, func *f, void *arg)
终止线程
- void pthread_exit(void *thread_return)
- 线程终止:
- 当比他更高一级的进程返回时线程隐式终止;
- 当调用 pthread_exit 时线程显式终止;
- 如果主线程调用了 pthread_exit,会等待所有其他线程终止后终止主线程 ,整个进程将 thread_return 作为返回值;
- 当调用 exit 时,整个进程都会被终止。
- int pthread_cancel(pthread_t tid)
- 调用pthread_cancel并不等于线程终止,它只提出请求。线程在取消请求发出后会继续运行;
- 直到到达某个取消点。取消点是线程检查是否被取消并按照请求进行动作的一个位置。
Joining(结合) and Detaching(分离) Threads
- int pthread_join(pthread_t tid, void **thread_return)
- 连接是线程之间同步的一种方法
- pthread_join 阻塞线程直到tid线程终止
- 与 linux中的 wait 函数不同 , pthread_join 函数可以只等候特定的线程终止
- 一个线程只能响应一个pthread_join() 请求。对同一个线程尝试多个join操作会发生逻辑错误。
- int pthread_detach(pthread_t tid)
- pthread_detach 函数将线程 tid 分离出来
- 默认情况下,线程创建时为可连接的
- 每个可连接线程应由另一个线程显式获取,或通过调用pthread_detach函数来分离
- 要将线程显式地创建为可连接或可分离的线程,请使用pthread_create() 例程中的attr 参数。
- 典型的步骤过程是: 声明pthread_attr_t数据类型的pthread属性变量
- 用pthread_attr_init() 初始化属性变量
- 使用pthread_attr_setdetachstate() 设置属性分离状态
- 完成后,释放属性通过pthread_attr_destroy() 使用的库资源
- int pthread_join(pthread_t tid, void **thread_return)