操作系统中 最核心的概念是进程:这是 对正在运行程序的一个抽象。将一个单独的 CPU 变换成 多个虚拟的 CPU
1、进程
1、先考虑 一个网络服务器,一些网页 请求从各处进入。当一个请求进入时,服务器 检查 其需要的网页是否在缓存中(缓存中保存了 近期访问的网页副本,以便快速访问)。如果是,则把网页发送回去;如果不是,则启动一个磁盘请求 以获取网页。然而,从 CPU 的角度来看,磁盘请求 需要漫长的时间。当等待磁盘请求 完成时,其他更多的请求 将会进入。如果 有多个磁盘存在,可以在满足第一个请求之前 就接二连三地对其他的磁盘发出 部分或全部请求。很明显,需要一些方法 去模拟并控制这种并发
“磁盘请求”指的是 从服务器的硬盘 或 其他存储设备中 读取数据的操作
2、在任何多道程序设计系统中,CPU 由一个进程 快速切换至 另一个进程。严格地说,在某一个瞬间,CPU 只能运行一个进程。但在1秒钟内,它可能运行多个进程,这样就产生并行的错觉,即 伪并行,以此来区分 多处理器系统(该系统 有两个或多个 CPU 共享同一个物理内存)的真正硬件并行
1.1 进程模型
1、计算机上 所有可运行的软件,通常 也包括操作系统,被组织成 若干顺序进程,简称进程。一个进程 就是 一个正在执行程序的实例,包括 程序计数器、寄存器 和 变量的当前值
从概念上说,每个进程 拥有它自己的虚拟 CPU。当然,实际上真正的 CPU 在各进程之间 来回切换。这种快速的切换 称作多道程序设计
2、4道程序 被抽象为 4个各自拥有自己控制流程(即每个程序 自己的逻辑程序计数器)的进程,并且每个程序都独立地运行。当然,实际上 只有一个物理程序计数器,所以 在每个程序运行时,它的逻辑程序计数器 被装入实际的程序计数器中。当该程序执行结束(或暂停执行)时,物理程序计数器的值 被保存在 内存中该进程的逻辑程序计数器中
控制流程是指 程序执行的顺序,即 指示程序的各部分(如代码块、函数、分支结构等)按预定的顺序运行
顺序执行,条件分支,循环结构,跳转
逻辑程序计数器(简称PC)是一种 记录当前正在执行的指令地址的寄存器。它在计算机执行过程中 起到跟踪的作用,确保程序按正确的顺序运行
指令地址记录:PC 存储下一条即将执行的指令的地址
跳转控制:当程序遇到控制流程语句(如跳转、分支)时,PC 会根据条件改变其指向,跳转到 指定的指令地址
程序恢复:当程序调用函数 或 发生异常时,PC 可以帮助程序 正确返回到上一次执行的状态

