From c8d730462fe5acc3d7de231c2c1c5a16e5904763 Mon Sep 17 00:00:00 2001 From: "chen.yang" Date: Wed, 18 May 2022 17:08:03 +0800 Subject: [PATCH] =?UTF-8?q?=E8=A1=A5=E5=85=85=20=E5=BA=95=E5=8D=8A?= =?UTF-8?q?=E9=83=A8=E6=9C=BA=E5=88=B6.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: chen.yang --- Chapter8_SOC_与_Linux/8.10_中断与时钟.md | 79 ++++++++++++++++++++---- 1 file changed, 67 insertions(+), 12 deletions(-) diff --git a/Chapter8_SOC_与_Linux/8.10_中断与时钟.md b/Chapter8_SOC_与_Linux/8.10_中断与时钟.md index b5f8cb3..6df0bcc 100644 --- a/Chapter8_SOC_与_Linux/8.10_中断与时钟.md +++ b/Chapter8_SOC_与_Linux/8.10_中断与时钟.md @@ -125,9 +125,9 @@ int platform_get_irq(struct platform_device *dev, unsigned int num); * interrupt handler after suspending interrupts. For system * wakeup devices users need to implement wakeup detection in * their interrupt handlers. - * @param devname An ascii name for the claiming device. 使用cat /proc/interrupt 可以查看中断程序名字。 + * @param devname An ascii name for the claiming device. 使用 cat /proc/interrupt 可以查看中断程序名字。 * @param dev_id 在中断共享时会用到,一般设置为这个设备的设备结构体或者 NULL。 - * 注册共享中断时不能为NULL,因为卸载时需要这个做参数,避免卸载其它中断服务函数。 + * 注册共享中断时不能为 NULL,因为卸载时需要这个做参数,避免卸载其它中断服务函数。 * @return * 0:表示成功 * -INVAL:表示中断号无效或处理函数指针为 NULL @@ -138,15 +138,15 @@ int request_threaded_irq(unsigned int irq, irq_handler_t handler, irq_handler_t thread_fn, unsigned long irqflags, const char *devname, void *dev_id); -int request_irq(unsigned int irq, - void (*handler)(int irq, void *dev_id, struct pt_regs *regs), - unsigned long irqflags, - const char *devname, +int request_irq(unsigned int irq, + void (*handler)(int irq, void *dev_id, struct pt_regs *regs), + unsigned long irqflags, + const char *devname, void *dev_id); /** * @param irq 申请时所用的硬件中断号。 - * @brief 这个是要卸载的中断 Action下的哪个服务函数 + * @brief 这个是要卸载的中断 Action 下的哪个服务函数 */ void free_irq(unsigned int irq, void *dev_id); ``` @@ -190,9 +190,9 @@ void free_irq(unsigned int irq, void *dev_id); * interrupt handler after suspending interrupts. For system * wakeup devices users need to implement wakeup detection in * their interrupt handlers. - * @param devname An ascii name for the claiming device. 使用cat /proc/interrupt 可以查看中断程序名字。 + * @param devname An ascii name for the claiming device. 使用 cat /proc/interrupt 可以查看中断程序名字。 * @param dev_id 在中断共享时会用到,一般设置为这个设备的设备结构体或者 NULL。 - * 注册共享中断时不能为NULL,因为卸载时需要这个做参数,避免卸载其它中断服务函数。 + * 注册共享中断时不能为 NULL,因为卸载时需要这个做参数,避免卸载其它中断服务函数。 * @return * 0:表示成功 * -INVAL:表示中断号无效或处理函数指针为 NULL @@ -216,12 +216,12 @@ devm_request_irq(struct device *dev, unsigned int irq, irq_handler_t handler, extern void devm_free_irq(struct device *dev, unsigned int irq, void *dev_id); ``` -禁能/使能中断源: +禁能 / 使能中断源: ```cpp // 禁能中断源 -void disable_irq(int irq); -void disable_irq_nosync(int irq); +void disable_irq(int irq); +void disable_irq_nosync(int irq); // 使能中断源 void enable_irq(int irq); ``` @@ -231,3 +231,58 @@ disable_irq_nosync() 与 disable_irq() 的区别在于前者立即返回,而 而 local_irq_save() 和 local_irq_disable() 系列函数只屏蔽本 CPU 内的所有中断。 ## 底半部机制 + +TODO: threaded_irq + +中断底半部的机制主要有:softirq、tasklet 和 work queue。 + +tasklet 基于 softirq 实现,所以两者很相近。work queue 与它们完全不同,它靠内核线程实现。 + +### softirq + +软中断支持 SMP,同一个 softirq 可以在不同的 CPU 上同时运行,softirq 必须是可重入的。软中断是在编译期间静态分配的,它不像 tasklet 那样能被动态的注册或去除。kernel/softirq.c 中定义了一个包含 32 个 softirq_action 结构体的数组。每个被注册的软中断都占据该数组的一项。因此最多可能有 32 个软中断。2.6 版本的内核中定义了六个软中断:HI_SOFTIRQ、TIMER_SOFTIRQ、NET_TX_SOFTIRQ、NET_RX_SOFTIRQ、SCSI_SOFTIRQ、TASKLET_SOFTIRQ。 + +软中断的特性: + +1. 一个软中断不会抢占另外一个软中断。 +2. 唯一可以抢占软中断的是中断处理程序。 +3. 其他软中断 (包括相同类型的) 可以在其他的处理其上同时执行。 +4. 一个注册的软中断必须在被标记后才能执行。 +5. 软中断不可以自己休眠 (即调用可阻塞的函数或 sleep 等)。 +6. 索引号小的软中断在索引号大的软中断之前执行。 + +### tasklet + +引入 tasklet,最主要的是考虑支持 SMP,提高 SMP 多个 cpu 的利用率;两个相同的 tasklet 决不会同时执行。tasklet 可以理解为 softirq 的派生,所以它的调度时机和软中断一样。对于内核中需要延迟执行的多数任务都可以用 tasklet 来完成,由于同类 tasklet 本身已经进行了同步保护,所以使用 tasklet 比软中断要简单的多,而且效率也不错。tasklet 把任务延迟到安全时间执行的一种方式,在中断期间运行,即使被调度多次,tasklet 也只运行一次,不过 tasklet 可以在 SMP 系统上和其他不同的 tasklet 并行运行。在 SMP 系统上,tasklet 还被确保在第一个调度它的 CPU 上运行,因为这样可以提供更好的高速缓存行为,从而提高性能。 + +tasklet 的特性:.不允许两个两个相同类型的 tasklet 同时执行,即使在不同的处理器上。 + +### work queue + +如果推后执行的任务需要睡眠,那么就选择工作队列。另外,如果需要用一个可以重新调度的实体来执行你的下半部处理,也应该使用工作队列。它是唯一能在进程上下文运行的下半部实现的机制,也只有它才可以睡眠。这意味着在需要获得大量的内存时、在需要获取信号量时,在需要执行阻塞式的 I/O 操作时,它都会非常有用。work queue 造成的开销最大,因为它要涉及到内核线程甚至是上下文切换。这并不是说 work queue 的低效,但每秒钟有数千次中断,就像网络子系统时常经历的那样,那么采用其他的机制可能更合适一些。 尽管如此,针对大部分情况工作队列都能提供足够的支持。 + +工作队列特性: + +1. 工作队列会在进程上下文中执行! +2. 可以阻塞。(前两种机制是不可以阻塞的) +3. 可以被重新调度。(前两种只可以被中断处理程序打断) +4. 使用工作队列的两种形式: + 1. 缺省工作者线程 (works threads) + 2. 自建的工作者线程 +5. 在工作队列和内核其他部分之间使用锁机制就像在其他的进程上下文一样。 +6. 默认允许响应中断。 +7. 默认不持有任何锁。 + +4、softirq 和 tasklet 共同点 + +软中断和 tasklet 都是运行在中断上下文中,它们与任一进程无关,没有支持的进程完成重新调度。所以软中断和 tasklet 不能睡眠、不能阻塞,它们的代码中不能含有导致睡眠的动作,如减少信号量、从用户空间拷贝数据或手工分配内存等。也正是由于它们运行在中断上下文中,所以它们在同一个 CPU 上的执行是串行的,这样就不利于实时多媒体任务的优先处理。 + +### 总结 + +| 下半部 | 上下文 | 顺序执行保障 | +|-------|-------|------------| +| 软中断 | 中断 | 没有 | +| Tasklet | 中断 | 同类型不能同时执行 | +| 工作队列 | 进程 | 没有(和进程上下文一样被调度) | + +简单地说,一般的驱动程序的编写者需要做两个选择。 首先,你是不是需要一个可调度的实体来执行需要推后完成的工作――从根本上来说,有休眠的需要吗?要是有,工作队列就是你的惟一选择。 否则最好用 tasklet。要是必须专注于性能的提高,那么就考虑 softirq。