进程与线程

进程

  • 一个 进程 就是一个 正在执行的程序的一个实例
  • 组成:程序+数据+进程控制块(PCB)
  • 进程(一个完整的、动态的执行过程\(\not =\) 程序(在硬盘上的静态代码和数据的集合)

进程控制块(PCB)

  • 操作系统通过 PCB 来掌握和控制进程的执行。
  • 主要内容:
    • 进程 ID (PID):唯一标识进程。
    • 处理机状态信息(运行现场/上下文):保存 CPU 执行该进程时的关键寄存器内容,主要用于进程切换
    • 进程控制信息:进程状态,优先级等。
  • PCB是作为进程存在的唯一标志

进程模型

  • 进程模型是操作系统为了支持多道程序设计而建立的一个概念框架
  • 操作系统将CPU的执行时间划分为一个个很小的时间片,并通过快速的切换,让多个进程在宏观上看起来是“同时”运行的,而在微观上是交替执行的。(分时处理)

进程创建

操作系统在以下典型情况下会创建进程:

  1. 系统初始化
    • 当计算机启动时,OS 会创建若干个核心进程(如 initsystemd 在 Linux 里),用来管理系统。
  2. 用户请求
    • 用户在终端输入命令、双击应用图标,系统需要为该程序创建一个新的进程来运行。
  3. 进程调用系统调用
    • 一个正在运行的进程可以通过系统调用,如 fork()来创建新的子进程。
  4. 批处理作业的初始化
    • 在批处理系统里,作业调度程序会根据队列情况为新作业创建进程。

进程终止

  • 进程正常结束(Normal Exit):自然终止,没有错误,也没有异常,就是任务完成了。
  • 进程发生错误(Error Exit):程序无法正常继续执行。
  • 发生致命错误(Fatal Error):程序触发了系统级错误,由 操作系统 强制终止。程序并非主动要求退出,而是 OS 判定它“有问题、必须干掉”。
  • 被其他进程杀死(Killed by Another Process):一个进程可以杀死另一个进程。

进程状态与切换

进程状态

  • 就绪态(Ready):已经准备好执行、拥有所有运行所需资源,但正在等待 CPU 分配(排队等候)。
  • 运行态(Running):正在 CPU 上执行指令。
  • 阻塞态(Blocked):等待某个事件发生后才能运行,否则不能运行。

状态转化

slHUN5ajpc8rVF4.png

  • Ready → Running(dispatch)调度器选择该进程,调度器/调度程序把 CPU 分配给它。
  • Running → Ready(preemption):时间片用完或被高优先级进程抢占(定时器中断触发),调度程序选择另一个进程
  • Running → Waiting(阻塞)发起阻塞操作(如读磁盘、等待 I/O、等待信号或锁),主动让出 CPU,等待某个外部事件发生。
  • Waiting → Ready等待事件完成(I/O 完成、信号到达),进程被放回就绪队列。

并发/并行

  • 并发:指一段时间内多个事件同时发生。(微观上交替发生,每个时刻最多一个事件发生
  • 并发执行:多道程序环境下(假设为单处理器系统),宏观上多个进程同时运行,但微观上是交替执行的,每个时刻最多只有一个进程在运行
  • 并行:指某一时刻多个事件同时发生。多道程序环境下,多处理器系统可以有多个进程并行执行,而在单处理器系统中,多个进程是并发执行的。

现代操作系统的特征:并发、共享、虚拟、异步性

线程模型

  • 进程是资源分配的基本单位线程是调度的独立单位进程中的线程共享该进程的所有资源(地址空间和大部分资源,如打开的文件描述符、全局变量、虚拟内存映射等)。
  • 线程是轻型的进程,是进程中的一个顺序执行流。
  • 进程中的一个执行任务(控制单元),负责当前进程中程序的执行。一个进程至少有一个线程,一个进程可以运行多个线程,多个线程可共享数据。
  • 每个线程可以独立调度、执行,同一类线程共享代码和数据空间每个线程都有自己独立的运行栈和程序计数器(PC),线程之间切换的开销小

一个进程中线程的公有资源

存在于进程的PCB中

  • 处理器信息: 进程执行时间等
  • 内存分配信息: 进程地址空间信息、页表信息等
  • 已分配的I/O设备和打开的文件(已获得的软硬件资源)

线程的独有资源

存在于线程的TCB中

  • 线程状态(就绪、运行和阻塞)
  • 寄存器
  • 程序指针
  • 执行堆栈(存放线程中的局部变量、参数以及返回地址等)

阻塞型系统调用(Blocking system call)

  • 通常与I/O相关: read(), fread(), getc(), write()
  • 系统调用完成前不返回调用进程/线程
  • 进程/线程进入阻塞状态等待
  • 当I/O完成, 进程/线程变成就绪状态,等待调度。
  • 简单

非阻塞型与I/O相关的系统调用

  • 异步 I/O
  • 复杂
  • I/O初始化完成后马上返回调用进程/线程, 进程/线程继续运行。
  • I/O操作一旦完成, 通过中断通知调用进程/线程。

用户级线程&内核级线程

用户级

YjzdTwh1qoQryeu.png

  • 用户级线程之间切换开销小,而且可以在不支持线程的操作系统实现
  • 但是当一个用户级线程因为I/O阻塞时, 整个进程都会阻塞

内核级

KzkTXnxrjPlEOC5.png

  • 内核级线程的实现是基于操作系统的,总体性能更加优越;但内核级线程的上下文切换开销相对较大

弹出式线程

  • 当一个信息到达时,系统创建一个线程来处理该信息
    xQTnW3cgUDdR6HK.png

调度程序激活机制

用户程序中有一个 线程库(user-level thread runtime),内核为线程库提供若干个称为 调度激活(Scheduler Activation) 的虚拟处理器。并且让(用户空间)运
行时系统将线程分配到处理器上。
当重要事件发生(如线程阻塞、CPU 重新分配等),内核会通过 上行调用(upcall) 通知用户态(以参数形式传递有问题的线程编号和所发生事件的一个描述),使用户级线程库能够做出合理的调度决策。

上行调用(Upcall)

  • 上行调用是内核通知用户态线程库发生关键事件的一种机制。
  • 内核对用户级线程库的“回调函数调用”
  • 发生某些事件时,内核会调用用户态注册好的处理函数,让用户态的线程库有机会更新自身状态。
  • 上行调用是内核向用户级线程库发送的通知,用来报告线程阻塞、唤醒、CPU变更等事件。

进程互斥与同步

  • 对临界资源(独享资源)的互斥访问
  • 合作进程间的速度协调

临界资源

临界资源 = 共享资源 + 需要互斥访问

  • 共享资源:多个进程/线程都可能访问它,因此要共享。
  • 互斥使用:但在某一时刻,为了避免冲突、竞争、数据错误,只允许一个进程进入对应的“临界区”访问它。即互斥使用。

竞争条件

竞争条件指:

多个并发执行的进程或线程,在没有正确同步的情况下,同时访问并修改共享数据,从而导致最终结果依赖于执行的先后顺序。

  • 多个线程“抢”共享资源
  • 谁先执行取决于调度器(不可控)
  • 从而程序结果 不可预测、不稳定

临界区

定义:程序中访问临界资源(共享资源)的那段必须互斥执行代码区域

  • 当代码在读写共享变量、修改共享缓冲区、操作共享数据结构时
  • 如果多个线程同时执行这段代码可能导致错误
  • 于是这段代码必须被保护起来 → 同一时刻只允许一个线程执行
  • 这段被保护的代码即为临界区。
Process {
  while (true) {
	  Do other work;
	  进入区(Enter Critical Section)
	      临界区(Critical Section)
          Access shared variables; 
          (Access on critical I/O devices;)
      退出区(Leave Critical Section)
	  Do other work ;
    }
}

临界区访问的四条准则

临界区的互斥访问

OYupjeraHIzDm5x.png

互斥性

任何时刻至多只能有 一个 进程/线程在临界区中执行。

若允许多个进程同时进入,会造成:

  • 数据被同时读写 → 竞争条件
  • 状态不一致 → 数据破坏
  • 执行结果不可预测

互斥是保证临界区正确性的最基本要求。

空则让进

如果没有进程在访问临界资源,那么有进程请求访问该临界资源的要求应该满足。
进展保证系统不被 无关进程阻塞,属于 活性(liveness) 要求。

有限等待

如果有进程在访问临界资源,则其他进程必须等待,但应该是有限的等待。
每个想进入临界区的进程必须在有限次轮换后进入,不可无限期等待

没有假设

不应对进程的推进顺序、速度,以及CPU的速度和数量做任何假设。

忙等待实现互斥的方法

关中断

一个进程进入临界区后关闭所有中断,在离开临界区前再重新开中断。
关中断后CPU就不能响应时钟以及其他中断请求,因而就不会有进程切换。

缺点:

  • 任何代码忘记 开中断 ,则系统直接“锁死”,无法做出任何响应

锁变量

设置一个共享变量 locklock = 0 表示临界区空闲;lock = 1 表示临界区已被占用
对于每个进程

Progress[i] (
	// busy waiting
	whlie(lock==1);
	
	// enter
	lock=1;
	
	// cirtical area
	access shared variable;
	
	// eixt
	lock=0;
)

lock=0,将lock 从 0 改成 1,进入临界区,其他进程看到 lock = 1 就在外面等待。

致命缺陷

当两个进程同时进行时,有可能同时进入临界区
它们可能同时看到锁是 0, 双方都执行 lock = 1, 两个进程同时进入临界区 → 互斥失败

严格轮换法

引入一个辅助变量,表示轮到哪一个进程进入临界区。

int turn;
Progress[i] (
	// busy waiting
	whlie(turn!=i);
	
	// enter
	// cirtical area
	access shared variable;
	
	// eixt
	turn = (turn+1)%n
)

缺陷

当进程p0想进入临界区时,turn=i,而且第i号进程若一直不想进入临界区,则进程p0将一直等待下去,无限等待。

Peterson解决方案

相当于是严格轮换法和使用flag的锁变量结合起来
Peterson 方案用两个辅助变量:interested 表明意愿,turn 表明礼让,通过轮流让步的方式实现严格的互斥、进展和有限等待,是经典的纯软件互斥算法。

#define ture 1
#define false 0
#define N 2
int turn=0;
int interested[N];

void enter_region(int pid) {
	int other = 1-pid;
	
	interesed[pid] = true;
	// 谁进来谁把 turn 让给对方,如果两人都想进入,就轮到 turn 指向的那一方。
	turn = other;
	while(turn==other && interested[other]==true);
}

void leave_region(int pid) {
	interested[pid] = false;
}

TSL指令

TSL(Test-and-Set Lock) 是一种原子指令(atomic instruction)。

  • 读取内存变量 lock 的值将 lock 设置为 1(上锁)
    它完成两个操作(“读+写”)并保证在执行期间不可被中断、不可被抢占

  • 原子性:即完成操作的期间不被中断、不被抢占,不可分割。

int lock = 0;

int TSL(int* lock) {
	// All done atomically
	int tmp = *lock;
	*lock = 1;
	return tmp;
}

void enter_region() {
	while(TSL(&lock));
}

void leave_region() {
	lock=0;
}

  • 由于 TSL 是原子的,所以即使多个 CPU 并发执行,也不可能同时获得锁。

忙等方案存在的问题

  • 忙等浪费了CPU时间
  • 优先级反转(Priority Inversion)问题:一个或者多个优先级较高的进程等待一个优先级较低的进程离开临界区。但是只要这些优先级较高的进程不阻塞,则在临界区的低优先级的进程无法调度,无法离开临界区。进程间会出现永久等待的情形。

睡眠与唤醒

当进程试图进入临界区但发现资源被占用时,不进行忙等待,而是主动睡眠(阻塞)让出 CPU(与忙等相比,不占用CPU时间)。 当临界区的持有者退出时,会唤醒一个等待的进程

void enter_region(){
    if (lock == 1)          // 锁被占用
        sleep();           // 当前进程阻塞,不占 CPU
    else
        lock = 1;         // 获取锁,进入临界区
}

void exit_region(){
    lock = 0;              //释放锁
    wakeup();              //唤醒一个等待者
}

信号量机制

一个信号量可以描述一种资源的分配情况,信号量中的两个成员变量分别表示当前可用的资源数量指向该资源等待队列的指针

一个信号量有2个操作:

  • Down / P: 操作对应于资源的申请
  • Up / V:操作对应于资源的释放(产生)
  • P、V操作是不可中断的原子操作(原语)。
typedef struct {
	int v;
	struct *waitingQueue;
} semaphore_t;
  • 在 P 操作中,当信号量值小于 0 时,操作系统将当前进程加入信号量的等待队列,并将其状态置为阻塞.
  • V 操作的核心作用就是通过 wakeup() 将在 P 操作中 sleep 的进程从等待队列唤醒,使其重新进入就绪队列。
void P(semaphore_t* s) {
	s->v -=1;
	if(s->v <0) {
		s->waitingQueue.add(cur_pid);
		block(cur_pid);
	}
}

void V(semaphore_t* s) {
	s->v +=1;
	if(s->v <=0) {
		int pid = s->waitingQueue.front();
		s->waitingQueue.pop();
		wakeup(pid);
	}
}

信号量解决互斥问题

semaphore_t s1;

void progressi() {
	// ... 
	// enter
	P(s1);
	
	// Critical section;
	access shared resources;
	
	// exit
	V(s1);
	//...
}

进程同步

  • 在多进程或多线程并发执行时,通过某种机制 协调它们的执行顺序,使得各进程在共享资源或需要按特定顺序完成任务时能够正确运行,而不会出现冲突、数据损坏或逻辑错误。

与进程互斥的区别

  • 进程互斥是由于各进程共享、竞争同一临界资源,从而造成这些进程之间的一种间接制约,这些进程之间并没有逻辑联系
  • 进程同步是相互合作的进程之间,为了保证结果的正确性,必须达成的速度上的协调,是有逻辑联系的直接制约
  • 进程互斥问题中,各个进程需要的是同一种资源;而进程同步问题,相互合作的两个进程需要的资源是不同的,但一个进程所需的资源由对方进程产生

使用信号量实现进程同步的例子

aApgHKLlEByYsnD.png

int n = buffer.size();
struct semaphore empty=n,full=0;
void c() {
	while(1) {
		P(empty);
		buffer_input(data);
		V(full);
	}
}

void p() {
	while(1) {
		P(full);
		data=buffer_output();
		V(empty);
	}
}

经典线程同步问题

生产者消费者问题(Producer-Consumer Problem)

  • 生产者(Producer) 负责向缓冲区中放入数据(生产产品)

  • 消费者(Consumer) 负责从缓冲区取走数据(消费产品)。

  • 生产者与消费者共享一个有界的缓冲区(bounded buffer),对缓冲区的访问必须 互斥

  • 同时缓冲区 不能溢出(生产者超量生产), 不能空取(消费者取空),要维持进程之间的同步
    zeRbYXSJc5Cv7yI.png

  • 使用信号量解决

信号量 含义
mutex 对缓冲区的互斥访问
empty 当前可放入的空槽数(初始 = 缓冲区大小)
full 当前可取出的产品数(初始 = 0)
void producer() {
	P(empty);    // 空槽不够就阻塞
	P(mutex);    // 进入临界区
	    put_item();
	V(mutex);    // 离开临界区
	V(full);     // 可取产品 +1
}

void consumer() {
	P(full);     // 没产品就阻塞
	P(mutex);    // 进入临界区
	    get_item();
	V(mutex);    // 离开临界区
	V(empty);    // 空槽 +1
}

哲学家就餐问题(The Dining Philosophers Problem)

  • 5 位哲学家 围坐在一张圆桌旁,面前有 5 个叉子。每位哲学家左右两侧各有一个叉子。
  • 哲学家不断重复两个动作:
    1. 思考(Thinking)
    2. 进餐(Eating) —— 需要同时拿起左叉子右叉子
  • 问题: 所有哲学家同时拿起自己左边的叉子,然后等待右边的叉子,就会造成 死锁
  • 解决方案:一个叉子最多只能同时被一个人拿起,需要互斥。控制最多拿起叉子的人数为4,则5个人里总有一个人可以进食。
struct semaphore forks[5]={1, 1, 1, 1, 1};
struct semaphore count=4;

void p(int i) {
	think();
	P(count);
	P(leftfork(i));
	p(rightfork(i));
	eat();
	V(rightfork(i));
	V(leftfork(i));
	V(count);
	
}

读者-写者问题(The Readers and Writers Problem)

  • 读者:读数据。写者:写数据
  • 访问规则:
    • 多个读者可以同时读数据
    • 任何时刻只能有一个写者写数据
    • 一个读者与一个写者不能同时访问数据
  • 一个写者不能与其它的读者或写者同时访问相应的临界资源,互斥
struct semaphore wrt=1;
struct semaphore mutex=1;
int cnt=0;
void writer(){
	P(wrt);
	write();
	V(wrt);
}

void reader(){
	P(mutex);
	cnt++;
	if(cnt==1) P(wrt);
	V(mutex);
	
	read();
	
	P(mutex);
	cnt--;
	if(cnt==0) V(wrt);
	V(mutex);
}

进程通信

进程通信指不同进程之间为了交换数据、协调动作而采取的一系列机制。
由于不同进程的地址空间互相隔离(内存不共享),它们无法像线程一样直接访问对方数据,因此需要 特殊的内核机制 来交换信息。

IPC的作用:

  • 数据传输:例如一个进程产生数据,另一个进程需要消费。
  • 事件通知:告诉进程某个事件发生(例如 I/O 完成)。
  • 资源共享:例如共享内存块。
  • 进程同步:保证访问共享资源的互斥与顺序。

共享内存系统

共享内存:提供一块可被多个进程同时访问的物理内存。多个进程通过将同一块物理内存映射到各自用户空间,实现最快的数据共享

zgWIiSctpFTECeo.png

共享内存本身没有互斥,如果没有同步,会出现:脏读 / 写,数据竞争,并发破坏数据结构,写入覆盖,因此需要一个同步机制。

管道

管道是一种单向的、基于字节流的进程通信机制。数据只能 从写端 → 读端 单向流动。 读进程和写进程都共享一个内核缓冲区。**虽然进程地址空间隔离,但内核对象是共享的,因此间接完成通信。

在 Linux 中,管道由内核维护一个结构:

  • 一个内核缓冲区(环形队列或线性缓冲)

  • 两个 file 描述符(读端 fd、写端 fd)

  • 两个等待队列(read 等待队列、write 等待队列)

  • 写数据时:

    • 检查管道是否被读取端关闭
    • 检查缓冲区是否有空间,若缓冲区满了 → 写进程会被阻塞(内核把写进程放入 write_waitq,进程 sleep,切换到其他进程执行,等待读进程读走数据后,缓冲区有空间,wakeup)
  • 读数据时:

    • 检查管道是否有数据,若无数据,写端未关闭 → 阻塞无数据,写端也关闭 → 返回 0(EOF)
    • 阻塞:读进程加入 read_waitq,sleep, 等待写进程写入数据后 wakeup(read_waitq)

消息传递系统-直接通信(信息缓冲队列)

  • 发送原语:Send(receiver, message)
  • 接收原语:Receive(sender, message)
  • Send(B, m1) // Receive(A, m2) or Receive(m2)
  • 实现:
typedef struct message buffer
{
	 sender: 发送者进程标识符
	 size: 消息长度
	 text: 消息正文
	 next: 指向下一个消息缓冲区的指针
 }
typedef struct PCB
{
	mq: 消息队列队首指针
	mutex: 消息队列互斥信号量,因为队列是临界资源,必须互斥。
	sm: 消息队列资源信号量
}

6REBNJPoQtDbrxL.png

进程调度

  • 进程调度是操作系统决定: 哪个进程在何时获取 CPU 进行运行的机制。
  • 只要当前进程不能继续运行(主动退出、阻塞、时间片到期、被抢占),或者更高优先级进程就绪,操作系统就会进行调度。

抢占式调度(Preemptive Scheduling)

  • 运行的进程可以基于某种原则(时间片或者优先权)被强制中断执行,系统进而将CPU分配给其他进程

非抢占式调度(Non-preemptive Scheduling)

  • 运行的进程一直占有CPU,直至它自愿释放。

进程调度的目标

  • 公平性(Fairness):让每个进程都能获得合理的 CPU 使用机会,避免进程“饥饿”。
  • 策略强制执行:保证规定的策略被执行,即系统必须确保所有进程 / 用户 / 操作 严格按照策略规定的方式执行
  • 平衡:保持系统的所有部分都忙碌,资源分配尽可能均衡
  • CPU利用率高(CPU Utilization):让 CPU 尽可能忙碌,不长时间空闲。
  • 最大吞吐量(Throughput):单位时间完成的进程数量尽可能多(适用于批处理系统)。
  • 最小响应时间(Response Time):尤其在交互式系统中,用户输入后要快速响应。
  • 最短周转时间(Turnaround Time):进程从提交到完成的时间尽可能短。
  • 预测性(Predictability):性能稳定,不出现响应时间大幅波动。
  • 满足实时性要求(Real-time Constraints):实时系统强调在 deadline 内完成任务,而不仅仅是性能指标。
  • 周转时间(Turnaround Time):从一个作业进入系统到完成所经过的时间
  • 等待时间(Waiting Time):一个作业从进入系统到完成过程中的等待时间
  • 响应时间(Response Time):从发出一个请求到得到响应之间的时间

不同系统着眼点不同

  • 所有系统:公平性、系统策略强制执行、资源使用均衡
  • 批处理系统:最大吞吐量、最小周转时间、最大CPU利用率
  • 交互式系统:最小响应时间、最佳用户体验
  • 实时系统:可预测性、满足截止时间要求

计算密集型(CPU-bound)进程

  • 这类进程的大部分时间都在 执行计算任务,需要大量使用 CPU 的算力,而 很少等待 I/O(如磁盘、网络、键盘输入等)。
  • 这类进程的性能瓶颈主要在 CPU。
  • 时间片相对较长的调度效果更好(减少上下文切换带来的开销)。

I/O 密集型(I/O-bound)进程

  • 这类进程的大部分时间在 等待 I/O 完成(磁盘读写、网络请求、数据库查询、用户输入等),真正计算的时间反而很短。
  • 真正的性能瓶颈在 I/O 设备,而不是 CPU。
  • 需要 更短的时间片优先级更高,这样它们可以在 I/O 完成后尽快得到 CPU,提升系统整体吞吐。

处理机调度算法

批处理系统的调度

先来先服务 (FCFS, First-Come First-Sevice)

  • 核心思想:先请求CPU的进程先分配到CPU
  • 属于非抢占式调度
  • 实现: FIFO队列(就绪队列),新的进程添加至队尾,调度程序总是选择队首程序进行调度
  • 缺点:
    • 对短作业不公平:短进程等待时间大,系统响应差。
    • 若一个长任务排在队首,后面所有短任务都被拖住。

非抢占式最短作业优先(SJF, Shortest Job First)

  • 核心思想:运行时间短的作业优先得到调度
  • 算法应用前提: 每个作业的运行时间需要预先知道
  • 平均周转/等待时间最小(理论最优)
  • SJF并不总是最优,如果短作业不断到来,长作业可能长期得不到执行机会。

抢占式最短作业优先/最短剩余时间优先(SRTF, Shortest Remaining Time First)

  • 核心思想:优先调度剩余运行时间最短的进程
  • 算法应用前提: 作业的运行时间要预先可知
  • 上下文切换代价高 :频繁抢占会增加调度器开销。
  • 长期饥饿风险更高 :长任务可能永远执行不完。

交互式系统调度

时间片轮转(Round-Robin)

  • 就绪队列中的进程轮流依次运行,每次运行时间不超过一个时间片,直至进程完成。
  • 时间片长度的选择:保证70-80%的作业在一个时间片内阻塞或者完成。典型的时间片为:10~100 ms
  • 时间片太大:系统响应时间不佳,有可能退化为FCFS算法
  • 时间片太小:太多的上下文切换,系统开销大。CPU利用率低

优先级调度(Priority Scheduling)

  • 每个作业都被指定一个优先级,每次调度优先级最高的一个进程
  • 相同优先级的进程采用FCFS方式调度。
  • 优先级可以是静态赋予或动态赋予。
  • 一些方案:
    • 每过一个时钟周期,增加等待CPU的进程的优先级,或者减小当前运行进程的优先级。
    • 每个进程指定一个允许运行的最大时间片。更加关注IO密集型进程(当这样的进程需要 CPU 时,应立即分配给它 CPU, 以便启动下一个IO 请求,这样就可以在另一 个进程计算的同时执行 I/O 操作),进程的优先级可以设置为1/f ,其中f为该进程在上一次运行时用掉的时间占最大时间片的比例。

多级队列调度(Multi-Queue)

  • 有多级就绪队列,每个就绪队列中的进程优先级不同。
  • 每个进程根据其优先级进入不同就绪队列,进程每次运行一个时间片。
  • 调度策略:每个就绪队列对应于一个优先级。优先调度优先级较高就绪队列中的进程;也可以根据其优先级的高低,分配给这些就绪队列不同的CPU运行时间比例。
    5CLbspA7THVFMUB.png

多级反馈队列(Multi-level Feedback)

  • 多级就绪队列,各有不同的优先级以及时间片。
  • 根据进程运行情况,进程会在不同的队列间移动,对应的优先级和时间片也产生变化。
  • 优先级高的队列中的进程优先得到调度,某个队列中的进程只有在所有比它优先级高的队列都为空时,才能得到调度。
  • 每个队列中的进程得到调度后,最多运行一个与该队列对应的时间片。
  • 实现:
    • 队列\(Queue_i\) 的时间片为:\(t_02^i\)
    • 如果 \(Queue_i\) 的进程在一个时间片内没有完成,它将进入队列 \(Queue_i +1\) (优先级较低)。
    • 对最低优先级队列中的进程采用FIFO调度方式
    • 一旦一个阻塞的进程完成了I/O操作,它将进入最高优先级的队列,以确保I/O密集型的进程优先得到调度。
  • 该调度算法对于各种类型的作业,都有一个很好的调度性能。
    k1LVwCrJbx9i3vF.png

最短进程优先(Shortest Process Time First)

  • 在交互式系统中,一个进程的生命周期可以看作运行一系列命令的过程。
  • 选择最短进程就是选择下一个命令执行时间最短的那个进程。
  • 根据每个进程过去各个命令运行的加权平均响应时间来推算下一个命令的执行时间。
    • 假设 \(T_{pi}\) 为第 \(i\) 次命令执行时间的预测值,\(T_i\)为其实际值,则第 \(i+1\)次命令执行时间的预测值为\(T_{p_{i+1}} = \alpha T_{p_i}+(1-\alpha)T_i\)
    • 有时把这种通过当前测量值和先前估计值进行加权平均而得到下一个估计值的技术称作老化
    • 老化算法在 \(\alpha= 1 /2\) 时特别容易实现

保证调度(Guaranteed Scheduling)

  • 一种完全不同的调度算法是向用户作出明确的性能保证,并付诸实现。假设系统有n个用户登录,则要保证每个用户获得1/n的CPU处理能力;类似地,\(n\) 个进程运行的单用户系统中,要保证每个进程获得 \(1/n\) 的CPU时间。
  • 为了实现所做的保证,系统必须跟踪各个进程自创建以来已使用了多少 CPU 时间,然后它计箕各个进程应获得的 CPU 时间 , 即自创建以来的时间除以n 。
  • 实现:计算实际已获得的CPU时间和应获得CPU时间之比率调度时选择对应比率最低的进程

彩票调度算法(Lottery Scheduling)

  • 向进程提供各种资源(如CPU时间)的彩票,调度时随机抽出一张彩票,拥有该彩票的进程获得相应的资源。
  • 实现:给每个进程分配一定数量的彩票。调度时,系统随机抽取一张彩票,抽中的进程得到 CPU 时间片。
  • 长期来看,每个进程获取的 CPU 时间比例 ≈ 它的彩票占比
  • 给进程分配的彩票数目体现该进程获得 CPU 的“权重”。

公平共享调度(Fair-Share Scheduling)

  • 这是一个基于用户的公平共享调度算法,要确保每个用户获得应有的资源(如CPU时间)份额
  • 假设有 \(n\) 个用户,第 \(i\) 个用户有 \(k_i\) 个进程,则每个用户应得到 \(1/n\) 的CPU时间,在\(1/n\)时间的基础上,再为用户下每个进程分配时间。

实时系统中的调度

  • 实时系统中的事件可以按照响应方式进一步分类为周期性(以规则的时间间隔发生)事件或 非周期性(发生时间不可预知)事件。
  • 假设系统有 \(m\) 个周期性事件,事件\(i\)发生的周期为\(P_i\),需要\(C_i\)秒CPU时间来处理该事件,那么可以处理负载的条件是

\[\sum_{i=1}^m\frac{C_i}{P_i}\leq1 \]

  • 满足这个条件的实时系统称为是 可调度的

线程调度

用户级线程

  • 由于内核并不知道有线程存在,所以内核选取一个进程,并给予时间片控制。
  • 由选择的进程中的线程调度程序决定哪个线程运行。如果该线程用完了进程的全部时间片,内核就会选择另一个进程运行。

s1dm8MXBFvCSGLq.png

每个线程运行一会儿,然后把CPU交回给线程调度程序。这样在内核切换到进程B之前,就会有序列A1,A2,A3,A1,A2,A3,A1,A2,A3,A1。

内核级线程

  • 内核选择一个特定的线程运行。它不用考虑该线程属于哪个进程。
  • 对被选择的线程赋予一个时间片,而且如果超过了时间片,就会强制挂起该线程。

一个线程在 50ms 的时间片内,5ms 之后被阻塞,在30ms的时间段中,线程的顺序会是
A1, B1, A2, B2, A3, B3

DYBJMWSOI73CLNl.png

posted @ 2025-11-30 22:46  NightRainLone  阅读(14)  评论(0)    收藏  举报