Exceptional Flow Control(异常控制流)

来源:互联网 发布:移动网络营业 编辑:程序博客网 时间:2024/06/04 20:24

  • 异常控制流的形式
  • 异常
  • 进程的上下文切换
  • 信号
    • 信号的发送与接收
    • 信号处理
  • 非本地跳转

转载请注明出处:http://blog.csdn.net/c602273091/article/details/53543145

异常控制流的形式

控制流一般是说处理器中比较平稳的程序执行过程。我们之前知道的改变控制流的方法有跳转和函数返回,但是还有很多情况我们没有考虑到。比如键盘输入、程序崩溃之类的。这些东西呢就是叫做异常控制流。

异常控制流一般有以下几种形式:
这里写图片描述

接下来就一个一个介绍他们。

异常

异常:用来响应低级的控制流的突变,把控制权转给内核以应对某些变化。

这里写图片描述

处理异常的时候需要询问异常表。根据异常表的描述进行操作。

在异常中,又分为:中断、陷阱、故障、终止。

这里写图片描述

中断是外部产生的,比如网络适配器。处理完这种中断之后,返回下一条指令。陷阱是用户请求系统调用的方法。比如用户需要执行execve,read等等。故障就是发生了错误,一般可以修复。比如缺页异常。终止:不可修复的错误。直接abort。

Tips:
1、一些常用的系统调用:
这里写图片描述

进程的上下文切换

进程就是正在执行的程序的一个实例。

在进程中有两个抽象:独立的逻辑控制流,感觉拥有了整个处理器;私有的地址空间,感觉独占了存储器系统。

这里写图片描述
CPU执行进程,感觉是在同时进行,这就是并发(Concurrency)然后每个进程都有自己拥有CPU的错觉。

在进程之间,有时候控制会传递,这就是进程的上下文切换。
这里写图片描述
进行了上下文切换,自然就是改变了原先的控制流。

PS:
1、系统调用错误的处理函数:
这里写图片描述
2、获取进程ID:
这里写图片描述
3、进程终止:exit() 一般返回0;错误的时候返回非0。进程收到SIGTTOU、SIGSTOP、SIGSTP、SIDTTIN的时候,进程停止,被挂起,直到接收到SIGCONT。
4、创建进程:fork() 调用一次,返回两次。
5、为了更好地理解fork的过程,使用进程图可以使思路清晰。
这里写图片描述
把图化成下面这样,只要箭头不是向右,那么就是不符合的。
这里写图片描述
再举个例子:
这里写图片描述
6、回收子进程:一般来说子进程需要被父进程回收,如果父进程没有机会回收(比如被意外终止),那么子进程就变成了zombie。如果长时间没有被回收,那么就会被init回收。父进程回收的时候一般使用waitpid或者wait。
7、waitpid:可以等待专门的进程。pid > 0就是专门的子进程;为-1的时候就是任何一个。
waitpid(pid, &status, 0);
这里写图片描述
8、wait(&status);回收一个子进程。当status不为NULL的时候,那么就可以有:
这里写图片描述
这里写图片描述
9、execve:运行程序。
int execve(char *filename, char *argv[], char *envp[])
执行一次,从不返回。除非找不到文件名才会返回-1。
它创建的进程的栈如下:
这里写图片描述
10、使用sleep,pause,sigsuspend挂起程序。

综合以上,一个进程相关的系统调用就是:
这里写图片描述

信号

当某些特殊的事件发生之后,系统就会发送信号给进程。比如按了ctrl-c,SIGINT信号就会发给正在前台运行的进程来通知关闭这个进程。而接收信号的进程可以选择接收,然后自己被终止或者挂起,直到接收到SIGCONT。但是进程也可以选择阻塞或者屏蔽这些信号。与此同时,进程也可以发送信号。比如父进程就可以使用KILL发送SIGKILL给子进程来关闭子进程。接下来详细说一下信号相关的操作。

信号的发送与接收

信号可以被阻塞,那就是被pending。每个进程只有一个类型为k的待处理信号。如果这个时候再发k信号过来,这个发过来的信号就会被丢弃。信号的发送,都是基于进程组的。进程分成不同的组,使用getpgrp可以获得某个进程的group id。一个进程可以使用setpgid设置group id。set(pid_t pid, pid_t gpid);

一般来说,内核会发送信号的原因有:内核检测到了系统事件,比如除0。比如按了键盘,也可以发送信号;一个进程调用了kill函数,显示发送信号。kill(pid, SIGKILL) 发给进程pidSIGKILL信号。alarm可以用来发信号给自己。每过secs个时间,发送SIGALARM信号给自己。unsigned int alarm(unsigned int secs).

接收信号:内核从处理异常的程序返回时,查看pending signal是否为空;为空的话就将控制权交到原来的进程处;否则就进行相应的信号处理。

在进程捕获信号的时候,有一个信号处理函数,对捕获的信号进行处理。
handler_ t *signal(int signum, hander_t *handler);
signum就是要处理的信号;handler就是信号处理函数。
handler可以设置成SIG_ IGN: 忽略signum信号;SIG_ DFL 恢复signum的默认行为。
请看下面的例子:
这里写图片描述

信号处理

单独处理一个信号是简单的,但是对多个信号处理的时候,信号处理就变得复杂了。待处理信号会被阻塞,待处理信号不会排队,系统调用可以被中断等等。举个例子,我们fork很多个子进程,然后在handler里面只能回收几个,小于fork的数目,有的子进程变成了zombie。那么就需要某些机制处理这个问题。这里就用到了sigpomask。这个函数可以显示地阻塞和释放信号集。
这里写图片描述

这里写图片描述
首先阻塞mask里面的信号,之前的信号集存在pre_ mask,然后处理结束之后,还原原来的信号集。

首先给出一个错误的回收信号的例子:
这里写图片描述
在信号处理函数里面,使用的是if,那么很多SIGCHILD信号发送过来的时候,很可能正在进行信号处理,有一个SIGCHILD信号被阻塞,别的都被丢弃了。

对以上的handler进行修改:
这里写图片描述

上面的修改解决了有些信号不能正常接收被丢弃的问题,但是上面的例子还有一个问题。那就是竞争问题。
这里写图片描述
这里写图片描述
如果fork的子进程执行结束,发送了SIGCHILD给handler,那么就会delete job,但是这个时候还没有add job,那么竞争就在这个时候发生了。

解决这个问题的方法如下:把子进程的SIGCHILD block就可以了。
这里写图片描述

在进行back ground job处理的时候,一般会显示地等待它结束。那么在这里就需要在父进程中加入显示等待。
显示等待可以使用while循环、pause、sleep、sigsuspend。
使用循环如下:(这种方法极大地浪费了CPU cycle,就是传说中的空转)
这里写图片描述

使用pause、sleep:
这里写图片描述

使用sigsuspend。
这里写图片描述

这里写图片描述
这里可以看到有一个pid,在handler里面改变它即可。一般来说,这个改成volatile的全局变量更加安全。

Tip:
这里写图片描述

这里写图片描述

使用简单的信号处理函数。
这里写图片描述

非本地跳转

nonlocal jump:将控制从一个函数转移到另外一个正在执行的函数。

使用setjmp和longjmp实现。longjmp调用一次,从不返回。setjmp调用一次,返回多次。

这里写图片描述

具体看CSAPP那本书,没有深入了解这个。

Pictures are adopted from course cmu 15-213Click here

1 0
原创粉丝点击