1200字范文,内容丰富有趣,写作的好帮手!
1200字范文 > Linux内核中断系统处理机制-详细分析

Linux内核中断系统处理机制-详细分析

时间:2019-01-14 16:12:24

相关推荐

Linux内核中断系统处理机制-详细分析

原文地址::/weixin_42092278/article/details/81989449

相关文章

1、Linux中断管理 (1)Linux中断管理机制----/arnoldlu/p/8659981.html

2、Linux内核中断处理机制----/big-devil/p/8589501.html

日期 内核版本 架构 作者 内容

-8-23 Linux-2.6.32

X86

Bystander Linux内核中断

一、中断概述

中断是指在CPU正常运行期间,由于内外部事件或由程序预先安排的事件引起的CPU暂时停止正在运行的程序,转而为该内部或外部事件或预先安排的事件服务的程序中去,服务完毕后再返回去继续运行被暂时中断的程序。

1.1中断类型

同步中断由CPU本身产生,又称为内部中断。这里同步是指中断请求信号与代码指令之间的同步执行,在一条指令执行完毕后,CPU才能进行中断,不能在执行期间。所以也称为异常(exception)。

异步中断是由外部硬件设备产生,又称为外部中断,与同步中断相反,异步中断可在任何时间产生,包括指令执行期间,所以也被称为中断(interrupt)。

异常又可分为可屏蔽中断(Maskable interrupt)和非屏蔽中断(Nomaskable interrupt)。而中断可分为故障(fault)、陷阱(trap)、终止(abort)三类。

从广义上讲,中断又可分为四类:中断、故障、陷阱、终止。这些类别之间的异同点请参考 表 1。

表 1:中断类别及其行为

类别

原因

异步/同步

返回行为

中断

来自I/O设备的信号

异步

总是返回到下一条指令

陷阱

有意的异常

同步

总是返回到下一条指令

故障

潜在可恢复的错误

同步

返回到当前指令

终止

不可恢复的错误

同步

不会返回

有些参考资料中按照中断来源进行分类,如下图所示(个人建议不采用这种方式):

图1-1

1.2区分中断号与中断向量

I/O设备把中断信号发送给中断控制器(8259A)时与之相关联的是一个中断号,当中断控制器把中断信号发送给CPU时与之关联的是一个中断向量。换个角度分析就是中断号是从中断控制器层面划分,中断向量是从CPU层面划分,所以中断号与中断向量之间存在一对一映射关系。在Intel X86中最大支持256种中断,从0到255开始编号,这个8位的编号就是中断向量。其中将0到31保留用于异常处理和不可屏蔽中断。

二、中断数据处理结构

Linux内核中处理中断主要有三个数据结构,irq_desc,irq_chip和irqaction。

在\include\linux\ irq.h中定义了

1)irq_desc用于描述IRQ线的属性与状态,被称为中断描述符。

/**

* struct irq_desc - interrupt descriptor

* @irq:interrupt number for this descriptor

* @timer_rand_state: pointer to timer rand state struct

* @kstat_irqs: irq stats per cpu

* @irq_2_iommu: iommu with this irq

* @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

* @last_unhandled: aging timer for unhandled count

* @irqs_unhandled: stats field for spurious unhandled interrupts

* @lock:locking for SMP

* @affinity:IRQ affinity on SMP

* @node: node index useful for balancing

* @pending_mask: pending rebalanced interrupts

* @threads_active: number of irqaction threads currently running

* @wait_for_threads: wait queue for sync_irq to wait for threaded handlers

* @dir:/proc/irq/ procfs entry

* @name: flow handler name for /proc/interrupts output

*/

