20145229《信息安全系统设计基础》第11周学习总结
教材学习内容总结
异常控制流
- 一个序列中的ak为相应指令Ik的地址,ak到ak+1的过渡为控制转移,这个序列称为处理器的控制流
- 系统必须对系统状态的变化做出反应——通过使控制流发生突变,这些突变成为异常控制流(ECF),ECF存在硬件层,操作系统层,应用层
- 非本地跳转是ECF的一种应用层形式
8.1 异常
- 异常是异常控制流的一种形式,一部分由硬件实现,一部分由操作系统实现
- 异常就是控制流中的突变,用来响应处理器中的变化
- 处理器检测到有事件的时候通过叫异常表的跳转表进入异常处理程序,处理完成发生:(1)将控制返回给当前指令 (2)将控制返回给下条指令 (3)终止被中断的程序
8.1.1 异常处理
- 系统给每种异常都分配了唯一的异常号,由处理器的设计者分配的(零除、缺页、存储器访问违例、断点以及算术溢出),由操作系统设计者内核分配的(系统调用和来自外部I/O系统的信号)
- 系统启用时,操作系统分配一张称异常表的跳转表,条目k包含异常k的处理程序的地址
- 异常号是到异常表中的索引,异常表的起始地址放在称为异常表基址寄存器的特殊CPU寄存器里
- 异常与过程调用的不同之处:
异常根据异常类型,返回到当前指令或者下一条指令
处理器也将一些额外的处理器压到栈中以便返回时重新开始需要这些状态
控制用户程序转到内核,将所有项目压到内核栈中
异常处理程序在内核模式下对所有系统资源有完全访问权限
8.1.2 异常的分类
- 异常:中断、陷阱、故障、终止
中断
- 中断是异步发生的,来自处理器外部的I/O设备的信号的结果
- 硬件中断不是由指令造成的,硬件中断的异常处理程序被称为中断处理程序
- 异步异常由处理器外部的I/O设备产生的,同步异常由指令产生的
- 故障、陷阱和终止是同步发生的,这类指令为故障指令
陷阱和系统调用
- 陷阱是有意的异常,陷阱处理程序将控制返回到下一条
- 陷阱最重要的用途为用户和内核之间提供一个接口称为系统调用
- 为了允许用户对内核发出的请求,处理器执行“syscall n”的指令,会导致一个异常处理的陷阱,这个处理程序会调用适合的内核程序满足用户
- 用户在用户模式中会限制很多可执行的指令,当系统调用运行内核模式,会允许系统调用执行指令并访问定义在内核中的栈
故障
- 故障由错误情况引起,能够由故障处理系统修正
- 如果处理器能修改这个错误则将控制返回引起故障的指令,重新执行;如果不能修正,则转入abort例程,终止引起故障的程序
- 经典的故障事例—— 缺页异常
终止
- 终止是不可恢复的错误造成的结果,通常是硬件错误
- 若碰到此类异常则转入abort例程,终止该应用程序
8.1.3 Linux/IA32的系统异常
- IA32有256种系统异常,0-31是Intel架构师定义的异常,对任何IA32都是一样的,32-255是由操作系统定义的中断和陷阱
Linux/IA32故障和终止
- 除法错误 当目标的除法指令试图除0或者结果太大的时候,发生出发错误(异常0),Unix直接终止程序,Linux外壳把错误报告为“浮点异常”
- 一般保护故障 因为程序引用未定义的虚拟存储器区域或者写一个只能读的文本导致的错误,Linux直接终止,将这种故障报告为“段故障”
- 换页,典型的重新执行产生故障的指令的异常示例,处理器会将磁盘虚拟器的页面映射到存储器的页面,然后重新执行指令
- 机器检查 在指令执行中检测到硬件产生错误发生的。机器检查处理程序从不返回给应用程序!!!
Linux/IA32系统调用
- 每个系统都有唯一的整数号,对应到一个到内核中跳转表的偏移量
- 在IA系统中,系统调用int n的陷阱指令,n可以是256条目中的任何一个索引。
- 历史上系统调用通过异常128提供
- C语言中庸syscall函数直接调用任何系统,实际中C语言提供打包好的方便函数,本书中我们称将系统调用和相关联的包装函数称为系统级函数
- 所有的到Linux系统调用的参数都是通过寄存器而不是栈进行传递的。栈指针%esp不能使用,因为进入内核模式时,内核会覆盖它
- 8.2 进程
- 异常是允许操作系统提供进程的概念的基本构造块,进程是计算机科学中最深刻最成功的概念之一
- 经典的进程定义是一个执行中的程序的实例
- 应用程序的关键抽象:
一个独立的逻辑控制流,使得我们以为程序在独占处理器
一个私有的地址空间,使得我们以为程序在独占存储器系统
8.2.1逻辑控制流
- 当使用调试器单步执行程序的时候,看到的一系列程序计数器(PC)值的序列叫做逻辑控制流或者逻辑流
- 进程是轮流使用处理器的,每个进程执行流的一部分然后被抢占,并不是独占处理器
- 每次处理器停顿随后继续执行程序,并不改变存储器的位置或寄存器的内容
8.2.2并发流
- 一个逻辑流的执行在时间上和另一个流重叠,称为并发流,两个流被称为并发的运行
- 多个流并发的执行的现象称为并发,一个进程与另一个进程轮流执行称为多任务,多任务也称为时间分片
- 两个流并发的运行在不同的处理器或者计算机上,称为并行流,他们并行的运行且并行的执行
私有地址空间
- 进程为每个程序提供了假象好像在独占地址空间
- 一台有n位地址的机器上,地址空间是2的n次方个可能地址
- 一个进程为每个程序提供自己的地址私有空间,和地址相关联的存储器字节不能被其他进程读写
8.2.4 用户模式和内核模式
- 处理器用控制寄存器中的一个模式位来使系统内核提供一个无懈可击的内核抽象,该寄存器描述了进程当前享有的特权。设置模式位后,进程就运行在内核模式,没有设置的时候就在用户模式
- 进程从用户模式到内核模式的唯一方法是通过如中断、故障或陷入系统调用这样的异常。
- Linux提供了一种机制,/proc文件系统,允许用户模式访问内核结构系统的内容
8.2.5 上下文切换
- 操作系统内核使用上下文切换机制来执行多任务
- 内核为每个进程维持一个上下文,上下文就是重新启动之前被抢占进程所需要的状态
- 在内核决定抢占当前进程并重新开始之前被抢占的进程时被称为调度,是由内核中调度器的代码执行的
- 调度过程:(1)保存当前进程A的上下文 (2)恢复之前被抢占的进程B被保存的上下文 (3)控制传递给进程B
- 内核可以自行决定是否调度,比如读取磁盘的时候,比如发生阻塞,或者是中断的时候,内核在这个间歇时间就调度给进程B,让进程B执行,当进程A完成发出信号的时候,内核又调度给进程A,不让用户等待,节约时间和资源
- 注意!!切换之前,进程A是在用户模式,然后A进入内核模式,然后B进入内核模式,最后B进入用户模式
8.3 系统调用错误处理
- 当Unix系统级函数遇到错误时,会返回-1,并设置errno来表示什么出错了,程序员应该总是检查错误
- strerror函数返回一个文本串描述和某个errno相关联的错误
- 使用错误处理包装可以简化代码
- 包装函数调用基本函数,检查错误,如果有问题立即终止
- 包装函数定义在caspp.c的文件中,原型定义在叫caspp.h的头文件中
8.4 进程控制
8.4.1 获取进程ID
- 每个进程都有唯一的正数进程ID(PID)
- getpid函数返回调用进程的PID,getppid函数返回父进程的PID
- getpid和getppid返回一个类型为pid_t的整数值,在linux系统中定义为int
8.4.2 创建和终止进程
- 进程有3种状态:(1)运行,在cpu上执行或者等待被执行 (2)停止,收到停止的信号停止,直到收到运行的信号 (3)终止 1.收到终止进程的信号 2.从主程序中返回 3.调用exit函数
- exit函数以status退出状态来终止程序
- 另一种终止方法为从主程序中返回一个整数值
- 父进程调用folk函数建立子进程,子进程几乎与父进程完全一样。但是子进程与父进程的PID不同
- folk函数被调用一次却会返回2次,因为在父进程和子进程中都会返回。父进程中返回子进程的PID,子进程中返回0。我们可以通过这个来分辨程序在父进程还是子进程执行
- 父进程和子进程是并发运行的独立进程,内核可以任意执行它们控制流中的指令
- 父进程和子进程是独立的进程,他们都有自己的私有地址空间。他们共享文件
8.4.3 回收子进程
- 一个进程终止时,内核不会直接清除,而是保持终止状态直到父进程回收,内核将子进程退出状态传给父进程,然后抛弃。终止还未被回收的进程称为僵死进程
- 如果父进程没有回收它的僵死子进程就终止了,就用init回收,init进程的PID为1
- 进程可以调用waitpid函数等待子进程终止或停止,默认options=0时,waitpid等待它终止时或者刚调用就终止,则返回已终止子进程的PID
(1)判断等待集合的成员
等待集合的成员由参数pid来确定:
- 如果pid>0:等待集合是一个单独子进程,进程ID等于pid
- 如果pid=-1:等待集合是由父进程所有的子进程组成
(2)检查已回收子进程的退出状态——status,在wait.h头文件中定义了解释status参数的几个宏
- WIFEXITED:如果子进程通过调用exit或一个返回正常终止,就返回真
- WEXITSTATUS:返回一个正常终止的子进程的退出状态。只有在WIFEXITED返回为真时,才会定义这个状态
- WIFSIGNALED:如果子进程是因为一个未被捕获的信号终止的,那么返回真
- WTERMSIG:返回导致子进程终止的信号的编号。只有在WIFSIGNALED返回为真时才定义这个状态
- WIFSTOPPED:如果引起返回的子进程当前是被停止的,那么返回真
- WSTOPSIG:返回引起子进程停止的信号的数量。只有在WIFSTOPPED返回为真时才定义这个状态
(3)错误条件
- 如果调用进程没有子进程,那么waitpid返回-1,并且设置errno为ECHILD。
- 如果waitpid被一个信号中断,那么他返回-1,并且设置errno为EINTR。
(4)wait函数 - wait函数是waitpid函数的简单版本,wait(&status)等价于waitpid(-1,&status,0).成功返回子进程pid,出错返回-1
8.4.4 让程序休眠
- sleep函数将一个进程挂起一段指定的时间,如果时间到了,返回0,否则返回要休眠的秒数
- pause函数可以让程序休眠至一个信号
8.4.5 加载并运行程序
- execve函数在当前进程上下文中加载并执行一个新程序,只有当出现错误时,才会返回到调用程序。execve调用一次且从不返回,失败返回-1
- Unix提供函数操作环境数组:
(1)getenv在环境数组中搜索“name=value”,找到了返回指向value的指针,否则返回NULL
(2)setenv和unsetenv函数:如果环境数组包含"name=oldvalue"的字符串,unsetenv会删除它,setenv会用newvalue代替oldvalue,只有在overwrite非零时成立。
(3)如果name不存在,setenv会将"name=newvalue"写进数组。
8.4.6 利用folk和execve运行程序
- fork函数是创建新的子进程,是父进程的复制体,在新的子进程中运行相同的程序,父进程和子进程有相同的文件表,但是不同的PID
- execve函数在当前进程的上下文中加载并运行一个新的程序,会覆盖当前进程的地址空间,但是没有创建一个新进程,有相同的PID,继承文件描述符。
8.5 信号
- 更高层的软件形式的异常,称为Unix信号,它允许进程中断其他其他进程
- 一个进程就是一条小消息,通知进程系统中发生的事件
信号术语
-
传递一个信号到目的进程的两个步骤:发送信号和接收信号。
-
发送信号的原因:(1)内核检测到一个系统事件 (2)一个进程调用了kill函数,显式的要求内核发送一个信号给目的进程。一个进程可以发送信号给它自己。
-
接收信号:(1)忽略 (2)终止 (3)执行信号处理程序,捕获信号
待处理信号:
- 只发出没有被接收的信号
- 任何时刻,一种类型至多只会有一个待处理信号,多的会被直接丢弃
- 一个进程可以选择性的阻塞接受某种信号,被阻塞仍可以被发送,但是不会被接收,直到解除阻塞。
- 一个待处理信号最多只能被接收一次。
进程组
- 每个进程都只属于一个进程组。
- 一个子进程和他的父进程属于同一进程组。
- 查看进程组id:getpgrp
- 修改进程组:setpgid
用/bin/kill程序发送信号
-
/bin/kill程序可以向另外的进程发送任意的信号
-
n是信号,m是进程或进程组
当n>0时,发送信号n到进程m
当n<0时,使信号|n|发送到进程组m中的所有进程。
用kill函数发送信号
- 进程通过调用kill函数发送信号给其他进程。
用alarm函数发送信号
- 进程可以通过调用alarm函数向它自己发送SIGALRM信号。
include <unistd.h>
unsigned int alarm(unsigned int secs);
- 返回前一次闹钟剩余的秒数,若没有返回0.
8.6 非本地跳转
- c语言提供的一种用户级异常控制流形式称为非本地跳转,控制直接将一个函数转移到另一个正在执行的函数,而不经过正常的调用-返回序列
- 非本地跳转通过setjmp和longjmp提供
- setjmp函数在env缓冲区中保存当前调用环境以供longjmp使用并返回0
- longjmp函数从env中恢复调用环境
- setjmp只调用一次返回多次,longjmp调用多次却从不返回
8.7 操作进程的工具
- STRACE:打印正在运行的程序和它的子进程调用的系统调用的轨迹
- PS:列出当前系统中的进程
- TOP:打印出当前进程资源使用的信息
- PMAP:显示进程的存储器映射
实践内容
environ.c
- getenv函数是环境变量值的函数。若环境变量存在,则返回环境变量值;若不存在,返回NULL。
environvar.c
- 打印一个环境变量表,全局变量environ包含该指针数组的地址
fifo
- fifo遵循先进先出,且一般为多个写进程,一个读进程
forkdemo1.c
- forkdemo1.c代码先是打印进程pid,后打印子进程pid
本周代码
https://git.oschina.net/ssqykdky/Linux20145229/tree/week11
其他(感悟、思考等,可选)
最开始看到这周内容的时候觉得特别多,觉得自己学不完,其实当你让自己陷入一个学习的状态后你会发现其实学习的乐趣很多,弄懂一个知识点很有成就感,这周花在书上的时间很多,以为自己考试会得心应手,结果自己发现了自己的一个问题,就是看书的时候老是只看中文,对于一些重点名词概念的英文根本不予理睬,导致了考试的时候老师出的英文自己感觉没有见过翻书才发现是自己很熟悉的中文概念。这里还想向老师咨询一个方法,这周写博客的时候博客园莫名其妙崩了好几次,也有可能是我的网络不稳定,但是这样会导致自己写的内容全部都没有了,重打概念重新找图花了好多时间后来都快崩溃了,想问问老师平时用什么写博客比较稳定。
学习进度条
代码行数(新增/累积) | 博客量(新增/累积) | 学习时间(新增/累积) | 重要成长 | |
---|---|---|---|---|
目标 | 5000行 | 30篇 | 400小时 | |
第一周 | 130/130 | 1/1 | 17/17 | |
第二周 | 90/270 | 1/1 | 16/16 | |
第三周 | 120/390 | 2/2 | 16/16 | |
第四周 | 89/479 | 1/1 | 17/17 | |
第五周 | 120/599 | 1/1 | 16/16 | |
第六周 | 110/709 | 1/1 | 18/18 | |
第七周 | 128/837 | 1/1 | 18/18 | |
第八周 | 5/842 | 1/1 | 16/16 | |
第九周 | 65/907 | 1/1 | 13/13 | |
第十周 | 160/1067 | 1/1 | 14/14 | |
第十一周 | 380/1447 | 1/1 | 15/15 |
参考资料
- [《深入理解计算机系统V2》学习指导]
- ...