2017-2018-1 20155327 《信息安全系统设计基础》第六周学习总结
教材学习内容总结
8.1异常
异常的剖析,如下图所示:
8.1.1 异常处理
1.异常表:当处理器检测到有事件发生时,它会通过跳转表,进行一个间接过程调用(异常),到异常处理程序。
2.异常号:系统中可能的某种类型的异常都分配了一个唯一的非负整数的异常号。异常号是到异常表中的索引。
3.异常类似于过程调用,但有一些重要的不同之处:
(1)过程调用时,在跳转到处理程序之前,处理器将返回地址压入栈中,然而,根据异常的类型,返回地址要么是当前指令(当事件发生时正在执行的指令)要么是下一条指令(如果事件不发生,将会在当前指令后执行的指令)。
(2)处理器也把一些额外的处理器状态压到栈里,在处理程序返回时,重新开始被中断的程序会需要这些状态。比如,一个A32系统将包含当前条件码和其他内容的EHAGS寄存器压入栈中。
(3)如果控制从一个用户程序转移到内核,那么所有这些项目都被压到内核栈中,而不是压到用户栈中。
(4)异常处理程序运行在内核模式下(见824节)这意味着它们对所有的系统资源都有完全的访问权限
一旦硬件触发了异常,异常处理程序则由软件完成。
8.1.2 异常的类别
异常的类别——中断、陷阱、故障和终止
1.中断处理:异步是指硬件中断不是由任何一条指令造成的,而是由外部I/O设备的事件造成的。
2.陷阱和系统调用:系统调用是一些封装好的函数,内部通过指令int n实现。重要的用途是提供系统调用。系统调用运行在内核模式中,并且可以访问内核中的栈。系统调用的参数是通过通用寄存器而不是栈来传递的,如,%eax存储系统调用号,%ebx,%ecx,%edx,%esi,%edi,%ebp最多存储六个参数,%esp不能用,因为进入内核模式后,会覆盖掉它。
3.故障:一个经典的的故障示例是缺页异常,当指令引用一个虚拟地址,而该虚拟地址相对应的物理页面不在存储器中,因此必须从磁盘中取出时,就会发生故障。
4.终止:终止是不可恢复的致命错误造成的结果,通常是一些硬件错误,比如DRAM或者SRAM位被损坏时发生的奇偶错误。终止处理程序从不将控制返回给应用程序。处理程序将控制返回给一个abort例程,该例程会终止这个应用程序。
8.1.3 linux/ia32系统中的异常
1.Linux/IA32故障和终止
除法错误;一般保护故障;缺页;机器健康
2.Linux/IA32系统调用:每个系统调用都有一个唯一的整数号,对应于一个到内核中跳转表的偏移量。
8.2 进程
1.进程(操作系统层):逻辑控制流,私有地址空间,多任务,并发,并行,上下文,上下文切换,调度。
2.进程就是一个执行中的程序实例。系统中的每个程序都是运行在某个进程的上下文中的。
3.进程提供给应用程序的关键抽象:一个独立的逻辑控制流 ;一个私有的地址空间。
8.2.1 逻辑控制流
程序计数器(PC)值的序列叫做逻辑控制流,简称逻辑流。如下图所示,处理器的一个物理控制流分成了三个逻辑流,每个进程一个。
8.2.2 并发流
1.并发流:并发流一个逻辑流的执行在时间上与另一个流重叠,叫做并行流
2.并发:多个流并发执行的一般现象称为并发。
3.多任务:多个进程并发叫做多任务。
4.并行:并发流在不同的cpu或计算机上,叫做并行。
8.2.3 私有地址空间
一个进程为每个程序提供它自己的私有地址空间。
8.2.4 用户模式和内核模式
1.运行应用程序代码的进程初始时是在用户模式中的。进程从用户模式变为内核模式的唯一方法是通过异常。
2.linux提供了/proc文件系统,它允许用户模式进程访问内核数据结构的内容。
8.2.5 上下文切换
1.上下文切换:操作系统内核使用叫上下文切换的异常控制流来实现多任务。
2.上下文切换:保存当前进程的上下文;恢复某个先前被抢占的进程被保存的上下文;将控制传递给这个新恢复的进程
3.调度:内核中的调度器实现调度。
4.当内核代表用户执行上下文切换时,可能会发生上下文切换。如果系统调用发生阻塞,那么内核可以让当前进程休眠,切换到另一个进程,如read系统调用,或者sleep会显示地请求让调用进程休眠。一般,即使系统调用没有阻塞,内核亦可以决定上下文切换,而不是将控制返回给调用进程。
8.3 系统调用错误处理
1.当Unix系统级函数遇到错误时,它们典型地会返回―1,并设置全局整数变量errno来表示什么出错了。程序员应该总是检查错误,但是不幸的是,许多人都忽略了错误检查,因为它使代码变得臃肿,而且难以读懂。
2.通过使用错误处理包装函数,我们可以更进一步地简化我们的代码。对于一个给定的基本函数foo,我们定义一个具有相同参数的包装函数Foo,但是第一个字母大写了。包装函数调用基本函数,检查错误,如果有任何问题就终止。比如,下面是fork函数的错误处理包装函数:
3.我们将在本书剩余的部分中都使用错误处理包装函数数。它们能够保持代码示例简洁,而又不会给你错误的假象,认为允许忽略错误检查。注意,当在本书中谈到系统级函数时,我们总是用它们的小写字母的基本名字来引用它们,而不是用它们大写的包装函数名来引用!关于Unix错误处理以及本书中使用的错误处理包装函数的讨论,请参见附录A。包装函数定义在一个叫做csapp.c的文件中,它们的原型定义在一个叫做。csapp.h的头文件中。
8.4 进程控制
8.4.1 获取进程
8.4.2 创建和终止进程
进程的三种状态——运行、停止和终止。
进程会因为三种原因终止进程:收到信号,该信号默认终止进程;从主程序返回;调用exit函数。
8.4.3 回收子进程
1.回收:当一个进程终止时,内核并不立即把它从系统中清除。相反,进程被保持在一种已终止的状态中,直到被它的父进程回收。
2.僵死进程:一个终止了但是还未被回收的进程称为僵死进程。
3.回收子进程的两种方法:内核的init进程;父进程waitpid函数
4.waitpid函数有点复杂,默认地(当options=0时),waitpid挂起调用进程的执行,知道它的等待集合中的一个子进程终止。
8.4.4 让进程休眠
1.sleep函数将一个进程挂起一段指定的时间。
2.如果请求的时间量已经到了,sleep返回0,否则返回还剩下的要休眠的秒数。后一种情况是可能的,如果因为sleep函数被一个信号中断而过早地返回。
3.pause函数让调用函数休眠,直到该进程收到一个信号。
8.4.5 加载并运行程序
1.execve函数加载并运行可执行目标文件filename,且带参数列表argv和环境变量列表envp。只有当出现错误时,例如找不到filename,execve才会返回到调用程序。所以,与fork一次调用返回两次不同,execve调用一次并从不返回。
2.参数中每个指针都指向一个参数串。按照惯例,argv[0]是可执行目标文件的名字。环境变量的列表是由一个类似的数据结构表示的。envp变量指向一个以null结尾的指针数组,其中每个指针指向个环境变量串,其中每个串都是形如“NAME=VALUE”的名字一值对。
8.4.6 利用fork和execve运行程序
1.像Unix外壳和Web服务器这样的程序大量使用了fork和e×ecve函数。外壳是一个交互型的应用程序,它代表用户运行其他程序。最早的外壳是Sh程序,后面出现了一些变种,比如csh、tcsh、ksh和bash。外壳执行一系列的读/求值(readeaUte)步骤然后终止。
2.如果builtin_command返回0,那么外壳创建一个子进程,并在子进程中执行所请求的程序。如果用户要求在后台运行该程序,那么外壳返回到循环的顶部,等待下一个命令行否则,外壳使用Waitpid函数等待作业终止。当作业终止时,外壳就开始下一轮迭代。注意这个简单的外壳是有缺陷的,因为它并不回收它的后台子进程。
8.5 信号
1.一种更高层次的软件形式的异常,称为unix信号,它允许进程中断其他进程。
2.低层的硬件异常是由内核异常处理程序处理的,正常情况下,对用户进程而言是不可见的。信号提供了一种机制,通知用户进程发生了这些异常。
8.5.1 信号术语
1.传送一个信号到目的进程是由两个步骤组成的发送信号。内核通过更新目的进程上下文中的某个状态,发送(递送)一个信号给目的进程。发送信号可以有如下两种原因:
(1)内核检测到一个系统事件。
(2)一个进程调用了kill函数,显式地要求内核发送一个信号给目的进程,一个进程可以发送信号给它自己。
2.接收信号:当目的进程被内核强迫以某种方式的发送做出反应时,目的进程就接收了信号。进程可以忽略这个信号,终止或者通过执行一个称为信号处理程序的用户层函数不活这个信号。
3.一个只发出而没有被接收的信号叫做待处理信号。在任何时刻,一种类型至多只会有一个待处理信号。
4.一个进程可以有选择性地阻塞接收某种信号。当一种信号被阻塞时,他仍可以被发送,但是产生的待处理信号不会被接收,直到进程取消对这种信号的阻塞。一个待处理信号最多只能被接收一次。
8.5.2 发送信号
1.进程组:每个进程都只属于一个进程组,进程组是由一个正整数进程组ID来标识的。 一个子进程和它的父进程同属于一个进程组,一个进程组可以通过使用setpgid函数来改变自己或者其他进程的进程组。
2.用/bin/kill程序发送信号,用/bin/kill程序可以向另外的进程发送任意的信号。
3.从键盘发送信号,从键盘发送信号外壳为每个作业创建一个独立的进程组。
4.用kill函数发送信号进程通过调用kill函数发送信号给其他进程(包括它们自己)。
5.用alarm函数发送信号进程可以通过调用alarm函数向他自己发送SIGALRM信号。
8.5.3 接收信号
1.当内核从一个异常处理程序返回,准备将控制传递给进程P时,他会检查进程P的未被阻塞的处理信号的集合。如果这个集合为空,那么内核将控制传递到P的逻辑控制流中的下一条指令;如果集合是非空的,那么内核选择集合中的某个信号K(通常是最小的K0,并且强制P接收信号K。收到这个信号会触发进程的某种行为。一旦进程完成了这个行为,那么控制就传递回P的逻辑控制流中的下一条指令。
2.每个信号类型都有一个预定的默认行为:
(1)进程终止
(2)进程终止并转储存储器
(3)进程停止直到被SIGCONT型号重启
(4)进程忽略该信号
3.signal函数可以通过下列三种方法之一来改变和信号signum相关联的行为:
(1)如果handler是SIG_IGN,那么忽略类型为signum的信号
(2)如果handler是SIG_DFL,那么类型为signum的信号行为恢复为默认行为
(3)否则,handler就是用户定义的函数的地址,这个函数成为信号处理程序,只要进程接收到一个类型为signum的信号,就会调用这个程序,通过把处理程序的地址传递到signal函数从而改变默认行为,这叫做设置信号处理程序。
8.5.4 信号处理问题
1.当一个程序要捕获多个信号时,一些细微的问题就产生了。
(1)待处理信号被阻塞。Unix信号处理程序通常会阻塞当前处理程序正在处理的类型的待处理信号。
(2)待处理信号不会排队等待。任意类型至多只有一个待处理信号。因此,如果有两个类型为K的信号传送到一个目的进程,而由于目的进程当前正在执行信号K的处理程序,所以信号K时阻塞的,那么第二和信号就简单地被简单的丢弃,他不会排队等待。
(3)系统调用可以被中断。像read、wait和accept这样的系统调用潜在地会阻塞进程一段较长的时间,称为慢速系统调用。在某些系统中,当处理程序捕获到一个信号时,被中断的慢速系统调用在信号处理程序返回时不再继续,而是立即返回给用户一个错误的条件,并将errno设置为EINTR。
8.5.5 可移植的信号处理
Signal包装函数设置的信号处理程序的信号处理语义:
(1)只有这个处理程序当前正在处理的那种类型的信号被阻塞
(2)和所有信号实现一样,信号不会排队等候
(3)只要有可能,被中断的系统调用会自动重启。
(4)一旦设置了信号处理程序,它就会一直保持,知道signal带着handler参数为SIG_IGN或者SIG_DFL被调用。
8.6 非本地跳转
1.c语言提供了一种用户级异常控制流形式,称为本地跳转。通过setjmp和longjmp函数来提供。
2.setjmp函数只被调用一次,但返回多次:一次是当第一次调用setjmp,而调用环境保存在缓冲区env中时,一次是为每个相应的longjmp调用。另一方面,longjmp只调用一次,但从不返回。sig—函数是setjmp和longjmp函数的可以被信号处理程序使用的版本。
3.非本地跳转的一个重要应用就是允许从一个深层嵌套的函数调用中立即返回,通常是由检测到某个错误情况引起的。
非本地跳转的另一个重要应用是使一个信号处理程序分支到一个特殊的代码位置,而不是返回到达中断了的指令位置。
8.7 操作进程的工具
Linux系统提供了大量的监控和操作进程的有用工具:
8.8 小结
1.异常控制流(ECF)发生在计算机系统的各个层次,是计算机系统中提供并发的基本机制在硬件层,异常是由处理器中的事件触发的控制流中的突变。控制流传递给一个软件处理程序,该处理程序进行一些处理,然后返回控制给被中断的控制流。
2.有四种不同类型的异常:中断、故障、终止和陷阱。当一个外部旧设备,例如定时器芯片或者一个磁盘控制器,设置了处理器芯片上的中断引脚时(对于任意指令)中断会异步地发生控制返回到故障指令后面的那条指令。一条指令的执行可能导致故障和终止同时发生故障处理程序会重新启动故障指令,而终止处理程序从不将控制返回给被中断的流。最后,陷阱就像是用来实现向应用提供到操作系统代码的受控的入口点的系统调用的函数调用。
教材学习中的问题和解决过程
了解异常及其种类
异常的一部分由硬件实现,一部分由操作系统实现,它就是控制流中的突变,用来响应处理器状态的某些变化。异常处理程序完成处理后,根据异常事件的类型会(执行一种):
将控制返回给当前指令(事件发生时正在执行的)。
将控制返回给下一条指令(没有异常将会执行的)。
- 种类
1.中断:来自IO的设备的信号,异步,总是返回到下一条指令。
(1)异步发生,是来自处理器外部的I/O设备的信号的结果。 硬件异常中断处理程序通常称为中断处理程序。
(2)异步异常是有处理器外部的I/O设备中的时间产生的,同步异常是执行一条指令的直接产物。
(3)陷阱、故障、终止时同步发生的,是执行当前指令的结果,我们把这类指令叫做故障指令。
2.陷阱:有意的异常,同步,总是返回到下一条指令
(1)陷阱最重要的用途是在用户程序和内核之间提供一个像过程一样的接口,叫做系统调用。
(2)普通的函数运行在用户模式中,用户模式限制了函数可以执行的指令的类型,而且它们只能访问与调用函数相同的栈。系统调用运行在内核模式中,内核模式允许系统调用执行指令,并访问定义在内核中的栈。
3.故障:是由错误情况引起的。
故障:潜在可恢复的错误,同步,可能返回到当前指令
4.终止:不可恢复的错误,同步,不会返回
终止被中断的程序。
理解进程和并发的概念
- 进程
进程是操作系统对一个正在运行的程序的抽象,即操作系统为该进程虚拟了独自的处理器资源,内存空间(又称虚拟地址空间)与磁盘空间
- 并发
从计算机底层实现原理来看,是CPU等硬件 在操作系统所抽象出来的多个进程(或线程)之间快速来回切换执行的过程;具体讲,某一极短时间间隔内,CPU只能执行一个进程(或线程)
掌握进程创建和控制的系统调用及函数使用:fork,exec,wait,waitpid,exit,getpid,getppid,sleep,pause,setenv,unsetenv,
fork函数
进程由fork函数创建,在unistd.h库中定义如下:
#include<unistd.h>
pid_t fork(void);
ock函数调用一次却返回两次;向父进程返回子进程的ID,向子进程中返回0,这是因为父进程可能存在很多过子进程,所以必须通过这个返回的子进程ID来跟踪子进程,而子进程只有一个父进程,他的ID可以通过getppid取得。
理解数组指针、指针数组、函数指针、指针函数的区别
- 指针
char *p,这定义了一个指针,指针指向的数据类型是字符型,char *(p)定义了一个指针P;
- 指针数组
char p[4], 为指针数组,由于[]的优先级高于,所以p先和[]结合,p[]是一个数组,暂时把p[]看成是q,也就是char *(q),定义了一个指针q,只不过q是一个数组罢了,故定义了一个数组,数组里面的数据是char *的,所以数组里面的数据为指针类型。所以char *p[4]是四个指针,这四个指针组成了一个数组,称为指针数组,既有多个指针组成的数组。
- 指针数组
char(p)[4],为数组指针,强制改变优先级,先与p结合,使p成为一个指针,这个指针指向了一个具有4个char型数据的数组。故p中存放了这个char型数组的首地址,可用数组指针动态内存申请:
char (p)[10]; p=(char)malloc(sizeof(char[x])*N);
char f(char,char),
为指针函数,()的优先级高于,故f先与()结合,成为函数f(),函数的返回值是char *类型的,故返回值是一个指针。
- 函数指针
char (f)(char,char),为函数指针,与f结合成为一个指针,这个指针指向函数的入口地址。函数名就是函数的首地址。函数指针是指向函数的指针变量。 因而“函数指针”本身首先应是指针变量,只不过该指针变量指向函数。这正如用指针变量可指向整型变量、字符型、数组一样,这里是指向函数。C在编译时,每一个函数都有一个入口地址,该入口地址就是函数指针所指向的地址。有了指向函数的指针变量后,可用该指针变量调用函数,就如同用指针变量可引用其他类型变量一样,在这些概念上是一致的。函数指针有两个用途:调用函数和做函数的参数。
理解信号机制:kill,alarm,signal,sigaction
发送信号两个原因:
内核检测到一个系统事件,比如被零除错误或子进程的终止。、
一个进程调用kill函数
1.进程组
•每个进程都只属于一个进程组。
•进程组ID:正整数
•一个子进程和他的父进程属于同一进程组。
•查看进程组id:getpgrp
•修改进程组:setpgid
2.用/bin/kill程序发送信号
/bin/kill程序可以向另外的进程发送任意的信号,格式是:
/bin/kill -n m
n是信号,m是进程或进程组
当n>0时,发送信号n到进程m
当n<0时,使信号|n|发送到进程组m中的所有进程。
3.从键盘发送信号
4.用kill函数发送信号
5.用alarm函数发送信号
进程可以通过调用alarm函数向它自己发送SIGALRM信号。
代码调试中的问题和解决过程
上周考试错题总结
1.下面和代码可移植性相关的C语言属性有( A B C)
A .#define
B .typedef
C .sizeof()
D .union
2.short int v=-12345;
unsigned short uv=(unsigned short) v;
那么(AC)
A .
v=-12345, uv=53191
B .
v=uv=0xcfc7
C .
v,uv的底层的位模式不一样
D .
v,uv的值在内存中是不一样的
结对及互评
点评模板:
- 博客中值得学习的或问题:
- xxx
- xxx
- ...
- 代码中值得学习的或问题:
- xxx
- xxx
- ...
- 其他
本周结对学习情况
- [结对同学学号1](博客链接)
- 结对照片
- 结对学习内容
- XXXX
- XXXX
- ...
其他(感悟、思考等,可选)
xxx
xxx
学习进度条
代码行数(新增/累积) | 博客量(新增/累积) | 学习时间(新增/累积) | 重要成长 | |
---|---|---|---|---|
目标 | 5000行 | 30篇 | 400小时 | |
第一周 | 200/200 | 2/2 | 20/20 | |
第二周 | 300/500 | 2/4 | 18/38 | |
第三周 | 500/1000 | 3/7 | 22/60 | |
第四周 | 300/1300 | 2/9 | 30/90 |
尝试一下记录「计划学习时间」和「实际学习时间」,到期末看看能不能改进自己的计划能力。这个工作学习中很重要,也很有用。
耗时估计的公式
:Y=X+X/N ,Y=X-X/N,训练次数多了,X、Y就接近了。
-
计划学习时间:XX小时
-
实际学习时间:XX小时
-
改进情况:
(有空多看看现代软件工程 课件
软件工程师能力自我评价表)
参考资料
- [《深入理解计算机系统V3》学习指导]