共计 8251 个字符,预计需要花费 21 分钟才能阅读完成。
这篇“linux 软中断和工作队列有什么用”文章的知识点大部分人都不太理解,所以丸趣 TV 小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“linux 软中断和工作队列有什么用”文章吧。
linux 中软中断和工作队列的作用是实现中断处理。软中断和工作队列是中断上下部机制中的下半部实现机制。软中断不能睡眠、不能阻塞、不能进程间切换,只能被硬件中断打断;而工作队列可以睡眠,也能被阻塞,能够在不同的进程间切换,以完成不同的工作。
本教程操作环境:linux5.9.8 系统、Dell G3 电脑。
linux 中软中断和工作队列的作用是实现中断处理。
1. 中断概念
中断是指在 CPU 正常运行期间,由于内外部事件或由程序预先安排的事件引起的 CPU 暂时停止正在运行的程序,转而为该内部或外部事件或预先安排的事件服务的程序中去,服务完毕后再返回去继续运行被暂时中断的程序。Linux 中通常分为外部中断(又叫硬件中断)和内部中断(又叫异常)。
在实地址模式中,CPU 把内存中从 0 开始的 1KB 空间作为一个中断向量表。表中的每一项占 4 个字节。但是在保护模式中,有这 4 个字节的表项构成的中断向量表不满足实际需求,于是根据反映模式切换的信息和偏移量的足够使得中断向量表的表项由 8 个字节组成,而中断向量表也叫做了中断描述符表(IDT)。在 CPU 中增加了一个用来描述中断描述符表寄存器(IDTR),用来保存中断描述符表的起始地址。
2. Linux 中断处理
2.1 系统中断号
由上述中断定义可知,系统中断向量表中共可保存 256 个中断向量入口,即 IDT 中包含的 256 个中断描述符(对应 256 个中断向量)。
而 0 -31 号中断向量被 intel 公司保留用来处理异常事件,不能另作它用。对这 0-31 号中断向量,操作系统只需提供异常的处理程序,当产生一个异常时,处理机就会自动把控制转移到相应的处理程序的入口,运行相应的处理程序;而事实 上,对于这 32 个处理异常的中断向量,2.6 版本的 Linux 只提供了 0 -17 号中断向量的处理程序,其对应处理程序参见下表、中断向量和异常事件对应表;也就是说,17-31 号中断向量是空着未用的。
中断向量号异常事件 Linux 的处理程序 0 除法错误 pide_error1 调试异常 Debug2NMI 中断 Nmi3 单字节,int 3Int34 溢出 Overflow5 边界监测中断 Bounds6 无效操作码 Invalid_op7 设备不可用 Device_not_available8 双重故障 Double_fault9 协处理器段溢出 Coprocessor_segment_overrun10 无效 TSSIncalid_tss11 缺段中断 Segment_not_present12 堆栈异常 Stack_segment13 一般保护异常 General_protection14 页异常 Page_fault15(intel 保留)Spurious_interrupt_bug16 协处理器出错 Coprocessor_error17 对齐检查中断 Alignment_check
0-31 号中断向量已被保留,那么剩下 32-255 共 224 个中断向量可用。这 224 个中断向量又是怎么分配的呢?2.6 版本的 Linux 中,除了 0x80 (SYSCALL_VECTOR) 用作系统调用总入口之外,其他都用在外部硬件中断源上,其中包括可编程中断控制器 8259A 的 15 个 irq;事实上,当 没有定义 CONFIG_X86_IO_APIC 时,其他 223(除 0x80 外) 个中断向量,只利用了从 32 号开始的 15 个,其它 208 个空着未用。
2.2 中断请求
2.2.1 中断请求概述
外部设备当需要操作系统做相关的事情的时候,会产生相应的中断。
设备通过相应的中断线向中断控制器发送高电平以产生中断信号,而操作系统则会从中断控制器的状态位取得那根中断线上产生的中断。而且只有在设备在对某一条中断线拥有控制权,才可以向这条中断线上发送信号。也由于现在的外设越来越多,中断线又是很宝贵的资源不可能被一一对应。因此在使用中断线前,就得对相应的中断线进行申请。无论采用共享中断方式还是独占一个中断,申请过程都是先讲所有的中断线进行扫描,得出哪些没有别占用,从其中选择一个作为该设备的 IRQ。其次,通过中断申请函数申请相应的 IRQ。最后,根据申请结果查看中断是否能够被执行。
2.2.2 中断相关结构
中断中核心处理数据结构为 irq_desc,它完整的描述了一条中断线,Linux 2.6。22.6 中源码如下。
irq_desc 定义在 include/linux/irq.h 中
/**
* struct irq_desc - interrupt descriptor
*
* @handle_irq: highlevel irq-events handler [if NULL, __do_IRQ()]
* @chip: low level interrupt hardware access
* @msi_desc: MSI descriptor
* @handler_data: per-IRQ data for the irq_chip methods
* @chip_data: platform-specific per-chip private data for the chip
* methods, to allow shared chip implementations
* @action: the irq action chain
* @status: status information
* @depth: disable-depth, for nested irq_disable() calls
* @wake_depth: enable depth, for multiple set_irq_wake() callers
* @irq_count: stats field to detect stalled irqs
* @irqs_unhandled: stats field for spurious unhandled interrupts
* @lock: locking for SMP
* @affinity: IRQ affinity on SMP
* @cpu: cpu index useful for balancing
* @pending_mask: pending rebalanced interrupts
* @dir: /proc/irq/ procfs entry
* @affinity_entry: /proc/irq/smp_affinity procfs entry on SMP
* @name: flow handler name for /proc/interrupts output */struct irq_desc {
irq_flow_handler_t handle_irq; struct irq_chip *chip; struct msi_desc *msi_desc; void *handler_data; void *chip_data; struct irqaction *action; /* IRQ action list */
unsigned int status; /* IRQ status */
unsigned int depth; /* nested irq disables */
unsigned int wake_depth; /* nested wake enables */
unsigned int irq_count; /* For detecting broken IRQs */
unsigned int irqs_unhandled;
spinlock_t lock;
#ifdef CONFIG_SMP
cpumask_t affinity;
unsigned int cpu;#endif#if defined(CONFIG_GENERIC_PENDING_IRQ) || defined(CONFIG_IRQBALANCE)
cpumask_t pending_mask;#endif#ifdef CONFIG_PROC_FS struct proc_dir_entry *dir;#endif
const char *name;
} ____cacheline_internodealigned_in_smp;
irq_desc
其相关联的几个结构体如下:
定义在 include/linux/ interrupt.h 中的中断行动结构体:struct irqaction
struct irqaction {
irq_handler_t handler;
unsigned long flags;
cpumask_t mask; const char *name; void *dev_id; struct irqaction *next; int irq; struct proc_dir_entry *dir;
};
定义在 include/linux 中的:irq_chip 芯片相关的处理函数集合
/**
* struct irq_chip - hardware interrupt chip descriptor
*
* @name: name for /proc/interrupts
* @startup: start up the interrupt (defaults to - enable if NULL)
* @shutdown: shut down the interrupt (defaults to - disable if NULL)
* @enable: enable the interrupt (defaults to chip- unmask if NULL)
* @disable: disable the interrupt (defaults to chip- mask if NULL)
* @ack: start of a new interrupt
* @mask: mask an interrupt source
* @mask_ack: ack and mask an interrupt source
* @unmask: unmask an interrupt source
* @eoi: end of interrupt - chip level
* @end: end of interrupt - flow level
* @set_affinity: set the CPU affinity on SMP machines
* @retrigger: resend an IRQ to the CPU
* @set_type: set the flow type (IRQ_TYPE_LEVEL/etc.) of an IRQ
* @set_wake: enable/disable power-management wake-on of an IRQ
*
* @release: release function solely used by UML
* @typename: obsoleted by name, kept as migration helper */struct irq_chip { const char *name;
unsigned int (*startup)(unsigned int irq); // 中断开始 void (*shutdown)(unsigned int irq); // 中断关闭 void (*enable)(unsigned int irq); // 中断使能 void (*disable)(unsigned int irq); // 中断禁用 void (*ack)(unsigned int irq); void (*mask)(unsigned int irq); void (*mask_ack)(unsigned int irq); void (*unmask)(unsigned int irq); void (*eoi)(unsigned int irq); void (*end)(unsigned int irq); void (*set_affinity)(unsigned int irq, cpumask_t dest); int (*retrigger)(unsigned int irq); int (*set_type)(unsigned int irq, unsigned int flow_type); int (*set_wake)(unsigned int irq, unsigned int on); /* Currently used only by UML, might disappear one day.*/#ifdef CONFIG_IRQ_RELEASE_METHOD void (*release)(unsigned int irq, void *dev_id);#endif
/*
* For compatibility, - typename is copied into - name.
* Will disappear. */
const char *typename;
};
2.2.3 中断请求实现
上下半部机制
我们期望让中断处理程序运行得快,并想让它完成的工作量多,这两个目标相互制约,如何解决——上下半部机制。
我们把中断处理切为两半。中断处理程序是上半部——接受中断,他就立即开始执行,但只有做严格时限的工作。能够被允许稍后完成的工作会推迟到下半部去,此后,在合适的时机,下半部会被开终端执行。上半部简单快速,执行时禁止一些或者全部中断。
下半部稍后执行,而且执行期间可以响应所有的中断。这种设计可以使系统处于中断屏蔽状态的时间尽可能的短,以此来提高系统的响应能力。上半部只有中断处理程序机制,而下半部的实现有软中断实现,tasklet 实现和工作队列实现。
我们用网卡来解释一下这两半。当网卡接受到数据包时,通知内核,触发中断,所谓的上半部就是,及时读取数据包到内存,防止因为延迟导致丢失,这是很急迫的工作。读到内存后,对这些数据的处理不再紧迫,此时内核可以去执行中断前运行的程序,而对网络数据包的处理则交给下半部处理。
上下半部划分原则
1)如果一个任务对时间非常敏感,将其放在中断处理程序中执行;
2)如果一个任务和硬件有关,将其放在中断处理程序中执行;
3)如果一个任务要保证不被其他中断打断,将其放在中断处理程序中执行;
4)其他所有任务,考虑放置在下半部执行。
下半部实现机制之软中断
软中断作为下半部机制的代表,是随着 SMP(share memory processor)的出现应运而生的,它也是 tasklet 实现的基础(tasklet 实际上只是在软中断的基础上添加了一定的机制)。软中断一般是“可延迟函数”的总称,有时候也包括了 tasklet(请读者在遇到的时候根据上下文推断是否包含 tasklet)。它的出现就是因为要满足上面所提出的上半部和下半部的区别,使得对时间不敏感的任务延后执行,软中断执行中断处理程序留给它去完成的剩余任务,而且可以在多个 CPU 上并行执行,使得总的系统效率可以更高。它的特性包括:
a)产生后并不是马上可以执行,必须要等待内核的调度才能执行。软中断不能被自己打断,只能被硬件中断打断(上半部)。
b)可以并发运行在多个 CPU 上(即使同一类型的也可以)。所以软中断必须设计为可重入的函数(允许多个 CPU 同时操作),因此也需要使用自旋锁来保护其数据结构。
下半部实现机制之 tasklet
tasklet 是通过软中断实现的,所以它本身也是软中断。
软中断用轮询的方式处理。假如正好是最后一种中断,则必须循环完所有的中断类型,才能最终执行对应的处理函数。显然当年开发人员为了保证轮询的效率,于是限制中断个数为 32 个。
为了提高中断处理数量,顺道改进处理效率,于是产生了 tasklet 机制。
Tasklet 采用无差别的队列机制,有中断时才执行,免去了循环查表之苦。Tasklet 作为一种新机制,显然可以承担更多的优点。正好这时候 SMP 越来越火了,因此又在 tasklet 中加入了 SMP 机制,保证同种中断只能在一个 cpu 上执行。在软中断时代,显然没有这种考虑。因此同一种软中断可以在两个 cpu 上同时执行,很可能造成冲突。
总结下 tasklet 的优点:
(1)无类型数量限制;
(2)效率高,无需循环查表;
(3)支持 SMP 机制;
它的特性如下:
1)一种特定类型的 tasklet 只能运行在一个 CPU 上,不能并行,只能串行执行。
2)多个不同类型的 tasklet 可以并行在多个 CPU 上。
3)软中断是静态分配的,在内核编译好之后,就不能改变。但 tasklet 就灵活许多,可以在运行时改变(比如添加模块时)。
下半部实现机制之工作队列(work queue)
上面我们介绍的可延迟函数运行在中断上下文中(软中断的一个检查点就是 do_IRQ 退出的时候),于是导致了一些问题:软中断不能睡眠、不能阻塞。由于中断上下文出于内核态,没有进程切换,所以如果软中断一旦睡眠或者阻塞,将无法退出这种状态,导致内核会整个僵死。但可阻塞函数不能用在中断上下文中实现,必须要运行在进程上下文中,例如访问磁盘数据块的函数。因此,可阻塞函数不能用软中断来实现。但是它们往往又具有可延迟的特性。
上面我们介绍的可延迟函数运行在中断上下文中,于是导致了一些问题,说明它们不可挂起,也就是说软中断不能睡眠、不能阻塞,原因是由于中断上下文出于内核态,没有进程切换,所以如果软中断一旦睡眠或者阻塞,将无法退出这种状态,导致内核会整个僵死。因此,可阻塞函数不能用软中断来实现。但是它们往往又具有可延迟的特性。而且由于是串行执行,因此只要有一个处理时间较长,则会导致其他中断响应的延迟。为了完成这些不可能完成的任务,于是出现了工作队列,它能够在不同的进程间切换,以完成不同的工作。
如果推后执行的任务需要睡眠,那么就选择工作队列,如果不需要睡眠,那么就选择软中断或 tasklet。工作队列能运行在进程上下文,它将工作托付给一个内核线程。工作队列说白了就是一组内核线程,作为中断守护线程来使用。多个中断可以放在一个线程中,也可以每个中断分配一个线程。我们用结构体 workqueue_struct 表示工作者线程,工作者线程是用内核线程实现的。而工作者线程是如何执行被推后的工作——有这样一个链表,它由结构体 work_struct 组成,而这个 work_struct 则描述了一个工作,一旦这个工作被执行完,相应的 work_struct 对象就从链表上移去,当链表上不再有对象时,工作者线程就会继续休眠。因为工作队列是线程,所以我们可以使用所有可以在线程中使用的方法。
Linux 软中断和工作队列的作用是什么
linux 中的软中断和工作队列的作用是实现中断处理;它们是中断上下部机制中的下半部实现机制。
1. 软中断一般是“可延迟函数”的总称,它不能睡眠,不能阻塞,它处于中断上下文,不能进程间切换,软中断不能被自己打断,只能被硬件中断打断(上半部),可以并发的运行在多个 CPU 上。所以软中断必须设计成可重入的函数,因此也需要自旋锁来保护其数据结构。
2. 工作队列中的函数处在进程上下文中,它可以睡眠,也能被阻塞,能够在不同的进程间切换,以完成不同的工作。
可延迟函数和工作队列都不能访问用户的进程空间,可延时函数在执行时不可能有任何正在运行的进程,工作队列的函数有内核进程执行,他不能访问用户空间地址。
以上就是关于“linux 软中断和工作队列有什么用”这篇文章的内容,相信大家都有了一定的了解,希望丸趣 TV 小编分享的内容对大家有帮助,若想了解更多相关的知识内容,请关注丸趣 TV 行业资讯频道。