深入理解linux内核读书笔记 (第四章)

来源:互联网 发布:无极传奇数据库编辑器 编辑:程序博客网 时间:2024/05/22 04:44

1.  中断被分为同步中断和异步中断。同步中断是cpu在执行指令过程中触发的, 异步中断是其他硬件设备在任意时间所引发的。

2. 英特尔手册中将同步中断称为异常,异步中断称为中断。

3. 中断一般由外部io设备触发,异常一般分为两种:1. 由于程序错误引发。 2. 异常的执行情况或请求,例如页故障和系统调用。

4. 内核处理中断时,将当前进程的eip和cs寄存器放入内核栈中,然后载入中断处理程序的地址。

5. 中断处理程序执行的是不是另外一个进程,而是另外一条执行路径,因此,比进程切换的开销要小一些。

6. 中断处理是可以嵌套的,但是在某些情况下,执行中断处理程序的时候,需要关闭中断。

7. 中断分为可屏蔽中断和不可屏蔽中断,一般的IO设备产生的中断是可屏蔽中断,紧急情况下的(硬件故障)属于不可屏蔽中断。

8. 异常又分为cpu检测到的异常和软件触发的异常。

9. cpu检测到的异常分为三类:

   (1) faults, 一般是可以恢复的,再次执行eip。(页故障)

   (2) traps, 恢复后, 执行eip + 1。 (debuger)

   (3) aborts, 无法恢复,一般会杀死相应进程。(硬件故障)

10. 软件触发的异常一般被当作traps处理,又叫软中断,一般应用于实现系统调用和调试。

11. 中断被屏蔽后不会丢失,当中断不被屏蔽后,pic会尽快地通知cpu。

12. 关闭中断会导致pic触发的中断被忽略,eflags中的IF位清零。汇编指令cli和sti分别用来关闭和打开中断。

13. 在SMP系统中,使用IOAPIC来对外部的中断进行路由,共有24个中断线,并且通过重定向表来确定中断向量和优先级。

14. 中断分发到cpu分为两类:

  (1)静态分发,在重定向表中配置接收中断的cpu。

  (2)动态分发,根据TPR寄存器的值,计算当前运行进程的优先级。TPR寄存器在每次进程切换的时候刷新。

15. APIC 系统可以让cpu之间发送IPI。

16. IOAPIC可以被配置为两种模式:

    (1) 经典的8259, LAPIC被禁止。

    (2) 标准的IOAPIC, 所有的外部中断都通过IOAPIC进行路由。

17.  x86系统大约有20个异常,20-31为intel保留未来使用。

18. IDT用来关联中断(异常)和对应的处理函数。IDT必须在使能中断前设置好。

19. IDT的每一项对应一个中断或异常,每一个项8个字节,因此最多需要256 × 8 = 2048 bytes。

20. IDT包含三种描述符

   (1)Task gate, 包含当中断发生时要切换到的TSS选择子。

   (2)Interrupt gate, 包含中断处理程序的段选择符和偏移,当转移相应的段之后, IF位被清零,后续中断被禁止。

   (3) Trap gate,和中断门类似,但是转移之后,中断不禁止。

21. linux用中断门来处理中断,用陷阱门来处理异常。

22. 发生中断时,内核会检查是否需要切换运行级别,如果需要的话,从tss中读取ss和esp并且载入相应寄存器,在新的栈中保存之前运行级的ss和esp。如果是faults的话,把eip和cs设置成引起fault的地址,方便再次恢复执行。最后把eflags, cs, eip也保存在栈中(如果有错误码的话,也保存),然后跳转到中断处理函数。

23. 中断结束后,调用iret,将栈上的cs,eip,eflags载入(有错误码的话,必须先弹出),如果中断处理程序的运行级别和CPL不同,载入ss和esp(之前运行级),最后检查ds,es,fs,gs, 如果发现运行级别小于CPL,清除该寄存器,防止用户态访问内核地址空间。

24.  中断嵌套要求在处理中断过程中,不能发生进程切换,因为恢复嵌套所需要的信息都在当前进程的内核栈中。

25. 大部分异常发生在用户态,内核态只有可能发生缺页异常。因此,异常最多有两个嵌套,先是系统调用,然后是缺页异常。

26. 中断处理可以抢占其他中断处理和异常处理,异常处理从来不会抢占中断处理。中断处理函数中不会出现导致缺页异常的操作,因为它会带来进程切换。

27. 在实模式下,BIOS会初始化IDT,但是linux不使用任何BIOS的函数, 因此,linux会将IDT移动到另外的内存地址并且再次初始化。

28. IDT被保存在idt_table 中,有256个选项,idt_descr包含IDT表的大小和起始地址。

29. 内核先将IDT表初始化为一个特定的处理函数,然后在进行第二次初始化的时候,填入正确的处理函数。

30. 内核利用异常来管理硬件资源,主要包括两个例子,一个是用device not available 和 cr0中的TS位来刷新浮点寄存器的值,令一个是页故障。

31. 异常处理的流程包括三个步骤:

      (1) 在内核栈保存大部分的寄存器。(用汇编编写)

      (2) 处理异常。

        (3)   通过ret_from_exception来退出异常处理函数。

32. trap_init函数来填充IDT表项,它会调用set_trap_gate, set_intr_get, set_system_gate, set_intr_gate等函数。

