CSAPP-异常控制流

进程

进程是一个执行中的程序的实例,它是操作系统分配资源(如CPU时间、内存等)的基本单位。同时操作系统给我们的程序提供一种假象:我们独占所有CPU和内存,而进程是操作系统管理程序的方式。

操作系统为进程维护着一个上下文,包含了程序正确运行所需的状态。

父进程&子进程

父进程在创建子进程后,子进程和父进程拥有各自独立的内存空间。但操作系统使用写时复制(Copy-On-Write,COW)策略来优化内存使用。在子进程创建时,父进程的内存内容并不被立即复制,而是父进程的内存页被映射到子进程的地址空间。这意味着,父子进程初期共享相同的内存内容,只有当子进程修改某个内存页时,操作系统才会为子进程创建该内存页的副本,从而避免不必要的内存复制。

同时,父进程负责在子进程结束后进行回收工作。当子进程调用 exit() 或者收到终止信号时,它的资源(如内存、文件描述符等)会被释放,但子进程的进程表条目仍然保留在进程表中,直到父进程调用 wait()waitpid() 获取子进程的退出状态并将其清理掉。父进程通过这些系统调用来回收子进程的状态信息,并从进程表中删除子进程条目。

僵死进程:如果进程在停止运行后没有被及时回收,这个子进程将会成为僵死进程。

孤儿进程:如果子进程还未被回收,而父进程先退出,子进程会成为孤儿进程。操作系统会将孤儿进程的父进程设置为 PID 为 1 的 init 进程(现在大部分 Linux 系统中为 systemd)。init 会负责回收这些孤儿进程,避免它们变成僵死进程,由它们负责回收工作。

进程&线程

线程是进程中的执行单元。它是程序执行的最小单位。线程拥有自己独立的执行路径,但它们与同一进程中的其他线程共享进程的资源(如地址空间、文件描述符等)。

内核态与用户态

为了提高进程的稳定性,操作系统和处理器提供一种叫位模式的功能。同时将进程划分为用户模式和内核模式。

用户模式:此时的进程运行着用户编写的代码,同时不允许执行特权指令。即不允许直接访问 IO 设备、停止处理器等,这些需要通过系统调用向操作系统请求服务。

内核模式:此时进程运行着内核代码,允许执行任何指令,同时允许进程执行对硬件的直接操作,访问内存、I/O 设备等。

注意:即使以超级用户 (Root) 身份运行程序,进程依旧分为用户模式和内核模式。不过此时程序具有很高的权限,可以执行很多特殊的系统调用。只有编写内核代码并将模块载入内核运行,这些代码才运行在内核模式。

进程从用户模式切换为内核模式,通过中断、故障等异常进行。

  • 系统调用:是程序主动请求内核服务的一种方式,例如 read(), write(), open() 等。这些调用会通过陷入(trap)机制从用户模式切换到内核模式。

  • 中断/异常:比如 I/O 完成、硬件故障等触发的中断也会导致用户模式切换到内核模式。

发生异常后,处理器从用户模式切换为内核模式,由内核执行相关操作并返回源程序,同时返回用户模式。注意,这并不意味着发生了进程切换,这些都是同一个进程在运行。尽管确实会影响性能。

上下文切换

前面提到:操作系统为进程维护着一个上下文,包含了程序正确运行所需的状态。比如:地址空间的页表、当前进程信息的进程表、进程已打开文件的文件表等。

当内核调度新进程时,内核会先抢占当前进程并进行以下操作:

  1. 保存当前进程上下文,以便日后恢复。

  2. 恢复被调度的进程的上下文。

  3. 将控制权转交给新进程。

值得一提的是,当发生异常导致进程从用户模式转向内核模式时,进程极有可能被执行上下文切换以运行其他进程。


CSAPP-异常控制流
https://blog.hydrogenroom.icu/post/6738efd6.html
作者
Hydrogen
发布于
2024年12月9日
许可协议