• Linux0.11-内核中断体系


    在哪里用到中断?

    1.硬件的中断响应 ---> 内核驱动中的中断
    2.系统调用的函数响应(sys call) --->系统调用
    3.自定义中断 --->软件的软中断模式
    4.信号中断 (kill -signalnum)
    5.系统的异常和错误 ---> 系统异常处理获取 了解系统异常的作用

    1. Linux的中断机制

    1.1 分类:

    硬件中断和软件中断
    硬中断:由电脑主机的8259A类似的硬件中断控制芯片发出的中断
    AR中断控制器发出的中断
    软中断:异常第一类:CPU自行保留的中断
    系统调用异常

    1.2 代码结构:

    asm.s -> trap.c
    system_call.s -> fork.c signal.c exit.c sys.c

    2. 中断的工作流程:

    2.1 Linux 中中断的工作流程

    1 将所有寄存器的值入栈(切换CPU模式)
    SS EFLAGS(标志位寄存器) ESP CS EIP

    2 将异常码入栈(中断号:哪个设备产生的中断,如果发生错误能找到谁发出的中断处理)
    他这里异常码是中断号,中断处理过程中的c代码要靠这个号找到中断处理函数

    3 将当前的函数返回值进行入栈(为了在中断执行后能够找到在哪里中断,能够复原)

    4 调出 对应的中断服务函数

    5 寄存器复原

    在这里插入图片描述

     

                                        中断前的处理过程,中断的回复过程     中断服务程序的实现

    硬件中断的处理过程                   asm.s                                    trap.c
    软件及系统调用的处理过程 system call.s                             fork.c signal .c exit.c sys.c

    3. 中断的代码实现过程

    类似于函数调用过程 :从指令角度掌握函数调用堆栈详细过程_~怎么回事啊~的博客-CSDN博客_堆栈的操作过程

            EIP寄存器,用来存储CPU要读取指令的地址,CPU通过EIP寄存器读取即将要执行的指令。每次CPU执行完相应的汇编指令之后,EIP寄存器的值就会增加。

            CS(code segment)代码段地址寄存器,存放代码段的起始地址

            DS(data segment)数据段地址寄存器,存放数据段的起始地址

            SS(stack segment)堆栈段地址寄存器,存放堆栈段的起始地址(每个进程都有自己的栈空间)

            ES(extra segment)附加段地址寄存器,存放附加段的起始地址

       asm.s     

    1. _divide_error:
    2. push dword ptr _do_divide_error ;// 首先把将要调用的函数地址入栈。这段程序的出错号为0
    3. no_error_code: ;// 这里是无出错号处理的入口处,见下面push 0 ;// "error code" ;// 将出错码入栈。
    4. xchg [esp],eax ;// _do_divide_error 的地址 -> eax中的值和esp中的值交换(esp现在是_do_divide_error 的地址),eax 被交换入栈。
    5. // 1 入栈寄存器
    6. push ebx
    7. push ecx
    8. push edx
    9. push edi
    10. push esi
    11. push ebp
    12. push ds ;// !!16 位的段寄存器入栈后也要占用4 个字节。
    13. push es
    14. push fs
    15. // 2 入栈错误码
    16. push 0 ;// "error code" ;// 将出错码入栈。
    17. // 3 入栈原调用返回地址处堆栈指针位置
    18. lea edx,[esp+44] ;// 取原调用返回地址处堆栈指针位置,并压入堆栈。
    19. push edx
    20. mov edx,10h ;// 内核代码数据段选择符。
    21. mov ds,dx
    22. mov es,dx
    23. mov fs,dx
    24. //4 调用中断服务函数
    25. call eax ;// 调用C 函数do_divide_error()。
    26. add esp,8 ;// 让堆栈指针重新指向寄存器fs 入栈处。
    27. //5 出栈
    28. pop fs
    29. pop es
    30. pop ds
    31. pop ebp
    32. pop esi
    33. pop edi
    34. pop edx
    35. pop ecx
    36. pop ebx
    37. pop eax ;// 弹出原来eax 中的内容。
    38. iretd

            这就是asm.s里面的一串代码,也就是中断的处理过程,可以看到第三行是no_error_code,是无错误码的,在asm.s下面的代码还有一串error_code是有错误码的,而代码也正如上面说的,而这里因为没有错误码,压入的错误码是0,然后会调用中断服务函数,也就是它call eax;而在第四行代码可以看到,它其实是将eax和esp里的内容做了一个交换,所以它其实是调用了最开始压入的函数地址,也就是_do_divide_error函数。

    no_error_code 代码在trap.c中:

    1. //asm.s调用的第一个函数在这里
    2. void do_divide_error(long esp, long error_code)
    3. {
    4. die("divide error",esp,error_code);
    5. }
    1. //esp_ptr 段指针
    2. //nr 出错的段号
    3. //总的来说die函数就是用来打印错误信息
    4. static void die(char * str,long esp_ptr,long nr)
    5. {
    6. long * esp = (long *) esp_ptr;
    7. int i;
    8. //以下基本在打印栈信息
    9. printk("%s: %04x\n\r",str,nr&0xffff);
    10. printk("EIP:\t%04x:%p\nEFLAGS:\t%p\nESP:\t%04x:%p\n",
    11. esp[1],esp[0],esp[2],esp[4],esp[3]);
    12. printk("fs: %04x\n",_fs());
    13. printk("base: %p, limit: %p\n",get_base(current->ldt[1]),get_limit(0x17));
    14. if (esp[4] == 0x17) {
    15. printk("Stack: ");
    16. for (i=0;i<4;i++)
    17. printk("%p ",get_seg_long(0x17,i+(long *)esp[3]));
    18. printk("\n");
    19. }
    20. str(i);
    21. printk("Pid: %d, process nr: %d\n\r",current->pid,0xffff & i);
    22. for(i=0;i<10;i++)
    23. printk("%02x ",0xff & get_seg_byte(esp[1],(i+(char *)esp[0])));
    24. printk("\n\r");
    25. do_exit(11);//退出中断 /* play segment exception */
    26. }

     

    asm.s中下面还有其他的的异常处理:

    1. // 都是先将异常处理函数 do_xxx 入栈,然后调用no_error_code 和上面的过程一致
    2. _debug://这是一个中断
    3. pushl $_do_int3 # _do_debug
    4. jmp no_error_code//跳转
    5. _nmi:
    6. pushl $_do_nmi
    7. jmp no_error_code
    8. _int3:
    9. pushl $_do_int3
    10. jmp no_error_code
    11. _overflow:
    12. pushl $_do_overflow
    13. jmp no_error_code
    14. _bounds:
    15. pushl $_do_bounds
    16. jmp no_error_code
    17. _invalid_op:
    18. pushl $_do_invalid_op
    19. jmp no_error_code

    trap_init

    1. //中断的初始化函数
    2. //set_trap_gate 优先级较低 只能由用户程序来调用
    3. //set_system_gate 优先级很高 能由系统和用户所有的程序调用
    4. void trap_init(void)
    5. {
    6. int i;
    7. set_trap_gate(0,÷_error);//如果被除数是0就会产生这个中断
    8. set_trap_gate(1,&debug);//单步调试的时候调用这个中断
    9. set_trap_gate(2,&nmi);
    10. set_system_gate(3,&int3); /* int3-5 can be called from all */
    11. set_system_gate(4,&overflow);
    12. set_system_gate(5,&bounds);
    13. set_trap_gate(6,&invalid_op);
    14. set_trap_gate(7,&device_not_available);
    15. set_trap_gate(8,&double_fault);
    16. set_trap_gate(9,&coprocessor_segment_overrun);
    17. set_trap_gate(10,&invalid_TSS);
    18. set_trap_gate(11,&segment_not_present);
    19. set_trap_gate(12,&stack_segment);
    20. set_trap_gate(13,&general_protection);
    21. set_trap_gate(14,&page_fault);
    22. set_trap_gate(15,&reserved);
    23. set_trap_gate(16,&coprocessor_error);
    24. for (i=17;i<48;i++)
    25. set_trap_gate(i,&reserved);
    26. set_trap_gate(45,&irq13);
    27. outb_p(inb_p(0x21)&0xfb,0x21);
    28. outb(inb_p(0xA1)&0xdf,0xA1);
    29. set_trap_gate(39,¶llel_interrupt);
    30. }
    31. //idt就是 Interrupt Descriptor Table 中断描述表,这里就是往表里填中断号和中断程序的地址
    32. #define set_trap_gate(n,addr) \
    33. _set_gate(&idt[n],15,0,addr)
    34. #define _set_gate(gate_addr,type,dpl,addr) \
    35. __asm__ ("movw %%dx,%%ax\n\t" \
    36. "movw %0,%%dx\n\t" \
    37. "movl %%eax,%1\n\t" \
    38. "movl %%edx,%2" \
    39. : \
    40. : "i" ((short) (0x8000+(dpl<<13)+(type<<8))), \
    41. "o" (*((char *) (gate_addr))), \
    42. "o" (*(4+(char *) (gate_addr))), \
    43. "d" ((char *) (addr)),"a" (0x00080000))

    4. 系统调用system_call

            系统调用表在sys_call_table中,所有的系统调用C函数放到了一个统一的sys_call_table
    中,比如open、write等等

    include\linux\sys.h

    1. //定义系统调用的sys_call_table
    2. fn_ptr sys_call_table[] = { sys_setup, sys_exit, sys_fork, sys_read,
    3. sys_write, sys_open, sys_close, sys_waitpid, sys_creat, sys_link,
    4. sys_unlink, sys_execve, sys_chdir, sys_time, sys_mknod, sys_chmod,
    5. sys_chown, sys_break, sys_stat, sys_lseek, sys_getpid, sys_mount,
    6. sys_umount, sys_setuid, sys_getuid, sys_stime, sys_ptrace, sys_alarm,
    7. sys_fstat, sys_pause, sys_utime, sys_stty, sys_gtty, sys_access,
    8. sys_nice, sys_ftime, sys_sync, sys_kill, sys_rename, sys_mkdir,
    9. sys_rmdir, sys_dup, sys_pipe, sys_times, sys_prof, sys_brk, sys_setgid,
    10. sys_getgid, sys_signal, sys_geteuid, sys_getegid, sys_acct, sys_phys,
    11. sys_lock, sys_ioctl, sys_fcntl, sys_mpx, sys_setpgid, sys_ulimit,
    12. sys_uname, sys_umask, sys_chroot, sys_ustat, sys_dup2, sys_getppid,
    13. sys_getpgrp, sys_setsid, sys_sigaction, sys_sgetmask, sys_ssetmask,
    14. sys_setreuid,sys_setregid };

    linux-自定义进程通信方式_~怎么回事啊~的博客-CSDN博客

    有关于0.11 system_call的调用过程 

    在kernel\system_call.s中

    1. _system_call:
    2. cmpl $nr_system_calls-1,%eax
    3. ja bad_sys_call
    4. // 1 压栈相关寄存器
    5. push %ds
    6. push %es
    7. push %fs
    8. pushl %edx
    9. pushl %ecx # push %ebx,%ecx,%edx as parameters
    10. pushl %ebx # to the system call
    11. movl $0x10,%edx # set up ds,es to kernel space
    12. mov %dx,%ds
    13. mov %dx,%es
    14. movl $0x17,%edx # fs points to local data space
    15. mov %dx,%fs
    16. //调用 系统调用表 eax中存放系统调用号
    17. call _sys_call_table(,%eax,4)
    18. pushl %eax
    19. movl _current,%eax
    20. cmpl $0,state(%eax) # state
    21. jne reschedule
    22. cmpl $0,counter(%eax) # counter
    23. je reschedule

     

  • 相关阅读:
    爱上开源之golang入门至实战第三章-性能分析-Heap
    硬盘io性能分析
    【Rust 入门学习】1.1 Rust 的安装、升级、卸载
    Day10_Git版本控制、项目总结,preview_220627,
    springCloud在pom中快速修改运行环境,让配置不再繁琐
    青少年护眼灯多大功率最合适?分享几款适合青少年的护眼台灯
    Oracle导出clob字段到csv
    小程序实现下拉刷新
    参数估计和假设检验的区别与联系
    HarmonyOS har制作与引用
  • 原文地址:https://blog.csdn.net/LIJIWEI0611/article/details/126321731