linux0.11 中断和异常

概念:

     1. 中断和异常是指明系统、处理器或当前执行程序(或任务)的某处出现一个事件,该事件需要处理器进行处理。通常,这种事件会导致执行控制被强迫从当前运行程序转移到中断处理程序或异常处理函数中。

         1.1中断发送在程序执行的随机时刻,以响应硬件发出的信号。系统硬件使用中断来处理外部事件。例如要求为外部设备提供服务。当前,软件也能通过Int n 指令产生中断。

         1.2异常发送在处理器执行一条指令时候,检测到一个出错条件时发送,例如被0除出错条件。处理器可以检测到各种出错条件,包括违反保护机制、页错误、以及机器内部错误。

     2. 处理器把赋予异常或中断的向量(标志号)用作中断描述符表IDT中的一个索引,来定位一个异常或中断的处理程序入口点的位置。允许的向量号从0-255,其中0-31保留用作80x86处理器定义的异常和中断。

     3. 处理器可以从两个地方接受中断,一个是外部硬件产生的中断。另一个是软件产生的中断。

        3.1 外部中断通过处理器芯片上两个引脚(intr和nmi)接收。通过INTR引脚接受的中断被称为可屏蔽中断,标志寄存器EFLAG的IF标志可用来屏蔽所用的这些硬件中断。

               NMI引脚接收到的信号,是不可屏蔽的。固定中断向量号为2.  

        3.2 需要注意的是::::::EFLAGS的IF标志不能够屏蔽使用INT 指令从软件产生的中断。

    4. 异常分为故障、陷阱、中止。

        故障是一种通常可以被纠正的异常,并且一旦被纠正程序就可以继续运行。当出现一个故障,处理器会把机器状态恢复到产生故障的指令之前的状态。此时异常处理程序的返回地址会指向产生故常的指令,而不是下一条指令。

        陷阱是一个引起陷阱的指令被执行后立刻会报告的异常。陷阱也能够让程序连贯的执行。陷阱处理程序的返回地址指向引起陷阱指令的随后一条指令,因此返回后会执行下一条指令,

       中止是一种不会总是报告导致异常的指令的精确位置的异常,并且不允许导致异常的程序重新继续执行。

现在开始进行分析代码:

       IDT表中可以存放3中类型的门描述符分别为中断门、陷阱门、任务门。在Linux中并没有使用任务门来切换任务。所以只要看看中断门、和陷阱门,对于它们两个的区别主要是在EFLAGS的IF标志位上,中断门会复位屏蔽中断。

      

参数:  gata_addr: 描述符地址   type: 描述符类型域值 dpl:描述符特权级 addr:偏移地址

#define _set_gate(gate_addr,type,dpl,addr) \
__asm__ ("movw %%dx,%%ax\n\t" \
                    "movw %0,%%dx\n\t" \
                    "movl %%eax,%1\n\t" \
                    "movl %%edx,%2" \
                    : \
                    : "i" ((short) (0x8000+(dpl<<13)+(type<<8))), \
                    "o" (*((char *) (gate_addr))), \
                    "o" (*(4+(char *) (gate_addr))), \
                    "d" ((char *) (addr)),"a" (0x00080000))


#define set_intr_gate(n,addr) \            
                    _set_gate(&idt[n],14,0,addr)         //中断门  dpl = 0


#define set_trap_gate(n,addr) \
                    _set_gate(&idt[n],15,0,addr)       //陷阱门   dpl  = 0


#define set_system_gate(n,addr) \
                    _set_gate(&idt[n],15,3,addr)     //陷阱门  dpl  = 3

现在我们来看看内核到底有设置了多少个有效的中断描述符、idt[n] 就是前面分析的head.s里面的_idt,在head.s里面进行了一些设置,共同指向一个过程,之后会在具体的进行设置IDT表项。

set_intr_data: 中断门   0x2e 硬盘中断

                                            0x20  时钟中断

                                            0x23 0x24 串行口1 和  串行口2 中断

set_trap_gate:  dpl = 0 的陷阱终端门    【0 - 2】  

                                                                      【6-16】 

                                                                      45:数学协处理器

                                                                      39:并行口1

set_system_gate: dpl = 3 的陷阱中断。

                                                              0x80 : 系统调用

                                                               3   、  4  、 5 

以上就是整个内核IDT表中有效的中断描述符。现在我们要知道为什么会有set_system_data dpl = 3

需要注意的是,它们为陷阱门,并不会去改变EFLAG中IF标志位,所以,在中断处理函数中,有可能在引起中断,而跳转到其他的中断函数中去。比如时钟中断(我感觉是这么回事的)

好了、当进行跳转需要进行特权检查需要符合如下:

CPL <= DPL

RPL <= DPL

