Linux Kernel中断漫谈 (下)

  • 3 x86下Linux的中断向量表
    • 3.1 异常中断向量(0 - 15, 0x0h - 0xfh)
    • 3.2 不可屏蔽中断向量(16 - 31, 0x10h - 0x1fh)
    • 3.3 可屏蔽中断向量(32 - 47, 0x20h - 0x2fh)
    • 3.4 软中断向量(48 - 255, 0x30h - 0xffh)
  • 4 protect mode中断模式切换
    • 4.1 Call gates & Task Gate
    • 4.2 Trap gates & Interrupt gates
    • 4.3 Fast System Call
    • 4.4 Interrupt Context
  • 5 Linux Top and Bottom Half
    • 5.1 Softirq & Tasklet
    • 5.2 WOrking Queue
  • 6 热插拔(Hot-Plug)
  • 7 时钟中断
  • 8 网络中断
  • 9 TX and RX

3 x86下Linux的中断向量表

电源点亮以后,BIOS的指令会被默认加载并开始执行。BIOS会负责初始化所需要的中断芯片(设置芯片的各个可写寄存器),并且在可以使用的内存中(real mode现在还是)初始化中断向量表,并且初始化中断处理函数。

One important code areas created by BIOS is Interrupts Vectors which provide abilities that operating systems and application programs use to invoke the facilities of the Basic Input/Output System, such as read one disk, flush screen or do printing. It also handle basic device interrupts : IRQ0-IRQ15(Interrupt Request).

For X86,BIOS then build up The memory mapping area(address) normally is between 0x0000 to 0x3FFF, totally 1KB, 256 slots and 4B for each slot.

4B是因为每个slot对应一个32位的地址(16bit segment selector, 16bit line address).256个中断向量,其中:

  • 0-15 异常intel内部保留,不可屏蔽
  • 16-31 非屏蔽中断,intel内部保留,不可屏蔽
  • 32-47 可屏蔽中断,可屏蔽
  • 47-255 软中断,linux系统本身使用128(0x80)来作为系统调用软中断,可屏蔽

指令INT xx可以用来触发中断向量xx,所需要的参数放置在寄存器AH中

另外,中断向量表示可以修改的(基地址都可以改掉),当操作系统从BIOS那里接手过系统的控制权时候,可以修改中断向量表来指向自定义的中断处理函数。linux就是这么干的,一方面把向量表换了个基位置为了开启保护模式,另外一部分overwrite部分向量表的实现

3.1 异常中断向量(0 - 15, 0x0h - 0xfh)

INT_NUM    Short Description PM
0x00    Division by zero
0x01    Debugger
0x02    NMI
0x03    Breakpoint
0x04    Overflow
0x05    Bounds
0x06    Invalid Opcode
0x07    Coprocessor not available
0x08    Double fault
0x09    Coprocessor Segment Overrun (386 or earlier only)
0x0A    Invalid Task State Segment
0x0B    Segment not present
0x0C    Stack Fault
0x0D    General protection fault
0x0E    Page fault
0x0F    reserved
0x10    Math Fault
0x11    Alignment Check
0x12    Machine Check
0x13    SIMD Floating-Point Exception
0x14    Virtualization Exception
0x15    Control Protection Exception

3.2 不可屏蔽中断向量(16 - 31, 0x10h - 0x1fh)

中断向量16开始就是CPU外部的中断。而中断向量16 - 31中断还提供很多神奇的功能。

这部分中断向量也可以叫做BIOS interrupt(!!!)。BIOS会调用它们来把向屏幕显示文字或者把控制权转个操作系统(著名的INT 19H,触发bootloader(!!!))。

部分不可屏蔽中断向量描述(大多需要配合AH):

INT_NUM    Short Description PM
0x10    Video Services
0x13    Low Level Disk Services
0x14    Serial Port Services
0x15    Miscellaneous System Services
0x16    Keyboard Services
0x17    Printer Services
0x19    OS Booting Load or Restart OS
0x1A    PIC Services

