Linux Kernel 下半部 软中断 tasklet

为了使中断能快速的返回,linux kernel 将中断分成两部分,

上半部(中断处理程序)和下半部(现在linux内核用软中断,tasklet, 工作队列来实现)
其中,tasklet 是基于软中断来实现的,属于软中断的一种类型。

工作队列比较熟悉了(https://blog.csdn.net/fervor_heart/article/details/9768229),不再赘述。

在linux 内核中中断处理的流程一般是这样的:
中断
->中断服务程序(tasklet_schedule()你的tasklet 下半部)
->下半部(tasklet/softirq)

1. tasklet

1.1 创建tasklet

创建一个tasklet,设置回调函数

void tasklet_init(struct tasklet_struct *t,
		  void (*func)(unsigned long), unsigned long data)
{
	t->next = NULL;
	t->state = 0;
	atomic_set(&t->count, 0);
	t->func = func;
	t->data = data;
}

#define DECLARE_TASKLET(name, func, data) \
struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(0), func, data }

#define DECLARE_TASKLET_DISABLED(name, func, data) \
struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(1), func, data }

 

1.2. 触发/调度你的tasklet

在中断服务程序里面调用 tasklet_schedule() 将会触发调度你的tasklet.

tasklet_schedule()
->raise_softirq_irqoff()
->wakeup_softirqd

1.3. 你的下半部究竟什么时候才会得到执行? ksoftirqd/N 内核线程运行的时候

linux 内核会为每个CPU 创建一个叫做ksoftirqd/N 的内核线程,用于处理该CPU 上的软中断。

p = kthread_create_on_node(run_ksoftirqd,
			   hcpu,
			   cpu_to_node(hotcpu),
			   "ksoftirqd/%d", hotcpu);


可以看到ksoftirqd/N 内核线程是非实时进程,优先级p->prio = 120,因此,内核并不保证中断下半部的代码能够很快执行,这点要注意。
USER     PID   PPID  VSIZE  RSS   PRIO  NICE  RTPRI SCHED  PCY  WCHAN    PC        NAME
root      3     2     0      0     20    0     0     0     fg  c00a1e00 00000000 S ksoftirqd/0
root      29957 2     0      0     20    0     0     0     fg  c00a1e00 00000000 S ksoftirqd/1
root      29981 2     0      0     20    0     0     0     fg  c00a1e00 00000000 S ksoftirqd/2

这时你可能会问,tasklet 和软中断究竟是怎么联系起来的呢? 为什么说tasklet 是软中断的一种特殊实现?
start_kernel()
->softirq_init()

看下sotfirq_init() 初始化软中断的时候都做了什么操作:

void __init softirq_init(void)
{
	int cpu;

	for_each_possible_cpu(cpu) {
		int i;

		per_cpu(tasklet_vec, cpu).tail =
			&per_cpu(tasklet_vec, cpu).head;
		per_cpu(tasklet_hi_vec, cpu).tail =
			&per_cpu(tasklet_hi_vec, cpu).head;
		for (i = 0; i < NR_SOFTIRQS; i++)
			INIT_LIST_HEAD(&per_cpu(softirq_work_list[i], cpu));//维护一个软中断链表
	}

	register_hotcpu_notifier(&remote_softirq_cpu_notifier);

	open_softirq(TASKLET_SOFTIRQ, tasklet_action);//这里是关键,调用open_softirq()设置 TASKLET_SOFTIRQ 类型的软中断的action 回调函数
	open_softirq(HI_SOFTIRQ, tasklet_hi_action);
}

看下include/linux/interrupt.h 中定义的softirq 的类型:

/* PLEASE, avoid to allocate new softirqs, if you need not _really_ high
   frequency threaded job scheduling. For almost all the purposes
   tasklets are more than enough. F.e. all serial device BHs et
   al. should be converted to tasklets, not to softirqs.
 */

enum
{
	HI_SOFTIRQ=0,
	TIMER_SOFTIRQ,
	NET_TX_SOFTIRQ,
	NET_RX_SOFTIRQ,
	BLOCK_SOFTIRQ,
	BLOCK_IOPOLL_SOFTIRQ,
	TASKLET_SOFTIRQ,//tasklet !!!
	SCHED_SOFTIRQ,
	HRTIMER_SOFTIRQ,
	RCU_SOFTIRQ,    /* Preferable RCU should always be the last softirq */

	NR_SOFTIRQS
};

在这段代码上面的注释中你也可以知道,一般的下半部实现你应该做的是使用tasklet 而不是添加新的软中断!
接着看open_softirq()的实现:

