改之前的博客格式,之前的格式无法修改
第八章 异常控制流
异常
异常是控制流中的突变,用来响应处理器状态中的某些变化。
- 异常处理
- 异常号:一些是有处理器的设计者分配(包括被零除、缺页、存储器访问违例、断电及算数溢出)其他由操作系统内核的设计者分配(包括系统调用和来自外部I/O设备的信号)。
- 异常号是到异常表中的索引。
- 异常与过程调用的区别(P482)
-
异常分为四类:中断、陷阱、故障和终止。
- 只有中断是异步发生的,其余三个是同步发生的
- 陷阱最重要的用途是在用户程序和内核之间提供一个像过程一样的接口--系统调用。系统调用运行在内核模式中
各种异常类型术语根据系统不同会有所不同,但对于每个系统,基本概念是相同的。
进程
进程是计算机科学中最深刻最成功的概念之一。系统中的每个程序都是运行在某个进程的上下文中的。(上下文是由程序正确运行所需的状态组成的)
- 一个独立的逻辑控制流
- 一个逻辑流的执行在时间上与另一个流重叠,称为并发流。
- 多个流并发地执行的一般现象称为冰法。
- 多任务也叫作时间分片:一个进程和其他进程轮流运行的概念称为多任务。
- 并行流是并发流的一个真子集。
- 私有地址空间
- 上下文切换
- 上下文切换是较高层形式的异常控制流。
- 内核使用上下文切换来实现多任务。
- 调度:在进程执行的某些时刻,内核可以决定抢占当前进程,并重新开始一个先前被抢占的进程。
- 当内核代表用户执行系统调用时,可能会发生上下文切换。一般,即使系统调用没有阻塞,内核也可以决定执行上下文切换,而非将控制返回给调用进程。
- 中断也可能引发上下文切换。
进程控制
- 获取进程ID
- 创建和终止进程
- 回收子进程
-
等待回收子进程
-
引入:一个进程可以通过调用waitpid函数来等待它的子进程终止或者停止
#include<sys/types.h>
#incldue<sys/wait.h>
pidt waitpid(pidt pid,int *status,int options);//如果成功,返回子进程的PID,如果为WNOHANG,则为0,其他错误则为-1
-
说明:默认地,即当options=0的时候,waitpid挂起调用进程的执行,直到它的等待集合中的一个子进程终止。如果等待集合中的一个进程在刚调用的时候就已经终止了,那么waitpid就立即返回。以上两种情况都会使得waitpid函数返回已经终止的子进程的PID,并且去除该进程。
-
判断等待集合的成员
如果pid>0,那么等待的集合就是一个单独的子进程,它的进程ID等于pid; 如果pid<-1,那么等待集合就是由父进程的所有子进程组成的
-
修改默认行为 可以通过将options设置为常量WNOHANG和WUNTRACED的各种组合,修改默认行为: WNOHANG:如果等待集合中的任何子进程都还没有都还没有终止,那么就立即返回0; WUNTRACED:挂起调用进程的执行,直到等待集合中的一个变成已经终止的或者被停止,然后返回导致返回的子进程的PID; WNOHANG|WUNTRACED:立即返回。
- 检查已回收子进程的退出状态 非空的status参数会被放上status参数的关于返回的子进程的状态信息(wait.h定义了status参数的几个宏) WIFEXITED:如果子进程通过调用exit函数或者一个返回即return政策终止,就返回真; WEXITSTATUS:返回一个正常终止的子进程的退出状态(在WIFEXITED返回为真的时候才定义这个状态) WIFSINGALED:如果子进程是因为一个未被捕获的信号终止的,那么就返回真; WTERMSIG:返回导致子进程终止的信号的编号。只有在WIFSINGALED为真的时候,才定义这个状态。
- 错误条件 如果调用进程没有子进程,那么waitpid函数返回-1,并且设置errno为ECHLD; 如果waitpid函数被一个信号中断,那么它返回-1,并设置errno为EINTR
- 应用举例
#include "csapp.h" #define N 2 int main() { int status,i; pidt pid; for(i =0;i<N;i++) if((pid = fork())==0) exit(100+i); while((pid = waitpid(-1,&status,0))>0) { if(WIFEXITED(status)) printf("child %d terminated normally with exit status = %d\n",pid,WEXITSTATUS(status)); else printf("child %d terminated abnormally\n",pid); } if(errno != ECHILD) unixerror("waitpid error"); exit(0); }
-
(1). 父进程创建N个子进程,然后子进程以一个唯一的退出状态退出;
(2).waitpid函数被阻塞直到某个子进程终止,然后进入while循环测试是否是正常终止的;是正常的话就输出;
(3).当回收了所有的子进程之后,再调用waitpid就返回-1,并且设置errno为ECHILD;如果不是正常终止的,就输出一个错误消息
- 函数使用
- sleep函数将一个进程挂起一段指定的时间
#include <unistd.h> unsigned int sleep(unsigned int secs);
- pause函数让调用函数休眠,直到该进程收到一个信号
#include <unistd.h> int pause(void);
- execve函数在当前进程的上下文中加载并运行一个新程序
#include <unistd.h> int execve(const char *filename,const char *argv[],const char *envp[]);
- getenv、setenv、unsetenv函数(P501)
- 关于fork和execve的区别: fork函数在新的子进程中运行相同的程序,新的子进程是父进程的一个复制品; 而execve函数在当前进程的上下文中加载并运行一个新的程序,会覆盖当前进程的地址空间,但并没有创建一个新进程。
- sleep函数将一个进程挂起一段指定的时间
信号
Unix信号是一种更高层的软件形式的异常
-
信号术语
-
发送信号
(1). 用/bin/kill程序发送信号
unix> /bin/kill -9 15213
(2).从键盘发送信号
(3).用kill函数发送信号
#include "csapp.h" int main(){ pid_t pid; if ((pid = Fork()) == 0){ Pause(); printf("control should never reach here!\n"); exit(0); } Kill(pid,SIGKILL); exit(0); }
(4).用alarm函数发送信号
#include "csapp.h" void handler(int sig) { static int beeps = 0; printf("BEEP\n"); if (++beeps<5) Alarm(1); else{ printf("BOOM!\n"); exit(0); } } int main() { Signal(SIGALRM,handler); Alarm(1); while(1){ ; } exit(0); }
-
接收信号
(1)signal函数可以改变和信号signum相关联的行为
- 处理信号
-
可移植的信号处理问题
(1)sigaction函数
-
- 非本地跳转 将控制直接从一个函数转移到另一个当前正在执行的函数
- 通过setjmp和longjmp提供
- 操作进程的工具
- STRACE:打印一个正在运行的程序和它的子进程调用的每个系统调用的轨迹
- PS:列出当前系统中的进程(包括僵死进程)
- TOP:打印出关于当前进程资源使用的信息
- PMAP:显示进程的存储器映射
- 总结
本章内容较多,且读起来叫枯燥,理解起来较为费力,代码部分更加明显。