20145321 《信息安全系统设计基础》第十一周学习总结
教材内容总结
异常控制流
- 异常控制流:现代操作系统通过使控制流发生突变来对系统状态做出反应,这些突变称为异常控制流。
- 异常控制流发生在计算机系统的各个层次
硬件层:硬件检测到的事件会触发控制突然装移到异常处理程序
操作系统层:内核通过上下文转换将控制从一个用户进程转移到另一个用户进程。
应用层:一个进程可以发送信号到到另一个进程,而接收者将会控制突然转移到它的一个信号处理程序。
一个程序可以通过回避通常的栈规则,并执行到其它函数中任意位置的非本地跳转来对错误做出反应。
异常
-
异常是异常控制流的一种形式,是控制流中的突变,用来响应处理器状态中的某些变化,由硬件和操作系统实现。
事件:状态变化,可能和当前指令的执行有关。
- 异常处理程序完成后
处理程序将控制返回给事件发生时正在执行的当前指令
处理程序将控制返回给没有发生异常将会执行的下一条指令
处理程序终止被中断的程序
- 异常表:当处理器检测到有事件发生时,它会通过跳转表,进行一个间接过程调用(异常),到异常处理程序。系统启动时操作系统分配和初始化一张异常表。
- 异常号:系统中可能的某种类型的异常都分配了一个唯一的非负整数的异常号。异常号是到异常表中的索引,异常表的起始地址放在一个叫做异常表基址寄存器的特殊CPU寄存器里。
- 异常的类别
- 系统调用
进程
- 进程的经典定义:一个执行中的程序的实例
- 系统中每个程序都是运行在某个进程上下文中的。
- 上下文是由程序正确运行所需的状态组成的。状态包括存放在存储器中的程序代码和数据,栈、通用目的寄存器内容、程序计数器、环境变量和打开文件描述符的集合。
- 进程提供给应用程序的关键抽象
一个独立的逻辑控制流,提供一个假象:程序独占地使用处理器
一个私有的地址空间,提供一个假象:程序独占地使用存储器系统
- 逻辑控制流
- 程序计数器(PC)值的序列叫做逻辑控制流,简称逻辑流。
- 并发流
- 一个逻辑流的执行在时间上与另一个流重叠(与是否在同一处理器无关),这两个流并发的运行。
- 上下文切换
- 内核重新启动一个被抢占的进程所需的状态。
- 上下文切换机制
保存当前进程的上下文
恢复某个先前被抢占的进程被保存的上下文
将控制传递给这个新恢复的进程。
- 上下文切换原因
内核代表用户执行系统调用时(进程休眠)
中断
系统调用错误处理
- 包装函数被封装在一个源文件(csapp.c)中,这个文件被编译和链接到每个程序中。一个独立的头文件(csapp.h)中包含这些包装函数的函数原型。
- 包装函数调用基本函数,检查错误,如果有任何问题就终止。
进程控制
- 每个进程都有一个唯一的正数进程ID(PID)。
#include <sys/types.h> #include <unistd.h> pid_t getpid(void); 返回调用进程的PID pid_t getppid(void); 返回父进程的PID(创建调用进程的进程)
- 进程的三种状态
运行 停止:被挂起且不会被调度 终止:永远停止。 - 收到信号,默认行为为终止进程 - 从主程序返回 - 调用exit函数
- 回收子进程
- 进程终止后还要被父进程回收,否则处于僵死状态。
- 如果父进程没有来得及回收,内核会安排init进程来回收他们。init进程的PID为1。
-
创建进程
-
父进程通过调用fork函数来创建一个新的运行子进程。fork函数定义如下:
-
#include <sys/types.h> #include <unistd.h> pid_t fork(void);
-
-
fork函数只被调用一次,但是会返回两次:父进程返回子进程的PID,子进程返回0.如果失败返回-1。
-
-
终止进程
-
exit函数
-
#include <stdlib.h> void exit(int status);
-
-
exit函数以status退出状态来终止进程。
-
信号
- 发送信号的两个不同步骤
发送信号:内核通过更新目的进程上下文中的某个状态,发送(递送)一个信号给目的进程。
接收信号:信号处理程序捕获信号的基本思想。
- 发送信号的两个原因
内核监测到一个系统事件,比如被零除错误或者子进程终止。
一个进程调用了kill函数,显式地要求内核发送一个信号给目的进程。一个进程可以发送信号给它自己。
- 待处理信号:一个只发出而没有被接收的信号.
代码实践
exec1.c
程序代码
#include <stdio.h> #include <unistd.h> int main() { char *arglist[3]; arglist[0] = "ls"; arglist[1] = "-l"; arglist[2] = 0 ;//NULL printf("* * * About to exec ls -l "); execvp( "ls" , arglist ); printf("* * * ls is done. bye"); return 0; }
- 这个代码中用了execvp函数。execvp()会从PATH 环境变量所指的目录中查找符合参数file 的文件名,找到后便执行该文件,然后将第二个参数argv传给该欲执行的文件。如果执行成功则函数不会返回,执行失败则直接返回-1,失败原因存于errno中。
exec2.c
程序代码
#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 "); execvp( arglist[0] , arglist ); printf("* * * ls is done. bye "); }
- 它与exec1的区别就在于exevp函数的第一个参数,exec1传的是ls,exec2直接用的arglist[0],不过由定义可得这两个等价,所以运行结果是相同的。
exec3.c
程序代码
#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 "); execlp("ls", "ls", "-l", NULL); printf("* * * ls is done. bye "); }
- 这个代码里使用了execlp函数。 execlp()会从PATH 环境变量所指的目录中查找符合参数file的文件名,找到后便执行该文件,然后将第二个以后的参数当做该文件的argv[0]、argv[1]……,最后一个参数必须用空指针(NULL)作结束。 指定了环境变量,然后依然执行了ls -l指令,成功后没有返回,所以最后一句话不会输出。运行结果同exec1。
forkdemo1.c
程序代码
#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 ", mypid); ret_from_fork = fork(); sleep(1); printf("After: my pid is %d, fork() said %d ", getpid(), ret_from_fork); return 0; }
- 这个代码先是打印进程pid,然后调用fork函数生成子进程,休眠一秒后再次打印进程id,这时父进程打印子进程pid,子进程返回0。
forkdemo2.c
程序代码
#include <stdio.h> #include <unistd.h> int main() { printf("before:my pid is %d ", getpid() ); fork(); fork(); printf("aftre:my pid is %d ", getpid() ); return 0; }
- 这个代码调用两次fork,一共产生四个子进程,所以会打印四个aftre输出。
forkdemo3.c
程序代码
#include <stdio.h> #include <stdlib.h> #include <unistd.h> int fork_rv; int main() { printf("Before: my pid is %d ", 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 parent. my child is %d ", getpid()); exit(0); } else{ printf("I am the parent. my child is %d ", fork_rv); exit(0); } return 0; }
- fork产生子进程,父进程返回子进程pid,不为0,所以输出父进程的那句话,子进程返回0,所以会输出子进程那句话。
forkdemo4.c
程序代码
#include <stdio.h> #include <stdlib.h> #include <unistd.h> int main(){ int fork_rv; printf("Before: my pid is %d ", 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 child. my pid=%d ", getpid()); printf("parent pid= %d, my pid=%d ", getppid(), getpid()); exit(0); } else{ printf("I am the parent. my child is %d ", fork_rv); sleep(10); exit(0); } return 0; }
- 先打印进程pid,然后fork创建子进程,父进程返回子进程pid,所以输出parent一句,执行sleep(10)语句,休眠十秒。子进程返回0,所以输出child与之后一句。
psh1.c
程序代码
#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] = '