DOS很大程度上直接继承并使用这些中断向量,而windows和linux会在BIOS触发INT 0x19h之后最终接管整个中断系统。

3.3 可屏蔽中断向量(32 - 47, 0x20h - 0x2fh)

芯片8259AIRQ0 - IRQ15被映射到中断向量32-47范围,也就是可以屏蔽的范围(APIC以后不一定一一对应!!!)。是否被屏蔽依赖于:

  • EFLAGS中的IF位,全局的觉得是否屏蔽
  • 芯片的MASK,决定单个IRQ的屏蔽

多个设备可以共享一个中断(!!!),当中断触发时,所有注册的handler都会被触发(!!!),每个handler需要自己去判断是不是自己的中断

3.4 软中断向量(48 - 255, 0x30h - 0xffh)

48开始,大部分空间都是操作系统可用的中断向量。有一部分提供专门用处,其中最特殊的是0x80h,负责系统调用,具体如下:

  • 128(0x80):系统调用,由程序主动触发,也可以称为软陷入。
  • 239(0xef):APIC本地时钟中断
  • 240 (0xf0) :Local APIC thermal interrupt (introduced in the Pentium 4 models)
  • 241-250(0xf1-0xfa) : Linux 保留作将来使用(注意是Linux内核保留,而非Intel保留 )
  • 251(0xfb):CALL_FUNCTION_VECTOR :发往所有的CPU(不包括发送者),强制这些CPU运行发送者传递过来的函数。
  • 252(0xfc):RESCHEDULE_VECTOR :当一个CPU接收这种类型的中断时,限定自己来应答中断。当从中断返回时,所有的重新调度都自动进行。
  • 253(0xfd):INVALIDATE_TLB_VECTOR :发往所有CPU(不包括发送者),强制他们的转换后援缓冲器(TLB)变为无效,
  • 254(0xfe):ERROR_APIC_VECTOR :错误的APIC向量,应该从不发生。
  • 255 (0xff):SPURIOUS_APIC_VECTOR:假的APIC向量,应该从不发生。

4 protect mode中断模式切换

在系统从real modeprotect mode切换时候,linux会把原有的中断向量表废止,新建自己的IDT用来处理中断。当然,因为real mode下的表废止了,而新表稍微建立,中断必须被屏蔽。real mode的中断向量长度是4bytes,在32bit protect下是8bytes,64bit protect下面是16bytes。

类似于中断向量表,IDT中保存了更多的控制信息。BIOS在调用INT 0x19h控制权交给bootloader(!!!),后者负责:

  • 关闭中断
  • 清理内存布局:删除原有中断向量表,构建GDT
  • 打开A20切换到protect模式
  • 通过GDT控制权交给linux

