信息安全系统设计基础第十一周学习总结
学习时间:10小时
学习内容:课本第八章
一、重点内容
1.重点理解异常的概念、种类、问题处理
(1)控制流的概念
从处理器加点开始,直到断点为止,PC假设一个值的序列 a0,a1,a2……,a(n-1)(其中,每个ak是某个相应的指令Ik的地址)。每次从ak到a(k+1)的过渡称为控制转移。这样的控制转移序列称为控制流。
(2)异常控制流的概念与意义
概念:相较于最简单的“平滑序列”类型的控制流(即PC中相邻的指令在存储器中也相邻),程序变量表示的内部程序状态中的变化、系统状态的变化等突发情况使得控制系统做出的反映成为异常控制流。
意义:
应用程序如何与操作系统实现交互,应用程序使用系统调用(system call)的ECF形式向操作系统请求服务,实现并发的基本机制。
(3)异常的产生、类型、处理、结果
产生:处理器中的变化(事件)触发从应用程序到异常处理程序的突发的控制转移,也就是异常;
类型:被零除,缺页,存储器访问违例,断点,算术溢出;系统调用,来着外部I/O设备的信号
处理:在任何情况下,当处理器检测到有事件发生时,它就会通过一张叫做异常表的跳转表进行一个间接过程调用,到一个专门处理这类时间的操作系统子程序(异常处理程序);
结果:当 exception handler处理结束之后,会有三种结果
处理程序将控制返回给事件发生的时候正在执行的指令;
处理程序将控制返回给如果没有发生异常将会执行的下一条指令;
处理程序终止被终端的程序
(4)异常的四个种类
中断、陷阱、故障、终止
(5)中断的原因
原因:由I/O设备的信号引起的结果,属于异步。
(6)陷阱的原因及理解
原因:有意的异常,是执行指令的结果,属于同步。
解释:陷阱最重要的用途是在用户和程序与内核之间提供一个像过程一样的接口,即 系统调用。
区别:系统调用和函数调用对于程序使用者或者编写者来说并无差别,然而二者在实现的过程中有很大差别。系统调用运行在内核模式下,可以访问定义在其中的栈;而函数调用只能访问与被调用函数相同的栈。
(7)故障的原因及过程
原因:由潜在的可恢复的错误的情况引起,属于同步的;可能能够被修复然后返回当前指令。
-
过程
- 当前指令导致故障;
- 控制转移给处理程序;
- 故障处理程序运行,如果可以修正这个错误,就将控制引起故障的指令从而重新执行它;否则,返回内核中的abort例程,abort终止引起故障的程序。
(8)终止的原因
原因:由不可恢复的致命错误造成;通常是一些硬件错误。
(9)常见的系统异常
除法错误、故障保护、缺页、系统调用
2.关于系统调用
所有到linux的系统调用的参数都是通过通用寄存器而非栈来传递。按照惯例,寄存器%eax包含系统调用号,寄存器%ebx,%ecx,%edx,%esi和%ebp包含最多六个参数。%esp不适用,因为当进入内核模式的时候,内核会覆盖它。
3.进程的相关内容
(1)进程的定义:
一个执行中的程序的实例。 每次用户通过向外壳输入一个可执行目标文件的名字,并运行一个程序的时候外壳就会创建一个新的进程;然后在这个新进程的上下文中运行这个可执行目标文件,应用程序也能够创建新的进程,然后再这个新进程的上下文中运行自己的代码或者其他应用程序。
(2) 进程提供给应用程序的几个关键抽象:
一个独立的逻辑控制流——提供好像程序独占处理器的假象;
一个私有的地址空间——提供好像程序独占存储系统的假象;
4.进程控制
(1)获取进程ID
a.含义:每个进程都有一个唯一的进程ID(PID);
b.获取:getpid函数获取进程的PID;getppid获取创建调用进程的进程(即它的父进程)的PID。
c.注释:以上两个函数的返回值为pid_t,在linux系统中,它在types.h中被定义为int
(2)创建进程
a.过程:父进程通过调用fork函数来创建一个新的运行子进程
b.特点:
-
- 新创建的子进程拥有和父进程相同的,但是独立的用户级虚拟地址空间拷贝。
- fork函数被创建之后,将返回两次。
- 子进程创建之后,与父进程并发执行;而二者执行的先后顺序是不可控制和预料的;
- 子进程和父进程拥有独立的地址空间。
(3)终止进程
终止类型:运行、停止、终止
a.运行:进程要么在CPU上运行,要么在等待被执行且最终被内核调度;
b.停止:进程的执行被挂起,且不会被调度。
c.终止:进程永远地停止。
终止的三种原因:
- 收到一个信号,其默认为终止程序;
- 从主程序返回;
- 调用exit函数(exit(int stauts),其中status是退出状态)
5.逻辑控制流
(1)进程计数器(PC)中的每一个值都唯一地对应于包含在程序的可执行目标文件中的指令,或者是包含在运行时动态链接的到程序的共享对象中的指令。这个PC值的序列叫做逻辑控制流。
(2)进程是轮流使用处理器的;每个进程执行它的流的一部分然后被挂起,其他进程执行。
(3)对于一个运行在其中一个进程上下文中的程序而言,它看上去就像是唯一地占用了处理器。
6.并发流
引入:计算机系统中有很多逻辑流的不同形式,比如异常处理程序、进程、信号处理程序等;
概念:一个逻辑流的执行在时间上与另一个流重叠,称为并发流;多个流并发执行的现象称为并发;一个进程和其他进程轮流运行,称为多任务;又叫做时间分片。
7.回收子进程
含义:当一个进程由于某种原因终止的时候,内核并不是把它从系统中清除,而是保持在已经终止的状态中,直到被它的父进程回收。这时,内核将子进程的退出状态传递给父进程,然后抛弃已经终止的进程。这之后,该进程才可以说是“不存在”了。
补充:已经终止但是尚未被回收的进程叫做僵死进程。
特例:如果父进程没有回收它的子进程就终止了,那么内核就会安排init函数来回收它们,init函数的返回值是1。
8.进程休眠
函数:
unsigned int sleep(unsigned int secs);//返回还要休眠的秒数
int pause(void);//该函数让调用函数休眠
9.加载并运行程序
-
函数:
#include<unistd.h> int execve(const char *filename,const char *argv[],const char *envp[]);//加载并运行可执行目标文件,如果成功则无返回值,如果不成功则返回-1
-
参数解释:
-
argv变量指向一个以null结尾的指针数组,其中每个指针都指向一个参数串。按照惯例,argv[0]指向可执行目标文件的名字
- envp变量指向一个以null结尾的指针数组,其中每一个指针指向一个环境变量串,格式是“NAME = VALUE”
-
argv变量指向一个以null结尾的指针数组,其中每个指针都指向一个参数串。按照惯例,argv[0]指向可执行目标文件的名字
-
运行 在上面的函数加载了filename之后,它调用了启动代码。启动代码设置栈,将控制传给新程序的主函数
int main(int argc,char **argv,char **envp);//这里argc描述argv数组中非空元素的个数
10.利用fork和execve函数运行程序
-
引入:上述两个函数被大量使用在unix外壳中。
外壳是一个交互型的应用级程序,它代表用户运行其他程序。外壳执行一系列读/求值步骤,然后终止。读是读取来自用户的一个命令行,求值是解析命令行然后运行程序。
11.非本地跳转
引入:C语言提供了一种用户级异常控制流形式,称为非本地跳转;它将一个函数转移到一个当前正在执行的函数,而省略了调用-返回序列这一步
-
函数:setjmp,longjmp
int setjmp(imp_buf env);返回0
int sigsetjmp(sigjmp_buf env,int savesigs);
void longjmp(jmp_buf env,int retval);
void siglongjmp(sigjmp_buf env,int retval); -
解释:以上两个函数配合使用
- setjmp函数在env缓冲区中保存当前调用环境(包括PC,栈指针,通用寄存器),以供后面的longjmp使用,并返回0;
- longjmp函数从env缓冲区中回复调用环境,然后触发一个从最近一次初始化env开始的setjmp函数调用的返回;
- setjmp函数返回,并带有非零的返回值retval
【在看了上面的解释之后,我仍然不理解非本地跳转的过程,于是又阅读了使用举例,疑惑才得到了解释:setjmp函数被调用一次而返回两次,依次是在保存当前环境的时候(返回0),另一次是被每一个相应的longjmp调用(返回错误类型)】
二、课后习题
1.考虑下面的程序
#include "csapp.h"
int main()
{
int x =1;
if(fork()==0)
printf("printf1:x=%d\n",++x);
printf("printf2:x =%d\n",--x);
exit(0);
}
问:子进程的输出是什么?父进程的输出是什么? A:子进程输出是 printf1:x=2
B:父进程输出是 printf2:x=0
2.列出下面的程序所有可能的序列:
int main()
{
if(Fork() == 0)
{
printf("a");
fflush(stdout);
}
else
{
printf("b");
fflush(stdout);
waitpid(-1,NULL,0);
}
printf("c");
fflush(stdout);
exit(0);
}
【子进程会输出“ac”,父进程会输出“bc”;而父子进程执行先后不是确定的,所以输出序列可能是:acbc,abcc,bacc,bcac】
3.编写一个sleep的包装函数,叫做snooze,带有下面的接口
unsigned int snooze(unsigned int secs);//除了它会打印一条语句来描述休眠了多少秒之外,和sleep函数一样
解答:
unsigned int snooze(unsigned int secs)
{
unsigned int rc = sleep(secs);
printf("Slept for %u of %u seconds\n",secs-rc,secs);
return rc;
}
三、参考资料
1.《深入理解计算机系统》第八章
2.刘蔚然博客,地址:http://www.cnblogs.com/lwr-/p/4984238.html(知识点9、10、11参考)
四、心得体会
我感觉第八章的内容很难,很多地方不好理解,需要时间去消化,来慢慢理解。