struct irq_desc{

unsigned intirq;

struct timer_rand_state *timer_rand_state;

unsigned int*kstat_irqs;

#ifdef CONFIG_INTR_REMAP

struct irq_2_iommu *irq_2_iommu;

#endif

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 intstatus;/* IRQ status */

unsigned intdepth;/* nested irq disables */

unsigned intwake_depth; /* nested wake enables */

unsigned intirq_count; /* For detecting broken IRQs */

unsigned long last_unhandled; /* Aging timer for unhandled count */

unsigned intirqs_unhandled;

spinlock_t lock;

#ifdef CONFIG_SMP

cpumask_var_t affinity;

unsigned intnode;

#ifdef CONFIG_GENERIC_PENDING_IRQ

cpumask_var_t pending_mask;

#endif

#endif

atomic_tthreads_active;

wait_queue_head_t wait_for_threads;

#ifdef CONFIG_PROC_FS

struct proc_dir_entry *dir;

#endif

const char *name;

}

2)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

*

* @bus_lock:function to lock access to slow bus (i2c) chips

* @bus_sync_unlock: function to sync and unlock slow bus (i2c) chips

*

* @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);

int(*set_affinity)(unsigned int irq,

const struct cpumask *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);

void (*bus_lock)(unsigned int irq);

void (*bus_sync_unlock)(unsigned int irq);

/* 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;

}

在\include\linux\ interrupt.h中定义了 irqaction用来描述特定设备所产生的中断描述符。

/**

* struct irqaction - per interrupt action descriptor

* @handler: interrupt handler function

* @flags: flags (see IRQF_* above)

* @name: name of the device

* @dev_id: cookie to identify the device

* @next: pointer to the next irqaction for shared interrupts

* @irq: interrupt number

* @dir: pointer to the proc/irq/NN/name entry

* @thread_fn: interupt handler function for threaded interrupts

* @thread: thread pointer for threaded interrupts

* @thread_flags: flags related to @thread

*/

struct irqaction {

irq_handler_t handler;

unsigned long flags;

const char *name;

void *dev_id;

struct irqaction *next;

int irq;

struct proc_dir_entry *dir;

irq_handler_t thread_fn;

struct task_struct *thread;

unsigned long thread_flags;

};

三、Linux中断机制

Linux中断机制由三部分组成:

中断子系统初始化:内核自身初始化过程中对中断处理机制初始化,例如中断的数据结构以及中断请求等。

中断或异常处理:中断整体处理过程。

中断API:为设备驱动提供API,例如注册,释放和激活等。

3.1中断子系统初始化

3.1.1中断描述符表(IDT)初始化

中断描述符表初始化需要经过两个过程:

第一个过程在内核引导过程。由两个步骤组成,首先给分配IDT分配2KB空间(256中断向量,每个向量由8bit组成)并初始化;然后把IDT起始地址存储到IDTR寄存器中。

第二个过程内核在初始化自身的start_kernal函数中使用trap_init初始化系统保留中断向量,使用init_IRQ完成其余中断向量初始化。

3.1.2中断请求队列初始化

init_IRQ调用pre_intr_init_hook,进而最终调用init_ISA_irqs初始化中断控制器以及每个IRQ线的中断请求队列。

3.2中断或异常处理

中断处理过程:设备产生中断,并通过中断线将中断信号送往中断控制器,如果中断没有被屏蔽则会到达CPU的INTR引脚,CPU立即停止当前工作,根据获得中断向量号从IDT中找出门描述符,并执行相关中断程序。

异常处理过程:异常是由CPU内部发生所以不会通过中断控制器,CPU直接根据中断向量号从IDT中找出门描述符,并执行相关中断程序。

图3-1

中断控制器处理主要有5个步骤:1.中断请求 2.中断响应 3.优先级比较 4.提交中断向量 5.中断结束。这里不再赘述5个步骤的具体流程。

CPU处理流程主要有6个步骤:1.确定中断或异常的中断向量 2.通过IDTR寄存器找到IDT 3.特权检查 4.特权级发生变化,进行堆栈切换 5.如果是异常将异常代码压入堆栈,如果是中断则关闭可屏蔽中断 6.进入中断或异常服务程序执行。这里不再赘述6个步骤的具体流程。

3.3中断API

内核提供的API主要用于驱动的开发。