DDPL <= CPL

CPL = 3、

DPL = 0 或等于3 (set_trap_gata 和set_system_gate) 设置为3,可以让在用户空间直接调用,例如0x80 0x3 0x4 0x5

但是如果设置为dpl = 0 的中断描述符是直接int xx通过不了特权级的检查的。这样也防止了用户进行模拟硬件中断。

对于异常处理函数有的有错误码的、有的没有产生错误码。

_divide_error:

      pushl $_do_divide_error

      jmp no_error_code 

_debug:

     pushl $_do_int3

     jmp   $_do_error_code

 ....

 ...

_double_fault:

      pushl $_do_double_fault

      jmp error_code

_invalud_tss:

      pushl $_do_invalid_tss

      jmp error_code

.....

....

它们就是这么一个模板。

但是其实它们两个在调用C函数时候内核堆栈可以说是一模一样的、只是没有产生错误码的异常就设置为0

堆栈内存布局;

         SS

         ESP

         EFLAGS

         CS

         EIP                     44

         EAX                    40

         EBX                    26

         ECX                    32

         EDX                    28

         EDI                      24

         ESI                      20

         EBP                    16

         DS                      12

         ES                        8

         FS                        4

        ERROR_CODE  0   

        ESP                    ///来自于   pushl %ebx   

注意的就是有条指令是 lss 44(%esp), %edx    ->edx存放的就是EIP的地址

                                         pushl %ebx

                                          .....

                                          call *%eax                 那么传递两个存数一个就是ESP  一个就是ERROR_CODE(或为0 对于没有错误码的异常)

                                                                               (ESP + 0)可以得到返回到产生中断的地址 (ESP+ 4)就是EAX  。。。 。。。如此。就可以得到产生异常时候的信息。

比较需要关注的是时钟中断和系统调用,这两个中断在最后要返回时候都需要进行信号检测。

系统调用:

system_call:
cmpl $nr_system_calls-1,%eax                                                               //调用号
ja bad_sys_call
push %ds
push %es
push %fs
pushl %edx
pushl %ecx # push %ebx,%ecx,%edx as parameters
pushl %ebx # to the system call
movl $0x10,%edx# set up ds,es to kernel space
mov %dx,%ds
mov %dx,%es
movl $0x17,%edx# fs points to local data space   ///0x17 指向用户的数据段
mov %dx,%fs                                                                         ///由fs 指向,那么和用户程序交换数据就靠这个寄存器
call sys_call_table(,%eax,4)                                               ///由调用的号调用到指定的函数。。
pushl %eax                                                                            ///返回值入栈
movl current,%eax
cmpl $0,state(%eax)# state
jne reschedule
cmpl $0,counter(%eax)# counter
je reschedule

ret:_form_sys_call:

.... ..

.....

....

我感觉下面是这么回事:

cmpl $0, state(%eax)

jne reschedule               

状态改变,就是调用的系统中断阻塞了该程序,然后需要跳度

cmpl $0, counter(%eax)

je reschedule  

判断时间片,上面说我说故,陷阱门是不屏蔽中断的,也许某个系统调用的中断函数中产生时钟中断,导致了时间为没有了,那么此时也需要进行调度。

最后进行信号检测。。。。

其次就是系统调用表:sys.h

fn_ptr sys_call_table[] = { sys_setup, sys_exit, sys_fork, sys_read,
sys_write, sys_open, sys_close, sys_waitpid, sys_creat, sys_link,
sys_unlink, sys_execve, sys_chdir, sys_time, sys_mknod, sys_chmod,
sys_chown, sys_break, sys_stat, sys_lseek, sys_getpid, sys_mount,
sys_umount, sys_setuid, sys_getuid, sys_stime, sys_ptrace, sys_alarm,
sys_fstat, sys_pause, sys_utime, sys_stty, sys_gtty, sys_access,
sys_nice, sys_ftime, sys_sync, sys_kill, sys_rename, sys_mkdir,
sys_rmdir, sys_dup, sys_pipe, sys_times, sys_prof, sys_brk, sys_setgid,
sys_getgid, sys_signal, sys_geteuid, sys_getegid, sys_acct, sys_phys,
sys_lock, sys_ioctl, sys_fcntl, sys_mpx, sys_setpgid, sys_ulimit,
sys_uname, sys_umask, sys_chroot, sys_ustat, sys_dup2, sys_getppid,
sys_getpgrp, sys_setsid, sys_sigaction, sys_sgetmask, sys_ssetmask,
sys_setreuid,sys_setregid };

先这样吧。之后在讲解一下fork()函数。

发布了21 篇原创文章 · 获赞 1 · 访问量 6101

猜你喜欢

转载自blog.csdn.net/darling54454/article/details/38023297