Linux中断处理:上半部和下半部
《Linux中断处理:上半部和下半部》
《Linux网络协议栈:中断下半部处理》
目录
中断处理 - 上半部(硬中断)
中断处理相关结构
注册中断处理入口
处理中断请求
中断处理 - 下半部(软中断)
softirq机制
注册softirq处理函数
处理softirq
tasklet机制
调度tasklet
中断处理 - 上半部(硬中断)
由于 APIC中断控制器
有点小复杂,所以本文主要通过 8259A中断控制器
来介绍Linux对中断的处理过程。
中断处理相关结构
前面说过,8259A中断控制器
由两片 8259A 风格的外部芯片以 级联
的方式连接在一起,每个芯片可处理多达 8 个不同的 IRQ(中断请求),所以可用 IRQ 线的个数达到 15 个。如下图:
在内核中每条IRQ线由结构体 irq_desc_t
来描述,irq_desc_t
定义如下:
typedef struct {unsigned int status; /* IRQ status */hw_irq_controller *handler;struct irqaction *action; /* IRQ action list */unsigned int depth; /* nested irq disables */spinlock_t lock;
} irq_desc_t;
下面介绍一下 irq_desc_t
结构各个字段的作用:
status
: IRQ线的状态。handler
: 类型为hw_interrupt_type
结构,表示IRQ线对应的硬件相关处理函数,比如8259A中断控制器
接收到一个中断信号时,需要发送一个确认信号才会继续接收中断信号的,发送确认信号的函数就是hw_interrupt_type
中的ack
函数。action
: 类型为irqaction
结构,中断信号的处理入口。由于一条IRQ线可以被多个硬件共享,所以action
是一个链表,每个action
代表一个硬件的中断处理入口。depth
: 防止多次开启和关闭IRQ线。lock
: 防止多核CPU同时对IRQ进行操作的自旋锁。
hw_interrupt_type
这个结构与硬件相关,这里就不作介绍了,我们来看看 irqaction
这个结构:
struct irqaction {void (*handler)(int, void *, struct pt_regs *);unsigned long flags;unsigned long mask;const char *name;void *dev_id;struct irqaction *next;
};
下面说说 irqaction
结构各个字段的作用:
handler
: 中断处理的入口函数,handler
的第一个参数是中断号,第二个参数是设备对应的ID,第三个参数是中断发生时由内核保存的各个寄存器的值。flags
: 标志位,用于表示irqaction
的一些行为,例如是否能够与其他硬件共享IRQ线。name
: 用于保存中断处理的名字。dev_id
: 设备ID。next
: 每个硬件的中断处理入口对应一个irqaction
结构,由于多个硬件可以共享同一条IRQ线,所以这里通过next
字段来连接不同的硬件中断处理入口。
irq_desc_t
结构关系如下图:
注册中断处理入口
在内核中,可以通过 setup_irq()
函数来注册一个中断处理入口。setup_irq()
函数代码如下:
int setup_irq(unsigned int irq, struct irqaction * new)
{int shared = 0;unsigned long flags;struct irqaction *old, **p;irq_desc_t *desc = irq_desc + irq;...spin_lock_irqsave(&desc->lock,flags);p = &desc->action;if ((old = *p) != NULL) {if (!(old->flags & new->flags & SA_SHIRQ)) {spin_unlock_irqrestore(&desc->lock,flags);return -EBUSY;}do {p = &old->next;old = *p;} while (old);shared = 1;}*p = new;if (!shared) {desc->depth = 0;desc->status &= ~(IRQ_DISABLED | IRQ_AUTODETECT | IRQ_WAITING);desc->handler->startup(irq);}spin_unlock_irqrestore(&desc->lock,flags);register_irq_proc(irq); // 注册proc文件系统return 0;
}
setup_irq()
函数比较简单,就是通过 irq
号来查找对应的 irq_desc_t
结构,并把新的 irqaction
连接到 irq_desc_t
结构的 action
链表中。要注意的是,如果设备不支持共享IRQ线(也即是 flags
字段没有设置 SA_SHIRQ
标志),那么就返回 EBUSY
错误。
我们看看 时钟中断处理入口
的注册实例:
static struct irqaction irq0 = { timer_interrupt, SA_INTERRUPT, 0, "timer", NULL, NULL};void __init time_init(void)
{...setup_irq(0, &irq0);
}
可以看到,时钟中断处理入口的IRQ号为0,处理函数为 timer_interrupt()
,并且不支持共享IRQ线(flags
字段没有设置 SA_SHIRQ
标志)。
处理中断请求
当一个中断发生时,中断控制层会发送信号给CPU,CPU收到信号会中断当前的执行,转而执行中断处理过程。中断处理过程首先会保存寄存器的值到栈中,然后调用 do_IRQ()
函数进行进一步的处理,do_IRQ()
函数代码如下:
asmlinkage unsigned int do_IRQ(struct pt_regs regs)
{int irq = regs.orig_eax & 0xff; /* 获取IRQ号 */int cpu = smp_processor_id();irq_desc_t *desc = irq_desc + irq;struct irqaction * action;unsigned int status;kstat.irqs[cpu][irq]++;spin_lock(&desc->lock);desc->handler->ack(irq);status = desc->status & ~(IRQ_REPLAY | IRQ_WAITING);status |= IRQ_PENDING; /* we _want_ to handle it */action = NULL;if (!(status & (IRQ_DISABLED | IRQ_INPROGRESS))) { // 当前IRQ不在处理中action = desc->action; // 获取 action 链表status &= ~IRQ_PENDING; // 去除IRQ_PENDING标志, 这个标志用于记录是否在处理IRQ请求的时候又发生了中断status |= IRQ_INPROGRESS; // 设置IRQ_INPROGRESS标志, 表示正在处理IRQ}desc->status = status;if (!action) // 如果上一次IRQ还没完成, 直接退出goto out;for (;;) {spin_unlock(&desc->lock);handle_IRQ_event(irq, ®s, action); // 处理IRQ请求spin_lock(&desc->lock);if (!(desc->status & IRQ_PENDING)) // 如果在处理IRQ请求的时候又发生了中断, 继续处理IRQ请求break;desc->status &= ~IRQ_PENDING;}desc->status &= ~IRQ_INPROGRESS;
out:desc->handler->end(irq);spin_unlock(&desc->lock);if (softirq_active(cpu) & softirq_mask(cpu))do_softirq(); // 中断下半部处理return 1;
}
do_IRQ()
函数首先通过IRQ号获取到其对应的 irq_desc_t
结构,注意的是同一个中断有可能发生多次,所以要判断当前IRQ是否正在被处理当中(判断 irq_desc_t
结构的 status
字段是否设置了 IRQ_INPROGRESS
标志),如果不是处理当前,那么就获取到 action
链表,然后通过调用 handle_IRQ_event()
函数来执行 action 链表中的中断处理函数。
如果在处理中断的过程中又发生了相同的中断(irq_desc_t
结构的 status
字段被设置了 IRQ_INPROGRESS
标志),那么就继续对中断进行处理。处理完中断后,调用 do_softirq()
函数来对中断下半部进行处理(下面会说)。
接下来看看 handle_IRQ_event()
函数的实现:
int handle_IRQ_event(unsigned int irq, struct pt_regs * regs, struct irqaction * action)
{int status;int cpu = smp_processor_id();irq_enter(cpu, irq);status = 1; /* Force the "do bottom halves" bit */if (!(action->flags & SA_INTERRUPT)) // 如果中断处理能够在打开中断的情况下执行, 那么就打开中断__sti();do {status |= action->flags;action->handler(irq, action->dev_id, regs);action = action->next;} while (action);if (status & SA_SAMPLE_RANDOM)add_interrupt_randomness(irq);__cli();irq_exit(cpu, irq);return status;
}
handle_IRQ_event()
函数非常简单,就是遍历 action 链表并且执行其中的处理函数,比如对于 时钟中断
就是调用 timer_interrupt()
函数。这里要注意的是,如果中断处理过程能够开启中断的,那么就把中断打开(因为CPU接收到中断信号时会关闭中断)。
中断处理 - 下半部(软中断)
由于中断处理一般在关闭中断的情况下执行,所以中断处理不能太耗时,否则后续发生的中断就不能实时地被处理。鉴于这个原因,Linux把中断处理分为两个部分,上半部
和 下半部
,上半部
在前面已经介绍过,接下来就介绍一下 下半部
的执行。
一般中断 上半部
只会做一些最基础的操作(比如从网卡中复制数据到缓存中),然后对要执行的中断 下半部
进行标识,标识完调用 do_softirq()
函数进行处理。
softirq机制
中断下半部
由 softirq(软中断)
机制来实现的,在Linux内核中,有一个名为 softirq_vec
的数组,如下:
static struct softirq_action softirq_vec[32];
其类型为 softirq_action
结构,定义如下:
struct softirq_action
{void (*action)(struct softirq_action *);void *data;
};
softirq_vec
数组是 softirq
机制的核心,softirq_vec
数组每个元素代表一种softirq。但在Linux中只定义了四种softirq,如下:
enum
{HI_SOFTIRQ=0,NET_TX_SOFTIRQ,NET_RX_SOFTIRQ,TASKLET_SOFTIRQ
};
HI_SOFTIRQ
是高优先级tasklet,而 TASKLET_SOFTIRQ
是普通tasklet,tasklet是基于softirq机制的一种任务队列(下面会介绍)。NET_TX_SOFTIRQ
和 NET_RX_SOFTIRQ
特定用于网络子模块的软中断(不作介绍)。
注册softirq处理函数
要注册一个softirq处理函数,可以通过 open_softirq()
函数来进行,代码如下:
void open_softirq(int nr, void (*action)(struct softirq_action*), void *data)
{unsigned long flags;int i;spin_lock_irqsave(&softirq_mask_lock, flags);softirq_vec[nr].data = data;softirq_vec[nr].action = action;for (i=0; i<NR_CPUS; i++)softirq_mask(i) |= (1<<nr);spin_unlock_irqrestore(&softirq_mask_lock, flags);
}
open_softirq()
函数的主要工作就是向 softirq_vec
数组添加一个softirq处理函数。
Linux在系统初始化时注册了两种softirq处理函数,分别为 TASKLET_SOFTIRQ
和 HI_SOFTIRQ
:
void __init softirq_init()
{...open_softirq(TASKLET_SOFTIRQ, tasklet_action, NULL);open_softirq(HI_SOFTIRQ, tasklet_hi_action, NULL);
}
处理softirq
处理softirq是通过 do_softirq()
函数实现,代码如下:
asmlinkage void do_softirq()
{int cpu = smp_processor_id();__u32 active, mask;if (in_interrupt())return;local_bh_disable();local_irq_disable();mask = softirq_mask(cpu);active = softirq_active(cpu) & mask;if (active) {struct softirq_action *h;restart:softirq_active(cpu) &= ~active;local_irq_enable();h = softirq_vec;mask &= ~active;do {if (active & 1)h->action(h);h++;active >>= 1;} while (active);local_irq_disable();active = softirq_active(cpu);if ((active &= mask) != 0)goto retry;}local_bh_enable();return;retry:goto restart;
}
前面说了 softirq_vec
数组有32个元素,每个元素对应一种类型的softirq,那么Linux怎么知道哪种softirq需要被执行呢?
在Linux中,每个CPU都有一个类型为 irq_cpustat_t
结构的变量,irq_cpustat_t
结构定义如下:
typedef struct {unsigned int __softirq_active;//表示哪种softirq触发了unsigned int __softirq_mask; //表示哪种softirq被屏蔽了...
} irq_cpustat_t;
其中 __softirq_active
字段表示有哪种softirq触发了(int类型有32个位,每一个位代表一种softirq),而 __softirq_mask
字段表示哪种softirq被屏蔽了。Linux通过 __softirq_active
这个字段得知哪种softirq需要执行(只需要把对应位设置为1)。
所以,do_softirq()
函数首先通过 softirq_mask(cpu)
来获取当前CPU对应被屏蔽的softirq,而 softirq_active(cpu) & mask
就是获取需要执行的softirq,然后就通过对比 __softirq_active
字段的各个位来判断是否要执行该类型的softirq。
tasklet机制
前面说了,tasklet机制是基于softirq机制的,tasklet机制其实就是一个任务队列,然后通过softirq执行。
在Linux内核中有两种tasklet,
- 高优先级tasklet
- 普通tasklet
这两种tasklet的实现基本一致,唯一不同的就是执行的优先级,高优先级tasklet会先于普通tasklet执行。
tasklet本质是一个队列,通过结构体 tasklet_head
存储,并且每个CPU有一个这样的队列,我们来看看结构体 tasklet_head
的定义:
struct tasklet_head
{struct tasklet_struct *list;
};struct tasklet_struct
{struct tasklet_struct *next;unsigned long state;atomic_t count;void (*func)(unsigned long);unsigned long data;
};
从 tasklet_head
的定义可以知道,tasklet_head
结构是 tasklet_struct
结构队列的头部,而 tasklet_struct
结构的 func
字段正式任务要执行的函数指针。Linux定义了两种的tasklet队列,分别为 tasklet_vec
和 tasklet_hi_vec
,定义如下:
struct tasklet_head tasklet_vec[NR_CPUS];
struct tasklet_head tasklet_hi_vec[NR_CPUS];
可以看出,tasklet_vec
和 tasklet_hi_vec
都是数组,数组的元素个数为CPU的核心数,也就是每个CPU核心都有一个高优先级tasklet队列和一个普通tasklet队列。
调度tasklet
如果我们有一个tasklet需要执行,那么高优先级tasklet可以通过 tasklet_hi_schedule()
函数调度,而普通tasklet可以通过 tasklet_schedule()
调度。这两个函数基本一样,所以我们只分析其中一个:
static inline void tasklet_hi_schedule(struct tasklet_struct *t)
{if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state)) {int cpu = smp_processor_id();unsigned long flags;local_irq_save(flags);t->next = tasklet_hi_vec[cpu].list;tasklet_hi_vec[cpu].list = t;__cpu_raise_softirq(cpu, HI_SOFTIRQ);local_irq_restore(flags);}
}
函数参数的类型是 tasklet_struct
结构的指针,表示需要执行的tasklet结构。tasklet_hi_schedule()
函数首先判断这个tasklet是否已经被添加到队列中,如果不是就添加到 tasklet_hi_vec
队列中,并且通过调用 __cpu_raise_softirq(cpu, HI_SOFTIRQ)
来告诉softirq需要执行 HI_SOFTIRQ
类型的softirq,我们来看看 __cpu_raise_softirq()
函数的实现:
static inline void __cpu_raise_softirq(int cpu, int nr)
{softirq_active(cpu) |= (1<<nr);
}
可以看出,__cpu_raise_softirq()
函数就是把 irq_cpustat_t
结构的 __softirq_active
字段的 nr位
设置为1。对于 tasklet_hi_schedule()
函数就是把 HI_SOFTIRQ
位(0位)设置为1。
前面我们也介绍过,Linux在初始化时会注册两种softirq,TASKLET_SOFTIRQ
和 HI_SOFTIRQ
:
void __init softirq_init()
{...open_softirq(TASKLET_SOFTIRQ, tasklet_action, NULL);open_softirq(HI_SOFTIRQ, tasklet_hi_action, NULL);
}
所以当把 irq_cpustat_t
结构的 __softirq_active
字段的 HI_SOFTIRQ
位(0位)设置为1时,softirq机制就会执行 tasklet_hi_action()
函数,我们来看看 tasklet_hi_action()
函数的实现:
static void tasklet_hi_action(struct softirq_action *a)
{int cpu = smp_processor_id();struct tasklet_struct *list;local_irq_disable();list = tasklet_hi_vec[cpu].list;tasklet_hi_vec[cpu].list = NULL;local_irq_enable();while (list != NULL) {struct tasklet_struct *t = list;list = list->next;if (tasklet_trylock(t)) {if (atomic_read(&t->count) == 0) {clear_bit(TASKLET_STATE_SCHED, &t->state);t->func(t->data); // 调用tasklet处理函数tasklet_unlock(t);continue;}tasklet_unlock(t);}...}
}
tasklet_hi_action()
函数非常简单,就是遍历 tasklet_hi_vec
队列并且执行其中tasklet的处理函数。
Linux中断处理:上半部和下半部相关推荐
- 【Linux驱动编程】Linux中断上半部和下半部
前言 cpu在执行程序时,如果有外部中断触发时,如定时器中断.串行总线中断等,cpu停止当前任务从而转去响应中断处理.对于中断函数的处理,原则是尽快处理完事务并退出中断,这一点也比较好理解,尽快处 ...
- linux中断的上半部和下半部
原文地址:linux中断的上半部和下半部 作者:td1442911376 http://blog.chinaunix.net/xmlrpc.php?r=blog/article&uid=246 ...
- 【Linux开发】linux设备驱动归纳总结(六):3.中断的上半部和下半部——tasklet...
linux设备驱动归纳总结(六):3.中断的上半部和下半部--tasklet xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ...
- linux中断处理汇编入口,Linux中断处理体系结构分析(一)
中断也是一种异常,之所以把它单独的列出来,是因为中断的处理与具体的开发板密切相关,除一些必须.共用的中断(比如系统时钟中断.片内外设UART中断)外,必须由驱动开发者提供处理函数.内核提炼出中断处理的 ...
- linux中断处理体系结构分析(一),Linux中断处理体系结构分析(二)
1.中断处理的体系结构 我们知道编写设备驱动程序一定要用到中断处理函数,这在驱动程序的编写中,占据很重要的一部分.在响应一个特定的中断的时候,内核会执行一个函数,该函数叫做中断处理程序(interru ...
- Linux中断处理体系结构分析(二)
1.中断处理的体系结构 我们知道编写设备驱动程序一定要用到中断处理函数,这在驱动程序的编写中,占据很重要的一部分.在响应一个特定的中断的时候,内核会执行一个函数,该函数叫做中断处理程序(interru ...
- ARM GIC简介与Linux中断处理分析
先简单说明一下GIC(具体详尽的介绍请查阅ARM GIC相关文档) GIC即general interrupt controller. 它是一个架构,版本历经了GICv1(已弃用),GICv2,GIC ...
- Linux中断处理驱动程序编写【转】
转自:http://blog.163.com/baosongliang@126/blog/static/1949357020132585316912/ 本章节我们一起来探讨一下Linux中的中断 中断 ...
- linux 在某个core上的中断 affinity c语言函数,Linux中断处理体系结构
各种的异常的C处理函数可以分为5类,他们分布在不同的文件中. 1.在arch/arm/kernel/trapsc.c中 未定义指令异常,总入口函数为do_undefinstr. 2.在arch/arm ...
最新文章
- 新手必看:Python 3.8六大新功能
- 加密与解密、OpenSSL(SSL/TLS)、OpenSSH(ssh)、dropbear
- SASS的一些使用体会(安装-配置-开启firefox的调试)
- linux 内核探测kprobe 初步了解
- 两个service事务统一_RocketMQ进阶 - 事务消息
- web默认字体最佳实践
- C语言isupper函数介绍、示例和实现
- 关于node中的板块问题
- php电子备课系统,电子备课-华思信息-智慧校园-智慧班牌-智慧课堂-智慧教育整体解决方案...
- windows驱动开发——使用sys文件
- [BZOJ5276] Skyfall [CF235E] Number Challenge [莫比乌斯反演]
- 1838.最高频元素的频数
- 基于likeadmin管理后台搭建—通用CRM管理系统
- RxSwift 的简单使用
- 2022-6-13 咒语和药水的成功对数,替换字符后匹配,统计得分小于 K 的子数组数目,......
- c++内存释放的几种方法
- hexo 添加图片,音乐,链接,视频
- Week Of Code 28
- Parcelable接口
- 七夕给女朋友准备的小惊喜网站制作(html+css+js)
热门文章
- 错误记录(三)identity和assigned 的区别
- vue 获取元素高度
- Django学习(一)---基本配置及创建项目、应用
- 在这里总结一些iOS开发中的小技巧,能大大方便我们的开发,持续更新。
- SSH框架与配置文件的简单搭建
- 【转】自学成才秘籍!机器学习深度学习经典资料汇总
- ASP.NET MVC的JavaScriptResult
- 超人气光棍节!现在时间虽然不是2011年11月11日11点11分11秒11毫秒11微秒11纳秒11皮秒11飞秒11阿秒11渺秒11......
- load control template file /_controltemplates/taxonomypicker.ascx failed
- Oracle sqlplus prelim 参数介绍