Linux irq中断处理-1-基础简介

一、中断线程化

1. Linux中中断除了中断分层之外,还有一种就是中断线程化

存在意义:
在Linux中,中断具有最高的优先级。不论在任何时刻,只要产生中断事件,内核将立即执行相应的中断处理程序,等到所有挂起的中断和软中断处理完毕后才能执行正常的任务,因此有
可能造成实时任务得不到及时的处理。中断线程化之后,中断将作为内核线程运行而且被赋予不同的实时优先级,实时任务可以有比中断线程更高的优先级。这样,具有最高优先级的实
时任务就能得到优先处理,即使在严重负载下仍有实时性保证。但是,并不是所有的中断都可以被线程化,比如时钟中断,主要用来维护系统时间以及定时器等,其中定时器是操作系统的
脉搏,一旦被线程化,就有可能被挂起,这样后果将不堪设想,所以不应当被线程化。

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);
形参:
1>:irq: 表示申请的中断号。
2>:handler: 表示中断服务函数,在硬件中断上下文中运行。
3>: thread_fn: 中断线程执行函数,在进程上下文中运行。中断服务函数中返回IRQ_WAKE_THREAD即可唤醒此线程执行。
4>: irqflags: 表示中断触发标志。
5>: devname: 表示请求中断的设备的名称。
6>.dev_id: 对应于request_irq()函数中所传递的第五个参数,可取任意值,但必须唯一能够代表发出中断请求的设备,通常取描述该设备的结构体。共享中断时所用。

如果要为设备设置线程irq处理程序,则需要提供@handler和@thread_fn。 @handler仍然在硬中断上下文中被调用,并且必须检查中断是否来自此设备。 如果是,则需要禁用设备上的中断并返
回IRQ_WAKE_THREAD,这将唤醒处理程序线程并运行@thread_fn。
这种拆分处理程序设计是支持共享中断所必需的。Dev_id必须是全局唯一的。 通常选用设备数据结构的地址。 由于处理程序接收到此值,因此使用它是有意义的。如果共享中断,则必须传递非
NULL dev_id,因为在释放中断时需要这样做。

注意:

1.如果中断服务函数handler为null,则表示只想在进程上下文中运行此中断,此时handler被此函数赋值为默认的irq_default_primary_handler(),它什么都不做,只返回IRQ_WAKE_THREAD,唤醒中断线程 。

2. handler() 运行在中断上下文,trace上只能中断信息中看到它,thread_fn() 运行在进程上下文,cpuinfo可以看到它,默认是RT线程,优先级为49,可运行在所有CPU上,支持绑核。

 

二、每CPU中断

1. 例子

arch_timer_register
    request_percpu_irq(ppi, arch_timer_handler_virt, "arch_timer", arch_timer_evt);

 

三、中断现场

在中断发生时需要保存发生中断前的现场,以免在中断处理过程中被破坏了。以ARM64处理器为例,我们需要在栈空间里保存如下内容:
(1) PSTATE 寄存器的值;
(2) PC 值;
(3) SP 值;
(4) X0~X30 寄存器的值。
注: 看下面结构体定义,应该不只保存这些内容。

中断也是异常的一种,因此保存和恢复中断现场的方法与保存和恢复异常现场的方法是一样的。为了方便编程,我们可以使用一个栈框数据结构 struct pt_regs 来描述需要保存的中断现场。

struct user_pt_regs { //arm64/ptrace.h
    __u64        regs[31];
    __u64        sp;
    __u64        pc;
    __u64        pstate;
};

/*
 * 此结构体定义了异常发生时寄存器在栈上的存储方式。请注意, sizeof(struct pt_regs) 
 * 必须是 16 的倍数(为了栈对齐)。struct user_pt_regs 必须作为 struct pt_regs 的前缀。
 * (gdb) p sizeof(struct pt_regs) $1 = 320
 */
struct pt_regs {
    union {
        struct user_pt_regs user_regs; //和下面结构体是同一个结构
        struct {
            u64 regs[31];
            u64 sp;
            u64 pc;
            u64 pstate;
        };
    };
    u64 orig_x0;
    s32 syscallno;
    u32 unused2;
    u64 orig_addr_limit;
    u64 pmr_save; //ARM64_HAS_IRQ_PRIO_MASKING 默认使能
    u64 stackframe[2];
};


1. 保存中断现场

中断发生时,我们需要把中断现场保存到当前进程的内核栈里,如下图所示。
(1) 栈框里的 pstate 保存发生中断时 SPSREL1 的内容。
(2) 栈框里的 pc 保存 ELR_EL1 的内容。
(3) 栈框里的 sp 保存栈顶的位置。
(4) 栈框里的 regs[30] 保存 LR 的值。
(5) 栈框里的 regs[0]~regs[29] 分别保存 X0~X29 寄存器的值。

img_v3_02u1_e11b7e77-1711-4a66-a14b-5353c1e63beg

 

2. 恢复中断现场

中断返回时,从进程内核栈恢复中断现场到CPU, 如图12.9所示。

img_v3_02u1_7e567aef-db9c-4a63-be55-a30126c46a9g

 

3. SPSR_EL1 和 ELR_EL1

SPSR_EL1 和 ELR_EL1 是异常处理的核心寄存器,分别负责保存异常发生前的处理器状态返回地址,确保系统能在异常处理后准确恢复执行。

当异常(如中断、系统调用)从低级别(如EL0用户态)进入EL1内核态时,硬件会自动将当前程序状态寄存器 PSTATE 的值存入 SPSR_EL1,同时将异常返回地址写入 ELR_EL1

SPSR_EL1(Saved Program Status Register for EL1)本质是 PSTATE 的"快照",存储了异常发生前的关键状态信息,包括条件码标志(N、Z、C、V)、中断屏蔽位(DAIF)等。例如,若用户程序因系统调用陷入内核,SPSR_EL1 会记录此时的中断使能状态,确保异常处理完成后能恢复原有的执行环境。

ELR_EL1(Exception Link Register for EL1)则存储异常返回的目标地址。当异常发生时,处理器将当前PC(程序计数器)的值调整后存入 ELR_EL1 —— 对于同步异常(如系统调用),返回地址通常是异常指令的下一条;对于异步异常(如中断),则是被打断指令的地址。以Linux内核为例,异常处理结束时执行 eret 指令,硬件会自动将 ELR_EL1 的值恢复到PC,实现从内核态到用户态的无缝跳转。

这对寄存器的协同工作体现了ARM64异常处理的"自动保存-手动恢复"设计哲学:异常进入时由硬件完成关键状态的保存,而恢复过程则通过软件控制eret指令触发,兼顾效率与灵活性。理解它们的作用,是掌握操作系统内核调度、中断处理等底层机制的基础——比如在调试内核崩溃时,ELR_EL1 指向的地址往往是定位问题的关键线索。

 

posted on 2018-08-25 20:52  Hello-World3  阅读(626)  评论(0)    收藏  举报

导航