33.  Double  fault 异常是通过task_gate 来处理的,当异常发生时,内核不信任当然的esp值,因此会通过IDT(第8个)找到任务门的描述符,它指向一个特殊的TSS

 (GDT的第32个项), 然后载入eip和esp,最终在私有的堆栈上执行doublefault_fn。

34. 异常入口函数首先选择性地压栈一个error code ,然后将异常处理函数的地址压栈,最后跳到error_code 标签处执行。error_code 执行以下操作:

    (1) 保存可能用到的寄存器到堆栈中。

    (2) 清除DF flags, 保证string操作时edi和esi的自增。

    (3)把error code 复制到 edx中,在原来的位置写入-1,这个值用来区分0x80(系统调用)和其他的异常。

    (4) 将do_handler_name 载入edi中,在原来的位置写入es的内容。

    (5) 将当前的内核栈顶载入eax中。

    (6) 将用户态的ds载入ds和es寄存器。

    (7) 调用edi中的处理函数。

35. 大部分的异常处理函数会调用do_trap将hardware error code和 exception vector 保存到当前进程中,然后发送一个相应的信号给当前进程。信号处理可以在用户态也可以在内核态,在内核态的话, 一般就是杀死进程。

36. 内核异常处理会检查异常是发生在用户态还是内核态,如果是在内核态,会继续检查是否由于错误的参数传递给系统调用,其他的内核异常会导致内核panic。

37. 异常处理结束后,会跳转到ret_from_exception函数来返回。

38. 中断处理中,发送信号给当然进程是没有意义的,因为当然进程并不一定是触发中断的进程。

39. 一个中断处理函数需要灵活地服务多个设备,因为向量可以被多个设备共享,所以对于中断共享的方式,多个中断服务程序会依次执行,直到确认中断源。

40. 中断处理过程中,相应的中断会被自动屏蔽,并且不能调用引起阻塞的函数。

41. 对于IO中断来说主要有以下步骤:

     (1)保存中断号和寄存器到内核栈中。

     (2) 向pic确认中断,允许中断排队。

     (3)依次执行关联该中断号的所有中断服务程序。

     (4) 通过ret_from_intr返回。

42. 中断向量:

     (1) 0-19: 不可屏蔽中断和异常

     (2)   20-31: 英特尔保留。

     (3) 32-127: 外部中断。

     (4) 128: 系统调用。

     (5) 129-238: 外部中断。

43.  中断处理的入口处,会将n-256压栈,这是个负数(正数用来表示系统调用), 然后跳到common_interrupt。

44. common_interrupt 会首先保存寄存器到堆栈,除了eflags, cs, eip, ss, 和 esp, 这些已经被处理器自动保存了。之后把用户态的ds载入ds和es, 最后调用do_irq, 

     regs指针保存在eax中。

45. enable_irq函数会检测是否有中断被遗漏:当IRQ_PEDING被设置,说明中断被确认但是没有被服务,它会调用hw_resend_irq来重新产生一个自我中断,并设置IRQ_REPLAY。


46. handle_IRQ_event函数:

   (1) 如果之前关闭了中断,重新打开。

     (2)  依次执行每个中断服务程序。

   (3)关闭中断。

   (4) 返回中断服务程序的返回值。(0表示没有识别该中断,否则为1)


47. 当设备不能够共享irq时,可以动态分配irq。设备驱动调用request_irq, 之后分配一个irqaction,并调用setup_irq来链接到相应的irqaction 链表里。

48. 可延时函数: softirq 和 tasklet 执行在中断上下文中。

49. softirq 和 tasklet的区别:

   (1) softirq是静态分配的,tasklet可以静态也可以动态分配。

   (2)softirq可以在几个cpu中并行执行,因此必须是可重入的。tasklet是序列化的,不需要可重入。

50. 可延时函数的激活和执行必须在同一个cpu上。

51. open_softirq 用来初始化softirq,raise_irq 用来激活softirq。如果in_interrupt返回0,就调用wake_softirq来激活当前cpu的ksoftirqd(如果必要的话)。

52. 检查是否有等待的softirq是周期性的,一般有如下几个检查点:

    (1) 调用local_bh_enable来使能当前cpu的softirq。

    (2)当do_irq结束IO中断处理后调用irq_exit宏的时候。

    (3) 在 I/O APIC中,当smp_apic_timer_interrupt函数完成当前的时间中断处理函数时。

    (4) 在SMP中,当一个cpu完成 CALL_FUNCTION_VECTOR处理器间中断时。

    (5) 当任意一个ksoftirqd内核线程被唤醒的时候。

53.  work queue 不同于softirq 和tasklet,可以运行在进程上下文中,是由kworker内核线程来运行,二中都不能访问用户空间内存。

54. 当内核从中断或异常返回时,需要处理以下事情:

    (1)如果没有更多的内核路径,应该切回到用户态。

    (2)如果有等待的调度请求,内核必须去实施一次调度,否则就返回当前进程。

    (3)如果有信号要发送给当前进程,内核必须去处理这个信号。

    (4)等等。

55. ret_from_intr 和 ret_from_exception基本流程相同,差别就在于是否开启内核抢占,如果开启抢占的话,ret_from_exception会关闭本地的中断。

56. 不论是返回到内核还是用户态,如果开启了内核抢占,都需要去检查是否有调度请求,如果有的话,需要实施调度。

0 0