Exceptional Control Flow(异常控制流)
处理器检测到异常时,通过异常表调用异常处理程序进行处理。
可以通过
改变控制流。但是当系统状态发生变化时,需要ECF进行处理。
操作系统层:进程切换
进程:
getpgrp() //返回当前进程所在进程组ID
setpgid(pid_t pid,pid_t pgid) //设置一个进程的进程组CP528
pid_t Fork(void)//包装函数
{
pid_t pid;
//Unix的系统级函数遇到错误时会返回-1
if ( (pid = fork()) < 0 )
unix_error("Fork error");
return pid;
}
void unix_error(char *msg) //错误处理函数
{
fprintf(stderr, "%s: %s\n", msg, strerror(errno));
exit(0);
}
2.获取进程信息:
3.创建进程:
父进程通过fork函数(返回该进程的子进程PID,返回2次)调用子进程(父进程的副本)
在父进程中;在新创建的子进程中,fork函数返回0
子进程共享父进程打开的文件
父子进程是并发运行的独立进程(有独立的地址空间)
execve函数在当前进程中加载并运行1个新程序(程序运行在进程的上下文中)。注意新程序的PID不变,覆盖当前的地址空间,继承调用函数时已打开的文件。
4.子进程回收
当进程终止后,内核会中断常规执行并通过信号通知。只有被其父进程回收时,系统才会清除该进程。
如果父进程在回收子进程之前被终止,内核通过init进程回收
应用层:
发送信号:内核更新目的进程上下文的某个状态
/* /bin/kill程序发送信号 */
linux> /bin/kill -9 24818//发送信号9给进程24818
linux> /bin/kill -9 24818//发送信号9给进程组24818的每个进程
/* kill函数发送信号 */
int kill(pid_t pid,int sig);//CP530
键盘发送信号
Ctrl+C:内核终止前台作业
Ctrl+Z:内核挂起前台作业
接收信号:操作系统中断了进程正常的控制流程。
每个信号类型都有默认行为:
进程可以通过signal函数修改默认行为。
//函数指针
typedef void(*sighandler_t)(int);
//设置信号处理函数
sighandler_t *signal(int signum, handler_t *handler);
void sigint_handler(int sig) // SIGINT 处理器
{
printf("不能通过ctrl+c关闭\n");
exit(0);
}
int main()
{
// 接收到由于Ctrl+C发出的信号,调用sigint_handler作为信号处理函数
if (signal(SIGINT, sigint_handler) == SIG_ERR)
unix_error("signal error");
// 等待接收信号
pause();
return 0;
}
阻塞信号:
信号处理器也可以被其他的信号处理器中断。
等待状态:信号已被发送但是未被接收。同类型的信号至多只会有一个待处理信号。
信号处理器和主程序并行且共享相同的全局数据结构,尤其要注意因为并行访问可能导致的数据损坏的问题。基本的指南:
规则 6
用 volatile sig_atomic_t 来声明全局标识符(flag)
这样可以防止出现访问异常
非本地跳转:从一个函数跳转到另一个函数中
本地跳转:在一个程序中通过 goto 语句进行流程跳转
//保存当前程序的堆栈上下文环境
/*仅在调用 setjmp 的函数内有效,如果调用 setjmp 的函数返回则失效。*/
int setjmp(jmp_buf env);
/*恢复由 setjmp 保存的程序堆栈上下文,即程序从调用 setjmp 处重新开始执行*/
int longjmp(jmp_buf env,int retval);
setjmp 函数被调用1次,返回2次。
1. 第1次调用setjmp 函数返回0;
2. 调用longjmp函数返回retval
允许从深层嵌套中避开调用栈直接返回。
(C++的try语句属于该函数更高级的版本,catch语句类似于setjmp 函数,throw语句类似于longjmp函数)