void open_softirq(int nr, void (*action)(struct softirq_action *))
{
	softirq_vec[nr].action = action;
}

tasklet_action() 被设置成了TASKLET_SOFTIRQ 类型的软中断的action。
在看ksoftirqd/N 内核线程的循环体:
->run_ksoftirqd()
->__do_softirq()

			trace_softirq_entry(vec_nr);
			h->action(h);//执行的就是相应类型软中断对应的action 函数,对于tasklet 来说也就是 tasklet_action()
			trace_softirq_exit(vec_nr);

可以看下tasklet_action()

static void tasklet_action(struct softirq_action *a)
{
	struct tasklet_struct *list;

	local_irq_disable();
	list = __this_cpu_read(tasklet_vec.head);
	__this_cpu_write(tasklet_vec.head, NULL);
	__this_cpu_write(tasklet_vec.tail, &__get_cpu_var(tasklet_vec).head);
	local_irq_enable();

	while (list) {
		struct tasklet_struct *t = list;

		list = list->next;

		if (tasklet_trylock(t)) {
			if (!atomic_read(&t->count)) {
				if (!test_and_clear_bit(TASKLET_STATE_SCHED, &t->state))
					BUG();
				t->func(t->data);// 最终调用到的就是创建tasklet的时候传入的func 回调函数。

 

2. 下面看一个tasklet 的例子

drivers/dma/iop-adma.c

2.1 创建tasklet

static int __devinit iop_adma_probe(struct platform_device *pdev)
{
	tasklet_init(&iop_chan->irq_tasklet, iop_adma_tasklet, (unsigned long)
		iop_chan);

 

2.2 调度tasklet

static irqreturn_t iop_adma_eot_handler(int irq, void *data)
{
	struct iop_adma_chan *chan = data;

	dev_dbg(chan->device->common.dev, "%s\n", __func__);

	tasklet_schedule(&chan->irq_tasklet);//调度tasklet, 这个函数执行在iop 的中断服务程序中

	iop_adma_device_clear_eot_status(chan);

	return IRQ_HANDLED;
}

static int __devinit iop_adma_probe(struct platform_device *pdev)
{

	for (i = 0; i < 3; i++) {
		irq_handler_t handler[] = { iop_adma_eot_handler,
					iop_adma_eoc_handler,
					iop_adma_err_handler };
		int irq = platform_get_irq(pdev, i);
		if (irq < 0) {
			ret = -ENXIO;
			goto err_free_iop_chan;
		} else {
			ret = devm_request_irq(&pdev->dev, irq,
					handler[i], 0, pdev->name, iop_chan);//中断服务程序handler 设置为 iop_adma_eot_handler()
			if (ret)
				goto err_free_iop_chan;
		}
	}

至于中断的handler 中断服务程序在调用流程以及 由devm_request_irq()申请中断中引发的threaded irq 在下文中详述。

<think>好的,用户现在问的是中断的上半下半,也就是top half和bottom half。我之前已经回答过关于Linux触摸事件传递流程的问题,现在需要处理这个新的主题。首先,我得回忆一下中断处理的基本知识,确保自己理解正确。 用户可能是在学习Linux内核的中断处理机制,或者在开发驱动时遇到了性能问题,需要优化中断处理流程。他们可能已经知道中断处理分为两分,但想深入了解两者的区别、使用场景以及具体实现方式。 我需要先解释中断上半下半的基本概念。上半负责紧急的任务,快速处理并退出,而下半处理耗时操作。要说明为什么需要这样分,比如避免长时间关闭中断导致系统不稳定。 接下来,应该详细说明两者的区别,包括执行上下文、是否可以中断、时间限制等。然后介绍常见的下半机制,比如软中断tasklet、工作队列等,每种机制的特点和适用场景。 可能用户还想知道如何在实际代码中使用这些机制,比如如何注册tasklet或者调度工作队列。需要给出简单的代码示例,但要注意用户可能对内核编程不太熟悉,所以示例要简洁明了。 还要注意用户的问题是否涉及具体的内核版本,但如果没有提到,就按通用情况回答。另外,要确保术语准确,比如“软中断”和“软件中断”可能容易混淆,需要明确区分。 最后,总结使用下半的最佳实践,比如何时选择哪种机制,避免常见错误,比如在中断上半中执行耗时操作。这样用户不仅能理解概念,还能应用到实际开发中。</think>### 分步解析:Linux中断处理中的「上半」与「下半」 --- #### **1. 核心概念** **中断处理的分割原理**: Linux内核将中断处理分为两个阶段,目的是**减少中断关闭时间**,提高系统响应能力。 - **上半(Top Half)**:**紧急且必须立即执行**的操作,在**中断上下文**中执行 - **下半(Bottom Half)**:**非紧急或耗时**的操作,在**进程上下文**或**软中断上下文**中执行 --- #### **2. 上半(Top Half)核心特性** **代码位置**:注册在`request_irq()`的中断处理函数 **关键限制**: - 执行在**中断上下文**(不可睡眠) - 必须**快速完成**(理想时间 < 1μs,最长不超过中断控制器允许的最晚响应时间) - **不可被其他中断打断**(除非明确启用嵌套中断) **典型操作**: 1. 硬件状态读取(如读取传感器数据) 2. 关键状态标记(如设置`pending`标志) 3. 触发下半(如调度tasklet或workqueue) **示例代码**: ```c // 上半中断处理函数示例 irqreturn_t my_top_half(int irq, void *dev_id) { struct my_device *dev = dev_id; // 1. 读取硬件寄存器 u32 status = readl(dev->reg_base + STATUS_REG); // 2. 标记需要延迟处理的状态 dev->pending_data = 1; // 3. 触发下半 tasklet_schedule(&dev->bottom_half_tasklet); return IRQ_HANDLED; } ``` --- #### **3. 下半(Bottom Half)实现方式** Linux提供多种下半机制,按**延迟要求**和**执行上下文**分类: | 机制 | 执行上下文 | 是否可睡眠 | 延迟要求 | 典型用途 | |---------------|------------------|------------|------------------|--------------------------| | **软中断** | 软中断上下文 | ❌ 不可 | 微秒级延迟 | 网络包处理(如NET_RX) | | **Tasklet** | 软中断上下文 | ❌ 不可 | 微秒级延迟 | 小型硬件事件处理 | | **工作队列** | 进程上下文 | ✔️ 可 | 毫秒级延迟 | 文件I/O、复杂计算 | | **线程化IRQ** | 内核线程上下文 | ✔️ 可 | 根据线程优先级 | 实时性要求较低的处理 | --- #### **4. 关键差异对比** $$ \begin{array}{|l|l|l|} \hline \textbf{特性} & \text{上半} & \text{下半} \\ \hline \text{执行上下文} & \text{中断上下文} & \text{进程/软中断上下文} \\ \text{可调度性} & \text{不可调度} & \text{可调度(工作队列)} \\ \text{中断状态} & \text{IRQ关闭} & \text{IRQ开启} \\ \text{时间敏感度} & \text{严格实时} & \text{允许延迟} \\ \text{内存分配} & \text{不可用GFP_KERNEL} & \text{可用GFP_KERNEL(工作队列)} \\ \hline \end{array} $$ --- #### **5. 下半实现示例** **Tasklet实现**: ```c // 定义tasklet处理函数 void my_bottom_half_tasklet(unsigned long data) { struct my_device *dev = (struct my_device *)data; // 处理实际任务(不可睡眠) process_data(dev->buffer); } // 初始化(通常在probe函数中) tasklet_init(&dev->bottom_half_tasklet, my_bottom_half_tasklet, (unsigned long)dev); // 清理(通常在remove函数中) tasklet_kill(&dev->bottom_half_tasklet); ``` **工作队列实现**: ```c // 定义工作处理函数 void my_work_handler(struct work_struct *work) { struct my_device *dev = container_of(work, struct my_device, my_work); // 可执行阻塞操作(如I/O) perform_io_operation(dev); } // 初始化工作队列 INIT_WORK(&dev->my_work, my_work_handler); // 调度工作(在上半中调用) schedule_work(&dev->my_work); ``` --- #### **6. 设计原则与最佳实践** 1. **黄金法则**: - 上半**只做必须立即完成的工作**,其他操作全推迟到下半 2. **选择机制**: - 需要**原子性** → 使用`tasklet`或软中断 - 需要**睡眠能力** → 使用工作队列或线程化IRQ 3. **性能陷阱**: - 避免在下半中嵌套触发同类中断 - 软中断需注意多CPU并发问题(需自行处理锁机制) --- #### **7. 实际应用场景** - **网络设备**:上半快速接收数据包,下半软中断)处理协议栈 - **块设备**:上半标记DMA完成状态,下半(工作队列)提交I/O完成通知 - **触摸屏**:上半读取坐标寄存器,下半tasklet)上报输入事件 --- ### **总结** 通过分割中断处理流程,Linux在保证实时响应的同时,有效降低了系统延迟。开发者需根据具体场景选择合适的下半机制,平衡**实时性需求**与**系统稳定性**。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值