0x80号中断 linux_逻辑中断号 硬件中断号 linux - CSDN
精华内容
参与话题
  • Linux系统调用:使用int 0x80

    千次阅读 2018-04-21 22:49:39
    系统调用 系统调用的概念 系统调用是计算机程序请求操作系统内核服务的方式,包括硬件相关的服务(例如访问硬盘驱动器)、创建和执行新的进程和进程调度等等。系统调用提供了进程和操作系统间的必要接口。...

    系统调用

    系统调用的概念

    系统调用是计算机程序请求操作系统内核服务的方式,包括硬件相关的服务(例如访问硬盘驱动器)、创建和执行新的进程和进程调度等等。系统调用提供了进程和操作系统间的必要接口。

    在大多数操作系统中,系统调用只能被用户空间进程使用。而在某些操作系统中,比如在OS/360及其后续的一些操作系统中,有特权的系统代码也会触发系统调用。

    系统调用的分类

    系统调用大体上可分为5类:

    • 进程控制
      • 加载
      • 执行
      • 结束,中止
      • 创建进程
      • 结束进程
      • 得到/设置进程属性
      • 等待(时间、时间、信号)
      • 内存的分配和去配
    • 文件管理
      • 文件的创建和删除
      • 打开和关闭
      • 读、写和重定位
      • 得到/设置文件属性
    • 设备管理
      • 设备的请求和释放
      • 读、写和重定位
      • 得到/设置设备属性
      • 设备的逻辑关联或去关联
    • 信息维护
      • 得到/设置时间或日期
      • 得到/设置系统数据
      • 得到/设置进程、文件或设备属性
    • 通信
      • 通信连接的创建和删除
      • 发送、接收信息
      • 转换状态信息
      • 远程设备的关联或去关联

    Linux系统调用:使用 int 0x80

    Linux提供了200多个系统调用,通过汇编指令 int 0x80 实现,用系统调用号来区分入口函数。

    Linux实现系统调用的基本过程是:

    • 应用程序准备参数,发出调用请求;
    • C库封装函数引导。该函数在Linux提供的标准C库,即 glibc 中。对应的封装函数由下列汇编指令实现(以读函数调用为例):
    ; NASM
    ; read(int fd, void *buffer, size_t nbytes)
    mov eax, 3          ; read系统调用号为3
    mov ebx, fd
    mov ecx, buffer
    mov edx, nbytes
    int 0x80            ; 触发系统调用
    • 执行系统调用。前两步在用户态工作,陷入后在内核态工作。系统调用处理程序根据系统调用号,按系统调用表中的偏移地址跳转,调用对应的内核函数;
    • 系统调用完成相应功能,将返回值存入 eax ,返回到中断处理函数;
    • 系统调用返回。内核函数处理完毕后,库函数读寄存器( eax )返回值,并返回给应用程序。恢复现场。

    应用程序调用系统调用的过程是:

    • 把系统调用号存入 eax
    • 把函数参数存入其它通用寄存器(约定顺序为 ebxecxedxesiedi ,更多的参数(通常不会出现这种情况)使用堆栈传递,也可以通过寄存器存放指向参数在用户空间的地址指针来传递);
    • 触发 0x80 号中断( int 0x80 )。
    • 示例:
    ; NASM
    ; 向显示器输出hello, world
    ; write(int fd, const void *buffer, size_t nbytes)
    ; exit(int status)
            global  _start
            section .text
    _start:
            mov     eax, 4              ; write系统调用号为4
            mov     ebx, 1              ; 文件描述符1:标准输出stdout
            mov     ecx, message        ; 要输出的信息
            mov     edx, message.len    ; 要输出的长度
            int     0x80
    
            mov     eax, 1              ; exit系统调用号为1
            mov     ebx, 0              ; 状态码0:正常退出
            int     0x80
    
            section .data
    message:
            db      "hello, world", 10
    .len    equ     $ - message

    Linux系统调用实现机制

    系统调用初始化

    系统调用处理程序 system_call() 的入口地址放在系统的中断表述符表IDT(Interrupt Descriptor Table)中,Linux系统初始化时,由 trap_init() 将其填写完整,其设置系统调用处理程序的语句为:

    set_system_gate(0x80, &system_call)

    经过初始化以后,每当执行 int 0x80 指令时,产生一个异常使系统陷入内核空间并执行128号异常处理程序,即系统调用处理程序 system_call()

    系统调用公共入口

    system_call() 是所有系统调用的公共入口,其功能是保护现场,进行正确性检查,根据系统调用号跳转到具体的内核函数。内核函数执行完毕时需调用 ret_from_sys_call() ,这时完成返回用户空间前的最后检查,用 RESTORE_ALL 宏恢复现场并执行 iret 指令返回用户断点。

    保护现场

    • 硬件(CPU)保护:ssespeflagscseip ,压入核心栈;
    • 软件(操作系统)保护
      • 使用 SAVE_ALL 宏将寄存器压入堆栈,加载内核的 dses ,往 edx 中放入 $(_KERNEL_DS) 以指明使用内核数据段,把内核数据段选择符装入 dses 。注意:该宏压入寄存器的顺序不是随意的,而是和系统调用的参数传递密切相关;
      • esdseaxebpediesiedxecxebx ,压入核心栈。

    系统调用处理时的核心栈内容:

    硬件完成
    ss
    esp
    eflags
    cs
    eip
    软件完成
    es
    ds
    eax
    ebp
    edi
    esi
    edx
    ecx
    ebx

    返回值传递

    当内核函数返回到 system_call() 时, eax 中存放着内核函数的返回值。要将这个返回值传递给应用程序,内核先将 eax 放入原先 SAVE_ALL 宏保存 eax 的位置,这样当 system_call() 调用 RESTORE_ALL 恢复寄存器时, eax 便被恢复成系统调用的返回值,完成了返回值从内核空间到用户空间的传递。

    系统调用号和系统调用表

    系统调用的数量由 NR_syscalls 宏给定,每个系统调用所对应的编号已预先在系统文件中定义,且都用一个宏表示,其定义有如下形式:

    #define _NR_exit 1
    #define _NR_fork 2
    #define _NR_read 3
    ...

    Linux的系统调用号和内核函数映射关系的系统调用表也被预先定义在系统文件中,具有如下形式:

    .data
    ENTRY(sys_call_table)
        .long SYMBOL_NAME(sys_ni_syscall)    /* 空项 */
        .long SYMBOL_NAME(sys_exit)
        .long SYMBOL_NAME(sys_fork)
        .long SYMBOL_NAME(sys_read)
    ...

    内核函数入口地址为: eax * 4 + sys_call_table

    参考

    [1] 维基百科(英文) - 系统调用:Wikipedia - System call
    [2] 维基百科(中文) - 系统调用:维基百科 - 系统调用
    [3] 《操作系统教程(第五版)》(费翔林、骆斌编著,高等教育出版社):1.3.4 Linux系统调用及其实现机制

    更多资料

    [1] Linux系统调用表:Linux系统调用表
    [2] NASM入门教程:NASM Tutorial
    [3] NASM官方文档:NASM官方文档

    展开全文
  • 系统调用是一个软中断中断号0x80,它是上层应用程序与Linux系统内核进行交互通信的唯一接口。 这个中断的设置在kernel/sched.c中441行函数中 [cpp] view plain copy void sched...

    系统调用是一个软中断,中断号是0x80,它是上层应用程序与Linux系统内核进行交互通信的唯一接口。

    这个中断的设置在kernel/sched.c中441行函数中

    [cpp]  view plain  copy
    1. void sched_init(void)  
    2. {  
    3.     int i;  
    4.     struct desc_struct * p;  
    5.   
    6.     if (sizeof(struct sigaction) != 16)  
    7.         panic("Struct sigaction MUST be 16 bytes");  
    8.     set_tss_desc(gdt+FIRST_TSS_ENTRY,&(init_task.task.tss));  
    9.     set_ldt_desc(gdt+FIRST_LDT_ENTRY,&(init_task.task.ldt));  
    10.     p = gdt+2+FIRST_TSS_ENTRY;  
    11.     for(i=1;i<NR_TASKS;i++) {  
    12.         task[i] = NULL;  
    13.         p->a=p->b=0;  
    14.         p++;  
    15.         p->a=p->b=0;  
    16.         p++;  
    17.     }  
    18. /* Clear NT, so that we won't have troubles with that later on */  
    19.     __asm__("pushfl ; andl $0xffffbfff,(%esp) ; popfl");  
    20.     ltr(0);  
    21.     lldt(0);  
    22.     outb_p(0x36,0x43);      /* binary, mode 3, LSB/MSB, ch 0 */  
    23.     outb_p(LATCH & 0xff , 0x40);    /* LSB */  
    24.     outb(LATCH >> 8 , 0x40);  /* MSB */  
    25.     set_intr_gate(0x20,&timer_interrupt);  
    26.     outb(inb_p(0x21)&~0x01,0x21);  
    27.     set_system_gate(0x80,&system_call);  
    28. }  

    最后一句就将0x80中断与system_call(系统调用)联系起来。

    通过int 0x80,就可使用内核资源。不过,通常应用程序都是使用具有标准接口定义的C函数库间接的使用内核的系统调用,即应用程序调用C函数库中的函数,C函数库中再通过int 0x80进行系统调用。
        所以,系统调用过程是这样的:
        应用程序调用libc中的函数->libc中的函数引用系统调用宏->系统调用宏中使用int 0x80完成系统调用并返回

    下面是sys_call_table的定义文件

    位于./include/sys.h

    [cpp]  view plain  copy
    1. extern int sys_setup ();    // 系统启动初始化设置函数。 (kernel/blk_drv/hd.c,71)  
    2. extern int sys_exit ();     // 程序退出。 (kernel/exit.c, 137)  
    3. extern int sys_fork ();     // 创建进程。 (kernel/system_call.s, 208)  
    4. extern int sys_read ();     // 读文件。 (fs/read_write.c, 55)  
    5. extern int sys_write ();    // 写文件。 (fs/read_write.c, 83)  
    6. extern int sys_open ();     // 打开文件。 (fs/open.c, 138)  
    7. extern int sys_close ();    // 关闭文件。 (fs/open.c, 192)  
    8. extern int sys_waitpid ();  // 等待进程终止。 (kernel/exit.c, 142)  
    9. extern int sys_creat ();    // 创建文件。 (fs/open.c, 187)  
    10. extern int sys_link ();     // 创建一个文件的硬连接。 (fs/namei.c, 721)  
    11. extern int sys_unlink ();   // 删除一个文件名(或删除文件)。 (fs/namei.c, 663)  
    12. extern int sys_execve ();   // 执行程序。 (kernel/system_call.s, 200)  
    13. extern int sys_chdir ();    // 更改当前目录。 (fs/open.c, 75)  
    14. extern int sys_time ();     // 取当前时间。 (kernel/sys.c, 102)  
    15. extern int sys_mknod ();    // 建立块/字符特殊文件。 (fs/namei.c, 412)  
    16. extern int sys_chmod ();    // 修改文件属性。 (fs/open.c, 105)  
    17. extern int sys_chown ();    // 修改文件宿主和所属组。 (fs/open.c, 121)  
    18. extern int sys_break ();    // (-kernel/sys.c, 21)  
    19. extern int sys_stat ();     // 使用路径名取文件的状态信息。 (fs/stat.c, 36)  
    20. extern int sys_lseek ();    // 重新定位读/写文件偏移。 (fs/read_write.c, 25)  
    21. extern int sys_getpid ();   // 取进程id。 (kernel/sched.c, 348)  
    22. extern int sys_mount ();    // 安装文件系统。 (fs/super.c, 200)  
    23. extern int sys_umount ();   // 卸载文件系统。 (fs/super.c, 167)  
    24. extern int sys_setuid ();   // 设置进程用户id。 (kernel/sys.c, 143)  
    25. extern int sys_getuid ();   // 取进程用户id。 (kernel/sched.c, 358)  
    26. extern int sys_stime ();    // 设置系统时间日期。 (-kernel/sys.c, 148)  
    27. extern int sys_ptrace ();   // 程序调试。 (-kernel/sys.c, 26)  
    28. extern int sys_alarm ();    // 设置报警。 (kernel/sched.c, 338)  
    29. extern int sys_fstat ();    // 使用文件句柄取文件的状态信息。(fs/stat.c, 47)  
    30. extern int sys_pause ();    // 暂停进程运行。 (kernel/sched.c, 144)  
    31. extern int sys_utime ();    // 改变文件的访问和修改时间。 (fs/open.c, 24)  
    32. extern int sys_stty ();     // 修改终端行设置。 (-kernel/sys.c, 31)  
    33. extern int sys_gtty ();     // 取终端行设置信息。 (-kernel/sys.c, 36)  
    34. extern int sys_access ();   // 检查用户对一个文件的访问权限。(fs/open.c, 47)  
    35. extern int sys_nice ();     // 设置进程执行优先权。 (kernel/sched.c, 378)  
    36. extern int sys_ftime ();    // 取日期和时间。 (-kernel/sys.c,16)  
    37. extern int sys_sync ();     // 同步高速缓冲与设备中数据。 (fs/buffer.c, 44)  
    38. extern int sys_kill ();     // 终止一个进程。 (kernel/exit.c, 60)  
    39. extern int sys_rename ();   // 更改文件名。 (-kernel/sys.c, 41)  
    40. extern int sys_mkdir ();    // 创建目录。 (fs/namei.c, 463)  
    41. extern int sys_rmdir ();    // 删除目录。 (fs/namei.c, 587)  
    42. extern int sys_dup ();      // 复制文件句柄。 (fs/fcntl.c, 42)  
    43. extern int sys_pipe ();     // 创建管道。 (fs/pipe.c, 71)  
    44. extern int sys_times ();    // 取运行时间。 (kernel/sys.c, 156)  
    45. extern int sys_prof ();     // 程序执行时间区域。 (-kernel/sys.c, 46)  
    46. extern int sys_brk ();      // 修改数据段长度。 (kernel/sys.c, 168)  
    47. extern int sys_setgid ();   // 设置进程组id。 (kernel/sys.c, 72)  
    48. extern int sys_getgid ();   // 取进程组id。 (kernel/sched.c, 368)  
    49. extern int sys_signal ();   // 信号处理。 (kernel/signal.c, 48)  
    50. extern int sys_geteuid ();  // 取进程有效用户id。 (kenrl/sched.c, 363)  
    51. extern int sys_getegid ();  // 取进程有效组id。 (kenrl/sched.c, 373)  
    52. extern int sys_acct ();     // 进程记帐。 (-kernel/sys.c, 77)  
    53. extern int sys_phys ();     // (-kernel/sys.c, 82)  
    54. extern int sys_lock ();     // (-kernel/sys.c, 87)  
    55. extern int sys_ioctl ();    // 设备控制。 (fs/ioctl.c, 30)  
    56. extern int sys_fcntl ();    // 文件句柄操作。 (fs/fcntl.c, 47)  
    57. extern int sys_mpx ();      // (-kernel/sys.c, 92)  
    58. extern int sys_setpgid ();  // 设置进程组id。 (kernel/sys.c, 181)  
    59. extern int sys_ulimit ();   // (-kernel/sys.c, 97)  
    60. extern int sys_uname ();    // 显示系统信息。 (kernel/sys.c, 216)  
    61. extern int sys_umask ();    // 取默认文件创建属性码。 (kernel/sys.c, 230)  
    62. extern int sys_chroot ();   // 改变根系统。 (fs/open.c, 90)  
    63. extern int sys_ustat ();    // 取文件系统信息。 (fs/open.c, 19)  
    64. extern int sys_dup2 ();     // 复制文件句柄。 (fs/fcntl.c, 36)  
    65. extern int sys_getppid ();  // 取父进程id。 (kernel/sched.c, 353)  
    66. extern int sys_getpgrp ();  // 取进程组id,等于getpgid(0)。(kernel/sys.c, 201)  
    67. extern int sys_setsid ();   // 在新会话中运行程序。 (kernel/sys.c, 206)  
    68. extern int sys_sigaction ();    // 改变信号处理过程。 (kernel/signal.c, 63)  
    69. extern int sys_sgetmask (); // 取信号屏蔽码。 (kernel/signal.c, 15)  
    70. extern int sys_ssetmask (); // 设置信号屏蔽码。 (kernel/signal.c, 20)  
    71. extern int sys_setreuid (); // 设置真实与/或有效用户id。 (kernel/sys.c,118)  
    72. extern int sys_setregid (); // 设置真实与/或有效组id。 (kernel/sys.c, 51)  
    73. // 系统调用函数指针表。用于系统调用中断处理程序(int 0x80),作为跳转表。  
    74. fn_ptr sys_call_table[] = { sys_setup, sys_exit, sys_fork, sys_read,  
    75.   sys_write, sys_open, sys_close, sys_waitpid, sys_creat, sys_link,  
    76.   sys_unlink, sys_execve, sys_chdir, sys_time, sys_mknod, sys_chmod,  
    77.   sys_chown, sys_break, sys_stat, sys_lseek, sys_getpid, sys_mount,  
    78.   sys_umount, sys_setuid, sys_getuid, sys_stime, sys_ptrace, sys_alarm,  
    79.   sys_fstat, sys_pause, sys_utime, sys_stty, sys_gtty, sys_access,  
    80.   sys_nice, sys_ftime, sys_sync, sys_kill, sys_rename, sys_mkdir,  
    81.   sys_rmdir, sys_dup, sys_pipe, sys_times, sys_prof, sys_brk, sys_setgid,  
    82.   sys_getgid, sys_signal, sys_geteuid, sys_getegid, sys_acct, sys_phys,  
    83.   sys_lock, sys_ioctl, sys_fcntl, sys_mpx, sys_setpgid, sys_ulimit,  
    84.   sys_uname, sys_umask, sys_chroot, sys_ustat, sys_dup2, sys_getppid,  
    85.   sys_getpgrp, sys_setsid, sys_sigaction, sys_sgetmask, sys_ssetmask,  
    86.   sys_setreuid, sys_setregid  
    87. };  
     

    其中sys_call_table的类型是fn_ptr类型,其中sys_call_table[0]元素为sys_setup,它的类型是fn_ptr类型,它实际上是函数sys_setup的

    入口地址。

    它的定义如下:

    typedef int (*fn_ptr) (); // 定义函数指针类型。

    下面的实例代码有助于理解函数指针:

    [cpp]  view plain  copy
    1. #include<stdio.h>  
    2. typedef int (*MyFunc)();  
    3. MyFunc Func1;  
    4. int Func2()  
    5. {  
    6.     printf("This is a sample output!/n");  
    7.     return 0;  
    8. }  
    9. int main()  
    10. {  
    11.     Func1=Func2;  
    12.     //Func2();  
    13.     //(*Func1)();  
    14.     printf("%x/n",(*Func1));  
    15.     printf("%x/n",Func2);  
    16.     return 0;  
    17. }  
     

     

    system_call系统调用入口函数

    [cpp] view plain copy
    1. #### int 0x80 --linux 系统调用入口点(调用中断int 0x80,eax 中是调用号)。  
    2. .align 2  
    3. _system_call:  
    4. cmpl $nr_system_calls-1,%eax # 调用号如果超出范围的话就在eax 中置-1 并退出。  
    5. ja bad_sys_call  
    6. push %ds # 保存原段寄存器值。  
    7. push %es  
    8. push %fs  
    9. pushl %edx # ebx,ecx,edx 中放着系统调用相应的C 语言函数的调用参数。  
    10. pushl %ecx # push %ebx,%ecx,%edx as parameters  
    11. pushl %ebx # to the system call  
    12. movl $0x10,%edx # set up ds,es to kernel space  
    13. mov %dx,%ds # ds,es 指向内核数据段(全局描述符表中数据段描述符)。  
    14. mov %dx,%es  
    15. movl $0x17,%edx # fs points to local data space  
    16. mov %dx,%fs # fs 指向局部数据段(局部描述符表中数据段描述符)。  
    17. # 下面这句操作数的含义是:调用地址 = _sys_call_table + %eax * 4。参见列表后的说明。  
    18. # 对应的C 程序中的sys_call_table 在include/linux/sys.h 中,其中定义了一个包括72 个  
    19. # 系统调用C 处理函数的地址数组表。  
    20. call _sys_call_table(,%eax,4)  
    21. pushl %eax # 把系统调用号入栈。  
    22. movl _current,%eax # 取当前任务(进程)数据结构地址??eax。  
    23. # 下面97-100 行查看当前任务的运行状态。如果不在就绪状态(state 不等于0)就去执行调度程序  
    24. # 如果该任务在就绪状态但counter[??]值等于0,则也去执行调度程序。  
    25. cmpl $0,state(%eax) # state  
    26. jne reschedule  
    27. cmpl $0,counter(%eax) # counter  
    28. je reschedule  
    29. # 以下这段代码执行从系统调用C 函数返回后,对信号量进行识别处理  
    30. ret_from_sys_call:  
    31. # 首先判别当前任务是否是初始任务task0,如果是则不必对其进行信号量方面的处理,直接返回。  
    32. # 103 行上的_task 对应C 程序中的task[]数组,直接引用task 相当于引用task[0]。  
    33. movl _current,%eax # task[0] cannot have signals  
    34. cmpl _task,%eax  
    35. je 3f # 向前(forward)跳转到标号3。  
    36. # 通过对原调用程序代码选择符的检查来判断调用程序是否是超级用户。如果是超级用户就直接  
    37. # 退出中断,否则需进行信号量的处理。这里比较选择符是否为普通用户代码段的选择符0x000f  
    38. # (RPL=3,局部表,第1 个段(代码段)),如果不是则跳转退出中断程序。  
    39. cmpw $0x0f,CS(%esp) # was old code segment supervisor ?  
    40. jne 3f  
    41. # 如果原堆栈段选择符不为0x17(也即原堆栈不在用户数据段中),则也退出。  
    42. cmpw $0x17,OLDSS(%esp) # was stack segment = 0x17 ?  
    43. jne 3f  
    44. # 下面这段代码(109-120)的用途是首先取当前任务结构中的信号位图(32 位,每位代表1 种信号),  
    45. # 然后用任务结构中的信号阻塞(屏蔽)码,阻塞不允许的信号位,取得数值最小的信号值,再把  
    46. # 原信号位图中该信号对应的位复位(置0),最后将该信号值作为参数之一调用do_signal()。  
    47. # do_signal()在(kernel/signal.c,82)中,其参数包括13 个入栈的信息。  
    48. movl signal(%eax),%ebx # 取信号位图??ebx,每1 位代表1 种信号,共32 个信号。  
    49. movl blocked(%eax),%ecx # 取阻塞(屏蔽)信号位图??ecx。  
    50. notl %ecx # 每位取反。  
    51. andl %ebx,%ecx # 获得许可的信号位图。  
    52. bsfl %ecx,%ecx # 从低位(位0)开始扫描位图,看是否有1 的位,  
    53. # 若有,则ecx 保留该位的偏移值(即第几位0-31)。  
    54. je 3f # 如果没有信号则向前跳转退出。  
    55. btrl %ecx,%ebx # 复位该信号(ebx 含有原signal 位图)。  
    56. movl %ebx,signal(%eax) # 重新保存signal 位图信息??current->signal。  
    57. incl %ecx # 将信号调整为从1 开始的数(1-32)。  
    58. pushl %ecx # 信号值入栈作为调用do_signal 的参数之一。  
    59. call _do_signal # 调用C 函数信号处理程序(kernel/signal.c,82)  
    60. popl %eax # 弹出信号值。  
    61. 3: popl %eax  
    62. popl %ebx  
    63. popl %ecx  
    64. popl %edx  
    65. pop %fs  
    66. pop %es  
    67. pop %ds  
    68. iret  

    ./include/unistd.h文件中系统调用符号和调用号的对应定义

    [cpp]  view plain  copy
    1. // 以下是内核实现的系统调用符号常数,用于作为系统调用函数表中的索引值。( include/linux/sys.h )  
    2. #define __NR_setup 0        /* used only by init, to get system going */  
    3. /* __NR_setup 仅用于初始化,以启动系统 */  
    4. #define __NR_exit 1  
    5. #define __NR_fork 2  
    6. #define __NR_read 3  
    7. #define __NR_write 4  
    8. #define __NR_open 5  
    9. #define __NR_close 6  
    10. #define __NR_waitpid 7  
    11. #define __NR_creat 8  
    12. #define __NR_link 9  
    13. #define __NR_unlink 10  
    14. #define __NR_execve 11  
    15. #define __NR_chdir 12  
    16. #define __NR_time 13  
    17. #define __NR_mknod 14  
    18. #define __NR_chmod 15  
    19. #define __NR_chown 16  
    20. #define __NR_break 17  
    21. #define __NR_stat 18  
    22. #define __NR_lseek 19  
    23. #define __NR_getpid 20  
    24. #define __NR_mount 21  
    25. #define __NR_umount 22  
    26. #define __NR_setuid 23  
    27. #define __NR_getuid 24  
    28. #define __NR_stime 25  
    29. #define __NR_ptrace 26  
    30. #define __NR_alarm 27  
    31. #define __NR_fstat 28  
    32. #define __NR_pause 29  
    33. #define __NR_utime 30  
    34. #define __NR_stty 31  
    35. #define __NR_gtty 32  
    36. #define __NR_access 33  
    37. #define __NR_nice 34  
    38. #define __NR_ftime 35  
    39. #define __NR_sync 36  
    40. #define __NR_kill 37  
    41. #define __NR_rename 38  
    42. #define __NR_mkdir 39  
    43. #define __NR_rmdir 40  
    44. #define __NR_dup 41  
    45. #define __NR_pipe 42  
    46. #define __NR_times 43  
    47. #define __NR_prof 44  
    48. #define __NR_brk 45  
    49. #define __NR_setgid 46  
    50. #define __NR_getgid 47  
    51. #define __NR_signal 48  
    52. #define __NR_geteuid 49  
    53. #define __NR_getegid 50  
    54. #define __NR_acct 51  
    55. #define __NR_phys 52  
    56. #define __NR_lock 53  
    57. #define __NR_ioctl 54  
    58. #define __NR_fcntl 55  
    59. #define __NR_mpx 56  
    60. #define __NR_setpgid 57  
    61. #define __NR_ulimit 58  
    62. #define __NR_uname 59  
    63. #define __NR_umask 60  
    64. #define __NR_chroot 61  
    65. #define __NR_ustat 62  
    66. #define __NR_dup2 63  
    67. #define __NR_getppid 64  
    68. #define __NR_getpgrp 65  
    69. #define __NR_setsid 66  
    70. #define __NR_sigaction 67  
    71. #define __NR_sgetmask 68  
    72. #define __NR_ssetmask 69  
    73. #define __NR_setreuid 70  
    74. #define __NR_setregid 71  
     

    这是一系列宏,它们的定义在unistd.h中,基本形式为#define _NR_name value,name为系统函数名字,value是一个整数值,是name所对应的系统函数指针在sys_call_table中的偏移量。

     

    系统调用宏也在本文件内定义,采用内联汇编,如下:

    [cpp]  view plain  copy
    1. // 以下定义系统调用嵌入式汇编宏函数。  
    2. // 不带参数的系统调用宏函数。type name(void)。  
    3. // %0 - eax(__res),%1 - eax(__NR_##name)。其中name 是系统调用的名称,与 __NR_ 组合形成上面  
    4. // 的系统调用符号常数,从而用来对系统调用表中函数指针寻址。  
    5. // 返回:如果返回值大于等于0,则返回该值,否则置出错号errno,并返回-1。  
    6. #define _syscall0(type,name) /  
    7. type name(void) /  
    8. { /  
    9. long __res; /  
    10. __asm__ volatile ( "int $0x80" /    // 调用系统中断0x80。  
    11. :"=a" (__res) /     // 返回值??eax(__res)。  
    12. :"" (__NR_##name)); /           // 输入为系统中断调用号__NR_name。  
    13.       if (__res >= 0) /      // 如果返回值>=0,则直接返回该值。  
    14.       return (type) __res; errno = -__res; /    // 否则置出错号,并返回-1。  
    15.       return -1;}  
    16. // 有1 个参数的系统调用宏函数。type name(atype a)  
    17. // %0 - eax(__res),%1 - eax(__NR_name),%2 - ebx(a)。  
    18. #define _syscall1(type,name,atype,a) /  
    19. type name(atype a) /  
    20. { /  
    21. long __res; /  
    22. __asm__ volatile ( "int $0x80" /  
    23. "=a" (__res) /  
    24. "" (__NR_##name), "b" ((long)(a))); /  
    25. if (__res >= 0) /  
    26. return (type) __res; /  
    27. errno = -__res; /  
    28. return -1; /  
    29. }  
    30. // 有2 个参数的系统调用宏函数。type name(atype a, btype b)  
    31. // %0 - eax(__res),%1 - eax(__NR_name),%2 - ebx(a),%3 - ecx(b)。  
    32. #define _syscall2(type,name,atype,a,btype,b) /  
    33. type name(atype a,btype b) /  
    34. { /  
    35. long __res; /  
    36. __asm__ volatile ( "int $0x80" /  
    37. "=a" (__res) /  
    38. "" (__NR_##name), "b" ((long)(a)), "c" ((long)(b))); /  
    39. if (__res >= 0) /  
    40. return (type) __res; /  
    41. errno = -__res; /  
    42. return -1; /  
    43. }  
    44. // 有3 个参数的系统调用宏函数。type name(atype a, btype b, ctype c)  
    45. // %0 - eax(__res),%1 - eax(__NR_name),%2 - ebx(a),%3 - ecx(b),%4 - edx(c)。  
    46. #define _syscall3(type,name,atype,a,btype,b,ctype,c) /  
    47. type name(atype a,btype b,ctype c) /  
    48. { /  
    49. long __res; /  
    50. __asm__ volatile ( "int $0x80" /  
    51. "=a" (__res) /  
    52. "" (__NR_##name), "b" ((long)(a)), "c" ((long)(b)), "d" ((long)(c))); /  
    53. if (__res>=0) /  
    54. return (type) __res; /  
    55. errno=-__res; /  
    56. return -1; /  
    57. }  

     


    from: http://blog.csdn.net/geekcome/article/details/6398414

    展开全文
  • linux 中断机制(一)

    2014-07-16 14:15:37
    linux0.11下的中断机制分析  异常就是控制流中的突变,用来响应处理器状态中的某些变化。当处理器检测到有事件发生时,它就会通过一张叫做异常表的跳转表,进行一个间接过程调用,到一个专门设计用来处理这类...

    原文:http://www.cnblogs.com/hongzg1982/articles/2115188.html   点击打开链接

    linux0.11下的中断机制分析

     异常就是控制流中的突变,用来响应处理器状态中的某些变化。当处理器检测到有事件发生时,它就会通过一张叫做异常表的跳转表,进行一个间接过程调用,到一个专门设计用来处理这类事件的操作系统子程序,这张表即中断描述符表IDT。本文将针对Linux0.11代码进行分析和调试,来了解中断机制,主要分析以下三个问题:

        1. 中断描述符表的建立。
        2. 一般中断的处理过程,以0x3号中断为例。
        3. 系统调用的处理过程,以fork系统调用为例。

           有关调试环境的建立请参考:linux0.11引导代码小窥内存分段机制

    中断描述符表的建立

           中断描述符表(IDT)的创建代码在boot/head.s中,与全局描述符表的创建类似,内核执行lidt idt_descr指令完成创建工作,全局变量idt_descr的定义如下:

    idt_descr:
           .word 256*8-1     # idt contains 256 entries
           .long _idt
    _idt: .fill 256,8,0       # idt is uninitialized

           lidt指令为6字节操作数,它将_idt的地址加载进idtr寄存器,IDT被设置为包含2568字节表项的描述符表。

           中断描述符表的初始化工作主要通过宏_set_get来完成,它定义于include/asm/system.h中,如下:

    #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))
           /*设置中断门函数,特权级0,类型386中断门*/
           #define set_intr_gate(n,addr) /
           _set_gate(&idt[n],14,0,addr)
           /*设置陷阱门函数,特权级0,类型386陷阱门*/
           #define set_trap_gate(n,addr) /
           _set_gate(&idt[n],15,0,addr)
           /*设置系统调用函数,特权级3,类型386陷阱门*/
           #define set_system_gate(n,addr) /
           _set_gate(&idt[n],15,3,addr)

           内核将用这些宏初始化IDT表,代码如下:

        /*摘自kernel/traps.c,trap_init函数*/
        set_trap_gate(0,÷_error);
        set_trap_gate(1,&debug);
        set_trap_gate(2,&nmi);
        set_system_gate(3,&int3);     /* int3-5 can be called from all */
        set_system_gate(4,&overflow);
        set_system_gate(5,&bounds);
        set_trap_gate(6,&invalid_op);
        set_trap_gate(7,&device_not_available);
        set_trap_gate(8,&double_fault);
        set_trap_gate(9,&coprocessor_segment_overrun);
        set_trap_gate(10,&invalid_TSS);
        set_trap_gate(11,&segment_not_present);
        set_trap_gate(12,&stack_segment);
        set_trap_gate(13,&general_protection);
        set_trap_gate(14,&page_fault);
        set_trap_gate(15,&reserved);
        set_trap_gate(16,&coprocessor_error);
        for (i=17;i<48;i++)
           set_trap_gate(i,&reserved);
        set_trap_gate(45,&irq13);
        set_trap_gate(39,¶llel_interrupt);
        /*摘自kernel/chr_drv/serial.c,rs_init函数*/
        set_intr_gate(0x24,rs1_interrupt);
        set_intr_gate(0x23,rs2_interrupt);
        /*摘自kernel/chr_drv/console.c,con_init函数*/
        set_trap_gate(0x21,&keyboard_interrupt);
        /*摘自kernel/sched.c,sched_init函数*/
        set_intr_gate(0x20,&timer_interrupt);
        set_system_gate(0x80,&system_call);
        /*摘自kernel/blk_drv/hd.c,hd_init函数*/
        set_intr_gate(0x2E,&hd_interrupt);
        /*摘自kernel/blk_drv/floppy.c,floppy_init函数*/
        set_trap_gate(0x26,&floppy_interrupt);

           每个中断向量号具体意义这里不做说明,有兴趣的同志可以参考清华大学出版社出版的《保护方式下的80386及其编程》和赵炯博士的《Linux内核完全注释》;中断调用的具体过程将在后面的例子中详细分析。现在我们关心的是初始化完毕的IDT,调试查看这张表的内容,选取0x0号、0x20号、0x80号中断作为例子。通过查看System.map文件可知:0x0号中断调用的divide_error函数地址为0x8dec0x20号中断调用的timer_interrupt函数地址为0x74f40x80号中断调用的system_call函数地址为0x7418。当内核第一次调用fork函数创建进程0的子进程时,IDT表已经初始化完毕,因此我们在fork函数地址0x753c处设置断点,启动bochsdgb进行调试,命令行如下:

    <bochs:1> break 0x753c
    <bochs:2> c
    (0) Breakpoint 1, 0x753c in ?? ()
    Next at t=16879006
    (0) [0x0000753c] 0008:0000753c (unk. ctxt): call .+0x93d4; e8931e0000
    <bochs:3> dump_cpu
    ……
    idtr:base=0x54b8, limit=0x7ff
    ……

           IDT基址为0x54b80号中断描述符的地址为0x54b8+0*8=0x54b820号中断描述符的地址为0x54b8+0x20*8= 0x55b880号中断描述符的地址为0x54b8+0x80*8=0x58b8,分别查看内存这三个地址的8字节内容,命令行如下:

    <bochs:4> x /2 0x54b8
    [bochs]:
    0x000054b8 <bogus+       0>:    0x00088dec      0x00008f00
    <bochs:5> x /2 0x55b8
    [bochs]:
    0x000055b8 <bogus+       0>:    0x000874f4      0x00008e00
    <bochs:6> x /2 0x58b8
    [bochs]:
    0x000058b8 <bogus+       0>:    0x00087418      0x0000ef00

           门描述符具有如下形式:

    m+7

    m+6

    m+5

    m+4

    m+3

    m+2

    m+1

    m+0

    Offset(31...16)

    Attributes

    Selector

    Offset(15...0)

    Byte m+5

    Byte m+4

    BIT7

    BIT6

    BIT5

    BIT4

    BIT3

    BIT2

    BIT1

    BIT0

    BIT7

    BIT6

    BIT5

    BIT4

    BIT3

    BIT2

    BIT1

    BIT0

    P

    DPL

    DT0

    TYPE

    000

    Dword Count

           因此调试信息显示,0x0号中断描述符中断调用地址为0x0008:0x00008dec,是一个特权级为0386陷阱门,0x20号中断描述符中断调用函数地址为0x0008:0x000074f4,是一个特权级为0386中断门,0x80号中断描述符中断调用函数地址为0x0008:0x00007418,是一个特权级为3386陷阱门。这和预先分析的情况一致。

    任务的内核态堆栈

           在分析中断响应过程之前,先介绍一下任务的内核态堆栈。

           当中断事件发生时,中断源向cpu发出申请,若cpu受理,则保存当前的寄存器状态、中断返回地址等许多信息,然后cpu转去执行相应的事件处理程序。中断处理完毕后,cpu将恢复之前保存的信息,并继续原来的工作。因为中断处理需要在内核态下进行,因此每个任务都有一个内核态堆栈,用来完成中断处理中保护现场和恢复现场的工作。这个内核态堆栈与每个任务的任务数据结构放在同一页面内,在创建新任务时,fork函数在任务tss内核级字段中设置,代码位于kernel/fork.ccopy_process函数中,如下:

        /*p即需创建的新任务*/
        p->tss.esp0 = PAGE_SIZE + (long) p;
        p->tss.ss0 = 0x10;

           tss.esp0tss.ss0的值在任务内核态工作时不会被改变,因此任务每次进入内核态工作时,这个堆栈总是空的。

    一般中断的处理过程

           0x3号中断用于暂停程序的执行,通过查看Linux代码,可以知道对这个中断的处理仅仅是打印一些寄存器状态信息。选取这个中断作为例子的意义在于:它有一个完整的保护现场和恢复现场的过程(比如0x0号中断的处理将直接终止进程而不需要恢复现场);中断信号可以由用户态的程序产生。

           0x3号中断处理程序int3kernel/asm.s中定义,如下:

    #源代码书写顺序并非如此,这样排列是为了阅读的方便
    _int3:
           pushl $_do_int3
           jmp no_error_code
    no_error_code:
           #以下入栈操作为保护现场的动作
           xchgl %eax,(%esp)
           pushl %ebx
           pushl %ecx
           pushl %edx
           pushl %edi
           pushl %esi
           pushl %ebp
           push %ds
           push %es
           push %fs
           pushl $0         # "error code"
           lea 44(%esp),%edx
           pushl %edx
           movl $0x10,%edx
           mov %dx,%ds
           mov %dx,%es
           mov %dx,%fs
           call *%eax   #调用实际中断处理函数
           addl $8,%esp
           #以下出栈操作为恢复现场的动作
           pop %fs
           pop %es
           pop %ds
           popl %ebp
           popl %esi
           popl %edi
           popl %edx
           popl %ecx
           popl %ebx
           popl %eax
           iret

           这里有个问题:如果发生特权级改变,用户态的堆栈指针在什么时候保存和恢复?答案是cpu响应中断时自动将这些数据入栈,执行iret指令时自动将这些数据出栈。下面的实验可以验证这一点。

           接下来的试验比较繁琐,按照以下步骤进行:

           1)编写产生0x3号中断的程序。
           2)在int3函数地址处设置断点,查看此时内核态堆栈的内容,即验证保护现场的动作。
           3)执行直到中断返回,验证iret指令的作用,即验证恢复现场的动作。

           编写产生0x3号中断的程序非常简单,启动bochs+linux-0.11-devel-040329(这个img由赵炯博士加入了gcc)。用vi创建编辑一个c文件int3.c,代码如下:

    #include <stdio.h>
    int main()
    {
      __asm__(“int3”);
      return 0;
    }

           编译这个文件产生执行程序int3

           通过查看System.map文件可知0x3号中断处理函数_int3的地址为0x8e2f。启动bochsdgb进行调试,命令行如下:

    <bochs:1> b 0x8e2f
    <bochs:2> c   #同时在启动的Linux下运行int3程序,将获得下面这些信息
    (0) Breakpoint 1, 0x8e2f in ?? ()
    Next at t=143245141
    (0) [0x00008e2f] 0008:00008e2f (unk. ctxt): push 0x7af4             ; 68f47a0000

           首先关注一下内核堆栈中的内容,当前任务(0x60-0x20)/8=8号任务的tss结构中的ss0esp0字段包含了内核态堆栈的段描述符和堆栈指针,tss结构的地址由GDT表的TSS描述符提供。继续调试,命令行如下:

    <bochs:3> dump_cpu
    ……
    esp:0xfa3fec  #这个值在在后面的分析将用到
    ……
    tr:s=0x60, dl=0x32e80068, dh=0x89fa, valid=1
    gdtr:base=0x5cb8, limit=0x7ff
    ……
    <bochs:4> x /2 0x5d18  #0x5cb8+0x60=0x5d18
    [bochs]:
    0x00005d18 <bogus+       0>:    0x32e80068      0x00008bfa
    <bochs:5> x /26 0x00fa32e8
    [bochs]:
    0x00fa32e8 <bogus+       0>:    0x00000000      0x00fa4000      0x00000010
    0x00000000
    0x00fa32f8 <bogus+      16>:    0x00000000      0x00000000      0x00000000
    0x00000000
    0x00fa3308 <bogus+      32>:    0x000398af      0x00000246      0x00000000
    0x00000005
    0x00fa3318 <bogus+      48>:    0x000574c0      0x00000014      0x03fffdd8
    0x03fffde4
    0x00fa3328 <bogus+      64>:    0x00000001      0x00000000      0x00000017
    0x0000000f
    0x00fa3338 <bogus+      80>:    0x00000017      0x00000017      0x00000017
    0x00000017
    0x00fa3348 <bogus+      96>:    0x00000068      0x80000000

           对这些调试信息按照tss字段的顺序排列得出下表:

    BIT31—BIT16

    BIT15—BIT1

    BIT0

    Offset

    Data

    0000000000000000

    链接字段

    0

    0x00000000

    ESP0

    4

    0x00fa4000

    0000000000000000

    SS0

    8

    0x00000010

    ESP1

    0CH

    0x00000000

    0000000000000000

    SS1

    10H

    0x00000000

    ESP2

    14H

    0x00000000

    0000000000000000

    SS2

    18H

    0x00000000

    CR3

    1CH

    0x00000000

    EIP

    20H

    0x000398af

    EFLAGS

    24H

    0x00000246

    EAX

    28H

    0x00000000

    ECX

    2CH

    0x00000005

    EDX

    30H

    0x000574c0

    EBX

    34H

    0x00000014

    ESP

    38H

    0x03fffdd8

    EBP

    3CH

    0x03fffde4

    ESI

    40H

    0x00000001

    EDI

    44H

    0x00000000

    0000000000000000

    ES

    48H

    0x00000017

    0000000000000000

    CS

    4CH

    0x0000000f

    0000000000000000

    SS

    50H

    0x00000017

    0000000000000000

    DS

    54H

    0x00000017

    0000000000000000

    FS

    58H

    0x00000017

    0000000000000000

    GS

    5CH

    0x00000017

    0000000000000000

    LDTR

    60H

    0x00000068

    I/O许可位图偏移

    000000000000000

    T

    64H

    0x80000000

                   1:任务8tss结构

           由表1可知:任务8内核态堆栈的起始堆栈指针为0x00fa4000。查看寄存器状态可知当前堆栈指针指向0x00fa3fec,与栈顶相差20/4 = 5个字,调试查看这5个字的内容,命令行如下:

    <bochs:6> x /5 0xfa3fec
    [bochs]:
    0x00fa3fec <bogus+       0>:    0x0000001c      0x0000000f      0x00010202
    0x03fffefc
    0x00fa3ffc <bogus+      16>:    0x00000017

           这些信息就是cpu在进入int3中断处理之前自动保存的信息,参考赵炯博士的《Linux内核完全注释》可知:在用户程序(进程)将控制权交给中断处理程序之前,cpu会首先将至少12字节的信息压入中断处理程序的堆栈中。这种情况与一个长调用(段间子程序调用)比较相像。Cpu会将代码段选择符合返回地址的偏移值压入堆栈。另一个与段间调用比较相像的地方是80386将信息压入到了目的代码的堆栈上。当发生中断时,这个目的堆栈就是内核态堆栈。另外cpu还总是将标志寄存器EFLAGS的内容压入堆栈。如果优先级别发生变化,比如从用户级改变到内核系统级,cpu还会将原代码的堆栈段值和堆栈指针压入中断程序的堆栈中。

           按照堆栈向下增长方向整理调试信息,如下表所示:

    0x0000

    SS

    0x00000017

    ESP

    0x03fffefc

    EFLAGS

    0x00010202

    0x0000

    CS

    0x0000000f

    EIP

    0x0000001c

             2:发生中断时堆栈的内容

           执行iret指令返回时也类似从一个段间子程序调用的返回,堆栈中的这些内容将自动弹出到响应寄存器中,完成中断返回恢复现场的动作。调试来验证这一过程,命令行如下:

    <bochs:7> n   #7,8,9指令都是为了找到iret的位置
    Next at t=172477604
    (0) [0x00008e34] 0008:00008e34 (unk. ctxt): jmp .+0x8df1              ; ebbb
    <bochs:8> n
    Next at t=172477605
    (0) [0x00008df1] 0008:00008df1 (unk. ctxt): xchg dword ptr ss:[esp], eax ; 87042
    4
    <bochs:9> u /30
    ……
    00008e20: (                    ): iretd                     ; cf
    <bochs:10> b 0x8e20
    <bochs:11> c
    (0) Breakpoint 2, 0x8e20 in ?? ()
    Next at t=172498467
    (0) [0x00008e20] 0008:00008e20 (unk. ctxt): iretd                     ; cf
    <bochs:12> n   #中断返回
    Next at t=172498468
    (0) [0x00fac01c] 000f:0000001c (unk. ctxt): xor eax, eax              ; 31c0
    <bochs:13> dump_cpu
    ……
    esp:0x3fffefc
    eflags:0x10202
    eip:0x1c
    cs:s=0xf, dl=0x0, dh=0x10c0fa00, valid=1
    ss:s=0x17, dl=0x3fff, dh=0x10c0f300, valid=1
    ……

           无需解释,表2和上面寄存器状态信息即可说明问题。

    系统调用的处理过程

           以系统调用fork函数为例,它的定义如下:

    /*摘自init/main.c*/
    static inline _syscall0(int,fork)
    /*摘自include/unistd.h*/
    #define __NR_fork 2
    /*摘自include/unistd.h*/
    #define _syscall0(type,name) /
    type name(void) /
    { /
      long __res; /
      __asm__ volatile ("int $0x80" /
           : "=a" (__res) /
           : "0" (__NR_##name)); /
      if (__res >= 0) /
           return (type) __res; /
      errno = -__res; /
      return -1; /
    }

           __NR_fork2是系统调用中断处理的跳转表的索引,这张系统调用函数指针表定义如下:

    /*摘自include/linux/sched.h*/
    typedef int (*fn_ptr)();
    /*摘自include/linux/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 };

           sys_call_table[2]的值是sys_fork函数指针,这个函数的功能不是我们研究的重点,有兴趣的同志可以参考其它资料。
           将宏_syscall0和__NR_fork展开:

    staic inline
    int fork(void)
    {
      long __res;
      __asm__ volatile ("int $0x80"
           : "=a" (__res)
           : "0" (2));  /* eax的值置为2*/
      if (__res >= 0)
           return (int) __res;
      errno = -__res;
      return -1;
    }

           现在fork函数的功能就很清楚了:将eax的值置为2,产生0x80中断,0x80中断的中断处理函数是system_call(还记得吗?set_system_gate(0x80,&system_call))。system_call定义如下:

    _system_call:
           cmpl $nr_system_calls-1,%eax  #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
           mov %dx,%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_from_sys_call:
           movl _current,%eax             # task[0] cannot have signals
           cmpl _task,%eax
           je 3f
           cmpw $0x0f,CS(%esp)        # was old code segment supervisor ?
           jne 3f
           cmpw $0x17,OLDSS(%esp)        # was stack segment = 0x17 ?
           jne 3f
           movl signal(%eax),%ebx
           movl blocked(%eax),%ecx
           notl %ecx
           andl %ebx,%ecx
           bsfl %ecx,%ecx
           je 3f
           btrl %ecx,%ebx   #有信号则调用信号处理程序
           movl %ebx,signal(%eax)
           incl %ecx
           pushl %ecx
           call _do_signal
           popl %eax       #恢复现场
    3:     popl %eax
           popl %ebx
           popl %ecx
           popl %edx
           pop %fs
           pop %es
           pop %ds
           iret      #中断返回

           cpu 处理0x80中断与一般中断处理过程是一样的:压入cseipeflags到目标堆栈,中断返回则从堆栈中弹出这些值到相应寄存器。其中断处理函数将通过系统调用函数指针表来处理相应系统调用。这个过程就不做验证了,有兴趣的同志可以参考一般中断处理的调试过程。

    eip的值

           cpu响应中断源时,压入的eip的值,中断返回将这个值弹出加载到eip,用这样的方式继续应用程序控制流。这个eip的值将根据不同的异常来确定:

    类别

    原因

    异步/同步

    返回行为

    中断

    来自I/O设备的信号

    异步

    总是返回到下一条指令

    陷阱

    有意的异常

    同步

    总是返回到下一条指令

    故障

    潜在可恢复的错误

    同步

    根据故障是否可修复决定要么重新执行当前指令,要么终止

    终止

    不可修复的错误

    同步

    不会返回

           3:异常的类别(摘自《深入理解计算机系统》)

           之前分析到的0x3号中断和0x80号中断即属于“陷阱”,因此它们中断处理完毕后总是由内核态转换到用户态(通过分段机制,段寄存器加载不同的段描述符),并返回到应用程序的下一条指令。

    后记

           中断处理的行为和长调用(段间子程序调用)的行为颇为相似,理解长调用的处理过程即可理解中断处理过程。计算机理论中很多概念都是相通的,因此,扎实的基本功完全可以触类旁通的指导我们开发应用程序。

    展开全文
  • IF标志位与中断的关系

    千次阅读 2016-05-05 23:04:45
    IF标志位与中断的关系

    在Linux中,中断分类如下:
    中断分类

    但Linux系统中只有一个中断向量表,所以,在中断类型与中断向量表里中断号的关系如下:

    中断向量号 中断内容 中断类型 中断描述符类型
    0(0x0) 除出错 Fault 陷阱门
    1(0x1) 调试 Fault/Trap 陷阱门
    2(0x2) NMI中断 Nonmaskable 陷阱门
    3(0x3) 断点 Trap 系统调用门
    4(0x4) 溢出 Trap 系统调用门
    5(0x5) 边界范围超出 Fault 系统调用门
    6(0x6) 无效操作码 Fault 陷阱门
    7(0x7) 设备不存在 Fault 陷阱门
    8(0x8) 双重出错 Abort 陷阱门
    9(0x9) 协处理器段超越 Fault 陷阱门
    10(0xA) 无效的任务状态段 Fault 陷阱门
    11(0xB) 段不存在 Fault 陷阱门
    12(0xC) 堆栈段错误 Fault 陷阱门
    13(0xD) 一般保护错误 Fault 陷阱门
    14(0xE) 页面错误 Fault 陷阱门
    15(0xF) 保留 陷阱门
    16(0x10) 浮点错误 Fault 陷阱门
    17(0x11) 对齐检查 Fault 陷阱门
    18(0x12) 机器检查 Fault 陷阱门
    19(0x13) SIMD浮点异常 Fault 陷阱门
    20-31 保留 陷阱门
    32(0x20) 时钟中断 Maskable 中断门
    33(0x21) 键盘中断 Maskable 陷阱门
    34(0x22) 连接从芯片 Maskable 陷阱门
    35(0x23) 串行口2 Maskable 中断门
    36(0x24) 串行口1 Maskable 中断门
    37(0x25) 并行口2 Maskable 陷阱门
    38(0x26) 软盘驱动器 Maskable 陷阱门
    39(0x27) 并行口1 Maskable 陷阱门
    40(0x28) 实时钟中断 Maskable 陷阱门
    41(0x29) 保留 Maskable 陷阱门
    42(0x2A) 保留 Maskable 陷阱门
    43(0x2B) 保留 Maskable 陷阱门
    44(0x2C) 鼠标口中断 Maskable 陷阱门
    45(0x2D) 数学协处理器 Maskable 陷阱门
    46(0x2E) 硬盘中断 Maskable 中断门
    47(0x2F) 保留 Maskable 陷阱门
    128(0x80) 系统调用 系统调用门


    上表给出的中断描述符类型参考Linux 0.11中的代码。后续Linux版本可能把0x20-0x2f的中断都设为了中断门。

    中断门和陷阱门的区别,就在于他们对标志寄存器EFLAGS中的中断允许标志位IF的影响。由中断门描述符执行的中断会复位IF标志(IF=0);而通过陷阱门执行的中断不会影响IF标志。
    IF标志复位可以避免其他中断干扰当前中断的处理,而随后的中断结束指令iret会从堆栈上恢复IF标志的原值(IF=1)。
    而在代码中,汇编指令cli可以清除IF标志(IF=0),汇编指令sti可以设置IF标志(IF=1)。

    经过上述描述,可以看到,如果在软件中清除了IF标志,那么肯定不会触发中断门的中断,那么此时会不会影响陷阱门的中断呢?
    根据《Linux内核完全注释》,“EFLAGS中的IF标志不能屏蔽使用INT指令从软件中产生的中断”,“IF标志并不影响发送到NMI引脚的非屏蔽中断,也不影响处理器产生的异常”。

    这说明清除IF标志后,可以触发陷阱门的中断(即异常)。

    展开全文
  • BIOS中断向量表

    千次阅读 2011-07-21 11:07:50
    1 -- 关于中断向量的几点注释1.... 系统刚引导时,内存0x00000到0x0003FF共1KB的空间用于存放中断向量表。每个中断向量占用4个字节,共可存储256个中断向量。2. 系统引导时,处在实模式下,只可寻址1MB,为什么要用4
  • 系统调用(int 0x80)详解

    万次阅读 2016-11-16 11:45:26
    在系统启动时,会在sched_init(void)函数中调用set_system_gate(0x80,&system_call),设置中断向量号0x80中断描述符: #define set_system_gate(n,addr) _set_gate(&idt[n],15,3,addr) 其中15表示此中断号对应的...
  • 当调试一块网卡的时候,内核报错,打印
  • linux字符驱动之中断按键

    万次阅读 2013-12-23 21:53:54
    在上一节中,我们讲解了如何自动创建设备节点,实现一个查询方式的按键驱动。测试的时候,大家都看到了,使用查询式的方法,占用CPU的利用率高达99%,那么有没有好的...这一节里,我们使用中断的方法来实现按键驱动。
  • 1.结构体ioctl_trans: struct ioctl_trans {  unsigned long cmd;  ioctl_trans_handler_t handler;  struct ioctl_trans *next; ...extern int register_
  • 盛科技术人员的说法是该芯片支持GPIO管脚中断和PCIE MSI中断,使用过程中二选一即可。目前PCIE MSI中断已经解决,需要调试GPIO管脚中断方式,ZYNQ连接示意图如下。如上图所示,四根线之间连入一个concat,再加上PCIE...
  • 实例分析Linux0.11内核中断机制

    千次阅读 2005-04-10 22:47:00
    异常就是控制流中的突变,用来响应处理器状态中的某些变化。当处理器检测到有事件发生时,它就会通过一张叫做异常表的跳转表,进行一个间接过程调用,到一个专门...2. 一般中断的处理过程,以0x3号中断为例。3. 
  • 系统调用是什么

    千次阅读 2018-08-09 11:12:17
    1.系统调用和普通函数完全不同,系统调用实际上是0x80号中断对应的中断处理程序的子程序。换句话说,在linux系统上,0x80中断是系统调用的统一入口。某个具体的系统调用是这个中断处理程序的子程序,进入具体某个...
  • android 电容屏(三):驱动调试之驱动程序分析篇

    万次阅读 多人点赞 2013-05-25 12:19:07
    关键词:android 电容屏 tp 工作队列 中断 坐点计算 电容屏主要参数 平台信息: 内核:linux2.6/linux3.0 系统:android/android4.0  平台:S5PV310(samsung exynos 4210)  作者:xubin341719(欢迎转载,请...
  • linux head.s 详解

    千次阅读 2011-06-26 17:44:00
    关于head.s的作用为head.s程序运行在32位保护模式下,其中主要包括初始设置的代码、时钟中断int 0x08的过程代码、系统调用中断int 0x80的过程代码以及任务A和任务B等的代码和数据。其中初始设置工作主要包括:①重新...
  • Linux shellcode 编写入门 (转)

    万次阅读 2008-07-15 14:32:00
    刺猬@http://blog.csdn.net/littlehedgehog无意当中在安全焦点上面看到的,很入门的一篇文章,不错:原文地址:http://www.xfocus.net/articles/200805/980.html一:什么是shellcode 话说某天某爱国黑客编译了一个...
  • Linux 中断详解

    万次阅读 2012-08-21 15:59:12
    数据结构体现了整个系统的构架,所以数据结构通常都是代码分析的很好的着手点,对Linux内核分析尤其如此。比如,把进程控制块结构分析清楚 了,就对进程有了基本的把握;再比如,把页目录结构和页表结构弄懂了
  • # head.s包含32位保护模式初始化设置代码、时钟中断代码、系统调用中断代码和两个任务的代码。 # 在初始化完成之后程序移动到任务0开始执行,并在时钟中断控制下进行任务0和1之间的切换操作。 LATCH = 11930 #...
  • 中断向量表和中断描述符表IDT

    千次阅读 2013-03-31 23:07:08
    CPU是根据中断号获取中断向量值,即对应中断服务程序的入口地址值。因此为了让CPU由中断号查找到对应的中断向量,就需要在内存中建立一张查询表,即中断向量表(在32位保护模式下该表称为中断描述符表)。80x86微机...
  • ZYNQ 中断详解

    万次阅读 2017-11-15 15:09:15
    软件中断(Software Generated Interrupt, SGI,中断号0-15)(16–26 reserved)  私有外设中断(Private Peripheral Interrupt, PPI,中断号27-31),  共享外设中断(Shared Peripheral Interrupt, SPI,中
  • 计算机的I/O端口地址表

    千次阅读 2012-04-04 15:50:00
    PC的I O端口地址表 (I/O端口地址表) ... I/O端口地址表  ...PC只用了10位地址线(A0-A9)进行译码,其寻址的范围为0H-3FFH,共有1024个I/O地址。...这1024个地址中前半段(A9=0,范围为0H-1FFH)是属于主机板I/O译码,...
1 2 3 4 5 ... 20
收藏数 13,895
精华内容 5,558
关键字:

0x80号中断 linux