随后linux接管(head.S

  • 清理内存:copy代码(包括kernel),创建新的GDT,初始化IDT,初始化分页机制(创建页表等等)
  • protect模式初始化:清理并设置寄存器等等

随后把控制权交给kernel。

real mode下的中断向量是没有权限控制的,protect mode需要同时take care权限的问题。为此,x86引入了gate的概念,一共有四种类型的gate:

  • Call gates :不存在IDT中
  • Trap gates :Only in IDT
  • Interrupt gates :Only in IDT
  • Task gates:IDT,LDT g ate一方面指向了对应的ISR(中断服务程序,Interrupt Service Routines),另一方面用于判断调用是否有权限。

Code modules in lower privilege segments can only access modules operating at higher privilege segments by means of a tightly controlled and protected interface called a gate. Attempts to access higher privilege segments without going through a protection gate and without having sufficient access rights causes a general-protection exception (#GP) to be generated."

4.1 Call gates & Task Gate

Call Gate是在protect mode下提供FAR CALL功能而引入的。FAR CALL有很多种type,Call Gate只是其中一种。

When the processor is operating in protected mode, the CALL instruction can be used to perform the following three types of far calls: o Far call to the same privilege level o Far call to a different privilege level (inter-privilege level call) o Task switch (far call to another task) In protected mode, the processor always uses the segment selector part of the far address to access the corresponding descriptor in the GDT or LDT. The descriptor type (code segment, call gate, task gate, or TSS) and access rights determine the type of call operation to be performed.

Task Gate也是为了Far Call,不过他会产生一个新的Task来执行中断处理程式,而新的Task在执行期间会屏蔽中断。

When an exception or interrupt handler is accessed through a task gate in the IDT, a task switch results. Handling an exception or interrupt with a separate task offers several advantages。

Call Gate同时可以作为privilege切换的方式,也可以作为16bit和32bit指令直接切换的方式。所以Call Gate可以作为系统调用实现的一种方式。

但Call Gate没有流行起来,主要原因INT 0x80h的引入,另外移植性和灵活性也一般。随后新的指令:sysenter/sysexit and syscall/sysret(AMD)引入并被广泛使用。

换句话说,现在有三种模式去调用一个system call(系统调用!!!):

  • 通过Call Gate
  • 通过Trap Gate 0x80h
  • 通过sysenter/sysexit and syscall/sysret(AMD

4.2 Trap gates & Interrupt gates

Trap gates和Interrupt gates类似于real mode中的中断向量,指向对应的中断处理程式。两者都有DPL(Descriptor Privilege Level)来表明调用者所需要权限,地址等信息。不同之处在于Interrupt gates屏蔽所有可屏蔽中断(IF flags = 0),再interrupt handler返回后恢复;Trap gates则会保持中断屏蔽原来的状态

在Intel的文档中没有说interrupt handler执行时候会屏蔽触发中断,因此理论上中断处理程式可能是需要re-enter的。但是linux会屏蔽当前触发的中断,因此,linux的ISR(中断处理程式)是可以写成非重入的,但是ISR本身不做全部中断屏蔽(除非手工屏蔽),还是可能会被其他中断所interrupt。

linux使用0x80h这个Trap Gate来实现系统调用,所以linux的系统调用执行的过程中是可能被其他中断所打断的

4.3 Fast System Call

通过Call Gate或者INT 0x80h系统调用时候都需要上下文切换额外的开销。而Fast System Call则重用当前的执行栈(!!!),因此可以省去这部分开销,对应的指令包括SYSENTER/SYSEXIT(by Intel)和SYSCALL/SYSRET(by AMD)。

Fast System Call执行时候会屏蔽中断(!!!),并在返回时恢复

if(CR0.PE == 0) Exception(GP(0));
if(SYSENTER_CS_MSR == 0) Exception(GP(0));
EFLAGS.VM = 0; //Insures protected mode execution
EFLAGS.IF = 0; //Mask interrupts
...

4.4 Interrupt Context

Interrupt Context对应的是User ContextProcess Context。User Context就是默认用户执行程序的context用户空间

当一个user space thread系统调用进入系统或者一个kernel thread在运行的时候,that is Process Context mode,这意味这个线程可以被block(sleep)。而Interrupt Context不能block(sleep),因此semaphore等依赖于block的方法也不能被使用。

值得注意的是,不能block(sleep)和不能被preempt是两个概念,前者依赖于时钟中断去唤醒,后者一般被其他中断所抢占。不能block(sleep)的原因不是因为逻辑上的错误,而更多是为了性能,概念上的完整性和实现的复杂上的考虑的结果。

5 Linux Top and Bottom Half

我们无法保证在单次函数处理时候情况下,中断处理既能及时又要能handle大批量的工作。因此Linux选择把中断处理分成两个部分快速响应并返回的Top Half处理繁重工作的Bottom Half

  • Top Half负责处理critical的工作,响应,copy需要的值,返回
  • Bottom Half一般会被队列起来,在空闲的时候依次处理

例如对于网卡中断Top Half负责响应中断,然后把数据网卡内部存储copy内存空间,然后返回。留下剩下的协议栈解包等工作给Bottom Half执行。

Top Half也就是所谓的ISR部分,而ISR的部分由Bottom Half负责。不过如何切分Top和Bottom依赖于device driver的开发者,并没有明确和清晰的边界,只有一些suggestion或者principle:

  • If the work is time sensitive, perform it in the interrupt handler.
  • If the work is related to the hardware, perform it in the interrupt handler.
  • If the work needs to ensure that another interrupt (particularly the same interrupt) does not interrupt it, perform it in the interrupt handler.
  • For everything else, consider performing the work in the bottom half.

另外要注意的是,Bottom Half执行时候context不屏蔽中断(!!!)的。

历史上遗留下来了多种Bottom Half的实现方式

  • Orignial Bottom Half(BH):based on globally synchronized; tossed to the curb in version 2.5
  • Task Queue :replaced by Work Queue in version 2.5
  • Softirqs :compile time registered,multi running instance for one type in SMP, introduced in version 2.3
  • Tasklets :dynamic registered,one running instance for one type even in SMP, introduced in version 2.3
  • Work Queue : replace Task Queue

5.1 Softirq & Tasklet

Linux内置了9个Softirqs Types,一般开发者也不会对此进行增加,而更加依赖于使用Tasklets来完成工作,除非需要非常高的性能要求:

Table Softirq Types

TaskletPrioritySoftirq DescriptionHI_SOFTIRQ0High-priority taskletsTIMER_SOFTIRQ1TimersNET_TX_SOFTIRQ2Send network packetsNET_RX_SOFTIRQ3Receive network packetsBLOCK_SOFTIRQ4Block devicesTASKLET_SOFTIRQ5Normal priority taskletsSCHED_SOFTIRQ6SchedulerHRTIMER_SOFTIRQ7High-resolution timersRCU_SOFTIRQ8RCU(Read-copy update) locking

Network和Block是最常用的I/O设备相关的Softirq,剩下负责时钟和内存同步。有意思的事,Tasklets其实是通过Softirq实现的。Softirq通过函数raise_softirq触发,例如网络就是调用raise_softirq(NET_TX_SOFTIRQ); 在每个core上面都有一个ksoftirqd的thread,负责执行Softirq的request。

Tasklets提供两个queue:普通和high priority的,不能在执行期间sleep,因此也不能使用semphore等同步方式。同时Tasklets保证执行时候是unique instance。这些限制是为了简化Tasklets的设计。但是Tasklets不屏蔽中断。

Linux为了防止user-space进程饿死问题,会timely check是否有足够多的Softirq触发了,随后才执行。

最后,Softirq和Tasklet在Interrupt Context运行。

5.2 WOrking Queue

Working Queue类似于Tasklets,不同之处在于Working QueueProcess Context下运行,所以可以block(sleep),也可以schedule。你甚至可以Working Queue就是一个kernel thread(pool),不停的处理中断有关的bottom half work。

6 热插拔(Hot-Plug)

Linxu依赖于udev(!!!)去热插拔某个硬件(包括/dev挂载点的创建)。当某个设备被插入系统系统时候,一个uevent被触发时,udev尝试:

  1. 按照系统中/etc/udev/rules.d/定义的rules去捕捉event
  2. create/remove device files,
  3. load/unload 对应的module(driver
  4. 通知用户空间

中断分配等工作就是在module(driver)被load/unload时候发生。

一台PnP兼容的电脑必须满足下列三个要素:

  • 操作系统必须兼容PnP
  • BIOS必须支持PnP
  • 要安装的设备本身必须是PnP设备

PnP设备会和BIOS(PIC)申请IRQ号,随后向OS注册,随后当Drvier被引入时候,就可以通过调用函数询问设备的IRQ号而不必要自己探测。

7 时钟中断

Linux中的sleep操作依赖于时钟中断去实现。当一个时钟中断触发时候,Linux会检查sleep队列中是否有需要唤醒的task,随后唤醒如果需要。

在计算机系统中存在着许多硬件计时器,例如Real Timer Clock(RTC)、Time Stamp Counter(TSC)和Programmable Interval Timer(PIT)等等。

  • Real Timer Clock ( RTC ):
    • 独立于整个计算机系统(例如: CPU 和其他 chip )
    • 内核利用其获取系统当前时间和日期
  • Time Stamp Counter ( TSC ):
    • 从 Pentium 起,提供一个寄存器 TSC,用来累计每一次外部振荡器产生的时钟信号
    • 通过指令rdtsc访问这个寄存器
    • 比起PIT,TSC可以提供更精确的时间测量
  • Programmable Interval Timer ( PIT ):
    • 时间测量设备
    • 内核使用的产生时钟中断的设备,产生的时钟中断依赖于硬件的体系结构,慢的为10 ms一次,快的为1 ms一次
  • High Precision Event Timer ( HPET ):
    • PITRTC 的替代者,和之前的计时器相比,HPET提供了更高的时钟频率(至少10 MHz )以及更宽的计数器宽度(64位
    • 一个 HPET 包括了一个固定频率的数值增加的计数器以及3到32个独立的计时器,这每一个计时器有包含了一个比较器和一个寄存器(保存一个数值,表示触发中断的时机)。每一个比较器都比较计数器中的数值和寄存器中的数值,当这两个数值相等时,将产生一个中断

其中PIT和HPET会触发时钟中断,而且HPET是可编程的。

8 网络中断

一个packet到达某个网卡(NIC),如下的流程会被触发:

  1. 数据会通过DMA的模式copy到RAM中的一个ring buffer上面
  2. NIC触发一个IRQ通知CPU,数据来了
  3. IRQ的handler被运行,raise一个Softirq
  4. IRQ返回,清理NIC的Interrupt Flag(!!!)
  5. Softirq运行触发NAPI
  6. NAPI继续运行,做一系列可能数据合并等优化,数据在整理后发送给Protocol Stacks

在此过程中可能会包括和其他Core直接的IPI(inter-processor interrupt)通讯

9 TX and RX

cat /proc/interrupts你会发现一些的网络相关的中断上面会有eth0-tx-0,eth0-rx-0或者eth0-TxRx-0的中断。其中Tx和Rx分别表示TransmitReceive一个网卡会有多个tx和rx,另外还有个单独的网卡的中断

PCI-MSI-edge      eth0
PCI-MSI-edge      eth0-TxRx-0
PCI-MSI-edge      eth0-TxRx-1
PCI-MSI-edge      eth0-TxRx-2
PCI-MSI-edge      eth0-TxRx-3
PCI-MSI-edge      eth0-TxRx-4
PCI-MSI-edge      eth0-TxRx-5
PCI-MSI-edge      eth0-TxRx-6
PCI-MSI-edge      eth0-TxRx-7

如上,可以看到会有8个发送接受合并中断。所以,理论上而言,开同时开8个链接可以最大化的提升网络性能?While, it is another story.

每个rx和tx也可以单独的分配一个中断

CPU0 CPU1 CPU2 CPU3 CPU4 CPU5
105: 141606 0 0 0 0 0 IR-PCI-MSI-edge eth2-rx-0
106: 0 141091 0 0 0 0 IR-PCI-MSI-edge eth2-rx-1
107: 2 0 163785 0 0 0 IR-PCI-MSI-edge eth2-rx-2
108: 3 0 0 194370 0 0 IR-PCI-MSI-edge eth2-rx-3
109: 0 0 0 0 0 0 IR-PCI-MSI-edge eth2-tx

不过具体每个中断名的意思依赖于driver和硬件,可能某个NIC的rx和tx就共享一个中断。

另外MSIPCI socket专门的一种中断触发机制,不走传统的INT引脚,通过PCI总线专门的存储结构写信息来触发中断(!!!)。

你可能感兴趣的:(Linux Kernel中断漫谈 (下))