2017-2018-1 20155324 《信息安全系统设计基础》第十三周学习总结
找出全书你认为最重要的一章,深入重新学习一下
- 完成这一章所有习题
- 详细总结本章要点
- 给你的结对学习搭档讲解你的总结并获取反馈
重温第八章
教材学习内容详细总结
8.1异常
- 异常就是控制流中的突变,用来响应处理器状态中的某些变化
8.1.1 异常处理
- 系统中可能的每种类型的异常都分配了一个唯一的非负整数的异常号。
- 处理器:被零除、缺页、存储器访问违例、断点以及算术溢出
- 操作系统:系统调用和来自外部I/O设备的信号
异常类似于过程调用,两者的区别是:
- 过程调用时,跳转到处理程序之前,处理器将返回地址压入栈中;而异常时,根据异常的不同类型,返回地址必是当前指令或下一条指令。
- 处理器会把一些额外的处理器状态压入栈中。
- 如果控制从用户程序转移到内核,所有的项目都被压到内核栈中。
- 异常处理程序运行在内核模式下。
异常的类别:
- 中断:异步发生,是来自处理器外部I/O设备的结果。
- 陷阱:有意的异常,是执行一条指令的结果。
- 故障:由错误情况引起,可能会被故障程序修正。
- 终止:不可恢复的致命错误造成的结果。
进程:执行中程序
- 一个逻辑流在执行时间上与另一个流重叠,称为并发流。
- 处理器通过设置模式位来提供限制应用可以执行的指令以及它可以访问的地址空间范围的功能。没有设置模式位时,进程运行在用户模式中。
- 上下文切换:用于实现多任务
8.1.3Linux系统中的异常
- Linux故障和终止 :
- 除法错误:异常号:0,通常报告为“浮点异常”
- 一般保护故障:异常号:13,Linux不会尝试回复这类故障,通常报告为“段故障”
- 缺页:异常号:14,是会重新执行产生故障的指令的一个异常事例
- 机器检查:异常号:18,从不返回控制给应用程序
- Linux系统调用:
- 每个系统调用都有一个唯一的整数号,对应于一个到内核中跳转表的偏移量
- 将系统调用和与他们相关的包装函数称为系统级函数
8.2进程
- 异常是允许操作系统提供进程的概念所需要的基本构造块。
- 进程:一个执行中的程序的实例。上下文是由程序正确运行所需要的状态组成的,这个状态包括存放在存储器中的程序的代码和数据,它的栈、通用目的寄存器的内容、程序计数器、环境变量以及打开文件描述符的集合。
- 进程提供给应用程序的关键抽象:
- 一个独立的逻辑控制流,独占地使用处理器;
- 一个私有的地址空间,独占地使用存储器系统。
8.2.1逻辑控制流
- 程序计数器:唯一的对应于包含在程序的可执行目标文件中的指令,或者是包含在运行时动态链接到程序的共享对象中的指令。这个PC值的序列叫做逻辑控制流,简称逻辑流。
8.2.2并发流
- 并发流:一个逻辑流的执行在时间上与另一个流重叠。
并发:多个流并发地执行的一般现象。
多任务:一个进程和其他进程轮流运行的概念。
时间片:一个进程执行它的控制流的一部分的每一时间段。
多任务也叫时间分片。
- 并行流:如果两个流并发的运行在不同的处理器核或者计算机上。
8.2.3私有地址空间
- 这个空间中某个地址相关联的那个存储器字节是不能被其他进程读或者写的
8.3系统调用错误处理
- 定义错误报告函数,简化代码
- 错误处理包装函数
8.4进程控制
进程总处于三种状态
(1)运行:进程要么在CPU上执行,要么在等待被执行且最终会被内核调度。
(2)停止:程序的执行被挂起,,且不会被调度。
(3)终止:进程用永远停止了。
终止原因:
(1)收到一个信号,默认行为是终止进程
(2)从主进程返回
(3)调用exit函数
- fork函数: 因为父进程的PID总是非零的,返回值就提供一个明确的方法来分辨程序是在父进程还是在子进程中执行。
- fork函数的特点:
(1)调用一次,返回两次
(2)并发执行
(3)相同的但是独立的地址空间
(4)共享文件
8.5信号
- Unix信号:更高层的软件形式的异常,允许进程中断其他进程
- P505:30种不同类型的信号表
- 当有多个线程在等待同一个信号量时,你不能预测V操作要重启哪一个线程。
- 信号量不变性:一个正在运行的程序绝不能进入这样一种状态,也就是一个正确初始化了的信号量有一个负值。
- 信号量定义:
type semaphore=record
count: integer;
queue: list of process
end;
var s:semaphore;
8.5.4信号处理问题
- 捕获多个信号时的问题:
待处理信号被阻塞
待处理信号不会排队等待
系统调用可以被中断
线程安全
- 线程安全:当且仅当被多个并发线程反复地调用时,它会一直产生正确的结果。
- 线程不安全:如果一个函数不是线程安全的,就是线程不安全的。
- 线程不安全的类:
- 不保护共享变量的函数
- 保持跨越多个调用的状态的函数。
- 返回指向静态变量的指针的函数。解决办法:重写函数和加锁拷贝。
- 调用线程不安全函数的函数。
死锁
- 死锁:一组线程被阻塞了,等待一个永远也不会为真的条件。
- 程序员使用P和V操作顺序不当,以至于两个信号量的禁止区域重叠。
- 重叠的禁止区域引起了一组称为死锁区域的状态。
- 死锁是一个相当难的问题,因为它是不可预测的。
- 互斥锁加锁顺序规则:如果对于程序中每对互斥锁(s,t),- 给所有的锁分配一个全序,每个线程按照这个顺序来请求锁,并且按照逆序来释放,这个程序就是无死锁的。
系统调用:进程控制
- fork系统调用
函数作用:创建一个子进程
形式:pid_tfork(void);
pid_t vfork(void);
说明: 使用vfork创子进程时,不会进程父进程的上下文
返回值:
[返回值=-1]子进程创建失败
[返回值=0]子进程创建成功
[返回值>0]对父进程返回子进程PID
#include<stdio.h>
#include<sys/stat.h>
#include<unistd.h>
int main() {
pid_t id = fork();
if (id < 0) {
perror("子进程创建失败!");
} else {
if (id == 0) {
printf("子进程工作:PID=%d,PPID=%d\n", getpid(), getppid());
}else
{
printf("父进程工作:PID=%d,PPID=%d,子进程PID=%d\n", getpid(), getppid(),id);
sleep(5);
}
}
}
wait函数
- 通过帮助文档,可以知道其头文件为:
#include <sys/types.h>
#include <sys/wait.h>
exec函数
其实说exec函数在严格意义上来讲是不对的,因为Linux中没有exec函数,而是有6个以exec开头的函数族,exec函数族提供了一个在进程中启动另一个程序执行的方法。
头文件#include <unistd.h>
,一共有六个函数族,其中只有execve为系统调用,运行另一个程序,用法为
int execve(const char *filename, char *const argv[],
char *const envp[]);
理解信号机制
处理方法可以分为三类:
第一种是类似中断的处理程序,对于需要处理的信号,进程可以指定处理函数,由该函数来处理。
第二种方法是,忽略某个信号,对该信号不做任何处理,就象未发生过一样。
第三种方法是,对该信号的处理保留系统的默认值,这种缺省操作,对大部分的信号的缺省操作是使得进程终止。进程通过系统调用signal来指定进程对某个信号的处理行为。
掌握管道和I/O重定向
掌握管道和I/O重定向
在Unix系统中,每个进程都有STDIN、STDOUT和STDERR这3种标准I/O,它们是程序最通用的输入输出方式。几乎所有语言都有相应的标准I/O函数,比如,C语言可以通过scanf从终端输入字符,通过printf向终端输出字符。
Linux 管理的一个最重要并且有趣的话题是 I/O 重定向。此功能在命令行中使你能够将命令的输入输出取自或送到文件中,或者可以使用管道将多个命令连接在一起以形成所谓的“命令管道”。
教材学习中的问题和解决过程
安装信号时对signal()
不理解,通过百度发现
第一个参数指定信号的值,第二个参数指定针对前面信号值的处理,可以忽略该信号(参数设为SIG_IGN);可以采用系统默认方式处理信号(参数设为SIG_DFL);也可以自己实现处理方式(参数指定一个函数地址)。如果signal()调用成功,返回最后一次为安装信号signum而调用signal()时的handler值;失败则返回SIG_ERR。
传递给信号处理例程的整数参数是信号值,这样可以使得一个信号处理例程处理多个信号。
#include <signal.h>
#include <unistd.h>
#include <stdio.h>
void sigroutine(int dunno)
{ /* 信号处理例程,其中dunno将会得到信号的值 */
switch (dunno) {
case 1:
printf("Get a signal -- SIGHUP ");
break;
case 2:
printf("Get a signal -- SIGINT ");
break;
case 3:
printf("Get a signal -- SIGQUIT ");
break;
}
return;
}
int main() {
printf("process id is %d ",getpid());
signal(SIGHUP, sigroutine); //* 下面设置三个信号的处理方法
signal(SIGINT, sigroutine);
signal(SIGQUIT, sigroutine);
for (;;) ;
}
收获:对于一个函数类型不太能够理解的话,可以通过分解的方式来理解。
课后作业及实现
进程A和B是互相并发的,就像B和C一样,因为它们各自的执行是重叠的,也就是一个进程在另一个进程结束前开始。进程A和C不是并发的,因为它们的执行没有重叠;A在C开始之前就结束了。
A.这里的关键点是子进程执行了两个prin七f语句。在fork返回之后,它执行了第8行的prin七fe然后它从if语句中出来,执行了第9行的printf语句。下面是子进程产生的输出:printfl:x=2 printf2: x=1
B.父进程只执行了第9行的printf:printf2: x=0
练习8.3列出下面程序所有可能的输出序列:
int main()
{
if(Fork()==0)
{
printf("a"),fflush(stdout);
}
else
{
printf("b"),fflush(stdout);
waitpid(-1,NULL,0);
}
printf("c"),fflush(stdout);
exit(0)
}
父进程调用fork()函数后,子进程满足if中的条件,会打印a,然后打印c;而父进程会执行b,挂起等待子进程终止。打印的最后一项是父进程的c。
子进程直接执行完成,则输出为acbc;父进程先执行后挂起等待子进程执行完成,则输出为bacc;子进程执行后父进程执行,则输出为abcc。
A.每次我们运行这个程序,就会产生6个输出行。
B.输出行的顺序根据系统不同而不同,取决于内核如何交替执行父子进程的指令。一般而言,满足下图的任意拓扑排序都是合法的顺序:
答:
答:
答:
只要休眠进程收到一个未被忽略的信号,sleep函数就会提前返回。但是,因为收到一个SIGINT信号的默认行为就是终止进程(见图8-25 ),我们必须设置一个SIGINT处理程序来允许sleep函数返回。处理程序简单地捕获SIGNAL,并将控制返回给sleep函数,该函数会立即返回。
上周错题总结
- 实验3中,在Ubuntu虚拟机中编译多线程程序时,gcc使用()选项
A .
-g
B .
-lthread
C .
-pthread
D .
-lpthread
正确答案: C 你的答案: D
实际环境中只有-pthread可用
- 实验1:mount -t nfs -o nolock 192.168.0.56:/root/share /host,其中的IP是()的IP
A .
Windows 宿主机
B .
Ubuntu虚拟机
C .
ARM实验箱
D .
以上都不对
正确答案: B 你的答案: C
嵌入式开发中,通过nfs系统把Ubuntu虚拟机的一个目录映射成ARM实验箱的Linux系统的一个目录进行调试是一个惯用法,程序调试没有问题了,再烧写到实验箱的Linux的系统中,这样实验箱重启了程序也可以用了。
- 有关套接字接口函数open_clientfd()、open_listenfd(),下面说法正确的是()
- A .
这两个函数中open_clientfd()只可以用于客户端编程
B .
这两个函数中open_clientfd()可以用于客户端和服务器端编程
C .
这两个函数中open_listenfd()只可以用于服务器端编程
D .
open_clientfd()中的port参数是客户端的端口
E .
open_clientfd()中的port参数是服务器端的端口
F .
open_clientfd()返回的clientfd可以有Unix I/O接口读写
G .
open_listenfd()返回的listenfd可以有Unix I/O接口读写
正确答案: A C E F 你的答案: A B D G
p660
- 有关socket 接口中的connect(),下面说法正确的是()
- A .
这个函数用于客户端编程
B .
这个函数用于服务器端编程
C .
调用connect会发生阻塞,连接成功程序会继执行
D .
调用connect()成功返回的文件描述符可以用来数据传输
正确答案: A C D 你的答案: A B D
p654
其他(感悟、思考等,可选)
本学期的课程虽然已经接近尾声了,但是我们学习的道路才刚刚开始。不能因为最后一段路就要放松警惕,更要提高自我意识,通过自己的努力来走好每一段路。
代码托管
学习进度条
代码行数(新增/累积) | 博客量(新增/累积) | 学习时间(新增/累积) | 重要成长 | |
---|---|---|---|---|
目标 | 5000行 | 30篇 | 400小时 | |
第一周 | 85/85 | 1/1 | 5/5 | |
第二周 | 150/230 | 1/2 | 10/15 | |
第三周 | 50/280 | 1/3 | 7/22 | |
第四周 | 70/350 | 1/4 | 5/27 | |
第五周 | 100/450 | 2/6 | 5/32 | |
第六周 | 50/500 | 1/7 | 10/42 | |
第七周 | 70/570 | 2/9 | 10/55 | |
第八周 | 80/650 | 1/10 | 8/63 | |
第九周 | 100/750 | 3/13 | 8/71 | |
第十周 | 80/830 | 1/14 | 8/79 | |
第十一周 | 50/880 | 2/16 | 10/89 | |
第十二周 | 40/920 | 1/17 | 10/89 | |
第十三周 | 60/980 | 2/19 | 10/89 |
尝试一下记录「计划学习时间」和「实际学习时间」,到期末看看能不能改进自己的计划能力。这个工作学习中很重要,也很有用。
耗时估计的公式
:Y=X+X/N ,Y=X-X/N,训练次数多了,X、Y就接近了。
- 计划学习时间:XX小时
- 实际学习时间:XX小时
- 改进情况:
(有空多看看现代软件工程 课件
软件工程师能力自我评价表)