2014/02/18 11:14
1,当异常处理程序完成处理后,根据引起异常的类型,会发生以下三种情况中的一种:
A,处理程序将控制返还给当前指令Icurr,即当事件发生时正在执行的指令。
B,处理程序将控制返还给Inext,即如果没有发生异常将会执行的下一条指令。
C,处理程序终止被中断的程序。
1.1异常表是一张跳转表,其中条目k包含异常k的处理程序代码的地址。异常号是到异常表中的索引,异常表的起始地址放在一个叫做异常表基址寄存器 (exception table base register)的特殊CPU寄存器里。即:异常k的条目的目的地址=异常表基址寄存器+异常号*4。
1.2异常可以分为四种类别:中断(interrupt),陷阱(trap),故障(fault)和终止(abort)。
1.2.1异步异常是由处理器外部的I/O设备中的事件产生的,同步异常是执行一条指令的直接产物。
1.2.2中断是异步发生的,是来自处理器外部的I/O设备的信号的结果。硬件中断不是由任何一条专门的指令造成的,从这个意义上来说,它是异步的。硬件中断的异常处理程序通常称为中断处理程序(interrupt handler)。
1.2.3陷阱是有意的异常,是执行一条指令的结果。陷阱处理程序将控制返回到下一条指令。陷阱最重要的用途是在用户程序和内核之间提供一个像过程一样的接口,叫做系统调用。
1.2.4故障由错误情况引起,它可能能够被故障处理程序修正。当故障发生时,处理器将控制转移给故障处理程序。如果处理器能够修正这个错误情况,它就将控制返回到引起故障的指令,从而重新执行它。否则,处理程序返回到内核中的abort例程,abort例程会终止引起故障的引用程序。(即根据故障是否能够被修复,故障处理程序要麼重新执行引起故障的指令,要麼终止)
1.2.5终止是不可恢复的致命错误造成的结果。终止处理程序将控制传递给一个内核abort例程,该例程会终止这个应用程序。终止处理程序从不将控制返回给应用程序。
1.2.6在IA32系统上,系统调用是通过一条int n的陷阱指令来提供的,其中n可能是IA32异常表中256个条目中任意一个的索引。在历史上,系统调用是通过异常0x80提供的。所有的到linux系统调用的参数都是通过通用寄存器而不是栈传递的。按照惯例,寄存器%eax包含系统调用号,寄存器%ebx,%ecx,%edx,%esi,%edi和%ebp包含最多六个任意的参数。栈指针%esp不能使用,因为进入内核模式时,内核会覆盖它。
2,进程
2.1进程的经典定义就是一个执行中的程序的实例。系统中的每个程序都是运行在某个进程的上下文(context)中的。上下文是由程序正确运行所需要的状态组成的。这个状态包括存放在处理器中的程序的代码和数据,它的栈,通用目的寄存器的内容,程序计数器,环境变量以及打开的文件描述符的集合。
2.2进程提供给应用程序的关键抽象:
a,一个独立的逻辑控制流,它提供一个假象,好像我们的程序独占地使用处理器。
b,一个私有的地址空间,它提供一个假象,好像我们的程序独占的使用存储器系统。
2.3进程会因为三个原因终止:
1)收到一个信号,该信号的默认行为是终止进程。
2)从主程序返回。
3)调用exit函数。
2.4exit函数
#include
void exit(int status)
exit函数以statu退出状态来终止进程,另一种设置退出状态的方法是从主程序中返回一个整数值。(The exit function terminates the process with an exit status of status. The other way to set the exit status is to return an integer value from the main routine.)
2.5fork:
新创建的子进程几乎但不完全与父进程相同。子进程得到与父进程用户级虚拟地址空间相同的(但是独立的)一份拷贝,包括文本,数据和bss段,堆以及用户栈。子进程还获得与父进程任何打开文件描述符相同的拷贝,这就意味着父进程调用fork时,子进程可以读写父进程中打开的任何文件。父进程和新创建的子进程之间最大的区别在于它们有不同的PID。
2.6当一个进程由于某种原因终止时,内核并不是立即把它从系统中清楚。相反,进程被保持在一种已终止的状态中,之道被它的父进程回收(reap)。当父进程回收已终止的子进程时,内核将子进程的退出状态传递给父进程,然后抛弃已终止的进程,从此时开始,该进程就不存在了,一个终止了但还未被回收的进程称为僵死进程。即使僵死进程没有运行,它们仍然消耗系统的存储器资源。
2.7execve函数在当前进程的上下文中加载并运行一个新的程序,它会覆盖当前进程的地址空间,但并没有创建一个新进程,新的程序仍然有相同的PID,并且继承了调用execve函数时已经打开的所有文件描述符。
3,多年前,主存储器是用一种称为磁芯存储器(core memory)的技术来实现的。转储存储器(dumping core)是一个历史术语,意思是把代码和数据存储器段的映像写到磁盘上。
4,一个只发出而没有被接收的信号叫做待处理信号(pending signal)。在任何时刻,一种类型至多只会有一个待处理的信号。如果一个进程有一个类型为k的待处理信号,那么任何接下来发送到这个进程的类型为k的信号都不会排队等待,它们只是被简单的丢弃。
一个进程可以有选择性的阻塞接收某种信号。当一个信号被阻塞时,它仍可以被发送,但是产生的待处理信号不会被接收,直到进程取消对这种信号的阻塞。
一个待处理信号最多只能被接收一次。内核为每个进程在pending位向量中维护着待处理信号的集合,而在blocked位向量中维护者被阻塞的信号集合。只要传送了一个类型为k的信号,内核就会设置pending中的第k位,而只要接收(进程接收到信号)一个类型为k的信号,内核就会清除pending中的第k位。
5,Unix外壳使用作业(job)这个抽象概念来表示为对一个命令行求值而创建的进程。在任何时刻,至多只有一个前台作业和0个或多个后台作业。
外壳为每个作业创建一个独立的进程组。典型的,进程组ID是取自作业中父进程中的一个。
6,信号
6.1发送信号:
内核通过更新目的进程上下文中的某个状态,发送(递送)一个信号给目的进程。
发送信号可以有如下两个原因:
1)内核检测到一个系统事件,比如除零错误或者子进程终止。
2)一个进程调用了kill函数,显示的要求内核发送一个信号给目的进程。(一个进程可以发送信号给它自己)
6.1接收信号:
当目的进程被内核强迫以某种方式对信号的发送做出反应时,目的进程就接收了信号。进程可以忽略这个信号,终止或者通过执行一个称为信号处理程序(signal handler)的用户层函数捕获这个信号。
当内核从一个异常处理程序返回,准备将控制传递给进程p时,它会检查进程p的未被阻塞的待处理信号的集合(pending&~blocked)。如果这个集合为空(通常情况下),那么内核将控制传递到p的逻辑控制流中的下一条指令(Inext)。
如果集合是非空的,那么内核选择集合中的某个信号k(通常是最小的k),并且强制p接收信号k。收到这个信号会除法进程的某种行为。一旦进程完成了这个行为,那么控制就传递回p的逻辑控制流中的下一条指令(Inext)。每个信号类型都有一个预定义的默认行为,是下面中的一种:
a,进程终止。
b,进程终止并转储存储器(dump core)。
c,进程停止直到被SIGCONT信号重启。
d,进程忽略该信号。
进程可以通过使用signal函数修改和信号相关联的默认行为。唯一的例外是SIGSTOP和SIGKILL,它们的默认行为是不能被修改的。
#include
typedef void(*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);
signal函数可以通过下列三种方法之一来改变和信号signum相关联的行为:
a,如果handler是SIG_IGN,那么忽略类型为signum的信号。
b,如果handler是SIG_DFL,那么类型为signum的信号行为恢复为默认行为。
c,否则,handler就是用户定义的函数的地址,这个函数称为信号处理程序(signal handler),只要进程接收到一个类型为signum的信号,就会调用这个程序。通过把处理程序的地址传递到signal函数从而改变默认行为,这叫做设置信号处理程序(installing the handler)。调用信号处理程序称为捕获信号。执行信号处理程序称为处理信号。
信号处理程序的执行中断main C函数(C程序中main函数)的执行,类似于底层异常处理程序终端当前应用程序的控制流的方式。因为信号处理程序的逻辑控制流与主函数的逻辑控制流重叠,信号处理程序和主函数并发的执行。
2014/02/23 18:24
7,signal函数说明:
#include
typedef void (*sighandler_t)(int);
sighandler_t signal(int signum,sighandler_t handler);
signal函数的另一种定义:
c traps and pitfalls 对signal函数解释得非常详细。
中
void ( *signal( int sig, void (* handler)( int ))) ( int )?
int (*p)()?
这是一个函数指针, p所指向的函数是一个?带任何参数, 并且返回值为int的一个函数.
int (*fun())()?
这个式子与上面式子的区别在于用fun()代替?p,而fun()是一个函数,所以说就可以看成是fun()这个函数执行之后,它的返回值是一
个函数指针,这个函数指针(其实就是上面的p)所指向的函数是一个?带任何参数,并且返回值为int的一个函数.
void (*signal(int signo, void (*handler)(int)))(int)?就可以看成是signal()函数(它自己是带两个参数,一个为整型,一个为函数指针
的函数), 而这个signal()函数的返回值也为一个函数指针,这个函数指针指向一个带一个整型参数,并且返回值为void的一个函数.