注册IRQ:

int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char *name, void *dev);

释放IRQ:

void free_irq(unsigned int, void *);

注:IRQ线资源非常宝贵,我们在使用时必须先注册,不使用时必须释放IRQ资源。

激活当前CPU中断:

local_irq_enable();

禁止当前CPU中断:

local_irq_disable();

激活指定中断线:

void enable_irq(unsigned int irq);

禁止指定中断线:

void disable_irq(unsigned int irq);

禁止指定中断线:

void disable_irq_nosync(unsigned int irq);

注:此函数调用irq_chip中disable禁止指定中断线,所以不会保证中断线上执行的中断服务程序已经退出。

3.4中断机制划分

由于中断会打断内核中进程的正常调度运行,所以要求中断服务程序尽可能的短小精悍;但是在实际系统中,当中断到来时,要完成工作往往进行大量的耗时处理。因此期望让中断处理程序运行得快,并想让它完成的工作量多,这两个目标相互制约,诞生——顶/底半部机制。

中断处理程序是顶半部——接受中断,它就立即开始执行,但只有做严格时限的工作。能够被允许稍后完成的工作会推迟到底半部去,此后,在合适的时机,底半部会被开终端执行。顶半部简单快速,执行时禁止一些或者全部中断。

底半部稍后执行,而且执行期间可以响应所有的中断。这种设计可以使系统处于中断屏蔽状态的时间尽可能的短,以此来提高系统的响应能力。顶半部只有中断处理程序机制,而底半部的实现有软中断,tasklet和工作队列实现。

图3-2 注:登记中断,将底半部处理程序挂到该设备的低半部执行队列中。

3.4.1顶/底半部划分原则:

1) 如果一个任务对时间非常敏感,将其放在顶半部中执行;

2) 如果一个任务和硬件有关,将其放在顶半部中执行;

3) 如果一个任务要保证不被其他中断打断,将其放在顶半部中执行;

4) 其他所有任务,考虑放置在底半部执行。

3.4.2底半部实现机制

图3-3

软中断:

软中断作为下半部机制的代表,是随着SMP(share memory processor)的出现应运而生的,它也是tasklet实现的基础(tasklet实际上只是在软中断的基础上添加了一定的机制)。软中断一般是“可延迟函数”的总称,有时候也包括了tasklet(请读者在遇到的时候根据上下文推断是否包含tasklet)。它的出现就是因为要满足上面所提出的上半部和下半部的区别,使得对时间不敏感的任务延后执行,软中断执行中断处理程序留给它去完成的剩余任务,而且可以在多个CPU上并行执行,使得总的系统效率可以更高。它的特性包括:

a)产生后并不是马上可以执行,必须要等待内核的调度才能执行。软中断不能被自己打断,只能被硬件中断打断(上半部)。

b)可以并发运行在多个CPU上(即使同一类型的也可以)。所以软中断必须设计为可重入的函数(允许多个CPU同时操作),因此也需要使用自旋锁来保护其数据结构。

内核中定义了几种软中断的用途:

enum

{

HI_SOFTIRQ=0,

TIMER_SOFTIRQ,

NET_TX_SOFTIRQ,

NET_RX_SOFTIRQ,

BLOCK_SOFTIRQ,

BLOCK_IOPOLL_SOFTIRQ,

TASKLET_SOFTIRQ,

SCHED_SOFTIRQ,

HRTIMER_SOFTIRQ,

RCU_SOFTIRQ, /* Preferable RCU should always be the last softirq */

NR_SOFTIRQS

};

Tasklet:

tasklet是通过软中断实现的,所以它本身也是软中断。

软中断用轮询的方式处理。假如正好是最后一种中断,则必须循环完所有的中断类型,才能最终执行对应的处理函数。显然当年开发人员为了保证轮询的效率,于是限制中断个数为32个。

为了提高中断处理数量,顺道改进处理效率,于是产生了tasklet机制。

