2017-2018-1 20155231 《信息安全系统设计基础》第14周学习总结
2017-2018-1 学号 《信息安全系统设计基础》第14周学习总结
重新学习第八章 异常控制流
教材学习内容详细总结
第一节 异常
异常是异常控制流的一种形式,由硬件和操作系统实现。简单来说,就是控制流中的突变。
事件:即状态变化,与当前指令的执行可能直接相关,也可能没有关系。
出现异常的处理方式:
- 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文件系统,将许多内核数据结构的内容输出为一个用户程序可以读的文本文件的层次结构。
但我在虚拟机中输入告诉我权限不够?
这个通过最后一节得知,是我输入方法不对,应该输入的是cat打印指令,再接后面的目录,如下图:
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函数
2.创建进程
父进程通过调用fork函数来创建一个新的运行子进程。fork函数定义如下:
#include <sys/types.h> #include <unistd.h>
pid_t fork(void)
fork函数只被调用一次,但是会返回两次:父进程返回子进程的PID,子进程返回0.如果失败返回-1.
`/* $begin fork */
include "csapp.h"
int main()
{
pid_t pid;
int x = 1;
pid = Fork(); //line:ecf:forkreturn
if (pid == 0) { /* Child */
printf("child : x=%d\n", ++x); //line:ecf:childprint
exit(0);
}
/* Parent */
printf("parent: x=%d\n", --x); //line:ecf:parentprint
exit(0);
}
/* $end fork */`
调用一次,返回两次
并发执行,内核能够以任何方式交替执行它们的逻辑控制流中的指令
相同和不同:
相同:用户栈、本地变量值、堆、全局变量值、代码
不同:私有地址空间
共享文件:子进程继承了父进程所有的打开文件。参考10.6节笔记。
调用fork函数n次,产生2的n次方个进程。
3.终止进程
用exitinclude
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:8.20
- 问题1解决方案:这道题不需要在环境变量里抓取
name
,只要用
execve
int main
(
int argc,char* args[])
{ execve
(
"/bin/ls"
, args, environ);
return 0
;
}
- 问题2:8.22
- 问题2解决方案:不太清楚
/bin/sh -c command
中,
-c
以及
command
的意义。
所以把
-c
和
command
都当做
/bin/sh
的参数
int mysystem(char *command)
{
int status;
char *argv[4];
char *a0 = "sh";
char *a1 ="-c";
if(fork()==0)
/*
子进程
*/
{
argv[0] = a0;
argv[1] = a1;
argv[2] = command;
argv[3] = NULL;
execve("/bin/sh",args,environ);
return -1;//执行异常
}
else
{/*父进程*/
if(wait(&status) > 0)
{
if(WIFEXITED(status) != 0)
return WEXITSTATUS
(status);
else
return status;}
else
return -1;//wait异常}
}
代码调试中的问题和解决过程
- 问题1:8.25
fgets
的定义 - 问题1解决方案:
定义如下:
char *fgets(char *buf,int bufsize,FILE *stream);
参数:
*buf:
字符型指针,指向用来存储所得数据的地址。
bufsize:
整型数据,指明
buf
指向的字符数组的大小。
*stream:
文件结构体指针,将要读取的文件流。
这个应该是用
alarm
发送信号给自己,然后在信号处理程序里面做文章。
显然,在
tfgets
里一开始需要调用
fgets
。然而,因为五秒时间到了,
fgets
还没有返
回,
所以我们必须在处理程序里直接跳转到某个地方进行
tfgets
的
NULL
返回。
这就需要
用到非本地跳转
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <signal.h>
#include <setjmp.h>
sigjmp_buf env;
void tfgets_handler(int sig)
{
signal
(SIGALRM, SIG_DFL);
siglongjmp(env,1);
}
char *tfgets(cha r*buf,int bufsize, FILE *stream)
{
staticconst int TimeLimitSecs = 5;
signal(SIGALRM, tfgets_handler)alarm(TimeLimitSecs);
int rc = sigsetjmp(env, 1);
if(rc ==0) return fgets(buf, bufsize, stream);
else
return NULL;
//alarm
,
time out
}
代码托管
上周考试错题总结
- 错题1及原因,理解情况
- 错题2及原因,理解情况
- ...
结对及互评
点评模板:
- 博客中值得学习的或问题:
- xxx
- xxx
- ...
- 代码中值得学习的或问题:
- xxx
- xxx
- ...
- 其他
本周结对学习情况
- [结对同学学号1](博客链接)
- 结对照片
- 结对学习内容
- XXXX
- XXXX
- ...
其他(感悟、思考等,可选)
xxx
xxx
学习进度条
代码行数(新增/累积) | 博客量(新增/累积) | 学习时间(新增/累积) | 重要成长 | |
---|---|---|---|---|
目标 | 5000行 | 20篇 | 400小时 | |
第14周 | 532 / 3821 | 1/15 | 14/199 | 重新学习第8章 |
尝试一下记录「计划学习时间」和「实际学习时间」,到期末看看能不能改进自己的计划能力。这个工作学习中很重要,也很有用。
耗时估计的公式
:Y=X+X/N ,Y=X-X/N,训练次数多了,X、Y就接近了。
-
计划学习时间:20小时
-
实际学习时间:14小时
-
改进情况:
(有空多看看现代软件工程 课件
软件工程师能力自我评价表)