baron · 19 小时前

Linux Kernel中断下半部分实现的三种方式

目录
1、软中断
2、tasklet
3、工作队列
总结
目前有三种中断的三种机制:

软中断

tasklet

工作队列

1、软中断

软中断是一组静态定义的下半部接口,有 32 个,可以在所有处理器上同时执行,类型相同也可以;在编译时静态注册。

软中断的相关函数:

注册软中断 open_softirq
触发软中断 raise_softirq
执行软中断 do_softirq
Linux Kernel中定义的软中断:

  1. (linux/include/linux/interrupt.h)
  2. enum
  3. {
  4. HI_SOFTIRQ=0,
  5. TIMER_SOFTIRQ,
  6. NET_TX_SOFTIRQ,
  7. NET_RX_SOFTIRQ,
  8. BLOCK_SOFTIRQ,
  9. IRQ_POLL_SOFTIRQ,
  10. TASKLET_SOFTIRQ,
  11. SCHED_SOFTIRQ,
  12. HRTIMER_SOFTIRQ,
  13. RCU_SOFTIRQ, / Preferable RCU should always be the last softirq /
  14. NR_SOFTIRQS
  15. };

软中断执行函数如下:

  1. (linux/kernel/softirq.c)
  2. asmlinkage __visible void do_softirq(void)
  3. {
  4. __u32 pending;
  5. unsigned long flags;
  6. if (in_interrupt())
  7. return;
  8. local_irq_save(flags);
  9. pending = local_softirq_pending();
  10. if (pending && !ksoftirqd_running(pending))
  11. do_softirq_own_stack();
  12. local_irq_restore(flags);
  13. }

代码一上来就判断是否在中断处理中,如果在立刻退出函数。这说明如果有其他软中断触发,则立即返回。所以,软中断不能被另外一个软中断抢占!唯一可以抢占软中断的是中断处理程序,所以软中断允许响应中断。虽然不能在本处理器上抢占,但是其他的软中断甚至同类型可以再其他处理器上同时执行。由于这点,所以对临界区需要加锁保护。

软中断给对时间要求最严格的下半部使用。目前只有网络,内核定时器和 tasklet 建立在软中断上。

2、tasklet

Tasklet是建立在软中断之上的下半部机制,tasklet和软中断很类似,但是tasklet的接口更简单,也不需要严格的锁机制。因为tasklet是使用软中断来实现的,所以tasklet本身就是软中断。

tasklet使用两种软中断来实现:HI_SOFTIRQ和TASKLET_SOFTIRQ。两者的唯一区别在于优先级,前者优先级更高,总是先于后者执行。

tasklet使用tasklet_struct结构来表示,每个结构体表示一个唯一的tasklet,定义在<linux/interrupt.h>中

  1. (linux/include/linux/interrupt.h)
  2. struct tasklet_struct
  3. {
  4. struct tasklet_struct *next;
  5. unsigned long state;
  6. atomic_t count;
  7. bool use_callback;
  8. union {
  9. void (*func)(unsigned long data);
  10. void (callback)(struct tasklet_struct t);
  11. };
  12. unsigned long data;
  13. };

Tasklet的使用:
(1)、声明一个新的tasklet

struct tasklet_struct my_tasklet = { NULL, 0, ATOMIC_INIT(0), my_tasklet_handler, dev };
或者
tasklet_init(t, tasklet_handler, dev); / dynamically as opposed to statically /
(2)、tasklet的处理程序
void tasklet_handler(unsigned long data)
注意:和软中断类似,tasklet不能睡眠(阻塞),因为软中断是运行在中断上下文中的,而tasklet是使用软中断来实现的。

(3)、tasklet的调度

使用tasklet_schedule()进行调度(类似软中断的触发),传入参数为指向tasklet_struct的指针。tasklet被调度后,内核会在合适的时机执行该taskelt。(详见前面的tasklet调度的实现)。如果一个tasklet在执行前被调度了多次,还是只会执行一次(tasklet链表中不会有重复的tasklet)。如果一个tasklet在运行中被调度了(比如被另一个处理器上执行的代码调度了),那么这个tasklet会被重新调度并在下次内核处理tasklet的时候再次执行。

3、工作队列

工作队列是和软中断或者tasklet不同的一种下半部机制。工作队列将工作推迟,交给内核线程执行(所以工作队列总是运行在进程上下文中)。工作队列的这种实现可以很好的利用进程上下文的优势,最重要的就是可以睡眠也可以被调度(抢占)。与之相反的是,软中断和tasklet是不能睡眠和被调度的。

可以自己创建工作队列,但是大部分驱动都会使用系统提供的缺省的工作队列类型events,该类型的工作队列的内核线程名字为 events/n,n为处理器编号,每个处理器对应一个内核线程。如果下半部的工作是处理器密集型并且对性能敏感的,可以考虑创建自己的内核线程。比如XFS文件系统就自己创建了两种内核线程。

工作队列的使用:–(缺省工作队列events)

(1)、创建工作(Creaing Work)

  1. DECLARE_WORK(name, void (func)(void ), void *data); //静态
  2. INIT_WORK(struct work_struct work, void (func)(void ), void data); //动态

(2)、定义工作队列处理函数
void work_handler(void *data)
(3)、对工作(work)进行调度

内核提供了两个函数对使用缺省工作队列events的工作进行调度

schedule_work(&work);

schedule_delayed_work(&work, delay);

这个函数会创建所有的内核线程(每个处理器一个),并且做些准备好让这些内核线程可以处理工作

(2)、工作队列调度

  1. int queue_work(struct workqueue_struct wq, struct work_struct work)
  2. int queue_delayed_work(struct workqueue_struct *wq,
  3. struct work_struct *work,
  4. unsigned long delay)

总结

屏幕截图 2025-01-17 145226.png

b192a7f64a81bff6e9a97c841e4cf52d.png

《ARMv8/ARMv9架构学习系列课程》全系列,共计51节课,超15h的视频课程

添加威♥:sami01_2023,回复ARM中文,领取ARM中文手册

推荐阅读
关注数
9491
文章数
256
vx: coding_the_world
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
安谋科技学堂公众号
关注安谋科技学堂
实时获取安谋科技及 Arm 教学资源
安谋科技招聘公众号
关注安谋科技招聘
实时获取安谋科技中国职位信息