信息安全系统设计基础第十一周学习总结
第八章 异常控制流
8.1异常
1、异常是异常控制流的一种形式,是控制流中的突变,用来响应处理器状态中的某些变化,由硬件和操作系统实现。
2、事件:状态变化,可能和当前指令的执行有关。
3、异常处理程序完成后会发生的情况:
1)处理程序将控制返回给事件发生时正在执行的当前指令
2)处理程序将控制返回给没有发生异常将会执行的下一条指令
3)处理程序终止被中断的程序
4、异常表与异常号
1)异常表:当处理器检测到有事件发生时,它会通过跳转表,进行一个间接过程调用(异常),到异常处理程序。系统启动时操作系统分配和初始化一张异常表。
2)异常号:系统中可能的某种类型的异常都分配了一个唯一的非负整数的异常号。异常号是到异常表中的索引,异常表的起始地址放在一个叫做异常表基址寄存器的特殊CPU寄存器里。
异常的类别——中断、陷阱、故障和终止
- 中断处理:异步是指硬件中断不是由任何一条指令造成的,而是由外部I/O设备的事件造成的。
- 陷阱和系统调用:系统调用是一些封装好的函数,内部通过指令int n实现。
陷阱最重要的用途是提供系统调用。系统调用运行在内核模式中,并且可以访问内核中的栈。
系统调用的参数是通过通用寄存器而不是栈来传递的,如,%eax存储系统调用号,%ebx,%ecx,%edx,%esi,%edi,%ebp最多存储六个参数,%esp不能用,因为进入内核模式后,会覆盖掉它。 - 故障
一个经典的的故障示例是缺页异常,当指令引用一个虚拟地址,而该虚拟地址相对应的物理页面不在存储器中,因此必须从磁盘中取出时,就会发生故障。 - 终止
终止是不可恢复的致命错误造成的结果,通常是一些硬件错误,比如DRAM或者SRAM位被损坏时发生的奇偶错误。终止处理程序从不将控制返回给应用程序。处理程序将控制返回给一个abort例程,该例程会终止这个应用程序。
8.2 进程
进程的经典定义:一个执行中的程序的实例。
系统中的每个程序都是运行在某个进程的上下文中的。
上下文:由程序正确运行所需的状态组成的。
进程提供给应用程序的关键抽象:
一个独立的逻辑控制流:独占的使用处理器
一个私有的地址空间:独占的使用存储器系统
1.逻辑控制流
(1)含义:
一系列的程序计数器PC的值,分别唯一的对应于包含子啊程序的可执行目标文件中的指令,或者是包含在运行时动态链接到程序的共享对象中的指令,这个PC值的序列就叫做逻辑控制流。
(2)运行:
进程轮流使用处理器,每个进程执行流的一部分,然后被抢占(暂时挂起)。每个进程执行它的流的一部分,然后被抢占,然后轮到其他进程。但是进程可以向每个程序提供一种假象,好像它在独占的使用处理器。
(3)示例:
异常处理程序、进程、信号处理程序、线程、Java进程
2.并发流
(1)含义
一个逻辑流的执行在时间上与另一个流重叠。【与是否在同一处理器无关】
这两个流并发的运行。
(2)几个概念
并发:多个流并发的执行
多任务:一个进程和其他进程轮流运行(也叫时间分片)
时间片:一个进程执行它的控制流的一部分的每一时间段
(3)并行
两个流并发的运行在不同的处理机核或者计算机上。
并行流并行的运行,并行的执行。
3.私有地址空间
进程为程序提供的假象,好像它独占的使用系统地址空间。一般而言,和这个空间中某个地址相关联的那个存储器字节是不能被其他进程读写的。
4.用户模式和内核模式
处理器通常是用某个控制寄存器中的一个模式位来提供这种功能的,该寄存器描述了进程当前享有的特权。
当设置了模式位时,进程就运行在内核模式中(超级用户模式)。
没有设置模式位时,进程就运行在用户模式中。
进程从用户模式变为内核模式的唯一方法是通过中断、故障或者陷入系统调用这样的异常实现的。
Linux提供/proc文件系统,允许用户模式进程访问内和数据结构的内容。
5.上下文切换
上下文:内核重新启动一个被抢占的进程所需的状态
操作系统内核使用上下文切换的较高层形式的异常控制流来实现多任务。
内核为每个进程维持一个上下文。
在进程执行的某些时刻,内核可以决定抢占当前进程,并重新开始一个先前被抢占的进程,成为调度。
由内核中成为调度器的代码处理的。
使用上下文切换的机制来控制转移到新的进程
保存当前进程的上下文
恢复某个先前被抢占的进程被保存的上下文
将控制传递给这个新恢复的进程
8.3、信号
(操作系统和应用程序之间):进程之间传送信号
一种更高层次的软件形式的异常,称为unix信号,它允许进程中断其他进程。
低层的硬件异常是由内核异常处理程序处理的,正常情况下,对用户进程而言是不可见的。信号提供了一种机制,通知用户进程发生了这些异常。
1、信号处理过程
1)发送信号:内核通过更新目的进程中上下文中的某个状态,发送一个信号给目的进程。发送信号有两个原因:a)内核检测到一个系统事件; b)一个进程调用kill函数,心事发送信号
2)接收信号:,目的进程就接收了信号。进程可以忽略这个信号,终止或者通过执行信号处理程序捕获这个信号。
注意:待处理信号,一种类型的信号只能有一种待处理信号,多余的不会排队,而是会舍掉 ; 信号还可以阻塞。
2、发送信号:/bin/kill , kill函数,键盘,alarm函数
进程组:每个进程都只属于一个进程组,进程组是由一个进程组ID来标识的。默认的,一个子进程和它的父进程同属于一个进程组。
在任何时刻,至多只有一个前台作业和0个或多个后台作业。外壳为每个作业创建一个独立的进程组,一个作业对应一个进程组。
用kill函数发送信号:发送SIGKILL信号
用alarm函数发送信号:发送SOGALARM信号
3、接收信号
进程可以通过使用signal函数来修改和信号相关的默认行为。唯一的例外是SIGSTOP和SIGKILL,它们的默认行为不能被修改。
4、信号处理问题
当一个程序捕获多个信号时,容易有一些细问问题:
- 待处理信号不会排队等待
- 待处理信号被阻塞
- 系统调用被中断
从输出中可以看出,尽管发送了3个SIGCHILD信号,但是只有两个信号被接受了。原因是,3~12行程序,每个子进程结束,可以触发一个该信号;一次该函数调用只能处理一个SIGCHILD信号。 3~14行程序,虽然仍然是一个子进程结束出发一个信号,单该函数通过循环,能尽可能多的处理多个SIGCHILD信号;同时,34~36行,防止系统调用被中断,手动启动系统调用。
5、可移植的信号处理:目的是为了统一同一信号在不通系统中的语义。sigaction函数,或者是它的包装函数Signal函数。
6、显示地阻塞和取消阻塞函数
7、同步流以避免并发的错误如何编写读写相同存储位置的并发流程序的问题,困扰着数代计算机科学家。 解决办法如下图所示程序所示,在调用fork之前,阻塞SIGCHLD信号(19~22行),然后在我们调用了addjob之后就取消阻塞这些信号(25行),我们保证了在子进程在被添加到作业列表之后回收该子进程。注意,子进程继承了它们父进程的被阻塞信号,所以我们必须在调用execve之前,小心地接触子进程中阻塞的SIGCHLD信号(31行)(这是因为,子进程继承了父进程的阻塞信号集合,但这是属于两个不同的集合,他们不共享内存)。
8.4 进程控制492
1.创建和终止进程492
进程的三种状态——运行、停止和终止。
进程会因为三种原因终止进程:收到信号,该信号默认终止进程;从主程序返回;调用exit函数。
2.回收子进程495
- 回收:当一个进程终止时,内核并不立即把它从系统中清除。相反,进程被保持在一种已终止的状态中,直到被它的父进程回收。
- 僵死进程:一个终止了但是还未被回收的进程称为僵死进程。
- 回收子进程的两种方法:1,内核的init进程 2,父进程waitpid函数
-
waitpid函数有点复杂,默认地(当options=0时),waitpid挂起调用进程的执行,知道它的等待集合中的一个子进程终止。
2.让进程休眠499
-
sleep函数将一个进程挂起一段指定的时间。
- 如果请求的时间量已经到了,sleep返回0,否则返回还剩下的要休眠的秒数。后一种情况是可能的,如果因为sleep函数被一个信号中断而过早地返回。我们将在8.5节中详细讨论信号
-
pause函数让调用函数休眠,直到该进程收到一个信号
3. 加载并运行程序500
1.execve函数加载并运行可执行目标文件filename,且带参数列表argv和环境变量列表envp。只有当出现错误时,例如找不到filename,execve才会返回到调用程序。所以,与fork一次调用返回两次不同,execve调用一次并从不返回。
参数中每个指针都指向一个参数串。按照惯例,argv[0]是可执行目标文件的名字。环境变量的列表是由一个类似的数据结构表示的。envp变量指向一个以null结尾的指针数组,其中每个指针指向个环境变量串,其中每个串都是形如“NAME=VALUE”的名字一值对。
4. 利用fork和execve运行程序502
像Unix外壳和Web服务器这样的程序大量使用了fork和e×ecve函数。外壳是一个交互型的应用程序,它代表用户运行其他程序。最早的外壳是Sh程序,后面出现了一些变种,比如csh、tcsh、ksh和bash。外壳执行一系列的读/求值(readeaUte)步骤然后终止。如果builtin_command返回0,那么外壳创建一个子进程,并在子进程中执行所请求的程序。如果用户要求在后台运行该程序,那么外壳返回到循环的顶部,等待下一个命令行否则,外壳使用Waitpid函数等待作业终止。当作业终止时,外壳就开始下一轮迭代。注意这个简单的外壳是有缺陷的,因为它并不回收它的后台子进程。修改这个缺陷就要求使用信号,我们将在下一节中讲述信号。
8.5 信号504
一种更高层次的软件形式的异常,称为unix信号,它允许进程中断其他进程。
低层的硬件异常是由内核异常处理程序处理的,正常情况下,对用户进程而言是不可见的。信号提供了一种机制,通知用户进程发生了这些异常。
1. 信号术语505
- 传送一个信号到目的进程是由两个步骤组成的
1.发送信号。内核通过更新目的进程上下文中的某个状态,发送(递送)一个信号给目的进程。
发送信号可以有如下两种原因:
1)内核检测到一个系统事件。
2)一个进程调用了kill函数,显式地要求内核发送一个信号给目的进程,一个进程可以发送信号给它自己。
2.接收信号。当目的进程被内核强迫以某种方式的发送做出反应时,目的进程就接收了信号。进程可以忽略这个信号,终止或者通过执行一个称为信号处理程序的用户层函数不活这个信号。 - 一个只发出而没有被接收的信号叫做待处理信号。在任何时刻,一种类型至多只会有一个待处理信号。
- 一个进程可以有选择性地阻塞接收某种信号。当一种信号被阻塞时,他仍可以被发送,但是产生的待处理信号不会被接收,直到进程取消对这种信号的阻塞。
- 一个待处理信号最多只能被接收一次。
2.发送信号506
1.进程组
进程组。每个进程都只属于一个进程组,进程组是由一个正整数进程组ID来标识的。 一个子进程和它的父进程同属于一个进程组,一个进程组可以通过使用setpgid函数来改变自己或者其他进程的进程组。
2.用/bin/kill程序发送信号
用/bin/kill程序可以向另外的进程发送任意的信号。
3.从键盘发送信号
从键盘发送信号外壳为每个作业创建一个独立的进程组。
4.用kill函数发送信号
进程通过调用kill函数发送信号给其他进程(包括它们自己)。
5.用alarm函数发送信号
进程可以通过调用alarm函数向他自己发送SIGALRM信号。
3.接收信号509
当内核从一个异常处理程序返回,准备将控制传递给进程P时,他会检查进程P的未被阻塞的处理信号的集合。如果这个集合为空,那么内核将控制传递到P的逻辑控制流中的下一条指令;如果集合是非空的,那么内核选择集合中的某个信号K(通常是最小的K0,并且强制P接收信号K。收到这个信号会触发进程的某种行为。一旦进程完成了这个行为,那么控制就传递回P的逻辑控制流中的下一条指令。
每个信号类型都有一个预定的默认行为:
(1)进程终止
(2)进程终止并转储存储器
(3)进程停止直到被SIGCONT型号重启
(4)进程忽略该信号
- signal函数可以通过下列三种方法之一来改变和信号signum相关联的行为:
(1)如果handler是SIG_IGN,那么忽略类型为signum的信号
(2)如果handler是SIG_DFL,那么类型为signum的信号行为恢复为默认行为
(3)否则,handler就是用户定义的函数的地址,这个函数成为信号处理程序,只要进程接收到一个类型为signum的信号,就会调用这个程序,通过把处理程序的地址传递到signal函数从而改变默认行为,这叫做设置信号处理程序。 - 但一个进程不活了一个类型为K的信号时,为信号K设置的处理程序被调用,一个整数参数被设置为K。这个参数允许同一个处理函数捕获不同类型的信号。
- 信号处理程序的执行中断main C函数的执行,类似于底层异常处理程序中断当前应用程序的控制流的方式,因为信号处理程序的逻辑控制流与主函数的逻辑控制流重叠,信号处理程序和主函数并发地运行。
4. 信号处理问题511
当一个程序要捕获多个信号时,一些细微的问题就产生了。
(1)待处理信号被阻塞。Unix信号处理程序通常会阻塞当前处理程序正在处理的类型的待处理信号。
(2)待处理信号不会排队等待。任意类型至多只有一个待处理信号。因此,如果有两个类型为K的信号传送到一个目的进程,而由于目的进程当前正在执行信号K的处理程序,所以信号K时阻塞的,那么第二和信号就简单地被简单的丢弃,他不会排队等待。
(3)系统调用可以被中断。像read、wait和accept这样的系统调用潜在地会阻塞进程一段较长的时间,称为慢速系统调用。在某些系统中,当处理程序捕获到一个信号时,被中断的慢速系统调用在信号处理程序返回时不再继续,而是立即返回给用户一个错误的条件,并将errno设置为EINTR。
参考资料
《深入理解计算机系统》第8章异常控制流