目录
硬中断特点
中断API
线程中断
系统标准的优先级顺序
中断信息查看
中断上半部与下半部
软中断与并发
硬中断特点
- 优先级最高
- 中断函数在中断上下文中,不能阻塞
不要间接或直接调用shedule()
在申请内存空间时,使用GFP_ATOMIC 标志(非阻塞,尽可能快);
不能在用户与内核空间传递数据
- 中断屏蔽:
默认情况下,在中断函数处理时,所有的本地CPU的中断是被屏蔽的
- 尽可能的快
几十微妙内,如果超过则使用中断下半部
下面这块程序存在的问题
my_interrupt()
{
struct mys *sp;
ack_intr();
x = read_regX();
sp = kzalloc(SIZE_HWBUF, GFP_KERNEL);
if (!sp)
return -ENOMEM;
sp = fetch_data_from_hw();
copy_to_user(ubuf, sp, count);
kfree(sp);
}
- kzalloc() 使用GFP_KERNEL标志,可能引起调用schedule(),内核会输出oops错误;
- copy_to_user()调用可能会引起页错误,引起上下文切换,触发schedule()
- 更一般的错误,a函数最终调用了schedule(),将会引起oops错误。a()--b()--c()--[] --g() --schedule()--[]
中断API
request_irq()
devm_request_irq()
*free_irq(unsigned int, void *);
详细分析request_irq函数的参数
#include <linux/interrupt.h>
int __must_check
request_irq(unsigned int irq, irq_handler_t (*handler_func)(int, void *),
unsigned long flags, const char *name, void *dev);
- 头文件 #include <linux/interrupt.h>
- irq:中断线
- irq_handler_t:中断处理函数
- flag:中断标志bitmask,中断标志在下节介绍
- name:驱动名
- *dev:给中断函数传递参数
中断标志
- IRQF_SHARED:在几个设备中间允许共享IRQ线
- IRQF_ONESHOT:经常用在线程中断中,以确保IRQ在线程处理程序完成之前保持禁用状态;
- __IRQF_TIMER:定时器中断 ,定时器中断标志组合如下
#define IRQF_TIMER(__IRQF_TIMER | IRQF_NO_SUSPEND | IRQF_NO_THREAD)
触发方式
- Level-triggered:中断在没有得到响应之前,一直触发。
- Edge-triggered:中断仅仅触发一次
中断掩码
芯片的中断控制器(PIC/GIC)有掩码寄存器,操作系统能够通过编程屏蔽或阻塞硬件中断。
- 尽可能的保持中断使能,这对于系统至关重要。如果中断被阻塞,外设不能响应,系统性能滞后或许受此影响。使用自旋锁锁定将导致中断和抢占被禁用。
- Linux系统的默认行为,发生硬件中断且该中断未被屏蔽时,内核确保在其中断(hardirq)处理程序执行时,本地CPU内核上执行处理程序的所有中断都被禁用。
- 多核系统上的中断:当IRQn在CPU内核1上执行时,除内核1外,其他中断在所有CPU内核上保持启用。因此,在多核系统硬件上,中断可以在不同的CPU内核上并行运行。就全局数据而言,只要他们互不干涉,这就没问题!如果有竞争,就必须使用锁定。
线程中断
//线程中断
request_threaded_irq()
devm_request_threaded_irq() (recommended!)
内核线程与用户模式线程非常相似,它在进程上下文中独立运行,并有自己的任务结构,能够被调度。
- 实现正在的实时
- 消除软中断的瓶颈:
消除的瓶颈如何理解?
- 线程中断运行在进程上下文中,,它被认为不像hardirq处理程序那样重要;因此,中断处理可能需要更长的时间。
- 当硬中断执行时,中断IRQ line 在所有核上都禁止。如果执行到完成需要一段时间,那么系统的响应可能会显著下降;而线程处理程序执行时,默认启用硬件IRQ line。这有助于提高性能和响应能力。
线程处理程序的限制
-
IRQF_ONESHOT 必须要存在
- 在处理NIC,多媒体等时处理速度会很慢,建议使用top/bottom机制
系统标准的优先级顺序
- 硬件中断 (抢占了一切资源)
- 实时线程(SCHED_FIFO. SCHED_RR)
- 处理器异常(系统调用,缺页异常等)
- 用户模式线程(SCHED_OTHER)
如何判定选择硬中断还是线程中断?
内核提供了request_any_context_irq,根据特定情况,它将把中断处理程序设置为hardirq处理程序或线程处理程序。
int __must_check
request_any_context_irq(unsigned int irq, irq_handler_t handler,
unsigned long flags, const char *name, void *dev_id);
返回值
- 如果运行在硬中断上下文,返回IRQC_IS_HARDIRQ;
- 如果运行在进程或线程环境中,返回IRQC_IS_NESTED;
- 返回负数,代表执行错误
中断信息查看
查看硬中断
# cat /proc/interrupts
CPU0 CPU1 CPU2 CPU3
0: 24 0 0 0 IO-APIC 2-edge timer
1: 15354 5040 12673 1046 IO-APIC 1-edge i8042
8: 0 1 0 0 IO-APIC 8-edge rtc0
9: 0 0 0 0 IO-APIC 9-fasteoi acpi
12: 0 58896 51200 29989 IO-APIC 12-edge i8042
14: 0 0 0 0 IO-APIC 14-edge ata_piix
15: 0 0 0 0 IO-APIC 15-edge ata_piix
16: 0 122949 0 289 IO-APIC 16-fasteoi vmwgfx, snd_ens1371
17: 37934 0 0 0 IO-APIC 17-fasteoi ehci_hcd:usb1, ioc0
18: 0 41 0 0 IO-APIC 18-fasteoi uhci_hcd:usb2
19: 0 0 67 31218 IO-APIC 19-fasteoi ens33
查看软中断
# cat /proc/softirqs
CPU0 CPU1 CPU2 CPU3
HI: 0 0 0 0
TIMER: 8700 9001 8961 10715
NET_TX: 12 3 2 2
NET_RX: 76 7 7 3
BLOCK: 2642 0 0 0
IRQ_POLL: 0 0 0 0
TASKLET: 0 0 1 0
SCHED: 5516 4294 3979 3572
HRTIMER: 0 0 0 0
RCU: 6481 7745 9292 7009
使用线程中断处理是现代的一种方式,使用下半部机制是传统的方法。
中断上半部与下半部
中断上半部处理比较快的程序,如执行纯粹小的需求,中断处于关闭状态;
下半部处理相对较慢的处理程序,也是大部分中断工作;中断使能状态;
上半部的函数有:
request_irq() / devm_request_irq() / request_threaded_irq() /
devm_request_threaded_irq()
下半部的函数:
tasklet,workqueue,softirq
下半部分仍然在原子或中断上下文中运行!
- 不能传输数据(用户和内核空间);
- 分配内存要使用GFP_ATOMIC;
- 不能直接间接调用schedule();
tasklet
#include <linux/interrupt.h>
void tasklet_init(struct tasklet_struct *t, void (*func)(unsigned long),
unsigned long data);
void tasklet_schedule(struct tasklet_struct *t);
/ * 下半部的处理 */
static void mydrv_tasklet(unsigned long data)
{
process_it(); // 中断的绝大多数处理
}
/*上半部处理 */
static irqreturn_t my_hardirq_handler(int irq, void *data)
{
tasklet_schedule(ts);//调度tasklet执行
return IRQ_HANDLED;
}
static struct tasklet_struct *ts;
static int __init mydriver_init(void)
{
struct device *dev;
/* misc设备注册*/
ret = misc_register(&keylog_miscdev);
dev = keylog_miscdev.this_device;
//申请内存
ts = devm_kzalloc(dev, sizeof(struct tasklet_struct), GFP_KERNEL);
//tasklet初始化,并注册回调函数
tasklet_init(ts, mydrv_tasklet, 0);
//申请中断 中断处理函数my_hardirq_handler
ret = devm_request_irq(dev, MYDRV_IRQ, my_hardirq_handler,
IRQF_SHARED, OURMODNAME, THIS_MODULE);
}
tasklet_hi_schedule()相比tasklet_schedule()有更高的软中断优先级。
softirq
// kernel/softirq.c
void open_softirq(int nr, void (*action)(struct softirq_action *))
{
softirq_vec[nr].action = action;
}
软中断类型以及解释
优先级序号 | Softirq | 说明 | 函数 |
0 | HI_SOFTIRQ | 最高的软件优先级 | tasklet_hi_action() |
1 | TIMER_SOFTIRQ | 定时器 | run_timer_softirq() |
2 | NET_TX_SOFTIRQ | 网络栈发送 | net_tx_action() |
3 | NET_RX_SOFTIRQ | 网络栈接收 | net_rx_action() |
4 | BLOCK_SOFTIRQ | Block进程 | blk_done_softirq() |
5 | IRQ_POLL_SOFTIRQ | 内核的块层轮询IRQ模式 | irq_poll_softirq() |
6 | TASKLET_SOFTIRQ | tasklet下半部 | tasklet_action() |
7 | SCHED_SOFTIRQ | CFS调度;将任务迁移到其他队列 | run_rebalance_domains() |
8 | HRTIMER_SOFTIRQ | 高精度定时器 | hrtimer_run_softrq() |
9 | RCU_SOFTIRQ | 执行RCU进程 | rcu_core_si()/ rcu_process_callbacks() |
软中断与并发
- tasklet 不能并发执行;softirq可以并发执行,因此需要锁;
- 软中断能被硬中断打断;
- 软中断不能抢占正在执行的软中断,虽然有更优的优先级,必须安装优先级使用;
- spin_lock_bh(),当被锁住的时候,禁止软中断;防止死锁
硬中断,tasklet与线程处理函数的比较
时间 | 使用 | 优缺点 |
<=10微妙 | hardirq | 最佳方案 |
[10,100]微妙 | hardirq或hardirq与tasklet(softirq) | 运行压力测试/工作负载,看看是否真的需要tasklet。对于线程处理程序或工作队列,不鼓励使用 |
100毫秒,非关键的设备 | hardirq,threaded handler, workqueue | 避免softirq处理,这有助于减少系统延迟,但可能导致处理速度稍慢 |
100毫秒,关键设备(网络、块、媒体设备) | hardirq,tasklet | 当大量中断到来时,可能会导致“livelock”问题和长时间的延迟 |
100毫秒,极其关键的工作或设备 | hardirq,hi-tasklet,softirq | 相当极端,不太可能情况 |
大量未执行的softirq可能导致livelock的情况,以下两种方式缓解了这个问题
- 线程中断或工作队列
- 调用ksoftirqd/n内核线程来接管softirq处理
上述的两个例子运行在进程上下文,缓解了线程不足的问题。
如何知道程序处在进程或中断上下文中? 内核源码中有函数
// include/linux/preempt.h
/*
* Are we doing bottom half or hardware interrupt processing?
*
* in_irq() - We're in (hard) IRQ context
* in_softirq() - We have BH disabled, or are processing softirqs
* in_interrupt() - We're in NMI,IRQ,SoftIRQ context or have BH disabled
* in_serving_softirq() - We're in softirq context
* in_nmi() - We're in NMI context
* in_task() - We're in task context