3、由于 CPU 在各进程之间 来回快速切換换,所以 每个进程执行其运算的速度 是不确定的
4、一个进程是 某种类型的一个活动,它有程序、输入、输出 以及 状态。单个处理器 可以被若干进程共享,它使用某种调度算法决定 何时停止一个进程的工作,并转而 为另一个进程提供服务
5、如果 一个程序运行了两遍,则算作两个进程。操作系统 能够使它们 共享代码,因此只有一个副本 放在内存中,但那只是一个技术性的细节,不会改变 有两个进程正在运行的概念
1.2 进程的创建
1、4种主要事件 会导致进程的创建:
1)系统初始化
2)正在运行的程序 执行了 创建进程的系统调用
3)用户请求创建一个新进程
4)一个批处理作业的初始化
2、启动操作系统时,通常 会创建若干个进程。其中 有些是前台进程,也就是 同用户(人类)交互 并且替他们完成工作的那些进程。其他的是 后台进程,这些进程 与特定的用户没有关系,相反,却具有某些专门的功能。例如,设计一个后台进程 来接收发来的电子邮件,这个进程在一天的大部分时间都在睡眠,但是当电子邮件到达时 就突然被唤醒了。也可以设计 另一个后台进程 来接收对该机器中 Web 页面的访问请求,在请求到达时 唤醒该进程 以便服务该请求
停留在后台 处理诸如 电子邮件、Web 页面、新闻、打印之类活动的进程 称为守护进程。在大型系统中 通常有很多守护进程。在UNIX 中,可以用 ps 程序 列出正在运行的进程;在 Windows 中,可使用任务管理器
3、一个正在运行的进程 经常发出 系统调用,以便 创建一个或多个新进程 协助其工作。在所要从事的工作 可以容易地划分成若干相关的 但没有相互作用的进程时,创建新的进程 就特别有效果。例如,如果有大量的数据 要通过网络调取 并进行顺序处理,那么创建一个进程取数据,并把数据放入共享缓冲区中,而让第二个进程 取走数据项并处理之。在多处理机中,让每个进程在不同的 CPU 上运行 会使整个作业运行得更快
4、在交互式系统中,键入一个命令 或者 点(双)击一个图标 就可以启动一个程序。这两个动作中的任何一个 都会开始一个新的进程,并在其中 运行所选择的程序。在基于命令行的 UNIX 系统中运行程序 X,新的进程 会从该进程接管开启它的窗口
当 在命令行运行一个程序时,系统 会为该程序生成一个新的进程,这个新进程 称为“子进程”。子进程 会从当前命令行的“父进程”(即启动该程序的 Shell 进程)继承一些属性,比如 文件描述符、环境变量等
新的子进程(运行程序 X 的进程)会接管父进程的控制终端,即 当前的命令行窗口。因此,这个子进程的标准输入、输出 和 错误 都会直接显示在这个窗口中,这意味着 在终端中 输入的任何内容 都会发送给 这个新的子进程,任何输出 也会显示在该终端中
在 UNIX 和 Windows 系统中,用户可以 同时打开多个窗口,每个窗口 都运行一个进程
5、最后一种创建进程的情形 仅在大型机的批处理系统中应用。用户 在这种系统中(可能是远程地)提交批处理作业。在操作系统 认为有资源可运行 另一个作业时,它创建一个新的进程,并运行 其输入队列中的下一个作业
6、从技术上看,在所有这些情形中,新进程 都是 由于一个已存在的进程 执行了 一个用于创建进程的系统调用 而创建的。这个进程 可以是 一个运行的用户进程、一个由键盘或鼠标启动的系统进程 或者 一个批处理管理进程。这个进程 所做的工作是,执行一个 用来创建新进程的系统调用。这个系统调用 通知操作系统 创建一个新进程,并且 直接或间接地指定 在该进程中运行的程序
在 UNIX 系统中,只有一个系统调用 可以用来 创建新进程:fork。这个系统调用 会创建一个 与调用进程相同的副本。在调用了 fork 后,这两个进程(父进程 和 子进程)拥有相同的 内存映像、同样的环境字符串 和 同样的打开文件
通常,子进程 接着执行 execve 或 一个类似的系统调用,以修改其内存映像 并运行一个新的程序。例如,当一个用户在 shell 中键入命令 sort 时,shell 就创建一个子进程,然后,这个子进程执行 sort。之所以要安排 两步建立进程(fork + execve),是为了在 fork 之后但在 execve 之前允许 该子进程处理其文件描述符,这样可以完成对标准输入文件、标准输出文件 和 标准错误文件的重定向
1)内存映像 指的是 进程所拥有的内存空间布局。内存映像 通常包括 代码段、数据段、堆栈段等。具体来说:
代码段:存储可执行代码。fork() 创建的子进程 会拷贝父进程的代码段,因此 子进程和父进程 执行的是 相同的程序代码
数据段:包括程序的全局变量 和 静态变量的内容
堆栈段:包含 函数调用、局部变量 和 返回地址
fork() 会复制 父进程的整个内存映像,使得子进程初始状态 和父进程一样,但在子进程和父进程中 修改变量会互不影响,因为实际上在内存中占据了不同的位置(在现代系统中,这种复制是 通过写时复制技术实现的,实际内存会延迟复制,直到有写操作发生)
2)环境字符串 是环境变量的集合,它为进程提供了 一组系统和用户定义的参数。例如,PATH、HOME、USER 等环境变量 就是环境字符串的一部分。每个进程 都会在自己的内存中 维护一个环境变量表,fork() 创建的子进程 会复制父进程的环境字符串,因此两者初始的环境变量是一致的
3)打开的文件 指的是 进程打开的文件描述符列表。每个 UNIX 进程会有一张 “文件描述符表”,其中记录了 该进程打开的文件或资源(包括 标准输入、输出、错误等)。fork() 创建的子进程 会继承父进程的打开文件,这意味着 子进程和父进程 会共享相同的文件描述符。由于 文件描述符是共享的,因此子进程和父进程 对文件的操作会互相影响,且文件的读写位置 也是共享的
文件描述符 是操作系统内核 用来表示和管理已打开文件的一个整数编号。在 UNIX 和 类似的操作系统中,文件描述符 是一种抽象概念,既可以代表文件,也可以代表 其他可以进行输入输出的资源,例如 网络套接字、管道等。每个文件描述符 都是唯一的,它让操作系统知道 进程正在访问什么资源
每打开一个文件 或 资源,系统会分配一个整数标识(从 0 开始),称为文件描述符
操作系统内核 通过文件描述符 追踪每个进程所打开的文件及其状态(如读、写权限),并维护文件的状态(如文件位置、缓冲区等)
在 fork() 操作后,父进程和子进程 共享文件描述符,意味着 两者可以共同访问同一个打开的文件或资源
在大多数 UNIX 系统中,以下三个文件描述符默认分配给每个进程,用于标准输入、输出 和 错误:
标准输入(stdin):文件描述符 0,默认用于接收输入(例如键盘输入)
标准输出(stdout):文件描述符 1,默认用于输出显示(例如终端显示)
标准错误(stderr):文件描述符 2,用于显示错误信息
文件描述符在编程中广泛用于 I/O 操作,例如:
打开文件:通过 open() 系统调用 获取文件描述符
读写文件:通过 read() 和 write() 系统调用,指定文件描述符 进行读写操作
关闭文件:通过 close() 释放文件描述符
int fd = open("file.txt", O_RDWR); // 打开文件 file.txt,返回一个文件描述符 fd
write(fd, "Hello", 5); // 使用文件描述符 fd 写入内容
close(fd); // 关闭文件描述符
在子进程中,可以对标准输入(文件描述符 0)、标准输出(文件描述符 1)和标准错误(文件描述符 2)进行重定向,以便控制子进程的输入输出行为
int fd = open("output.txt", O_WRONLY | O_CREAT, 0666); // 打开文件
dup2(fd, STDOUT_FILENO); // 将文件描述符 `fd` 重定向为标准输出
close(fd); // 关闭原文件描述符,避免浪费资源
重定向完成后,子进程的标准输出 就会写入 output.txt,而不是 默认的终端
假设你想在子进程中执行一个程序(如 ls 命令),并将输出写入到 output.txt 而不是终端,代码可以如下:
int fd = open("output.txt", O_WRONLY | O_CREAT, 0666); // 打开文件
if (fd < 0) {
perror("open");
exit(1);
}
pid_t pid = fork();
if (pid == 0) { // 子进程
dup2(fd, STDOUT_FILENO); // 重定向标准输出到文件
close(fd); // 关闭多余文件描述符
execlp("ls", "ls", NULL); // 执行 `ls` 命令
perror("execlp"); // 如果 exec 失败,则打印错误
exit(1);
} else if (pid > 0) {
wait(NULL); // 父进程等待子进程完成
}
通过 fork 和 execve 分两步建立进程的方式,子进程 可以在执行新程序前 对文件描述符进行自定义设置,如重定向输入输出,从而实现 对输入输出的精确控制
网络套接字 和管道 都是用于在 UNIX 和类似的操作系统中 实现进程间通信(IPC)的方法
7、在 UNIX 和 Windows 中,进程创建之后,父进程和子进程 有各自不同的地址空间。如果其中某个进程 在其地址空间中修改了一个字,这个修改对其他进程而言 是不可见的。在 UNIX 中,子进程的初始地址空间 是父进程的一个副本,但是这里涉及 两个不同的地址空间,不可写的内存区 是共享的。某些 UNIX 的实现 使程序正文在两者间共享,因为 它不能被修改。或者,子进程共享父进程的所有内存,但这种情况下 内存通过写时复制共享,这意味着 一旦两者之一想要修改部分内存,则这块内存 首先被明确地复制,以确保 修改发生在私有内存区域
通过 fork() 创建子进程时,子进程的初始地址空间 是父进程的一个副本,但这并不意味着 父进程和子进程的内存 内容完全相同。在操作系统的实现中,子进程的内存空间 与父进程的内存空间 相对独立,但在内存管理上,某些区域可以共享,以提高效率
fork() 后,子进程的内存 只是父进程的内存引用,子进程和父进程 实际上 共享同一块物理内存
只有当 子进程或父进程 尝试修改某一块内存(即发生写操作)时,操作系统 才会为子进程分配新的物理内存空间,将原来的内容 复制到新空间中
在父进程和子进程之间,不可写的内存区(如代码段、只读数据段)通常是完全共享的
代码段:包含程序的指令代码,不会改变,因此可以安全地共享
只读数据段:包含常量数据等内容,不会改变,因此也可以安全共享
1.3 进程的终止
1、通常由下列条件引起:
正常退出(自愿的)
出错退出(自愿的)
严重错误(非自愿)
被其他进程杀死(非自愿)
当编译器 完成了 所给定程序的编译之后,编译器 执行一个系统调用,通知操作系统它的任务已经完成。在 UNIX 中该调用是 exit
进程终止的第二个原因是 由进程引起的错误,通常是 由于程序中的错误所致。例如,执行了一条非法指令、引用不存在的内存,或除数是零等。有些系统中 (如 UNIX),进程可以通知操作系统,它希望 自行处理某些类型的错误,在这类错误中,进程会收到信号(被中断),而不是 在这类错误出现时终止
进程可以通过信号(signal)机制通知操作系统,让它在发生某些类型的错误或事件时,不直接终止进程,而是发送一个信号通知进程,以便进程有机会自行处理这个错误
信号是一种异步通知机制,操作系统 可以用它来通知进程发生了特定的事件(例如错误、用户输入、中断等)。信号是一种“中断”行为,会暂时停止当前进程的正常执行
进程可以 定义一个自定义的信号处理函数来应对信号。信号到达时,操作系统 会中断进程的正常执行,将控制权交给这个处理函数,进程可以在 处理函数中执行自定义的错误处理逻辑
#include <stdio.h>
#include <signal.h>
void handle_sigfpe(int sig) {
printf("Caught divide by zero error!\n");
// 可以进行自定义错误处理,如重设计算等
}
int main() {
signal(SIGFPE, handle_sigfpe); // 注册 SIGFPE 信号处理函数
int a = 5;
int b = 0;
int c = a / b; // 触发 SIGFPE
printf("Result is: %d\n", c);
return 0;
}
常见的信号类型
SIGSEGV:非法内存访问(段错误)
SIGFPE:算术错误(如除零)
SIGINT:键盘中断(如 Ctrl+C)
SIGTERM:终止信号,通常用于请求进程终止
第四种 终止进程的原因是,某个进程执行一个系统调用 通知操作系统杀死某个其他进程。在 UNIX 中,这个系统调用是 kill
1.4 进程的层次结构
1、进程只有一个父进程(但是可以有零个、一个、两个或多个子进程)
在 UNIX 中,进程和它的所有子进程 以及 后裔共同组成一个进程组。当用户从键盘发出一个信号时,该信号 被送给当前与键盘相关的进程组中 的所有成员(它们通常是 在当前窗口创建的所有活动进程)。每个进程 可以分别捕获该信号、忽略该信号 或 采取默认的动作,即被该信号杀死
另一个例子,可以 用来说明进程层次的作用,考虑 UNIX 在启动时 如何初始化自己。一个称为 init 的特殊进程 出现在 启动映像中。当它开始运行时,读入 一个说明终端数量的文件。接着,为每个终端创建一个新的进程。这些进程 等待用户登录。如果有一个用户登录成功,该登录进程 就执行一个 shell 准备接收命令。所接收的这些命令 会启动更多的进程,以此类推。这样,在整个系统中,所有的进程 都属于以 init 为根的一棵树
在 UNIX 中,进程 就不能剥夺 其子继承的“继承权”
1.5 进程的状态
1、一个进程的输出结果 可能作为另一个进程的输入。在 shell 命令 cat chapter1 chapter2 chapter3 | grep tree 中,第一个进程运行cat, 将三个文件连接并输出。第二个进程运行 grep, 它从输入中 选择所有包含单词 “tree” 的那些行。可能发生这种情况:grep 准备就绪 可以运行,但输入 还没有完成。于是必须阻塞 grep, 直到输入到来
当一个进程 在逻辑上 不能继续运行时,它就会被阻塞,典型的例子是 它在等待可以使用的输入。还可能 有这样的情况:一个概念上 能够运行的进程被迫停止,因为操作系统 调度另一个进程占用了 CPU
这两种情况是完全不同的。在第一种情况下,进程挂起是 程序自身固有的原因(在键入用户命令行之前,无法执行命令)。第二种情况 则是由系统技术上的原因引起的(由于没有足够的 CPU, 所以不能使每个进程 都有一台私用的处理器)
2、进程的三种状态
1)运行态 (该时刻进程 实际占用 CPU)
2)就绪态 (可运行,但因为其他进程 正在运行而暂时停止)
3)阻塞态 (除非 某种外部事件发生,否则不能运行)

