首页 > 代码库 > 20145216史婧瑶《信息安全系统设计基础》第十一周学习总结
20145216史婧瑶《信息安全系统设计基础》第十一周学习总结
20145216史婧瑶《信息安全系统设计基础》第十一周学习总结
教材内容总结
第八章 异常控制流
- 平滑:指在存储器中指令都是相邻的。
- 突变:出现不相邻,通常由诸如跳转、调用、和返回等指令造成。 异常控制流ECF:即这些突变。
关于ECF:
1.ECF是操作系统用来实现I/O、进程和虚拟存器的基本机制
2.应用程序通过使用一个叫做陷阱或者系统调用的ECF形式,向操作系统请求服务
3.ECF是计算机系统中实现并发的基本机制
4.软件异常机制——C++和Java有try,catch,和throw,C中非本地跳转是setjmp和longjmp
第一节 异常
异常是异常控制流的一种形式,由硬件和操作系统实现。简单来说,就是控制流中的突变。
事件:即状态变化,与当前指令的执行可能直接相关,也可能没有关系。
出现异常的处理方式:
1.处理器检测到有异常发生
2.通过异常表,进行间接过程调用,到达异常处理程序
3.完成处理后:返回给当前指令;返回给下一条指令;终止
1.异常处理
- 异常号:系统为每种类型的异常分配的唯一的非负整数。
- 异常表:系统启动时操作系统就会初始化一张条转变,使得条目k包含异常k的处理程序的地址。
- 关系:异常号是到异常表中的索引,异常表的起始地址放在异常表基址寄存器。
异常类似于过程调用,区别在:
1.处理器压入栈的返回地址,是当前指令地址或者下一条指令地址。
2.处理器也把一些额外的处理器状态压到栈里
3.如果控制一个用户程序到内核,所有项目都压到内核栈里。
4.异常处理程序运行在内核模式下,对所有的系统资源都有完全的访问权限。
2.异常的类别
- 故障指令:执行当前指令导致异常
- 中断处理程序:硬件中断的异常处理程序。
(1)中断
- 异步发生
- 来自处理器外部的I/O设备的信号的结果
- 返回下一条指令
(2)陷阱
- 陷阱是有意的异常
- 是执行一条指令的结果
- 最重要的用途——系统调用
(3)故障
- 由错误状况引起,可能能够被故障处理程序修正
- 结果要么重新执行指令(就是返回当前指令地址),要么终止
- 典型示例:缺页异常
(4)终止
- 是不可恢复的致命错误造成的结果
- 通常是一些硬件错误
3.Linux/IA32系统中的异常
一共有256种不同的异常类型。
(1)Linux/IA32故障和终止
- 除法错误/浮点异常 异常0 终止程序
- 一般保护故障/段故障 异常13 终止程序
- 缺页 异常14 返回当前地址
- 机器检查 异常18 终止程序
(2)Linux/IA32系统调用
每一个系统调用都有一个唯一的整数号,对应于一个到内核中跳转表的偏移量。
系统调用的实现方法:
在IA32中,系统调用通过一条陷阱指令提供:
int n;//n为异常号
所有的到Linux系统调用的参数都是通过寄存器传递的。惯例如下:
- %eax:包含系统调用号
- %ebx,%ecx,%edx,%esi,%edi,%ebp:包含最多六个任意参数
- %esp:栈指针,不能使用
第二节 进程
- 进程的经典定义:一个执行中的程序的实例。
- 系统中的每个程序都是运行在某个进程的上下文中的。
- 上下文:由程序正确运行所需的状态组成的。
-
进程提供给应用程序的关键抽象:
一个独立的逻辑控制流:独占的使用处理器 一个私有的地址空间:独占的使用存储器系统
1.逻辑控制流
(1)含义
一系列的程序计数器PC的值,分别唯一的对应于包含在程序的可执行目标文件中的指令,或者是包含在运行时动态链接到程序的共享对象中的指令,这个PC值的序列就叫做逻辑控制流。
(2)关键点
进程是轮流使用处理器的。每个进程执行它的流的一部分,然后被抢占,然后轮到其他进程。但是进程可以向每个程序提供一种假象,好像它在独占的使用处理器。
(3)逻辑流示例
异常处理程序、进程、信号处理程序、线程、Java进程
2.并发流
(1)含义
一个逻辑流的执行在时间上与另一个流重叠。【与是否在同一处理器无关】
这两个流并发的运行。
(2)概念
- 并发:多个流并发的执行
- 多任务:一个进程和其他进程轮流运行(也叫时间分片)
- 时间片:一个进程执行它的控制流的一部分的每一时间段
(3)并行
两个流并发的运行在不同的处理机核或者计算机上。
并行流并行的运行,并行的执行。
3.私有地址空间
进程为程序提供的假象,好像它独占的使用系统地址空间。一般而言,和这个空间中某个地址相关联的那个存储器字节是不能被其他进程读写的。
4.用户模式和内核模式
简单的说,用户模式和内核模式的区别就在于用户的权限上,权限指的是对系统资源使用的权限。
具体的区别是有无模式位,有的话就是内核模式,可以执行指令集中的所有指令,访问系统中任何存储器位置;没有就是用户模式。
进程从用户模式变为内核模式的唯一方法是通过异常——中断,故障,或者陷入系统调用。
Linux的聪明机制——/proc文件系统,将许多内核数据结构的内容输出为一个用户程序可以读的文本文件的层次结构。
5.上下文切换
操作系统内核使用上下文切换这种较高层形式的异常控制流来实现多任务。上下文切换机制建立在较底层异常机制之上。
(1)上下文:内核重新启动一个被抢占的进程所需的状态。
由一些对象的值组成:
- 通用目的寄存器
- 浮点寄存器
- 程序计数器
- 用户栈
- 状态寄存器
- 内核栈
- 内核数据结构:页表、进程表、文件表
(2)调度和调度器
在进程执行的某些时刻,内核可以决定抢占当前进程,并重新开始一个先前被抢占的进程。这种决定叫做调度,是由内核中称为调度器的代码处理的。
(3)上下文切换机制
1.保存当前进程的上下文
2.恢复某个先前被抢占的进程被保存的上下文
3.将控制传递给这个新恢复的进程。
(4)可能发生上下文切换的原因:
- 内核代表用户执行系统调用时
- 中断
第三节 系统调用错误处理
这一节主要是对附录A的内容的重复解释,在第十周已经学习过。
简单总结就是,系统会使用错误处理包装函数,系统级函数是小写,他们的包装函数名大写,包装函数调用基本函数,有任何问题就终止,如果没有问题和基本函数是一样的。
第四节 进程控制
一、获取进程ID
每个进程都有一个唯一的正数进程ID(PID)。
#include <sys/types.h>
#include <unistd.h>
pid_t getpid(void); 返回调用进程的PID
pid_t getppid(void); 返回父进程的PID(创建调用进程的进程)
二、创建和终止进程
1.进程总是处于下面三种状态之一
- 运行
- 停止:被挂起且不会被调度
-
终止:永远停止。原因:
1.收到信号,默认行为为终止进程 2.从主程序返回 3.调用exit函数 4.创建进程
父进程通过调用fork函数来创建一个新的运行子进程。fork函数定义如下:
#include <sys/types.h>
#include <unistd.h>
pid_t fork(void);
父进程返回子进程的PID,子进程返回0.如果失败返回-1.
- 调用一次,返回两次
- 并发执行,内核能够以任何方式交替执行它们的逻辑控制流中的指令
- 相同点:用户栈、本地变量值、堆、全局变量值、代码
- 不同点:私有地址空间
- 共享文件:子进程继承了父进程所有的打开文件
- 调用fork函数n次,产生2的n次方个进程。
3.终止进程
exit函数:
#include <stdlib.h>
void exit(int status);
exit函数以status退出状态来终止进程,该函数无返回值。
三、回收子进程
进程终止后还要被父进程回收,否则处于僵死状态。
如果父进程没有来得及回收,内核会安排init进程来回收他们。init进程的PID为1.
一个进程可以通过调用waitpid函数来等待它的子进程终止或停止。waitpid函数的定义如下:
#include <sys/types.h>
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *status, int options);
成功返回子进程PID,如果WNOHANG,返回0,其他错误返回-1.
1.判断等待集合的成员——pid
- pid>0:等待集合是一个单独子进程,进程ID等于pid
- pid=-1:等待集合是由父进程所有的子进程组成
- 其他
2.修改默认行为——options
设置为常量WNOHANG和WUNTRACED的各种组合:
3.检查已回收子进程的退出状态——status
在wait.h头文件中定义了解释status参数的几个宏:
- WIFEXITED:如果子进程通过调用exit或一个返回正常终止,就返回真
- WEXITSTATUS:返回一个正常终止的子进程的退出状态。只有在WIFEXITED返回为真时,才会定义这个状态
- WIFSIGNALED:如果子进程是因为一个未被捕获的信号终止的,那么返回真
- WTERMSIG:返回导致子进程终止的信号的编号。只有在WIFSIGNALED返回为真时才定义这个状态
- WIFSTOPPED:如果引起返回的子进程当前是被停止的,那么返回真
- WSTOPSIG:返回引起子进程停止的信号的数量。只有在WIFSTOPPED返回为真时才定义这个状态
4.错误条件
如果调用进程没有子进程,那么waitpid返回-1,并且设置errno为ECHILD。
如果waitpid被一个信号中断,那么他返回-1,并且设置errno为EINTR。
5.wait函数
wait函数是waitpid函数的简单版本,wait(&status)等价于waitpid(-1,&status,0).
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status);
成功返回子进程pid,出错返回-1
四、让进程休眠
1.sleep函数
sleep函数使一个进程挂起一段指定的时间。定义如下:
#include <unistd.h>
unsigned int sleep(unsigned int secs);
返回值是剩下还要休眠的秒数,如果到了返回0.
2.pause函数
#include <unistd.h>
int pause(void);
让调用函数休眠,直到该进程收到一个信号,总是返回-1。
五、加载并运行程序——execve函数
#include <unistd.h>
int execve(const char *filename, const char *argv[], const char *envp[]);
成功不返回,失败返回-1.
execve函数调用一次,从不返回。
- filename:可执行目标文件
- argv:参数列表
- envp:环境列表
操作环境数组——getnev函数
#include <stdlib.h>
char *getenv(const char *name);
若存在则为指向name的指针,无匹配是null
在环境数组中搜寻字符串"name=value",如果找到了就返回一个指向value的指针,否则返回null。
操作环境数组——setenv和unsetenv函数
#include <stdlib.h>
int setenv(const char *name, const char *newvalue, int overwrite);
若成功返回0,错误返回-1
void unsetenv(const char *name);
无返回值
如果环境数组包含"name=oldvalue"的字符串,unsetenv会删除它,setenv会用newvalue代替oldvalue,只有在overwrite非零时成立。
如果name不存在,setenv会将"name=newvalue"写进数组。
fork函数和execve函数的区别:
fork函数是创建新的子进程,是父进程的复制体,在新的子进程中运行相同的程序,父进程和子进程有相同的文件表
,但是不同的PID
execve函数在当前进程的上下文中加载并运行一个新的程序,会覆盖当前进程的地址空间,但是没有创建一个新进程
,有相同的PID,继承文件描述符。
第五节 信号
1.基本概念
信号是一种进程间通信的方法,应用于异步事件的处理,实质是软中断,在软件层面。
2.kill -l 或者 man 7 signal——查看信号信息
每个信号都有一个编号和宏定义名称。
3.信号生命周期
信号产生、信号注册、信号注销、信号处理
(1)信号产生——四种类型
-
用户产生——Ctrl+C。 stty -a,查看哪些按键可以产生信号
-
硬件产生——除零错误
- 进程产生——kill指令
- 内核产生——闹钟超时
(2)信号处理——三种方法
- 执行默认操作
- 忽略信号
- 捕捉信号:执行信号处理函数,切换到用户态。
(3) 多信号处理
处理方法:
1.递归,调用同一个处理函数
2.忽略第二个信号
3.阻塞第二个信号直至第一个处理完毕
第六节 非本地跳转
- c语言中,用户级的异常控制流形式,通过setjmp和longjmp函数提供。
- setjump函数在env缓冲区中保存当前调用环境,以供后面longjmp使用,并返回0.
- 调用环境:程序计数器,栈指针,通用目的寄存器
- longjmp函数从env缓冲区中恢复调用环境,然后触发一个从最近一次初始化env的setjmp调用的返回。然后setjmp返回,并带有非零的返回值retval。
注:
setjmp函数只被调用一次,但返回多次;
longjmp函数被调用一次,但从不返回。
第七节 操作进程的工具
- STRACE:打印一个正在运行的程序和他的子程序调用的每个系统调用的痕迹
- PS:列出当前系统中的进程,包括僵死进程
- TOP:打印出关于当前进程资源使用的信息
- PMAP:显示进程的存储器映射
代码实践
exec
1.exec1
- exec1.c中execvp()会从PATH 环境变量所指的目录中查找符合参数file 的文件名,找到后便执行该文件,然后将第二个参数argv传给该欲执行的文件
- 如果执行成功则函数不会返回,执行失败则直接返回-1,失败原因存于errno中
- 编译运行结果如下,没有返回“* * * man is done. bye”
2.exec2
- 若将execvp函数传入的arglist[0]改为arglist[1],此时execvp函数没有调用成功,于是打印出“* * * ls is done. bye”这句话
3.exec3
- 函数中execlp()会从PATH 环境变量所指的目录中查找符合参数file的文件名,找到后便执行该文件,然后将第二个以后的参数当做该文件的argv[0]、argv[1]……最后一个参数必须用空指针(NULL)作结束
fork
1.forkdemo1
- 打印进程pid,然后调用fork函数生成子进程,休眠一秒后再次打印进程id,这时父进程打印子进程pid,子进程返回0。
2.forkdemo2
- 调用两次fork,一共产生四个子进程,所以会打印四个aftre输出.
3.forkdemo3
- fork产生子进程,父进程返回子进程pid,不为0,所以输出父进程的那句话,子进程返回0,所以会输出子进程那句话。
4.forkdemo4
- 先打印进程pid,然后fork创建子进程,父进程返回子进程pid,所以输出parent一句,休眠十秒;子进程返回0,所以输出child与之后一句。
5.forkgdb
- 父进程打印是先打印两句,然后休眠一秒,然后打印一句,子进程先打印一句,然后休眠一秒,然后打印两句。并且这两个线程是并发的,所以可以看到在一个线程休眠的那一秒,另一个线程在执行,并且线程之间相互独立互不干扰。
wait
1.waitdemo1
- waitdemo1.c的功能是如果有子进程,则终止子进程,成功返回子进程pid。
2.waitdemo2
- waitdemo2.c比起1来就是多了一个子进程的状态区分,把状态拆分成三块,exit,sig和core
env
1.environ.c
-
environ.c代码运行如下
- 代码中涉及到getenv函数和setenv函数
- getenv函数在环境数组中搜索字符串“name=value”。如果找到了,就返回一个指向value的指针,否则就返回NULL
-
setenv函数是修改或添加环境变量的函数
2.environvar.c
- environvar.c代码简单打印环境变量表,运行结果如下
- 每个程序都有一个环境表,它是一个字符指针数组,其中每个指针包含一个以NULL结尾的C字符串的地址。
- 全局变量environ则包含了该指针数组的地址
fifo
- fifo是一种文件类型,可以通过查看文件stat结构中的stmode成员的值来判断文件是否是FIFO文件。fifo是用来在进程中使用文件来传输数据的,也具有管道特性,可以在数据读出的时候清除数据
1.consemer.c管道写端
2.producer.c管道读端
pipe
1.pipedemo.c
-
who把输出送给stdout,sort从stdin中读入数据,那也就是说who的stdout和sort的stdin连成了一个。
-
result=pipe(int array[2]);array[0]是读端的文件描述符,array[1]是写端的文件描述符。
-
pipe调用首先获得两个“最低可用文件描述符”,赋给array[0]和array[1],然后再把这两个文件描述符连接起来。
2.pipedemo2.c
-
在程序中,显示从键盘到进程,从进程到管道,再从管道到进程以及从进程回到终端的数据传输流。
signal
1.sigactdemo.c
2.sigactdemo2.c
-
休息seconds秒后返回;或者被信号中断且信号处理函数返回后sleep()返回0。所以如果不计较返回值的话,pause()的功能相当于无限期的sleep()。
学习中遇到的问题和解决过程
问题:在虚拟机中使用/proc文件系统,结果显示权限不够,如图所示:
解决方法:通过最后一节内容得知,是由于输入方法不对,应该输入的是cat打印指令,再接后面的目录,如下图:
代码托管截图
链接:https://git.oschina.net/sjy519/linux-program-C/tree/master
其他(感悟、思考等,可选)
本周学习了异常控制流,当学习教材上的内容时,我感到很吃力,真的很难理解,可能多看几遍能稍微掌握一些,但是过了一段时间再看学过的知识点,又感觉很陌生,光是看书就看了很长时间,不知道这周真正学会的知识有多少。
学习进度条
代码行数(新增/累积) | 博客量(新增/累积) | 学习时间(新增/累积) | 重要成长 | |
---|---|---|---|---|
目标 | 3000行 | 30篇 | 300小时 | |
第一周 | 0/0 | 1/2 | 25/40 | 学习了Linux基础知识和核心命令 |
第二周 | 0/0 | 0/2 | 0/40 | |
第三周 | 300/300 | 3/5 | 40/80 |
学习了vim、gcc、gdb指令; 学习了信息表示和处理
|
第五周 | 200/500 | 1/6 | 45/125 |
学习了程序的机器级表示 |
第六周 | 150/650 | 1/7 | 40/165 |
学习了处理器体系结构
|
第七周 | 100/750 | 1/8 | 40/205 |
学习了存储器层次结构
|
第八周 | 46/796 | 2/10 | 40/245 |
复习了以前的知识点 |
第九周 | 124/920 | 1/11 | 40/285 |
学习了系统级I/O的相关内容 |
第十周 | 510/1430 | 3/14 | 32/317 |
重点学习了一些命令 |
第十周一 | 440/1870 | 3/17 | 35/352 |
学习了异常控制流的相关知识点 |
20145216史婧瑶《信息安全系统设计基础》第十一周学习总结