The Hardware Software Interface Section 8 Processes
课程主页:https://courses.cs.washington.edu/courses/cse351/16sp/
课程资料:
实验部分:https://github.com/vuquangtrong/HW-SW_Interface_Course
实验说明:https://courses.cs.washington.edu/courses/cse351/13sp/lab-0.html
课件:http://academictorrents.com/details/b63a566df824b39740eb9754e4fe4c0140306f4b
课程视频:https://www.bilibili.com/video/BV1Zt411s7Gg?from=search&seid=8781593976070799647
这次回顾第八部分,这部分介绍了进程。
第8节:虚拟内存
异常控制流
控制流
- 处理器只做一件事:
- 从启动到关闭,CPU只需读取并执行(解释)一系列指令,一次一条命令
- 此序列是CPU的控制流
改变控制流
- 到目前为止,有两种用于更改控制流的机制:
- 跳跃和分支
- call和return
对程序状态的变化做出反应
- 对于有用的系统而言不够:难以对系统状态的变化做出反应
- 数据来自磁盘或网络适配器
- 除以零的指令
- 用户在键盘上按Ctrl+C
- 系统计时器到期
- 系统需要“异常控制流”的机制
异常
- 一个异常是响应某些事件(即处理器状态更改)将控制权转移到OS内核。
- 内核是操作系统的内存驻留部分
- 事件示例:除以0,算术溢出,页面错误,$\text{I/O}$请求完成,键入Ctrl+C
- 处理完引起异常的事件的类型,会发生以下三种情况
- 处理程序将控制返回给当前指令$I_{\text {curr} }$,即当事件发生时正在执行的指令。
- 处理程序将控制返同给 $I_{\text {next } },$ 如果没有发生异常将会执行的下一条指令。
- 处理程序终止被中断的程序。
异常表
- 每种类型的事件都有一个唯一的异常号k
- k=异常表的索引(也称为中断向量)
- 每次发生异常k时都调用程序k
异步异常(中断)
- 由处理器外部的事件引起
- 通过设置处理器的中断引脚来指示
- 处理程序返回到“下一条”指令
- 例子:
- 定时器中断
- 每隔几毫秒,一个外部计时器芯片就会触发一个中断
- 内核用来从用户程序取回控制权
- 来自外部设备的$\text{I/O}$中断
- 在键盘上按Ctrl+C
- 来自网络的数据包到达
- 磁盘中的数据到达
- 定时器中断
同步异常
- 由执行指令引起
- 陷阱
- 有意的异常
- 示例:系统调用,断点陷阱,特殊说明
- 将控制权返回“下一条”指令
- 故障
- 无意但可能可以恢复
- 示例:页面错误(可恢复),保护错误(不可恢复),浮点异常
- 重新执行产生故障的“当前”指令或中止
- 终止
- 意外和无法恢复
- 示例:非法指令,奇偶校验错误,机器检查
- 中止当前程序
- 陷阱
Trap示例:打开文件
用户调用:open(filename, options)
函数open执行系统调用指令int
0804d070 <__libc_open>: . . . 804d082: cd 80 int $0x80 804d084: 5b pop %ebx . . .
操作系统必须查找或创建文件,并准备读取或写入文件
返回整数文件描述符
故障示例:Page Fault
int a[1000];
main ()
{
a[500] = 13;
}
用户写入内存位置
用户内存的那部分(页)当前在磁盘上
80483b7: c7 05 10 9d 04 08 0d movl $0xd,0x8049d10
页面处理程序必须将页面加载到物理内存中
返回错误指令:mov再次执行!
第二次尝试成功
故障示例:无效的内存引用
int a[1000];
main ()
{
a[5000] = 13;
}
80483b7: c7 05 60 e3 04 08 0d movl $0xd,0x804e360
- 页面处理程序检测到无效地址
- 向用户进程发送SIGSEGV信号
- 用户进程退出时出现“分段故障”
总结
- 异常
- 需要非标准控制流程的事件
- 由外部(中断)或内部(陷阱和故障)生成
- 异常处理后,可能会出现以下三种情况之一:
- 重新执行当前指令
- 使用下一条指令继续执行
- 中止导致异常的进程
什么是进程
- 我们为什么要学习进程?
- 进程是我们计算机系统中的另一种抽象——进程抽象提供了程序和底层CPU+内存之间的接口。
- 进程与异常控制流有什么关系?
- 异常控制流是操作系统用来使多个进程在同一系统上运行的机制。
- 什么是程序?处理器?进程?
进程
- 定义:进程是正在运行的程序的实例。
- 计算机科学中最深刻的想法之一。
- 与“程序”或“处理器”不同。
- 进程为每个程序提供了两个关键的抽象:
- 逻辑控制流程
- 每个程序似乎都专用于CPU。
- 由称为上下文切换的内核机制提供。
- 专用地址空间
- 每个程序似乎都专用于主存储器。
- 由称为虚拟内存的内核机制提供。
- 逻辑控制流程
进程并发
- 每个进程都是一个逻辑控制流。
- 如果两个进程的时间重叠,则两个进程将同时运行(并发)。
- 否则,它们是顺序的。
- 例子(运行在单核上):
- 并发:$A \& B, A \& C$
- 顺序:$B\& C$
用户视角下的并发
- 并发进程的控制流在时间上实际上是不相交的
- 但是,我们可以将并发进程视为彼此并行运行
用户模式和内核模式
为了使操作系统内核提供一个无懈可击的进程抽象,处理器必须提供一种机制,限制一个应用可以执行的指令以及它可以访问的地址空间范围。处理器通常是用某个控制寄存將中的一个模式位(mode bit)来提供这种功能的,当设置了该模式,进程就运行在内核模式中,该模式下执行指令集中的任何指令;当没有设置模式位时,进程就运行用户模式中,该模式下不允许执行特权指令。
上下文切换
- 进程由共享的内存驻留OS代码块(称为内核)管理
- 重要提示:内核不是一个独立的进程,而是作为某些现有进程的一部分运行的。
- 控制流通过上下文切换从一个进程传递到另一个进程
创建进程
创建新的进程
- fork-exec模型:
- fork()创建当前进程的副本
- execve()将当前进程的代码和地址空间替换为其他程序的代码
- fork()和execve()是系统调用
- 注意:Windows中的进程创建与Linux的fork-exec模型略有不同
- 其他系统需要的进程管理:
- getpid()
- exit()
- wait()/waitpid()
fork: 创建新的进程
pid_t fork(void)
创建一个与调用进程(父进程)相同的新进程(子进程)
返回0到子进程
将子进程的ID(pid)返回给父进程
pid_t pid = fork(); if (pid == 0) { printf("hello from child\n"); } else { printf("hello from parent\n"); }
fork是唯一的(并且常常令人困惑),因为它被调用一次,但是返回两次
理解Fork
Fork例子
- 父进程和子进程都运行相同的代码
- 通过fork()的返回值区分父项与子项
- fork()未定义之后哪个先运行
- 以相同的状态开始,但是每个都有一个私有副本
- 相同的变量,相同的调用堆栈,相同的文件描述符…
void fork1()
{
int x = 1;
pid_t pid = fork();
if (pid == 0) {
printf("Child has x = %d\n", ++x);
} else {
printf("Parent has x = %d\n", --x);
}
printf("Bye from process %d with x = %d\n", getpid(), x);
}
Fork-Exec
Fork-Exec
- fork-exec模型:
- fork()创建当前进程的副本
- execve()将当前进程的代码和地址空间替换为其他程序的代码
// Example arguments: path="/usr/bin/ls”,
// argv[0]="/usr/bin/ls”, argv[1]="-ahl", argv[2]=NULL
void fork_exec(char *path, char *argv[])
{
pid_t pid = fork();
if (pid != 0) {
printf("Parent: created a child %d\n”, pid);
} else {
printf("Child: exec-ing new program now\n");
execv(path, argv);
}
printf("This line printed by parent only!\n");
}
Exec新程序
在Linux shell中运行命令“ls”时发生的情况的高层次图:
execve:加载和运行程序
- ```c
int execve(
)char *filename, char *argv[], char *envp[]
- 加载并在当前进程中运行: - 可执行filename - 带参数列表argv - 和环境变量列表envp - Env变量:“name=value”字符串(例如"PWD=/homes/iws/pjh") - execve不返回(除非出现错误) - 覆盖代码,数据和堆栈 - 保留pid,打开的文件以及其他一些项目 ![](https://github.com/Doraemonzzz/md-photo/blob/master/Hardware%20Software%20Interface/Section8/2020083006.jpg?raw=true) ##### exit:结束进程 - void exit(int status) - 退出进程 - 状态码:0用于正常退出,非零用于异常退出 - atexit()注册退出时要执行的函数 ```c void cleanup(void) { printf("cleaning up\n"); } void fork6() { atexit(cleanup); fork(); exit(0); }
僵死进程
- 理念
- 当进程终止时,它仍然消耗系统资源
- 示例:退出状态,各种OS表
- 被称为“僵死进程”
- 当进程终止时,它仍然消耗系统资源
- 回收
- 由父进程对终止的子进程执行(使用wait或waitpid)
- 父进程被给予退出状态信息
- 内核然后删除“僵死进程”
- 如果父进程不回收怎么办?
- 如果任何父进程在没有回收子进程的情况下终止,那么内核会安排init进程(pid == 1)成为孤儿进程的养父,然后进行回收操作。
- 因此,长期运行的进程中会进行显式回收,否则会消耗系统的内存资源。
- 例如,shells和服务器
wait:与子进程同步
- 父进程通过调用wait为子进程回收
- int wait (int * child_status)
- 暂停当前进程,直到其子进程之一终止
- 返回值是终止的子进程的pid
- 如果child_status != NULL,则它将指向的整数将设置为一个值,该值指示子进程终止的原因和退出状态:
- 使用wait.h中定义的宏进行检查
- WIFEXITED,WEXITSTATUS,WIFSIGNALED,WTERMSIG,WIFSTOPPED,WSTOPSIG,WIFCONTINUED
- 使用wait.h中定义的宏进行检查
wait例子
void fork_wait() {
int child_status;
pid_t child_pid;
if (fork() == 0) {
printf("HC: hello from child\n");
} else {
child_pid = wait(&child_status);
printf("CT: child %d has terminated\n”, child_pid);
}
printf("Bye\n");
exit(0);
}
进程管理总结
- fork为我们提供了同一进程的两个副本(但是fork()向两个进程返回了不同的值)
- execve用一个新的进程替代真正运行的进程
- 两进程程序:
- 第一个fork()
- if (pid == 0) { //child code } else { //parent code }
- 两种不同的程序:
- 第一个fork()
- if (pid == 0) { execve() } else { //parent code }
- 现在运行两个完全不同的程序
- 两进程程序:
- wait/waitpid用于同步父/子执行并获得子进程
总结
- 进程
- 在任何给定时间,系统都有多个活动进程
- 一次只能执行一个,但是每个进程似乎都可以完全控制处理器
- 操作系统会定期在活动进程之间“上下文切换”
- 使用异常控制流实现
- 进程管理
- fork-exec模型