在操作系统 发现进程 不能继续运行下去时,发生转换1。当一个进程 从管道或设备文件(例如终端)读取数据时,如果 没有有效的输入存在,则进程 会被自动阻塞
转换 2 和 3 是 由进程调度程序引起的,进程调度程序 是操作系统的一部分,进程 甚至感觉不到调度程序的存在
调度程序的主要工作 就是决定应当运行哪个进程、何时运行 及 它应该运行多长时间
当进程等待的 一个外部事件发生时(如一些输入到达),则发生转换 4
一些进程 正在运行 执行用户键入命令所对应的程序。另一些进程是 系统的一部分,它们的任务是 完成下列一些工作:比如,执行文件服务请求、管理磁盘驱动器 和 磁带机的运行细节等。当发生一个磁盘中断时,系统会做出决定,停止运行当前进程,转而运行磁盘进程,该进程在此之前 因等待中断而处于阻塞态。这样就可以不再考虑中断,而只是考虑用户进程、磁盘进程、终端进程等。这些进程在等待时总是处于阻塞状态。在已经读入磁盘或键入字符后,等待它们的进程就被解除阻塞,并成为 可调度运行的进程
执行文件服务请求 的详细工作流程:
1)文件访问请求:当用户或应用程序 需要访问某个文件时,它会向操作系统发出请求。请求内容 包括文件路径、操作类型(如读取、写入、删除等),以及所需的权限信息
2)权限检查:操作系统会 首先检查请求方 是否具备对该文件的操作权限。权限检查 通常会参考 文件系统的访问控制列表(ACL)或 权限位信息,以确保 只有经过授权的用户 或 进程 才能对文件进行指定的操作
3)文件定位和打开:在确认权限后,操作系统会通过文件路径 查找文件的物理存储位置。文件系统通过索引 或路径解析 来定位文件,并在内存中分配一个文件描述符,以便后续的操作
4)执行文件操作:
读取操作:如果是读取请求,操作系统 会从磁盘或缓存中读取文件数据,将数据传递给请求方。读取操作 可能涉及磁盘调度和内存管理,以提高读取效率
写入操作:如果是写入请求,系统会将新数据 写入到文件所在的存储位置,同时 可能会进行数据缓存,以便在磁盘空闲时批量写入,从而减少磁盘操作的频率
修改操作:若是修改文件的请求,系统会对指定的内容或结构 进行更改,更新文件在磁盘中的记录
删除操作:若请求删除文件,操作系统 会从文件系统中移除文件的路径信息,并标记对应的存储空间可用
5)数据缓存管理:为了提升访问效率,操作系统可能会将常用的文件或文件块 加载到 内存缓存中,以加速后续访问。对于文件的写入操作,缓存还会进行写回策略的管理,确保数据安全和一致性
6)关闭文件并释放资源:当文件服务请求完成后,系统会关闭文件,将文件描述符从内存中移除,并释放占用的资源
7)记录和日志:为了维护系统的完整性和安全性,操作系统 会记录文件服务请求的操作日志。这样不仅有助于错误恢复,还可以追踪文件操作历史 以满足审计需求
3、中断 用于处理 中途发生的各种事件或请求。中断 使处理器能够暂停当前的任务,转而去 处理紧急或优先级更高的任务,完成后 再返回原任务继续执行。中断机制 在计算机系统中非常关键,因为 它帮助系统及时响应硬件事件、系统异常 或 软件请求,提高整体运行效率和灵活性
-
中断的分类
1)硬件中断:由外部硬件设备(如键盘、鼠标、硬盘、网络设备等)产生。例如,当用户按下键盘时,会产生一个硬件中断请求,处理器 暂停当前任务去响应该中断,以便系统可以接收键盘输入
2)软件中断:由软件程序显式发出,通常 通过指令调用 来请求操作系统服务。系统调用 就是一种软件中断,用于程序向操作系统请求资源 或 执行特定操作
3)异常:异常是 由于程序执行过程中 出现错误(如除零错误、非法内存访问)或 特殊情况(如页错误)而触发的中断。操作系统 通过异常处理来 纠正或终止发生问题的程序 -
中断的工作流程
当发生中断时,处理器 会暂停当前指令的执行,并按以下流程处理中断:
保存现场:为了让中断处理结束后 能够回到中断前的状态,处理器 会先将当前的寄存器状态(包括程序计数器PC、通用寄存器等)保存到内存中
识别中断源:处理器会确定中断的来源和类型,以便 将中断引导到相应的中断处理程序。操作系统 通常会根据中断号和中断向量表找到对应的中断服务例程
执行中断服务例程(ISR):处理器跳转到中断源对应的中断服务程序,处理相应的任务(由操作系统 或 硬件驱动程序执行的特定程序,用于 响应和处理中断请求。ISR 是处理器从正常执行的程序中 “跳转” 到的代码段,用来完成中断请求的任务)。例如,如果是键盘中断,ISR将从键盘读取数据,并将数据存入内存中的指定区域
恢复现场并返回:中断服务程序 完成任务后,处理器 会从内存中恢复之前保存的寄存器状态,继续执行中断发生前 被暂停的程序
-
中断的优先级
由于 可能有多个中断同时发生,操作系统和处理器 通过优先级 来管理中断的处理顺序。通常,硬件中断 比软件中断优先级更高,异常优先级 则根据错误类型有所不同 -
中断的应用
中断广泛应用于多任务系统和实时系统中,确保系统可以随时响应外部设备和内部任务的请求。例如:
外设驱动:如键盘、鼠标、显示器等设备与系统的交互
系统时钟:通过 时钟中断实现定时任务调度
错误处理:当系统发生异常时,中断可用于捕获错误,防止系统崩溃
4、中断服务例程的设计要求
避免阻塞:ISR 不应执行任何会长时间阻塞的操作(如磁盘或网络访问),这些任务通常由 ISR 交给其他进程或线程处理
线程安全:由于 ISR 在系统的非阻塞上下文中 执行,因此 必须避免并发问题,尤其在多核系统中,ISR 应小心处理共享数据
尽量减少中断嵌套:如果有多个 ISR,系统通常会设定优先级,以控制哪些 ISR 可以在另一个 ISR 执行时打断,以避免长时间的中断嵌套影响性能
中断服务例程的主要任务:识别和响应中断,处理中断事件,清除中断信号,更新状态或数据,恢复并返回
5、操作系统的 最底层是调度程序,在它上面有许多进程。所有 关于中断处理、启动进程和停止进程的具体细节 都隐藏在调度程序中
1.6 进程的实现
1、为了实现进程模型,操作系统 维护着一张表格(一个结构数组),即进程表。每个进程 占用一个进程表项。(即 进程控制块。)该表项 包含了 进程状态的重要信息,包括 程序计数器、堆栈指针、内存分配状况、打开文件的状态、账号和调度信息,以及 其他在进程由运行态 转换到 就绪态或阻塞态时 必须保存的信息,从而保证 该进程随后能再次启动,就像从未被中断一样

