Linux内核中断的实现方式,linux kernel笔记——中断

cpu与磁盘、网卡、键盘等外围设备(相对于cpu和内存而言)交互时,cpu下发I/O请求到这些设备后,相对cpu的处理能力而言,磁盘、网卡等设备需要较长时间完成请求处理。

那么在请求发出到处理完成这段时间,应如何设定cpu的行为,既能让这期间运行的其他程序得到执行,又能在外设处理完成后,cpu及时获取到处理完成的消息?

可以按以下方式设定cpu行为:

cpu以轮询(polling)的方式,每隔一段时间询问请求是否处理完成

cpu下发请求后执行其他进程,磁盘等外设完成处理后,主动告知cpu

对于第1种方式,轮询会带来不必要的cpu消耗。第2种是现行的方式,外设通过电信号告知cpu的机制被称为中断(interrupt)。

IRQ line与ISR

不同设备发起的中断以不同的数值标识,这些标识中断的数值被称为中断请求线(interrupt request line, IRQ line),IRQ line与中断的关系相当于系统调用号与系统调用的关系。

cpu接收到中断后会将中断递交给内核处理,内核中有相应的函数完成中断处理(interrupt handler或interrupt service routine,ISR)。

中断上下文

每个中断对应一个中断处理函数,当中断处理函数被调用时,内核处于中断上下文(interrupt context)。不同于进程上下文(process context),中断处理过程中不能发生阻塞、休眠,即不进行进程调度,其基于以下原因:

中断处理函数需要较快的响应速度,尽快告知外设已接收到中断的消息,以让外设继续工作

中断处理中没有独立的函数栈,如果被调度,不会保存寄存器的值,因而回不到调度前执行的代码

中断屏蔽

从是否可屏蔽的角度,中断可分为:

可屏蔽中断(maskable interrupt):可通过设定中断屏蔽寄存器EFLAGS中IF标志位关闭的中断

不可屏蔽中断(non-maskable interrupt, NMI):无法通过设置标志位屏蔽的中断,如电源掉电、时钟中断

对于可屏蔽中断,关闭中断的方式有以下3种:

使用cli(clear interrupt)指令,在全局范围关闭所有中断,使用sti(set interrupt)指令恢复

调用local_irq_disable或local_irq_save关闭当前cpu中断,使用local_irq_enable或local_irq_restore恢复

调用disable_irq或disable_irq_nosync在全局范围关闭某一特定中断线,使用enable_irq或synchronize_irq恢复

因中断是异步的,屏蔽中断可用于防止中断嵌套。非正常的中断关闭会带来很多不良结果,例如cpu不响应键盘中断时,用户无法使用键盘操作;又例如cpu不响应时钟中断,则不能进行进程调度,依赖于时钟中断的任务都无法完成,机器基本变成僵尸。

中断处理流程

外设发起中断,cpu接收中断并传递给内核处理,下图说明了该过程:

0818b9ca8b590ca3270a3433284dd417.png

内核代码中,中断号被传递到do_IRQ处理,do_IRQ调用__do_IRQ函数,__do_IRQ中调用handle_IRQ_event函数,其调用相应的中断处理函数进行中断处理;处理完成,返回到do_IRQ函数,之后调用irq_exit,在irq_exit函数中,调用do_softirq处理软中断,软中断相关内容将在后文讲述。

proc接口

proc文件系统向用户开放了各个中断的情况:

linux # cat /proc/interrupts

CPU0      CPU1

0:    178037   155726    IO-APIC-edge    timer

1:      49578      7372    IO-APIC-edge    i8042

8:           34         23    IO-APIC-edge    rtc0

……

NMI:         0           0    Non-maskable interrupts

LOC:   85437    86438    Local timer interrupts

……

以上输出显示了中断号、各个cpu接收中断次数、中断控制器(interrupt controller)和中断名称等信息,从/proc/stat也可查到中断相关信息。

cpu亲和力

多核cpu给中断处理方式带来了新的问题:若某个中断到来,应该由哪个核处理?

我们可以通过proc的接口,设定特定的cpu处理某个中断,这被称为cpu对中断的亲和力(affinity),利用/proc/irq/{interrupt num}/smp_affinity可以查看或设定cpu亲和力:

linux# cat /proc/irq/[0,1]/smp_affinity

3

1

以上查询结果显示,0号、1号cpu均可处理0号中断,而1号中断更倾向于交给1号cpu处理。若要设定1号cpu处理1号中断,可执行以下命令:

linux # echo 2 > /proc/irq/1/smp_affinity

中断负载平衡

irqbalance为用户态下的一个守护进程,其可平均地在多核cpu间分发中断:

linux # ps -elf | grep irqbalance | grep -v grep

1 S root 2408 1 0 80 0 - 2242 - Jul12 ? 00:00:01 /usr/sbin/irqbalance

下半部

内核接收到cpu传递过来的中断后,为做到快速响应外设,中断的处理被分成两部分:

