20155208 《信息安全系统设计基础》第十四周学习总结
学习目标
-
找出全书你认为学得最差的一章,深入重新学习一下,要求(期末占5分):
-
总结新的收获
-
给你的结对学习搭档讲解或请教,并获取反馈
我本次重新学习的章节是第八章
教材学习内容总结
8.1 异常
-
异常就是控制流中的突变,用来响应处理器状态中的某些变化。
-
异常控制流:现代系统通过使控制流发生突变来对这些情况作出反应。
-
平滑:指在存储器中指令都是相邻的。
-
突变:出现不相邻,通常由诸如跳转、调用、和返回等指令造成。
-
异常控制流ECF:即这些突变。
-
8.1.1 异常处理
-
系统中可能的每种类型的异常都分配了一个唯一的非负整数的异常号。
-
关于ECF:
1.ECF是操作系统用来实现I/O、进程和虚拟存器的基本机制
2.应用程序通过使用一个叫做陷阱或者系统调用的ECF形式,向操作系统请求服务
3.ECF是计算机系统中实现并发的基本机制
4.软件异常机制——C++和Java有try,catch,和throw,C中非本地跳转是setjmp和longjmp
-
异常号的分配:
1、处理器的设计者:被除零、缺页、存储器访问违例、断点以及算数溢出。
2、操作系统内核的设计者分配的:系统调用和来自意外的I/O设备的信号。
-
异常号:到异常表中的索引异常表基址寄存器:异常表的起始地址存放的位置。3、异常与过程调用的异同:
1、过程调用时,在跳转到处理器之前,处理器将返回地址压入栈中。然而,根据异常的类型,返回地址要么是当前指令,要么是下一条指令。
2、处理器把一些额外的处理器状态压入栈里,在处理程序返回时,重新开始被中断的程序会需要这些状态。
3、如果控制从一个用户程序转移到内核,那么所有这些项目都被压到内核栈中,而不是压到用户栈中。
4、异常处理程序运行在内核模式下,意味着它们对所有的系统资源都有完全的访问权限。
-
出现异常的处理方式:
1.处理器检测到有异常发生
2.通过异常表,进行间接过程调用,到达异常处理程序
3.完成处理后:①返回给当前指令②返回给下一条指令③终止
-
8.1.2异常的类别
-
中断: 来自I/O设备的信号,异步,总是返回到下一条指令
-
陷阱: 有意的异常,同步,总是返回到下一条指令
-
故障: 潜在可恢复的错误,同步,可能返回到当前指令
-
终止: 不可恢复的错误,同步,不会返回
-
故障指令:执行当前指令导致异常
-
中断处理程序:硬件中断的异常处理程序。
8.2 进程
-
异常是允许操作系统提供进程的概念所需要的基本构造块。
-
进程:一个执行中的程序的实例。
-
上下文是由程序正确运行所需要的状态组成的,这个状态包括存放在存储器中的程序的代码和数据,它的栈、通用目的寄存器的内容、程序计数器、环境变量以及打开文件描述符的集合。
-
进程提供给应用程序的关键抽象:一个独立的逻辑控制流,独占地使用处理器;一个私有的地址空间,独占地使用存储器系统。
-
一共有256种不同的异常类型。
-
8.2.1 逻辑控制流
程序计数器:唯一的对应于包含在程序的可执行目标文件中的指令,或者是包含在运行时动态链接到程序的共享对象中的指令。这个PC值的序列叫做逻辑控制流,简称逻辑流。
-
8.2.2 并发流
-
并发流:一个逻辑流的执行在时间上与另一个流重叠。
并发:多个流并发地执行的一般现象。
多任务:一个进程和其他进程轮流运行的概念。
时间片:一个进程执行它的控制流的一部分的每一时间段。
多任务也叫时间分片。
-
并行流:如果两个流并发的运行在不同的处理器核或者计算机上
-
8.2.3 私有地址空间
-
这个空间中某个地址相关联的那个存储器字节是不能被其他进程读或者写的
-
8.2.4 用户模式和内核模式
-
当设置了模式位时,进程就运行在内核模式中
-
没有设置模式位时,进程就运行在用户模式中。用户模式中的进程不允许执行特权指令。
-
进程从用户模式变位内核模式的唯一方法是通过诸如中断、故障或者陷入系统调用这样的异常。 /proc文件系统允许用户模式进程访问内核数据结构的内容
-
8.2.5 上下文切换
-
上下文切换:是较高形式的异常控制流来实现多任务
-
调度:在进程执行的某些时刻,内核可以决定抢占当前进程,并重新开始一个先前被抢占的进程。这种决定叫做调度
-
当内核代表用户执行系统调用时,可能会发生上下文切换。如果系统调用因为等待某个事件发生阻塞,那么内核可以让当前进程休眠,切换到另一个进程
-
中断也可能引发上下文切换
8.3 系统调用错误处理
-
错误处理包装函数:包装函数调用基本函数,检查错误,如果有任何问题就终止。 8.4 进程控制
-
8.4.1获取进程ID
每个进程都有一个唯一的正数进程ID gitpid函数返回调用进程的PID,getppid函数返回它的父进程的PID(创建调用进程的进程)
#include<sys/types.h>
#include<unistd.h>
pid_t getpid(void);
pid_t getppid(void);
-
8.4.2 创建和终止进程
进程状态:运行、停止、终止 exit函数以status退出状态来终止进程
fork函数
父进程通过调用fork函数创建一个新的运行子进程
当父进程调用fork时,子进程可以读写父进程中打开的任何文件
父进程和子进程之间最大的区别在于有不同的PID
调用一次,返回两次
并发执行
相同但是独立的地址空间
共享文件
- 8.4.3 回收子进程 僵死进程没有运行,他们仍然消耗系统的存储器资源 一个进程可以通过调用waitpid函数来等待它的子进程终止或停止
#include<sys/types.h>
#include<sys/wait.h>
pid_t waitpid(pid_t pid,int *status,int options);
1、判定等待集合的成员(由参数pid确定) pid>0,等待集合就是一个单独的子进程,它的进程ID等于pid pid=-1,等待集合就是由父进程所有的子进程组成的
2、修改默认行为 WNOHANG:默认行为是挂起调用进程,直到有子进程终止 WUNTRCED:默认行为是只返回已经终止的子进程 WNOHANG|WUNTRCED:立即返回
3、检查已回收子进程的退出状态
4、错误条件 如果调用进程没有子进程,那么waitpid返回-1,设置errno为ECHILD 如果waidpid函数被一个信号中断,那么它返回-1,并设置errno为EINTR
5、wait函数
6、使用waitpid的示例
-
8.4.4 让进程休眠
-
sleep函数将一个进程挂起一段指定的时间
#include<unistd.h>
unsigned int sleep(unsigned int secs);
- pause函数让调用函数休眠,直到该进程收到一个信号
#include<unisted.h>
int pause(void);
- 8.4.5 加载并运行程序
execve函数
在当前进程的上下文中加载并运行一个新程序,execve
调用一次并从不返回 argv参数列表,envp环境变量getenv函数
8.5 信号
-
底层的硬件异常是由内核异常处理程序处理的,正常情况下,对用户进程而言是不可见的。
-
其他信号对应于内核或者其他用户进程中较高层的软件事件
-
8.5.1 信号术语
发送信号的两个不同步骤:
1、发送信号:内核通过更新目的进程上下文中的某个状态,发送(递送)一个信号给目的进程。
2、接收信号:信号处理程序捕获信号的基本思想。
发送信号的两个原因:
1、内核监测到一个系统事件,比如被零除错误或者子进程终止。
2、一个进程调用了kill函数,显式地要求内核发送一个信号给目的进程。一个进程可以发送信号给它自己。
待处理信号:一个只发出而没有被接收的信号
1、一个进程可以有选择性地阻塞接收某种信号。
2、待处理信号不会被接收,直到进程取消对这种信号的阻塞。
一个待处理信号最多只能被接受一次,pending位向量:维护着待处理信号集合,blocked向量:维护着被阻塞的信号集合。
非本地跳转
-
c语言中,用户级的异常控制流形式,通过setjmp和longjmp函数提供。
-
setjump函数在env缓冲区中保存当前调用环境,以供后面longjmp使用,并返回0.
-
调用环境:程序计数器,栈指针,通用目的寄存器
-
longjmp函数从env缓冲区中恢复调用环境,然后触发一个从最近一次初始化env的setjmp调用的返回。然后setjmp返回,并带有非零的返回值retval。
注:
-
setjmp函数只被调用一次,但返回多次;
-
longjmp函数被调用一次,但从不返回。
操作进程的工具
-
STRACE:打印一个正在运行的程序和他的子程序调用的每个系统调用的痕迹
-
PS:列出当前系统中的进程,包括僵死进程
-
TOP:打印出关于当前进程资源使用的信息
-
PMAP:显示进程的存储器映射
代码调试
exec
exec1
#include <unistd.h>
int main()
{
char *arglist[3];
arglist[0] = "ls";
arglist[1] = "-l";
arglist[2] = 0 ;//NULL
printf("* * * About to exec ls -l\n");
execvp( "ls" , arglist );
printf("* * * ls is done. bye");
return 0;
}
- exec1.c中execvp()会从PATH 环境变量所指的目录中查找符合参数file 的文件名,找到后便执行该文件,然后将第二个参数argv传给该欲执行的文件 如果执行成功则函数不会返回,执行失败则直接返回-1,失败原因存于errno中 编译运行结果如下,没有返回“* * * man is done. bye”
exec2
- 将exevp函数传入的arglist[0]改为arglist[1],此时exevp函数没有调用成功,于是打印出“* * * ls is done. bye”这句话
#include <stdio.h>
#include <unistd.h>
int main()
{
char *arglist[3];
arglist[0] = "ls";
arglist[1] = "-l";
arglist[2] = 0 ;
printf("* * * About to exec ls -l\n");
execvp( arglist[0] , arglist );
printf("* * * ls is done. bye\n");
}
- 运行截图如下:
exec3
- 函数中execlp()会从PATH 环境变量所指的目录中查找符合参数file的文件名,找到后便执行该文件,然后将第二个以后的参数当做该文件的argv[0]、argv[1]……最后一个参数必须用空指针(NULL)作结束
#include <stdio.h>
#include <unistd.h>
int main()
{
char *arglist[3];
char *myenv[3];
myenv[0] = "PATH=:/bin:";
myenv[1] = NULL;
arglist[0] = "ls";
arglist[1] = "-l";
arglist[2] = 0 ;
printf("* * * About to exec ls -l\n");
// execv( "/bin/ls" , arglist );
// execvp( "ls" , arglist );
// execvpe("ls" , arglist, myenv);
execlp("ls", "ls", "-l", NULL);
printf("* * * ls is done. bye\n");
}
- 运行截图如下:
fork
forkdemo1
- 打印进程pid,然后调用fork函数生成子进程,休眠一秒后再次打印进程id,这时父进程打印 子进程pid,子进程返回0。
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
int ret_from_fork, mypid;
mypid = getpid();
printf("Before: my pid is %d\n", mypid);
ret_from_fork = fork();
sleep(1);
printf("After: my pid is %d, fork() said %d\n",
getpid(), ret_from_fork);
return 0;
}
- 运行截图如下:
forkdemo2
- 调用两次fork,一共产生四个子进程,所以会打印四个aftre输出。
#include <stdio.h>
#include <unistd.h>
int main()
{
printf("before:my pid is %d\n", getpid() );
fork();
fork();
printf("aftre:my pid is %d\n", getpid() );
return 0;
}
- 运行截图如下:
forkdemo3
- fork产生子进程,父进程返回子进程pid,不为0,所以输出父进程的那句话,子进程返回0,所以会输出子进程那句话。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
int fork_rv;
printf("Before: my pid is %d\n", getpid());
fork_rv = fork(); /* create new process */
if ( fork_rv == -1 ) /* check for error */
perror("fork");
else if ( fork_rv == 0 ){
printf("I am the son. my pid=%d\n", getpid());
exit(0);
}
else{
printf("I am the father. my son is %d\n", fork_rv);
exit(0);
}
return 0;
}
- 运行截图如下:
forkdemo4
- 先打印进程pid,然后fork创建子进程,父进程返回子进程pid,所以输出parent一句,休眠十秒;子进程返回0,所以输出child与之后一句。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
int fork_rv;
printf("Before: my pid is %d\n", getpid());
fork_rv = fork(); /* create new process */
if ( fork_rv == -1 ) /* check for error */
perror("fork");
else if ( fork_rv == 0 ){
printf("I am the son. my pid=%d\n", getpid());
printf("parent pid= %d, my pid=%d\n", getppid(), getpid());
exit(0);
}
else{
printf("I am the father. my son is %d\n", fork_rv);
sleep(10);
exit(0);
}
return 0;
}
- 运行截图如下:
forkgdb
- 父进程打印是先打印两句,然后休眠一秒,然后打印一句,子进程先打印一句,然后休眠一秒,然后打印两句。并且这两个线程是并发的,所以可以看到在一个线程休眠的那一秒,另一个线程在执行,并且线程之间相互独立互不干扰
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int gi=0;
int main()
{
int li=0;
static int si=0;
int i=0;
pid_t pid = fork();
if(pid == -1){
exit(-1);
}
else if(pid == 0){
for(i=0; i<5; i++){
printf("son li:%d\n", li++);
sleep(1);
printf("son gi:%d\n", gi++);
printf("son si:%d\n", si++);
}
exit(0);
}
else{
for(i=0; i<5; i++){
printf("father li:%d\n", li++);
printf("father gi:%d\n", gi++);
sleep(1);
printf("father si:%d\n", si++);
}
exit(0);
}
return 0;
}
- 运行截图如下:
psh
psh1
- 输入要执行的指令,回车表示输入结束,然后输入的每个参数对应到函数中,再调用对应的指令。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define MAXARGS 20
#define ARGLEN 100
int execute( char *arglist[] )
{
execvp(arglist[0], arglist);
perror("execvp failed");
exit(1);
}
char * makestring( char *buf )
{
char *cp;
buf[strlen(buf)-1] = '\0';
cp = malloc( strlen(buf)+1 );
if ( cp == NULL ){
fprintf(stderr,"no memory\n");
exit(1);
}
strcpy(cp, buf);
return cp;
}
- 运行截图:
psh2
- 比起1来,多了循环判断,不退出的话就会一直要你输入指令,并且对于子程序存在的状态条件。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <signal.h>
#define MAXARGS 20
#define ARGLEN 100
char *makestring( char *buf )
{
char *cp;
buf[strlen(buf)-1] = '\0';
cp = malloc( strlen(buf)+1 );
if ( cp == NULL ){
fprintf(stderr,"no memory\n");
exit(1);
}
strcpy(cp, buf);
return cp;
}
void execute( char *arglist[] )
{
int pid,exitstatus;
pid = fork();
switch( pid ){
case -1:
perror("fork failed");
exit(1);
case 0:
execvp(arglist[0], arglist);
perror("execvp failed");
exit(1);
default:
while( wait(&exitstatus) != pid )
;
printf("child exited with status %d,%d\n",
exitstatus>>8, exitstatus&0377);
}
}
int main()
{
char *arglist[MAXARGS+1];
int numargs;
char argbuf[ARGLEN];
numargs = 0;
while ( numargs < MAXARGS )
{
printf("Arg[%d]? ", numargs);
if ( fgets(argbuf, ARGLEN, stdin) && *argbuf != '\n' )
arglist[numargs++] = makestring(argbuf);
else
{
if ( numargs > 0 ){
arglist[numargs]=NULL;
execute( arglist );
numargs = 0;
}
}
}
return 0;
}
- 运行截图:
上周错题总结整理
1、Y86-64中()指令没有访存操作.
A . rrmovl
B . irmovq
C . rmmovq
D . pushq
E . jXX
F . ret
正确答案: A B E
2、The following table gives the parameters for a number of different caches. For each cache, determine the number of cache sets (S), tag bits (t), set index bits (s), and block offset bits (b)
A . 第三行S为1
B . 第一行t为24
C . 第二行s为5
D . 第三行b的值为5
正确答案: A C D
3、有关磁盘操作,说法正确的是()
A . 对磁盘扇区的访问时间包括三个部分中,传送时间最小。
B . 磁盘以字节为单位读写数据
C . 磁盘以扇区为单位读写数据
D . 读写头总处于同一柱面
正确答案: A C D
错误总结: 磁盘操作中,读写头总处于同一柱面。
4、有关RAM的说法,正确的是()
A . SRAM和DRAM掉电后均无法保存里面的内容。
B . DRAM将一个bit存在一个双稳态的存储单元中
C . 一般来说,SRAM比DRAM快
D . SRAM常用来作高速缓存
E . DRAM将每一个bit存储为对一个电容充电
F . SRAM需要不断刷新
G . DRAM被组织为二维数组而不是线性数组
正确答案: A C D E G
错误总结: SRAM常用来作高速缓存(SRAM:静态随机存取存储器)
代码托管
结伴学习
其他(感悟、思考等,可选)
这次的博客是自己选一章觉得自己之前学的比较差的进行再一次学习,所以这次的学习就要更加的深入,更加的详细,将上次遗留的问题进行解决,这次的收获也特别大,而且这次也和接伴对象一起学习,收获也很大。