与每一 I/O 类关联的是 一个称作中断向量 的位置(靠近内存底部的固定区域)。它包含 中断服务程序的入口地址。假设当一个磁盘 中断发生时,用户进程3正在运行,则中断硬件 将程序计数器、程序状态字、有时还有一个或多个寄存器压入堆栈,计算机随即跳转到 中断向量所指示的地址。这些是硬件完成的所有操作,然后软件,特别是 中断服务例程就接管一切剩余的工作
所有的中断 都从保存寄存器开始,对于当前进程而言,通常是 保存在进程表项中。随后,会从堆栈中 删除由中断硬件机制 存入堆栈的那部分信息(如果中断服务例程 继续使用当前进程的堆栈空间进行操作,可能会 修改或覆盖当前进程的数据。这会导致数据不一致 或破坏当前进程的运行状态)
因此,删除中断硬件机制自动压入的部分,并切换到临时堆栈,可以有效避免对当前进程的堆栈内容造成干扰),并将堆栈指针 指向一个由进程处理程序所使用的临时堆栈(在中断或函数调用时,处理器 会将堆栈指针 重定位到 一个专门的内存区域(即临时堆栈),供中断服务例程(ISR)或 处理程序使用,以避免 与当前进程的堆栈发生冲突)。一些诸如 保存寄存器值和设置堆栈指针等 操作,无法用 C 语言这一类高级语言描述,所以 这些操作 通过一个短小的汇编语言例程来完成,通常该例程 可以供所有的中断使用,因为 无论中断是怎样引起的,有关保存寄存器的工作 则是完全一样的
当该例程结束后,它调用一个C过程 处理某个特定的中断类型剩下的工作。在完成有关工作之后,大概 就会使某些进程就绪,接着 调用调度程序,决定随后该运行哪个进程。随后将控制 转给一段汇编语言代码,为当前的进程 装入寄存器值 以及 内存映射并启动该进程运行
2、中断发生后 操作系统最底层的工作步骤:
1)硬件压入 堆栈程序计数器等
2)硬件从中断向量 装入新的程序计数器
3)汇编语言过程 保存寄存器值
4)汇编语言过程 设置新的堆栈
5)C中断服务例程运行(典型地读和缓冲输入)
6)调度程序 决定下一个将运行的进程
7)C过程返回至 汇编代码
8)汇编语言过程 开始运行新的当前进程
但 关键是每次中断后,被中断的进程 都返回到 与中断发生前完全相同的状态
1.7 多道程序设计模型
1、更好的模型是从概率的角度 来看 CPU 的利用率。假设一个进程等待 I/O 操作的时间 与其停留在内存中的时间比为 p。当内存中有 n 个进程时,则所有 n 个进程都在等待 I/O 的概率是 pn