Tasklet采用无差别的队列机制,有中断时才执行,免去了循环查表之苦。Tasklet作为一种新机制,显然可以承担更多的优点。正好这时候SMP越来越火了,因此又在tasklet中加入了SMP机制,保证同种中断只能在一个cpu上执行。在软中断时代,显然没有这种考虑。因此同一种软中断可以在两个cpu上同时执行,很可能造成冲突。

总结下tasklet的优点:

(1)无类型数量限制;

(2)效率高,无需循环查表;

(3)支持SMP机制;

它的特性如下:

1)一种特定类型的tasklet只能运行在一个CPU上,不能并行,只能串行执行。

2)多个不同类型的tasklet可以并行在多个CPU上。

3)软中断是静态分配的,在内核编译好之后,就不能改变。但tasklet就灵活许多,可以在运行时改变(比如添加模块时)。

工作队列:

上面我们介绍的可延迟函数运行在中断上下文中,于是导致了一些问题,说明它们不可挂起,也就是说软中断不能睡眠、不能阻塞,原因是由于中断上下文出于内核态,没有进程切换,所以如果软中断一旦睡眠或者阻塞,将无法退出这种状态,导致内核会整个僵死。因此,可阻塞函数不能用软中断来实现。但是它们往往又具有可延迟的特性。而且由于是串行执行,因此只要有一个处理时间较长,则会导致其他中断响应的延迟。为了完成这些不可能完成的任务,于是出现了工作队列,它能够在不同的进程间切换,以完成不同的工作。

工作队列能运行在进程上下文,它将工作给一个内核线程,作为中断守护线程来使用。多个中断可以放在一个线程中,也可以每个中断分配一个线程。我们用结构体workqueue_struct表示工作者线程,工作者线程是用内核线程实现的。而工作者线程是如何执行被推后的工作——有这样一个链表,它由结构体work_struct组成,而这个work_struct则描述了一个工作,一旦这个工作被执行完,相应的work_struct对象就从链表上移去,当链表上不再有对象时,工作者线程就会继续休眠。因为工作队列是线程,所以我们可以使用所有可以在线程中使用的方法。

如何选择下半部机制:

软中断和tasklet运行在中断上下文,工作队列运行在进程上下文。如果需要休眠则选择工作队列,否则选择tasklet;如果对性能要求较高则选择软中断。

从易用性考虑,首选工作队列,然后tasklet,最后是软中断,因为软中断需要静态创建。

从代码安全考虑,如果对底半部代码保护不够安全,则选择tasklet,因为相对软中断,tasklet对锁要求低,上面也简述它们工作方式以及运用场景。

四、多处理器系统中断相关概念

4.1处理器间中断

在多处理器系统中,操作系统需要在多个处理器中协调操作,所以需要处理器中断(Inter-Processor-Interrupt,IPI)实现,IPI是一种特殊硬件中断,由CPU送出,其他CPU接收,处理CPU之间通信和同步操作。以下是x86中SMP定义的IPI,中断向量号用十六进制表示:

SPURIOUS_APIC_VECTOR 0xff

ERROR_APIC_VECTOR 0xfe

RESCHEDULE_VECTOR 0xfd

CALL_FUNCTION_VECTOR 0xfc

CALL_FUNCTION_SINGLE_VECTOR 0xfb

THERMAL_APIC_VECTOR 0xfa

THRESHOLD_APIC_VECTOR 0xf9

REBOOT_VECTOR 0xf8

INVALIDATE_TLB_VECTOR_END 0xf7

INVALIDATE_TLB_VECTOR_START 0xf0

4.2中断亲和力

将一个或多个中断服务程序绑定到特定的CPU上处理,这就是中断亲和力(SMP IRQ affinity)。我们可以使用中断亲和力来均衡各个CPU的负载,提高系统处理能力。

以上便是本人对Linux中断的理解,若有纰漏欢迎指正!

————————————————

版权声明:本文为CSDN博主「Bystander_J」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。

原文链接:/weixin_42092278/article/details/81989449

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。