2017-2018-1 20155323 《信息安全系统设计基础》第十四周学习总结
找出全书你认为学得最差的一章,深入重新学习一下,要求:
总结新的收获
给你的结对学习搭档讲解或请教,并获取反馈
参考上面的学习总结模板,把学习过程通过博客(随笔)发表,博客标题“学号 《信息安全系统设计基础》第十四周
学习总结”,博客(随笔)要通过作业提交。
异常控制流
我认为第八章异常控制流是我学得最差的地方,本周本博客重新深入学习一下。
- 控制流:控制转移序列。
- 控制转移:从一条指令到下一条指令。
- 异常控制流:现代操作系统通过使控制流发生突变来对系统状态做出反应,这些突变称为异常控制流。
- 作为程序员,理解ECF很重要,这有很多原因:
理解ECF将帮助你理解重要的系统概念。ECF是操作系统用来实现I/O、进程和虚拟存储器的基本机制,在能够真正理解这些重要概念之前,你必须须理解ECF。
理解ECF将帮助你理解应用程序是如何与操作系统交互的。应用程序通过一个叫做陷阱或者系统调用的ECF形式,向操作系统请求服务。
理解ECF将帮助你编写有趣的新应用程序。操作系统为应用程序提供了强大的即机制用来创建新进程、等待进程终止通知其他进程系统中的异常事件,以及检测和响应这些事件。如果你理解这些ECF机制,那么你就能用它们来编写诸如Unⅸ外壳和Web服务器之类的有趣程序了。
理解ECF将帮助你理解并发。ECF是计算机系统中实现并发的基本机制。
理解ECF是理解并发的第一步。
理解ECF将帮助你理解软件异常如何工作。理解这些低级函数将帮助你理解高级软件异常如何得以实现。
8.1 异常
下图是异常的剖析:
8.1.1 异常处理
- 异常表:当处理器检测到有事件发生时,它会通过跳转表,进行一个间接过程调用(异常),到异常处理程序。
- 异常号:系统中可能的某种类型的异常都分配了一个唯一的非负整数的异常号。异常号是到异常表中的索引。
- 异常类似于过程调用,但有一些重要的不同之处:
- 1.过程调用时,在跳转到处理程序之前,处理器将返回地址压入栈中,然而,根据异常的类型,返回地址要么是当前指令(当事件发生时正在执行的指令)要么是下一条指令(如果事件不发生,将会在当前指令后执行的指令)。
- 2.处理器也把一些额外的处理器状态压到栈里,在处理程序返回时,重新开始被中断的程序会需要这些状态。比如,一个A32系统将包含当前条件码和其他内容的EHAGS寄存器压入栈中。
- 3.如果控制从一个用户程序转移到内核,那么所有这些项目都被压到内核栈中,而不是压到用户栈中。
- 4.异常处理程序运行在内核模式下(见824节)这意味着它们对所有的系统资源都有完全的访问权限
一旦硬件触发了异常,异常处理程序则由软件完成。
8.1.2 异常的类别
- 异常的类别——中断、陷阱、故障和终止
- 中断处理:异步是指硬件中断不是由任何一条指令造成的,而是由外部I/O设备的事件造成的。
- 陷阱和系统调用:系统调用是一些封装好的函数,内部通过指令int n实现。
- 陷阱最重要的用途是提供系统调用。系统调用运行在内核模式中,并且可以访问内核中的栈。
- 系统调用的参数是通过通用寄存器而不是栈来传递的,如,%eax存储系统调用号,%ebx,%ecx,%edx,%esi,%edi,%ebp最多存储六个参数,%esp不能用,因为进入内核模式后,会覆盖掉它。
- 一个经典的的故障示例是缺页异常,当指令引用一个虚拟地址,而该虚拟地址相对应的物理页面不在存储器中,因此必须从磁盘中取出时,就会发生故障。
- 终止是不可恢复的致命错误造成的结果,通常是一些硬件错误,比如DRAM或者SRAM位被损坏时发生的奇偶错误。终止处理程序从不将控制返回给应用程序。处理程序将控制返回给一个abort例程,该例程会终止这个应用程序。
8.1.3 linux/x86-64系统中的异常
8.2 进程
- 进程(操作系统层):逻辑控制流,私有地址空间,多任务,并发,并行,上下文,上下文切换,调度。
- 进程就是一个执行中的程序实例。系统中的每个程序都是运行在某个进程的上下文中的。
- 进程提供给应用程序的关键抽象:
一个独立的逻辑控制流
一个私有的地址空间。
8.2.1 逻辑控制流
- 程序计数器(PC)值的序列叫做逻辑控制流,简称逻辑流。如下图所示,处理器的一个物理控制流分成了三个逻辑流,每个进程一个。
8.2.2 并发流
- 并发流:并发流一个逻辑流的执行在时间上与另一个流重叠,叫做并行流
- 并发:多个流并发执行的一般现象称为并发。
- 多任务:多个进程并发叫做多任务。
- 并行:并发流在不同的cpu或计算机上,叫做并行。
8.2.3 私有地址空间
- 一个进程为每个程序提供它自己的私有地址空间。
8.2.4 用户模式和内核模式
- 运行应用程序代码的进程初始时是在用户模式中的。进程从用户模式变为内核模式的唯一方法是通过异常。
- linux提供了/proc文件系统,它允许用户模式进程访问内核数据结构的内容。
8.2.5 上下文切换
- 上下文切换:操作系统内核使用叫上下文切换的异常控制流来实现多任务。
1.保存当前进程的上下文
2.恢复某个先前被抢占的进程被保存的上下文
3.将控制传递给这个新恢复的进程
- 调度:内核中的调度器实现调度。
- 当内核代表用户执行上下文切换时,可能会发生上下文切换。如果系统调用发生阻塞,那么内核可以让当前进程休眠,切换到另一个进程,如read系统调用,或者sleep会显示地请求让调用进程休眠。一般,即使系统调用没有阻塞,内核亦可以决定上下文切换,而不是将控制返回给调用进程。
- 中断也可能引起上下文切换。如,定时器中断。
8.4.1 获取进程ID
- 每个进程都有一个唯一的正数的进程ID。
- getpid函数返回调用进程的PID,
getppid
函数返回它的父进程的PID。上面两个函数返回一个同类型为pid_t
的整数值,在linux系统中,它在types.h
中被定义为int
。
8.4.2 创建和终止进程
- 进程总处于三种状态
1.运行:进程要么在CPU上执行,要么在等待被执行且最终会被内核调度。
2.停止:程序的执行被挂起,,且不会被调度。
3.终止:进程用永远停止了。
- 终止原因:
1.收到一个信号,默认行为是终止进程
2.从主进程返回
3.调用exit函数
- 父进程通过调用fork函数创建一个新的运行的子进程。
- 子进程和父进程的异同:
不同:有不同的PID
相同:用户级虚拟地址空间,包括:文本、数据和bss段、堆以及用户栈。任何打开文件描述符,子进程可以读写父进程中打开的任何文件。
- fork函数: 因为父进程的PID总是非零的,返回值就提供一个明确的方法来分辨程序是在父进程还是在子进程中执行。
- fork函数的特点:
调用一次,返回两次
并发执行
相同的但是独立的地址空间
共享文件
8.4.3 回收子进程
- 当父进程回收已终止的子进程时,内核将子进程的退出状态传递给父进程,然后抛弃已终止的进程。
- 一个终止了但还未被回收的进程称为僵死进程。
- 一个进程可以通过调用waitpid函数来等待它的子进程终止或者停止。
include <sys/types.h>
include <sys/wait.h>
pid_t waitpid(pid_t pid,int *status,int options);//返回:若成功,返回子进程的PID;若WNOHANG,返回0;若其他错误,返回-1。
- 默认当option=0时,waitpid挂起调用进程的执行,直到它的等待集合中的一个子进程终止。
- 判定等待集合的成员由参数pid确定:
pid>0:等待集合是一个单独的子进程,进程ID等于pid。
pid=-1:等待结合就是由父进程所有的子进程组成的。
- 修改默认行为通过options设置:
WNOHANG:默认行为是挂起调用进程。
WUNTRACED:默认行为是只返回已终止的子进程。
WNOHANG|WUNTRACED:立即返回,如果等待集合中没有任何子进程被停止或者已终止,那么返回值为0,或者返回值等于那个被停止或者已经终止的子进程的PID。
- 检查已回收子进程的退出状态:
wait.h头文件定义了解释status参数的几个宏:
WIFEXITED:如果子进程通过调用exit或者一个返回正常终止,就返回真;
WEXITSTATUS:返回一个正常终止的子进程的退出状态。只有在WIFEXITED返回真时,才会定义这个状态。
- 错误条件
若调用进程没有子进程,那么waitpid返回-1,并且设置errno为ECHILD;
若waitpid函数被一个信号中断,那么返回-1,并设置errno为EINTR
wait函数
include <sys/types.h>
include <sys/wait.h>
pid_t wait(int *status);//返回:若成功,返回子进程的PID;若错误,返回-1。
- 调用wait(&status)等价于调用waitpid(-1.&status,0)
8.4.4 让进程休眠
- sleep函数:将进程挂起一段指定的时间
include
unsigned int sleep(unsigned int secs//返回:还要休眠的秒数
如果请求的时间量已经到了,返回0,否则返回还剩下的要休眠的秒数。
- pause函数:让调用函数休眠,直到该进程收到一个信号。
include
int pause(void);//返回:总是-1
8.4.5 加载并运行程序
- execve函数:在当前进程的上下文中加载并运行一个新程序。
include
int execve(const char *filename,const char *argv[],const char *envp[]);//返回:若成功,则不返回,若错误,返回-1
- filename:可执行目标文件
- argv:带参数列表
- envp:环境变量列表
- 特点:execve调用一次从不返回
- getenv函数:在环境数组中搜素字符串“name =VALUE”,若找到了,就返回一个指向value的指针,否则它就返回NULL。
- execve函数在当前进程的上下文中加载并运行一个新的进程。它会覆盖当前进程的地址空间,并没有创建一个新的进程,新的进程仍然有相同的PID,并且继承了调用execve函数时已打开的所有文件描述符。
8.5 信号
- 一种更高层次的软件形式的异常,称为unix信号,它允许进程中断其他进程。
- 低层的硬件异常是由内核异常处理程序处理的,正常情况下,对用户进程而言是不可见的。信号提供了一种机制,通知用户进程发生了这些异常。
8.5.1 信号术语
- 传送一个信号到目的进程是由两个步骤组成的
1.发送信号。内核通过更新目的进程上下文中的某个状态,发送(递送)一个信号给目的进程。
发送信号可以有如下两种原因:
1)内核检测到一个系统事件。
2)一个进程调用了kill函数,显式地要求内核发送一个信号给目的进程,一个进程可以发送信号给它自己。
2.接收信号。当目的进程被内核强迫以某种方式的发送做出反应时,目的进程就接收了信号。进程可以忽略这个信号,终止或者通过执行一个称为信号处理程序的用户层函数不活这个信号。
- 一个只发出而没有被接收的信号叫做待处理信号。在任何时刻,一种类型至多只会有一个待处理信号。
- 一个进程可以有选择性地阻塞接收某种信号。当一种信号被阻塞时,他仍可以被发送,但是产生的待处理信号不会被接收,直到进程取消对这种信号的阻塞。
- 一个待处理信号最多只能被接收一次。
8.5.2 发送信号
- 进程组:
每个进程都只属于一个进程组,进程组是由一个正整数进程组ID来标识的。 一个子进程和它的父进程同属于一个进程组,一个进程组可以通过使用setpgid函数来改变自己或者其他进程的进程组。
- 用/bin/kill程序发送信号:
用/bin/kill程序可以向另外的进程发送任意的信号。
- 从键盘发送信号
从键盘发送信号外壳为每个作业创建一个独立的进程组。
- 用kill函数发送信号
进程通过调用kill函数发送信号给其他进程(包括它们自己)。
- 用alarm函数发送信号
进程可以通过调用alarm函数向他自己发送SIGALRM信号。
8.5.3 接收信号
- 当内核从一个异常处理程序返回,准备将控制传递给进程P时,他会检查进程P的未被阻塞的处理信号的集合。如果这个集合为空,那么内核将控制传递到P的逻辑控制流中的下一条指令;如果集合是非空的,那么内核选择集合中的某个信号K(通常是最小的K0,并且强制P接收信号K。收到这个信号会触发进程的某种行为。一旦进程完成了这个行为,那么控制就传递回P的逻辑控制流中的下一条指令。
- 每个信号类型都有一个预定的默认行为:
进程终止
进程终止并转储存储器
进程停止直到被SIGCONT型号重启
进程忽略该信号
- signal函数可以通过下列三种方法之一来改变和信号signum相关联的行为:
如果handler是SIG_IGN,那么忽略类型为signum的信号
如果handler是SIG_DFL,那么类型为signum的信号行为恢复为默认行为
否则,handler就是用户定义的函数的地址,这个函数成为信号处理程序,只要进程接收到一个类型为signum的信号,就会调用这个程序,通过把处理程序的地址传递到signal函数从而改变默认行为,这叫做设置信号处理程序。
但一个进程不活了一个类型为K的信号时,为信号K设置的处理程序被调用,一个整数参数被设置为K。这个参数允许同一个处理函数捕获不同类型的信号。
- 信号处理程序的执行中断main C函数的执行,类似于底层异常处理程序中断当前应用程序的控制流的方式,因为信号处理程序的逻辑控制流与主函数的逻辑控制流重叠,信号处理程序和主函数并发地运行。
8.5.4 信号处理问题
- 捕获多个信号时的问题:
待处理信号被阻塞
待处理信号不会排队等待
系统调用可以被中断
8.5.5 可移植的信号处理
- Signal包装函数设置的信号处理程序的信号处理语义:
只有这个处理程序当前正在处理的那种类型的信号被阻塞
和所有信号实现一样,信号不会排队等候
只要有可能,被中断的系统调用会自动重启。
一旦设置了信号处理程序,它就会一直保持,知道signal带着handler参数为SIG_IGN或者SIG_DFL被调用。
8.5.6 显式地阻塞和取消阻塞
- 应用程序可以使用sigprocmask函数显式地阻塞和取消阻塞选择的信号
- sigprocmask函数改变当前已阻塞信号的集合
8.6非本地跳转
- 通过setjmp和longjmp函数来提供
- setjmp函数只被调用一次,但返回多次:一次是当第一次调用setjmp,而调用环境保存在缓冲区env中时,一次是为每个相应的longjmp调用
- longjmp只调用一次,但从不返回
- 非本地跳转的一个重要应用就是允许从一个深层嵌套的函数调用中立即返回,通常是由检测到某个错误情况引起的
- 非本地跳转的另一个重要应用是使一个信号处理程序分支到一个特殊的代码位置,而不是返回到达中断了的指令位置
8.7 操作进程的工具
- Linux系统提供了大量的监控和操作进程的有用工具:
STRACE:打印一个正在运行的程序和它的子进程调用的每个系统调用的轨迹。对于好奇的的工具。用-StatiC编译你的程序,能传到一个更干净的、不带学生而言,这是一个令人着迷有大量与共享库相关的输出的轨迹。
PS:列出当前系统中的进程(包括僵死进程)
TOP:打印出关于当前进程资源使用的信息。
PMAP:显示进程的存储器映射。proc:一个虚拟文件系统,以ASCII文本格式输出大量内核数数据结构的内容,用户程序可 cat 2 / proc / load avg” , 观察在Linux系统上的平均负载。
教材学习中的问题和解决过程
问题1:用户空间和内核空间的区别是什么
解答:两者最大的区别就是其权限。内核模式可以访问系统中的任何资源,执行任何特权指令。而用户模式是不允许这些特权操作的,当在用户模式下调用了一些特权指令,或使用非法区域的内存等操作时,就会导致致命的保护故障。
本周结对学习情况
结对学习内容
第八章