2017-2018-1 20155339 《信息安全系统设计基础》第六周学习总结 教材学习内容总结
- 控制转移:从ak指令到a(k+1)指令的过渡。
- 控制转移序列称为处理器的控制流。
- 现代操作系统通过使控制流发生突变来对系统状态做出反应,这些突变称为异常控制流。
- 异常是异常控制流的一种形式,他一部分由硬件实现,一部分由操作系统实现。
- 异常就是控制流中的突变,用来响应处理器状态中的某些变化。
- 当处理器检测到有事件发生时,它会通过叫做异常表的跳转表,进行一个间接过程调用(异常),到一个专门处理这类问题的操作系统子程序。
- 系统中可能的某种类型的异常都分配了一个唯一的非负整数的异常号,异常号是异常表的索引,异常表的起始地址放在一个叫做异常表基址寄存器的特殊CPU寄存器里。
- 异常可以分为四类:中断、陷阱、故障和终止。
- 中断是异步发生的,是来自处理器外部的I/O设备的信号的结果。硬件中断的异常处理程序通常称为中断处理程序。
- 陷阱是有意的异常,最重要的用途是在用户程序和内核之间提供一个向过程一样的接口,叫做系统调用。对于程序员来说系统调用和函数调用一样,实则不同。
- 为了允许内核服务的受控访问,使用“syscall n”指令,该指令会跳转到一个异常处理程序的陷阱,这个处理程序解析参数,并调用适当的内核程序。
- 故障由错误引起,它可能能够被故障处理程序修正。一个经典的故障实例是缺页异常。
- 终止是一个致命的不可控的错误。
- Linux/x86-64故障和终止
1.除法错误(异常0):产生原因:应用试图除零,或者当一个除法指令的操作数对于目标操作数来说太大了的时候。
2.一般故障保护(异常13):通常是因为一个程序引用一个未定义的虚拟存储区域,或者程序试图写一个只读文本段。
3.缺页(异常14):重新执行产生故障的一个异常示例。处理程序将磁盘上虚拟存储器相应页面映射到物理存储器的一个页面,然后重新开始执行这条指令。
4.机器检查(异常18):在导致故障的指令执行中检测到致命的硬件错误时发生。
- Linux/x86-64系统调用:每个系统调用都有一个唯一的整数号,对应于一个到内核中跳转表的偏移量(注意与异常表不同)。
- 所以Linux系统调用的参数都是通过通用寄存器而不是栈传递的。
read函数和write函数:
1.read:ssize_t read(int fd, void *buf, size_t count);
返回:若成功则为读的字节数,若EOF则为0,若出错为-1。
2.write:ssize_t write(int fd, const void *buf, size_t count);
返回:若成功则为写入的字节数,若出错则为-1。实例练习运行结果如下:
- 一个执行中的程序的实例就是进程,系统中的每个程序都是定义在运行在某个进程的上下文中的。上下文是由程序正确运行所需的状态组成的。状态包括存放在存储器中的程序代码和数据,它的栈、通用目的寄存器内容、程序计数器、环境变量以及打开文件描述符的集合。
- 一个独立的逻辑控制流,提供一个假象,某个程序独占地使用处理器。
- 一个私有的地址空间,提供一个假象,某个程序独占地使用存储器系统。
- PC的值唯一地对应于包含在程序可执行目标文件中的指令,或者是包含在运行时动态链接到程序的共享对象中的指令。这个PC值的序列叫做逻辑控制流,简称逻辑流。
- 一个逻辑流的执行在时间上与另一个流重叠,称为并发流。
- 1.并发:多个流并发的执行。
2.多任务(时间分片):一个进程和其他进程轮流运行。
3.时间片:一个进程执行它的控制流的一部分的每一时间段。
4.并行:两个流并发的运行在不同的处理机核或者计算机上。
- 进程为每个程序提供他自己的私有地址空间,一般而言,和这个空间中某个地址相关联的存储器字节不能被其他程序读或者写。从这个意义上讲,这个地址是私有的。
- 地址空间底部是保留给用户程序的,代码段总是从地址0x400000开始的,顶部保留给保留给内核。
- 设置模式位,进程运行在内核模式(也叫超级用户模式),没有设置模式位就运行在用户模式。
- linux提供了/proc文件系统,它允许用户模式进程访问内核数据结构的内容。
- 操作系统内核使用叫上下文切换的异常控制流来实现多任务。
- 在进程执行的某些时刻,内核可以决定抢占当前进程,并重新开始一个先前被抢占了的进程,这叫做调度。
- 调度由内核中的调度器处的代码实现。
Unix系统级函数遇到错误时,它们通常会返回-1,并设置全局变量errno来表示哪里出错了。
fork等函数的使用
fork函数
首先查看fork函数的帮助文档,如下图,可知该函数为系统调用函数,该函数的头文件为#include
用法:pid_t fork(void);
,从帮助文档可以知道,该函数创建了一个子程序。
- 由于不怎么看得懂这些英文,之后的学习是在网上搜索学习的,fork创建的虽然是叫子程序,但是两者是同时进行的,如果fork成功,父进程中fork的返回值是子进程的进程号,子进程中fork的返回值是0,由于子进程的进程号都是大于0的,所以可以用来区分父进程以及子进程,如果fork不成功,父进程会返回错误。
- 需要注意的是:子进程的pid是0,子进程的getpid()是它自己的进程号;父进程中的pid值为子进程进程号,只有父进程执行的getpid()才是他自己的进程号。
以下运行结果展现了fork的以上特性
因此当留在父进程或者进入子进程之后,还有别的工作时,该进程依旧会执行完这些命令,如下面这个示例的结果
wait函数
- 通过帮助文档,可以知道其头文件为:
1.除法错误(异常0):产生原因:应用试图除零,或者当一个除法指令的操作数对于目标操作数来说太大了的时候。
2.一般故障保护(异常13):通常是因为一个程序引用一个未定义的虚拟存储区域,或者程序试图写一个只读文本段。
3.缺页(异常14):重新执行产生故障的一个异常示例。处理程序将磁盘上虚拟存储器相应页面映射到物理存储器的一个页面,然后重新开始执行这条指令。
4.机器检查(异常18):在导致故障的指令执行中检测到致命的硬件错误时发生。
read函数和write函数:
1.read:ssize_t read(int fd, void *buf, size_t count);
返回:若成功则为读的字节数,若EOF则为0,若出错为-1。
2.write:ssize_t write(int fd, const void *buf, size_t count);
返回:若成功则为写入的字节数,若出错则为-1。实例练习运行结果如下:
2.多任务(时间分片):一个进程和其他进程轮流运行。
3.时间片:一个进程执行它的控制流的一部分的每一时间段。
4.并行:两个流并发的运行在不同的处理机核或者计算机上。
Unix系统级函数遇到错误时,它们通常会返回-1,并设置全局变量errno来表示哪里出错了。
首先查看fork函数的帮助文档,如下图,可知该函数为系统调用函数,该函数的头文件为#include
用法:pid_t fork(void);
,从帮助文档可以知道,该函数创建了一个子程序。
以下运行结果展现了fork的以上特性
因此当留在父进程或者进入子进程之后,还有别的工作时,该进程依旧会执行完这些命令,如下面这个示例的结果
#include
#include
用法:pid_t wait (int *status)
wait()会暂时停止目前进程的执行,直到有信号来到或子进程结束。如果在调用wait()时子进程已经结束,则wait()会立即返回子进程结束状态值。子进程的结束状态值会由参数status 返回,而子进程的进程识别码也会一起返回。如果不在意结束状态值,则status可以设成NULL,status 是一个整型指针,是该子进程退出时的状态:status 若为空,则代表不记录子进程结束状态,status 若不为空,则由status记录子进程的结束状态值,当成功的时候,返回子进程识别码(PID),出错的时候返回-1,失败原因存于errno中。
- 一个练习,父进程wait子进程结束的练习,结果如下图
waitpid函数
- 头文件与wait函数头文件相同,waitpid()会暂时停止目前进程的执行,直到有信号来到或子进程结束。如果在调用waitpid()子进程已经结束,则waitpid()会立即返回子进程结束状态值。子进程的结束状态值会由参数status返回,而子进程的进程识别码也会一起返回。如果不在意结束状态值,则参数status可以设成NULL。参数pid为欲等待的子进程识别码。
- 用法:
pid_t waitpid(pid_t pid,int * status,int options)
- 传入的pid:
1.pid
<-1&#xff1a;等待进程组识别码为pid绝对值的任何子进程
2.pid&#61;-1: 等待任何子进程&#xff0c;相当于wait()
3.pid&#61;0&#xff1a;等待进程组识别码与目前进程相同的任何子进程
4.pid>0&#xff1a;等待任何子进程识别码为pid的子进程&#xff0c;直到出现进程ID等于pid的进程。 - 成功&#xff1a;1.返回子进程识别码(PID)
2.使用选项WNOHANG且没有子进程退出返回0
练习了书上的代码&#xff0c;比较了有无waitpid的情况&#xff0c;得到以下截图
exec函数
- 其实说exec函数在严格意义上来讲是不对的&#xff0c;因为Linux中没有exec函数&#xff0c;而是有6个以exec开头的函数族&#xff0c;exec函数族提供了一个在进程中启动另一个程序执行的方法。
- 头文件
#include
&#xff0c;一共有六个函数族&#xff0c;其中只有execve为系统调用&#xff0c;运行另一个程序&#xff0c;用法为
int execve(const char *filename, char *const argv[],char *const envp[]);
同时保留原程序运行的方法是&#xff1a;fork&#43;exec。
通过execve运行ls命令&#xff0c;结果如下&#xff1a;
getenv函数、setenv函数、unsetven函数
getenv函数&#xff1a;获得环境变量值的函数&#xff0c;参数是环境变量名name&#xff08;如”HOME”、”PATH”&#xff09;。如果环境变量存在&#xff0c;函数会返回环境变量值&#xff0c;即value的首地址&#xff1b;如果环境变量不存在&#xff0c;函数返回NULL&#xff0c;练习得到如下运行结果
- setenv()用来改变或增加环境变量的内容。用法&#xff1a;
int setenv(const char *name,const char * value,int overwrite);
参数name为环境变量名称字符串&#xff08;如”HOME”、”PATH”&#xff09;。
参数 value则为变量内容&#xff0c;参数overwrite用来决定是否要改变已存在的环境变量。如果overwrite不为0&#xff0c;而该环境变量原已有内容&#xff0c;则原内容会被改为参数value所指的变量内容。如果overwrite为0&#xff0c;且该环境变量已有内容&#xff0c;则参数value会被忽略。 unsetven()删除环境变量&#xff0c;删除成功返回0。
一个综合的练习运行结果
kill函数
- kill()可以用来发送信号给其他进程&#xff08;包括他们自己&#xff09;。如果pid大于0&#xff0c;那么kill函数发送信号号码sig给进程pid。如果pid等于零&#xff0c;那么kill发送信号sig给调用进程所在进程组中的每个进程&#xff0c;包括调用自己的进程。如果小于零&#xff0c;kill发送信号sig给进程组|pid|中的每个进程。
getpid、getppid、sleep、pause、exit
- 头文件&#xff1a;
#include
- getpid&#xff08;&#xff09;用来取得目前进程的进程识别码&#xff0c;许多程序利用取到的此值来建立临时文件&#xff0c;以避免临时文件相同带来的问题。返回值&#xff1a; 目前进程的进程识别码&#xff0c;在之前的fork中已经使用过了。
- getppid&#xff08;&#xff09;用来取得目前进程的 父进程识别码。
- sleep&#xff08;&#xff09;&#xff0c;对于这个函数我的理解是&#xff0c;睡眠多久再执行&#xff0c;在Linux中sleep中的单位是秒&#xff0c;对上面的wait代码进行了改进来对sleep的功能以及wait的功能做一个更深层次的剖析&#xff0c;得到以下结果&#xff1a;
增加sleep前的截图&#xff1a;
增加sleep后的截图
增加了sleep之后&#xff0c;由于睡眠了1秒钟&#xff0c;所以先输出This is the child process pid &#61;4761。
- pause()会令目前的进程暂停(进入睡眠状态), 直到被信号(signal)所中断。
- exit关闭所有文件&#xff0c;终止正在执行的进程&#xff0c;头文件&#xff1a;stdlib.h&#xff0c;关于参数&#xff1a;exit(1)表示异常退出.这个1是返回给操作系统的。exit(x)&#xff08;x不为0&#xff09;都表示异常退出。exit(0)表示正常退出。
signal函数
- 头文件
#include
用法&#xff1a;sighandler_t signal(int signum, sighandler_t handler);
- signal信号函数&#xff0c;第一个参数表示需要处理的信号值&#xff08;SIGHUP&#xff09;&#xff0c;第二个参数为处理函数或者是一个表示。
- 其中信号值有以下这些&#xff1a;
pipe函数
- 管道是一种把两个进程之间的标准输入和标准输出连接起来的机制&#xff0c;从而提供一种让多个进程间通信的方法&#xff0c;当进程创建管道时&#xff0c;每次都需要提供两个文件描述符来操作管道。其中一个对管道进行写操作&#xff0c;另一个对管道进行读操作。对管道的读写与一般的IO系统函数一
致&#xff0c;使用write()函数写入数据&#xff0c;使用read()读出数据。
- 头文件&#xff1a;
#include
用法&#xff1a;int pipe(int filedes[2]);
返回值&#xff1a;成功&#xff0c;返回0&#xff0c;否则返回-1。参数数组包含pipe使用的两个文件的描述符。fd[0]:读管道&#xff0c;fd[1]:写管道。 - 必须在fork()中调用pipe()&#xff0c;否则子进程不会继承文件描述符。两个进程不共享祖先进程&#xff0c;就不能使用pipe。但是可以使用命名管道。
dup函数
- dup函数包括dup、dup2、dup3&#xff0c;用于复制文件描述符。
#include
int dup(int oldfd);
int dup2(int oldfd, int newfd);
dup 和dup2都是返回新的描述符。或者返回-1并设置 errno变量。
教材学习中的问题和解决过程
&#xff08;一个模板&#xff1a;我看了教材p512的这一段文字&#xff1a; Unix系统级函数遇到错误时&#xff0c;它们通常会返回-1&#xff0c;并设置全局变量errno来表示哪里出错了&#xff0c;有这个问题&#xff1a; 通常返回-1&#xff0c;那哪些情况返回值不是-1呢。 我查了资料&#xff0c;资料中只有提到返回-1的情况&#xff0c;根据我的实践&#xff0c;我得到的也都是-1。 但是我还是不太懂&#xff0c;我的困惑是有哪些不是-1呢&#xff0c;或者这个我们是不是不需要掌握呢。
- 问题1&#xff1a;wait和waitpid两函数的说明
- 问题1解决方案&#xff1a;上网搜索之后得到&#xff0c;如果父进程的所有子进程都还在运行&#xff0c;调用wait将使父进程阻塞&#xff0c;而调用waitpid时&#xff0c;如果在options参数中指定WNOHANG可以使父进程不阻塞而立即返回0。waitpid提供了一个 wait的非阻塞版本&#xff0c;有时希望取得一个子进程的状态&#xff0c; 但不想进程阻塞。
代码调试中的问题和解决过程
- 问题1&#xff1a;在练习教材上p518的代码时不知道fflush是什么
问题1解决方案&#xff1a;查找帮助文档&#xff0c;其头文件
#include
用法int fflush(FILE *stream);
功 能: 清除文件缓冲区&#xff0c;文件以写方式打开时将缓冲区内容写入文件。如果成功刷新,fflush返回0。指定的流没有缓冲区或者只读打开时也返回0值。返回EOF指出一个错误。- 问题2&#xff1a;在进行课上的代码的调试时&#xff0c;编译出现了的错误
问题2解决方案&#xff1a;通过上网知道通过od查看问题出在哪里&#xff0c;进行修改
代码托管
上周考试错题总结
- 6.多选&#xff1a;我们用一个十六进制的数表示长度w&#61;4的位模式&#xff0c;把数字解释为补码&#xff0c;关于其加法逆元的论述正确的是&#xff08;&#xff09;
- A .0x8的加法逆元是-8
- B .0x8的加法逆元是0x8
- C .0x8的加法逆元是8
- D .0xD的加法逆元是3
- E .0xD的加法逆元是0x3
- 7.大多数计算机使用同样的机器指令来执行无符号和有符号加法。
- A .正确
- B .错误
- C .不确定。
- 8.多选&#xff1a;我们用一个十六进制的数表示长度w&#61;4的位模式&#xff0c;对于数字的无符号加法逆元的位的表示正确的是&#xff08;&#xff09;
- A .0x8的无符号加法逆元是0x8
- B .0xD的无符号加法逆元是0xD
- C .0xF的无符号加法逆元是0x1
- D .0xF的无符号加法逆元是1
- 14.多选&#xff1a;
short int v&#61;-12345;
unsigned short uv&#61;(unsigned short) v;
那么
- A .v&#61;-12345, uv&#61;53191
- B .v&#61;uv&#61;0xcfc7
- C .v,uv的底层的位模式不一样
- D .v,uv的值在内存中是不一样的
解析&#xff1a;有符号数和无符号数的转换&#xff0c;值不同&#xff0c;位模式不变
- 22.C语言中&#xff0c;字符串被编码为一个以0结尾的字符数组。
- A .正确
- B .错误
- 解析&#xff1a;null的值是0
- 23.下面和代码可移植性相关的C语言属性有&#xff08;&#xff09;
- A .#define
- B .typedef
- C .sizeof()
- D .union
- 解析&#xff1a;#define可以定义宏使得变量可移植&#xff0c;typedef可以使得类型可移植&#xff0c;sizeof()使得不同类型长度可移植。
结对及互评
本周结对学习情况
- [结对同学学号20155306](https://home.cnblogs.com/u/0831j/)
- 结对照片
- 结对学习内容第六章学习任务
其他&#xff08;感悟、思考等&#xff0c;可选&#xff09;
这周的学习任务比较大&#xff0c;在连续几天的努力下终于完成了&#xff0c;只是还需要消化一下。
由于这周进行了缓冲区溢出攻击实验&#xff0c;虚拟机崩了&#xff0c;无法回到图形化界面&#xff0c;也无法git&#xff0c;所以现在的statistics只有本周的代码&#xff0c;并且之前的只有commit&#xff0c;没有push&#xff0c;在虚拟机崩了之后挣扎了很久&#xff0c;依旧没有办法&#xff0c;网上说只能重装&#xff0c;所以重装虚拟机后代码都是同一时间push的&#xff0c;但并非抄袭。!
学习进度条
代码行数&#xff08;新增/累积&#xff09; | 博客量&#xff08;新增/累积&#xff09; | 学习时间&#xff08;新增/累积&#xff09; | 重要成长 | |
---|---|---|---|---|
目标 | 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 | |
第五周 | 300/1300 | 2/9 | 30/90 | |
第六周 | 706/2006 | 1/10 | 50&#43;/140&#43; |
尝试一下记录「计划学习时间」和「实际学习时间」&#xff0c;到期末看看能不能改进自己的计划能力。这个工作学习中很重要&#xff0c;也很有用。
耗时估计的公式
&#xff1a;Y&#61;X&#43;X/N &#xff0c;Y&#61;X-X/N&#xff0c;训练次数多了&#xff0c;X、Y就接近了。
参考&#xff1a;软件工程软件的估计为什么这么难&#xff0c;软件工程 估计方法
计划学习时间:20小时
实际学习时间:50&#43;小时
改进情况&#xff1a;
(有空多看看现代软件工程 课件
软件工程师能力自我评价表)
参考资料
- 《深入理解计算机系统V3》学习指导
- wait函数