n 称为 多道程序设计的道数
2、一个等待用户 从终端输入的交互式进程是 处于 I/O 等待状态时,80%甚至更多的I/O等待时间是普遍的。即使是 在服务器中,做大量磁盘 I/O 操作的进程 也会花费同样或更多的等待时间
它假设所有 n 个进程是独立的,即内存中的5个进程中,3个运行,2个等待,是完全可以接受的。但在单CPU中,不能同时运行3个进程,所以当 CPU 忙时,已就绪的进程也必须等待 CPU
假设计算机有8GB内存,操作系统及相关表格占用2GB,每个用户程序也占用2GB。这些内存空间允许3个用户程序同时驻留在内存中。若80%的时间用于I/O等待,则CPU的利用率(忽略操作系统开销)大约是 1−0.83 ,即大约49%。在增加8GB字节的内存后,可从3道程序设计提高到7道程序设计,因此CPU利用率提高到79%。换言之,第二个8GB内存提高了30%的吞吐量
第一次 增加内存是一个划算的投资,而第二个则不是
2、线程
每个进程 有一个地址空间和一个控制线程,经常存在 在同一个地址空间中 准并行运行 多个控制线程的情形
2.1 线程的使用
1、需要在一个进程中再有一类进程。在许多应用中 同时发生着多种活动。其中某些活动 随着时间的推移会被阻塞。通过将这些应用程序分解成 可以准并行运行的多个顺序线程,程序设计模型 会变得更简单
有了 进程模型 的抽象,才不必考虑中断、定时器 和 上下文切换,而只需考察 并行进程。类似地,只是 在有了多线程概念之后,才加入了一种新的元素:并行实体 拥有共享同一个地址空间和所有可用数据的能力
定时器是 硬件计时器,通常用于 跟踪和管理时间。定时器会在设定的时间 间隔产生定时中断,通知操作系统 进行周期性任务(例如任务调度、系统资源监控)。使得操作系统 能够周期性地 检查并切换任务,确保 系统公平地分配处理器资源
操作系统中进程与线程的深入解析
1万+

被折叠的 条评论
为什么被折叠?



