20165229 《信息安全系统设计基础》第7周学习总结
教材学习内容总结
1、了解异常及其种类
-
现代系统通过使控制流发生突变来对这些情况做出反应。称这些突变为异常控制流。
-
异常是异常控制流的一种形式,一部分由硬件实现,一部分由操作系统实现。
-
状态的变化称为事件,在任何情况下,当处理器检测到有事件发生时,它就会通过一张叫做异常表的跳转表,进行一个间接过程调用,到一个专门设计用来处理这类事件的操作系统子程序(异常处理程序)。当异常处理程序完成后,根据引起引起异常的事件类型,会发生以下三种情况的一种:
1.处理程序将控制返回给当前指令,即事件发生之时正在执行的指令。 2.处理程序将控制返回给如果没有异常将会执行的下一条指令。 3.处理程序终止被中断的程序。
-
系统中可能的每种异常都被分配了唯一一个非负整数的异常号,异常表中的条目k中包含异常k的处理程序地址。在运行时可通过检测到的事件确定的相应异常号来查表,转到相应的处理程序。异常表的起始地址存放在一个叫做异常表基址寄存器的特殊寄存器里。
-
异常类和过程调用的不同之处:
1.根据异常类型,返回地址是当前地址或者下一条指令。 2、处理器也会把额外的处理器状态压到栈中,在处理程序返回时,重新开始被中断的程序会需要这些状态。 3、如果控制从一个用户程序转移到内核,那么所有项目都会被压到内核栈中而不是用户栈。 4、异常处理程序运行在内核模式下,这意味着他们对所有的系统资源拥有完全的访问权限。
-
异常的类别
截图1
1、中断是异步发生的,是来自处理器外部的I/O设备的信号的结果。硬件中断的异常处理程序通常称为中断处理程序。剩下的异常类型都是同步发生的,是执行当前指令的结果,这一类指令称为故障指令。
2、陷阱是有意的异常,陷阱处理程序将控制返回到下一条指令。最重要的用途是在用户程序和内核之间提供一个向过程一样的接口,叫做系统调用。
为了允许内核服务的受控访问,使用“syscall n”指令,跳转到一个异常处理程序的陷阱,处理程序对参数解码并调用适当的内核程序。
3、故障由错误情况引起,可能被故障处理程序修正。故障发生时,处理器将控制转移给故障处理程序,若能修正,就将控制返回到引起故障的指令,重新执行;若不能修正,处理程序返回abort例程,abort例程会终止引起故障的应用程序。
4、终止是不可恢复的致命错误造成的结果,通常是一些硬件错误。终止处理程序将控制直接返回给abort例程,直接终止该应用程序。
2、进程
1、进程就是一个执行中的程序的实例,系统中的每个程序都是运行在某个进程的上下文中的。异常是允许操作系统提供进程的概念所需要的基本构造块。是轮流使用处理器的。
截图2
2、这张图的关键点在于进程是轮流使用处理器的。
并发
截图3
截图4
进程控制
1、每个进程都有一个唯一的非零正数进程ID(PID)。getpid函数返回调用进程的PID。getppid返回它的父进程的PID。
2、进程总是处于以下三种状态之一:
运行。要么在CPU上执行,要么等待被执行且最终会被内核调度。
停止。进程的执行被挂起,且不会被调度。(与信号有关)
终止。进程永远地停止了。进程终止的原因:1)收到一个信号,默认行为是终止程序。2)从主程序返回。3)调用exit函数
fork函数
1、调用一次,返回两次。一次返回到父进程,一次返回到新创建的子进程。
2、并发执行,父进程和子进程是并发运行的独立程序。内核能够以任意方式交替执行他们的逻辑控制流中的指令。
3、相同但是独立的地址空间,父进程和子进程地址空间都相同,但对于变量所做的改变都是独立的。
4、共享文件,子进程继承了父进程所有的打开文件。
5、通过Fork()返回值来判断父进程和子进程。
指针数组与数组指针:
- 指针数组:即用于存储指针的数组,也就是数组元素都是指针
形式如:int *p[n]
表示定义有n个指针分别为:p[0]、p[1]、...、p[n-1]
- 数组指针:即指向数组的指针,指针指向一个类型和元素个数都固定的数组
形式如:int (*p)[n]
表示定义一个指向一个数组的指针p
指针函数与函数指针:
- 指针函数:即返回值是指针类型的函数
形式如:void *p()
表示p为有关指针的一个函数
- 函数指针:即指向函数的指针,函数名就是函数指针
形式如:void (*p)()
表示p为一个指向函数的指针
发送信号的主要函数
- 发送信号的主要函数有:kill()、raise()、 sigqueue()、alarm()、setitimer()以及abort()。
1)、KILL:
定义函数:
include<signal.h>
int kill(pid_t pid, int sig);
函数说明:
这个函数的功能是将信号发送给进程或者进程组。
pid > 0 :要发送信号的进程号
pid = 0 :信号被发送到所以和当前进程在同一个进程组的进程
pid = -1 : 信号发送给发送进程有权限向他们发送信号的系统上的所以进程
pid < -1 :信号发送给其进程组ID等于pid的绝对值
几个比较常用的信号
SIGIGN:忽略改信号
SIGDEF:采用系统默认方式处理信号
SIGCHLD:在一个进程终止或者停止时,将该信号发送给其父进程,父进程的wait函数通常用来捕捉这个
信号
SIGINT:当用户按中断键(delete/ctrl+c)时将产生这个信号
SIGKILL:此信号可以用于杀死一个进程
SIGSTOP:这是个作业控制信号,用于停止一个进程
这个信号和SIGKILL是仅有的两个不能被捕获或忽略的信号
SYSUSR1/2:用户定义的信号,用于应用程序
2)ALARM:
调用alarm函数可以设定一个闹钟,也就是告诉内核在seconds秒之后给当前进程发SIGALRM信号, 该信号的默认处理动作是终止当前进程。 这个函数的返回值是0或者是以前设定的闹钟时间还余下的秒数。
alarm(设置信号传送闹钟)
定义函数:
unsigned int alarm(unsigned int seconds);
函数说明:
alarm()用来设置信号SIGALRM在经过参数seconds指定的秒数后传送给目前的进程。如果参数seconds
为0,则之前设置的闹钟会被取消,并将剩下的时间返回。
返回值:
返回之前闹钟的剩余秒数,如果之前未设闹钟则返回0。
范例:
void handler() {
printf("hellon");
}
main()
{
int i;
signal(SIGALRM,handler);
alarm(5);
for(i=1;i<7;i++){
printf("sleep %d ...n",i);
sleep(1);
}
}
执行
sleep 1 ...
sleep 2 ...
sleep 3 ...
sleep 4 ...
sleep 5 ...
hello
sleep 6 ...
Linux主要有两个函数实现信号的安装:signal()、sigaction()。其中signal()只有两个参数,不支持信号传递信息,主要是用于前32种非实时信号的安装;而sigaction()是较新的函数(由两个系统调用实现:sys_signal以及sys_rt_sigaction),有三个参数,支持信号传递信息,主要用来与 sigqueue() 系统调用配合使用,当然,sigaction()同样支持非实时信号的安装。sigaction()优于signal()主要体现在支持信号带有参数。
3)SIGNAL:
定义函数:
#include <signal.h>
void (*signal(int signum, void (*handler))(int)))(int);
函数说明:
可以分解成两个函数:
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler));
第一个参数指定信号的值,第二个参数指定针对前面信号值的处理,可以忽略该信号(参数设为SIG_IGN);可以采用系统默认方式处理信号(参数设为SIG_DFL);也可以自己实现处理方式(参数指定一个函数地址)。
如果signal()调用成功,返回最后一次为安装信号signum而调用signal()时的handler值;失败则返回SIG_ERR。
传递给信号处理例程的整数参数是信号值,这样可以使得一个信号处理例程处理多个信号。
4)Sigaction:
定义函数:
int sigaction(int signum,const struct sigaction *act,struct sigaction *oldact));
函数说明:
信号安装函数sigaction(int signum,const struct sigaction *act,struct sigaction *oldact)的第二个参数是一个指向sigaction结构的指针(结构体名称与函数名一样,千万别弄混淆了)。在结构sigaction的实例中,指定了对特定信号的处理,信号所传递的信息,信号处理函数执行过程中应屏蔽掉哪些函数等。当然,此指针也可以为NULL,进程会以默认方式处理信号。
函数的关键在于stuct sigaction
stuct sigaction
{
void (*)(int) sa_handle;
sigset_t sa_mask;
int sa_flags;
}
函数的功能:
-
阻塞,sigaction函数有阻塞的功能,比如SIGINT信号来了,进入信号处理函数,默认情况下,在信号处理函数未完成之前,如果又来了一个SIGINT信号,其将被阻塞,只有信号处理函数处理完毕,才会对后来的SIGINT再进行处理,同时后续无论来多少个SIGINT,仅处理一个SIGINT,sigaction会对后续SIGINT进行排队合并处理。
-
sa_mask,信号屏蔽集,可以通过函数sigemptyset/sigaddset等来清空和增加需要屏蔽的信号,上面代码中,对信号SIGINT处理时,如果来信号SIGQUIT,其将被屏蔽,但是如果在处理SIGQUIT,来了SIGINT,则首先处理SIGINT,然后接着处理SIGQUIT。
-
sa_flags如果取值为0,则表示默认行为。还可以取如下俩值,但是我没觉得这俩值有啥用。
SA_NODEFER,如果设置来该标志,则不进行当前处理信号到阻塞
SA_RESETHAND,如果设置来该标志,则处理完当前信号后,将信号处理函数设置为SIG_DFL行为
管道的概念:
管道是一种最基本的IPC机制,作用于有血缘关系的进程之间,完成数据传递。调用pipe系统函数即可创建一个管道。管道是半双工的,数据只能向一个方向流动;需要双方通信时,需要建立起两个管道;只能用于父子进程或者兄弟进程之间(具有亲缘关系的进程);单独构成一种独立的文件系统:管道对于管道两端的进程而言,就是一个文件,但它不是普通的文件,它不属于某种文件系统,而是自立门户,单独构成一种文件系 统,并且只存在与内存中。 数据的读出和写入:一个进程向管道中写的内容被管道另一端的进程读出。写入的内容每次都添加在管道缓冲区的末尾,并且每次都是从缓冲区的头部读出数据。管道有
如下特质:
-
其本质是一个伪文件(实为内核缓冲区)
-
由两个文件描述符引用,一个表示读端,一个表示写端。
-
规定数据从管道的写端流入管道,从读端流出。
管道的原理: 管道实为内核使用环形队列机制,借助内核缓冲区(4k)实现。
管道的局限性:
1、数据自己读不能自己写。
2、数据一旦被读走,便不在管道中存在,不可反复读取。
3、由于管道采用半双工通信方式。因此,数据只能在一个方向上流动。
4、只能在有公共祖先的进程间使用管道。
常见的通信方式有,单工通信、半双工通信、全双工通信。
- PIPE函数:
定义函数:
int pipe(int fd[2]);
函数参数:fd[2],管道的两个文件描述符,之后就是可以直接操作这两个文件描述符。其中fd[0]为读取
端,fd[1]为写入端
函数功能:创建一个简单的管道,若成功则为数组fd分配两个文件描述符,其中fd[0] 用于读取管道,fd[1]用于写入管道。
返回值:成功返回0,失败返回-1;
读fd[0]: close(fd[1]); read(fd[0], buf, BUF_SIZE);
写fd[1]: close(fd[0]); read(fd[1], buf, strlen(buf));
DUP和DUP2:
1)dup:
定义函数:
int dup ( int filedes )
函数返回一个新的描述符,这个新的描述符是传给它的描述符的拷贝,若出错则返回 -1。由dup返回的新文件描述符一定是当前可用文件描述符中的最小数值。这函数返回的新文件描述符与参数 filedes 共享同一个文件数据结构。
2)dup2:
定义函数:
int dup2( int filedes, int filedes2 )
同样,函数返回一个新的文件描述符,若出错则返回 -1。与 dup 不同的是,dup2 可以用 filedes2 参数指定新描述符的数值。如果 filedes2 已经打开,则先将其关闭。如若 filedes 等于 filedes2 , 则 dup2 返回 filedes2 , 而不关闭它。同样,返回的新文件描述符与参数 filedes 共享同一个文件数据结构。
函数原型:
int dup(int oldfd);
int dup2(int oldfd,int newfd);
dup和dup2函数调用成功时返回一个oldfd文件描述符的副本,失败则返回-1。所不同的是,由dup函数返回的文件描述符是当前可用文件描述符中最小数值,而dup2函数则可以利用参数newfd指定欲返回的文件描述符。如果参数newfd指定的文件描述符已经打开,系统先将其关闭,然后将oldfd指定的文件描述符赋值到该参数。如果newfd等于oldfd,则dup2返回newfd,而不是关闭它。
示例代码(可以写也可以不写)
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void)
{
int fd, save_fd;
char msg[] = "This is a test of dup() & dup2()
";
int test;
fd = open("zhonghe.txt", O_RDWR|O_CREAT, S_IRUSR|S_IWUSR);
if(fd<0) {
perror("open");
exit(1);
}
save_fd = dup(STDOUT_FILENO); //运行后save_fd指向STDOUT_FILEN
save_fd指向标准输出
printf("save_fd=%d
",save_fd); //测试用
test=dup2(fd, STDOUT_FILENO); //运行后STDOUT_FILENO指向fd所指向的文件,即STDOUT_FILENO指向zhonghe.txt
printf("dup2_1=%d
",test); //测试用 此时的标准输出不再指向显示器,因此该段测试将写入zhonghe.txt文件中
close(fd);
write(STDOUT_FILENO, msg, strlen(msg)); //此时STDOUT_FILENO所指向的是zhonghe.txt文件不再是标准输出流,因此该段将写入zhonghe.txt文件中
test=dup2(save_fd, STDOUT_FILENO); //运行后STDOUT_FILENO指向save_fd所指向的文件,即标准输出流
printf("dup2_2=%d
",test); //测试用 此时标准输出流重新指回显示器,因此该段测试将写入显示器
write(STDOUT_FILENO, msg, strlen(msg)); //此时STDOUT_FILENO所指向的便回标准输出流该段将写入显示器
close(save_fd);
return 0;
}
运行结果:
save_fd=4
dup2_2=1
This is a test of dup() & dup2()
并且在目录下创建名为zhonghe.txt文件,内容为:
dup2_1=1
This is a test of dup() & dup2()
掌握进程创建和控制的系统调用及函数使用:fork,exec,wait,waitpid,exit,getpid,getppid,sleep,pause,setenv,unsetenv,