上半部(top halves):不得不做的工作放在上半部,也即中断处理程序中,例如告知外设接收到中断、将数据从外设中拷贝到内存

下半部(bottom halves):不紧急的工作延后完成,如处理上半部中从外设拷贝来的数据

例如网卡接收数据过程中,首先网卡发起中断告诉cpu取数据,然后内核从网卡读取数据存入缓存,再之后内核解析数据并将数据送到应用层。如果以上工作都让中断处理程序来处理,过程太长,会导致丢失新来的中断。更优的方式是将以上工作分成两部分,从网卡读取数据到缓存由上半部完成,解析数据等较不紧急的工作由下半部完成。

下面我们看softirq和tasklet两种下半部实现方式。

softirq

softirq在2.3版本内核被引入,相关代码在中定义,内核中直接使用softirq的场景较少。

可用的softirq有以下几种,在中定义:

enum

{

HI_SOFTIRQ=0,

TIMER_SOFTIRQ,

NET_TX_SOFTIRQ,

NET_RX_SOFTIRQ,

BLOCK_SOFTIRQ,

BLOCK_IOPOLL_SOFTIRQ,

TASKLET_SOFTIRQ,

SCHED_SOFTIRQ,

HRTIMER_SOFTIRQ,

RCU_SOFTIRQ, /* Preferable RCU should always be the last softirq */

NR_SOFTIRQS

};

提交一个softirq需要调用raise_softirq函数,raise_softirq调用raise_softirq_irqoff,该函数将相应软中断标识为pending、完成软中断提交。

标识为pending的软中断可在以下时机得到处理:

1.硬中断处理完成、do_IRQ函数即将返回时处理

do_IRQ调用irq_exit函数,irq_exit调用invoke_softirq,即do_softirq函数,do_softirq调用__do_softirq。在__do_softirq函数中,最多处理10个软中断,若软中断的个数超过10,则调用wakeup_softirq唤醒ksoftirqd内核线程进行软中断处理

2.ksoftirqd内核线程被唤醒后处理

以上说明了ksoftirqd线程被唤醒的一种情况,wakeup_softirqd还会在raise_softirq_irqoff函数中被调用,也即软中断被提交之后,可唤醒ksoftirqd完成软中断处理

3.显式地调用do_softirq进行软中断处理

在内核网络代码中,netif_rx_ni函数会主动调用do_softirq进行软中断处理

tasklet

tasklet基于softirq实现,其本质也是softirq,对应softirq枚举类型中的HI_SOFTIRQ和TASKLET_SOFTIRQ,HI_SOFTIRQ优先级较高。

相比softirq,即使相同类型的softirq也可同时在不同的cpu上处理,而相同类型的tasklet不可同时在不同cpu上处理,不同类型的tasklet可以。

可通过tasklet_schedule或tasklet_hi_schedule提交tasklet,这两个函数最终调用raise_softirq_irqoff提交软中断。

因tasklet提交的同样是软中断,所以还是由do_softirq函数完成tasklet的处理。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
实现一个 Linux 内核中断程序,需要了解以下几个步骤: 1. 定义中断处理程序(handler) 2. 注册中断处理程序 3. 触发中断 以下是一个简单的 Linux 内核中断程序的代码示例: ```c #include <linux/interrupt.h> #include <linux/kernel.h> irqreturn_t my_interrupt(int irq, void *dev_id) { printk("Interrupt occurred!\n"); return IRQ_HANDLED; } static int __init my_init(void) { int irq = 1; // 中断号 int result = request_irq(irq, my_interrupt, IRQF_SHARED, "my_interrupt", &my_dev_id); if (result) { printk(KERN_ERR "Failed to register interrupt handler!\n"); return -EFAULT; } else { printk(KERN_INFO "Interrupt handler registered successfully!\n"); return 0; } } static void __exit my_exit(void) { free_irq(1, &my_dev_id); printk(KERN_INFO "Interrupt handler unregistered successfully!\n"); } module_init(my_init); module_exit(my_exit); MODULE_LICENSE("GPL"); ``` 在这个示例中,我们定义了一个名为 `my_interrupt` 的中断处理程序,当中断被触发时,该函数会输出一条信息并返回 `IRQ_HANDLED` 表示中断已被处理。 在 `my_init` 函数中,我们使用 `request_irq` 函数注册了中断处理程序。这个函数的第一个参数是中断号,第二个参数是中断处理程序的函数指针,第三个参数是中断标志,第四个参数是中断处理程序的名字,第五个参数是中断处理程序的设备 ID。 最后,在 `my_exit` 函数中,我们使用 `free_irq` 函数释放了中断处理程序。 要触发中断,可以使用硬件或软件方法,具体取决于你的应用场景。例如,如果你正在编写一个驱动程序,你可以在设备上触发中断来测试你的驱动程序是否能够正确响应中断

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值