【linux kernel】 中断处理

 

 

欢迎转载,转载时需保留作者信息,谢谢。

邮箱:[email protected]

博客园地址:http://www.cnblogs.com/embedded-tzp

Csdn博客地址:http://blog.csdn.net/xiayulewa

 

环境: http://www.cnblogs.com/embedded-tzp/p/4443876.html

 

 

1. arm硬件中断

从头介绍中断原理太麻烦,一般来说看这篇文章的人对中断必然已经有了一定的认识。不管哪款处理器,中断的基本概念是一样的:

中断向量表。

中断优先级。

中断现场保护与恢复。

 

1.1. 中断向量表

 

中断向量表,程序发生中断后会跳转到该表执行,一般在里面放置mov pc, addr_of_isr_func形式的指令,以中断向量表为跳板,跳转到中断服务程序中。

一般处理器中断向量表地址是固定的,arm920t固定在0x00000000,或者0xFFFF0000处,其地址是通过配置CP15的Register 1实现的。详细可阅读ARM920T_TRM1_S.pdf

默认中断向量表是在0x00000000处,arm920t的中断向量表如下:

        

上图为arm中断处理的流程,在程序正常运行时, 发生定时器中断,跳转到中断向量表处执行对应指令,而irq中断向量地址指令为goto irq_isr, 跳转到中断irq中断服务程序,此时首先保存现场,执行完中断逻辑后,再恢复现场,后中断返回,从之前被中断处的下一条指令继续运行。

 

 

 

1.2. Linux内核中的中断向量表

在entry-armv.S (src\arch\arm\kernel)中定义中断向量表。

    .section .vectors, "ax", %progbits

__vectors_start:

    W(b)    vector_rst

    W(b)    vector_und

    W(ldr)  pc, __vectors_start + 0x1000

    W(b)    vector_pabt

    W(b)    vector_dabt

    W(b)    vector_addrexcptn

    W(b)    vector_irq

W(b)    vector_fiq

 

 

1.3. linux中断初始化配置

 

硬件:有主中断SRCPND,子中断SUBSRCPND。主中断也可称为父中断。

 

处理器中断初始化: MACHINE_START:s3c2440_init_irq中初始化, 同时初始化static struct s3c_irq_intc *s3c_intc[3];变量

 

中断(irq,abt,und)的堆栈初始化setup_arch→setup_processor→cpu_init→struct stack *stk = &stacks[cpu] (irq,abt,und)

 

映射中断向量表:setup_arch→paging_init→devicemaps_init

 

定义中断向量表:src\arch\arm\kernel\entry-armv.S(其大部分定义在entry-header.S (src\arch\arm\kernel)):__vectors_start

 

中断方式初始化:如上升沿等,在init_s3c2440base里面定义

 

 

1.4. Linux中断执行流程

1.4.1. 中断入口与现场保护

假定程序运行在用户态,中断类型为irq,则:

 

   中断发生→W(b)   vector_irq( vector_irq 由vector_stub irq, IRQ_MODE, 4定义)→执行宏vector_stub定义的vector_irq中断服务程序, 定义见下:

    vector_stub irq, IRQ_MODE, 4

 

    .long   __irq_usr           @  0  (USR_26 / USR_32)

    .long   __irq_invalid           @  1  (FIQ_26 / FIQ_32)

    .long   __irq_invalid           @  2  (IRQ_26 / IRQ_32)

    .long   __irq_svc           @  3  (SVC_26 / SVC_32)

.long   __irq_invalid           @  4

 

vector_stub的作用是保存中断现场,获取中断前处理器状态,然后将状态设置为SVC mode,后跳转到相应的中断才处理程序。 前面假定中断前为用户态,所以中断发生后,会最终跳转到__irq_usr处执行。

1.4.2.    中断服务程序

__irq_usr→irq_handler→handle_arch_irq(是个函数指针,全局变量=s3c24xx_handle_irq, 在s3c24xx_init_intc中被赋值)→s3c24xx_handle_intc→handle_IRQ→generic_handle_irq→desc->handle_irq→如果是父中断,则执行s3c_irq_demux(在s3c24xx_irq_map中设置,执行chained_irq_enter→irq_ack清除主中断)→generic_handle_irq→desc->handle_irq即handle_edge_irq(chip->irq_ack清除次级中断相关位,在s3c24xx_irq_map中设置)→handle_irq_event→ handle_irq_event_percpu→action->handler(由request_irq申请,循环处理action = action->next)

 

1.4.3.    中断返回与现场恢复

以__irq_usr为例:b ret_to_user_from_irq(entry-common.S (src\arch\arm\kernel)定义)→ work_pending (在entry-common.S (src\arch\arm\kernel)定义)→ do_work_pending→ schedule

 

 

handle_arch_irq赋值流程:MACHINE_START的init_irq(s3c2440_init_irq)→s3c24xx_init_intc→set_handle_irq

 

desc->handle_irq注册流程:MACHINE_START::s3c2440_init_irq→s3c24xx_init_intc→irq_domain_add_legacy(传入 

s3c24xx_irq_ops)→ops->map(即s3c24xx_irq_ops->map),即s3c24xx_irq_map→根据中断类型如果是边沿触发,则 

desc->handle_irq=handle_edge_irq

 

 

 

    用到的变量与文件对应关系: 

src\arch\arm\include\asm\Unified.h: #define PSR_ISETSTATE 0

src\arch\arm\include\asm\linkage.h: #define ENDPROC(name) \

  .type name, %function; \

  END(name)

src\include\linux\linkage.h:#define END(name) \

  .size name, .-name

#endif

 

Asm-offsets.c (src\arch\arm\kernel):  DEFINE(S_FRAME_SIZE,    sizeof(struct pt_regs))

entry-header.S (src\arch\arm\kernel)): why .req   r8     @ Linux syscall (!= 0)

Unified.h (src\arch\arm\include\asm):#define BSYM(sym) sym

src\arch\arm\kernel\Asm-offsets.c: DEFINE(SYS_ERROR0,     0x9f0000);

 

1.5. linux中断注册

request_irq→request_threaded_irq→__setup_irq→new->irq = irq;   *old_ptr = new;(old_ptr指向action链表的最后一项)

 

 

1.6. linux中断地址映射

 

linux中断地址分配:MACHINE_START中mini2440_map_io→s3c24xx_init_io →iotable_init(s3c_iodesc, ARRAY_SIZE(s3c_iodesc)); // 该函数分析详细见gpio章节。

由上述可见,#define S3C_VA_IRQ  S3C_ADDR(0x00000000), 知irq虚拟地址为0xF6000000,且因为串口物理地址和虚拟地址对应关系确定了,irq物理地址和虚拟地址也确定了,因为PA1-PA2 = VA1-VA2

 

1.7. linux中断数据结构

 

 

由上图可见,中断号可以索引所有的中断对象。

中断概貌:最重要的结构是全局变量struct irq_desc irq_desc[NR_IRQS]; 每一个中断号对应一个struct irq_desc, 在MACHINE_START中的s3c2440_init_irq主要是初始化该结构体。每个中断号和具体的irq_desc[i]对应,根据中断号就能知道该中断的所有信息,如该中断是父中断还是子中断,该中断的处理函数。父中断读取子中断源,通过desc->handle_irq = s3c_irq_demux(不一定是这个函数,为了描述方便举例的,后同)执行具体的子中断函数,子中断再执行desc->handle_irq = handle_edge_irq,最终都会

执行desc->action->handler。 总结起来 desc->handle_irq是执行函数分配,desc->action->handler执行具体的中断函数,

 

 1.4.  函数集合

request_irq

free_irq

 

 

1.5.  软中断

重要结构体:

struct softirq_action softirq_vec[NR_SOFTIRQS]:open_softirq注册时设置

irq_cpustat_t irq_stat[]: raise_softirq_irqoff→or_softirq_pending置位bitmap:irq_stat[cpu].__softirq_pending

 

执行流程:

路径1:open_softirq注册→raise_softirq→raise_softirq_irqoff→raise_softirq_irqoff→wakeup_softirqd→softirq_threads.thread_fn(即run_ksoftirqd,其注册过程见下) →__do_softirq→ h->action(h); 即为具体注册函数

路径2:open_softirq注册→raise_softirq→raise_softirq_irqoff→raise_softirq_irqoff→irq_exit(在中断退出时执行)→invoke_softirq→do_softirq(或wakeup_softirqd) →__do_softirq→ h->action(h); 即为具体注册函数

 

Ksoftirqd:是个线程do_basic_setup→do_initcalls→spawn_ksoftirqd→smpboot_register_percpu_thread(&softirq_threads)(softirq_threads.store = Ksoftirqd, .thread_fn = run_ksoftirqd)

 

 

1.6.  底半部

1.6.1.   Tasklet

struct tasklet_struct

 

执行流程:tasklet_init注册→tasklet_schedule→__tasklet_schedule→raise_softirq_irqoff(TASKLET_SOFTIRQ)触发软件中断,后续见<<中断.软中断>>

 

始终在软中断期间运行,始终会在调度他们的同一CPU上运行,接收一个unsigned long参数

 

注意:

* 不允许访问用户空间;
* 不允许访问current指针;
* 不能执行休眠或调度。

特征:

* 一个tasklet可被禁用或启用;只用启用的次数和禁用的次数相同时,tasklet才会被执行;
* 和定时器类似,tasklet可以注册自己;
* tasklet可被调度在一般优先级或者高优先级上执行,高优先级总会首先执行;
* 如果系统负荷不重,则tasklet会立即得到执行,且始终不会晚于下一个定时器滴答;
* 一个tasklet可以和其他tasklet并发,但对自身来讲必须严格串行处理,即一个tasklet
   不会在多个处理器上执行。

 

接口:

void tasklet_init(struct tasklet_struct *t, void (*func)(unsigned long),

     unsigned long data); /* 初始化tasklet,func指向要执行的函数,data为传递给函数func的参数 */

DECLARE_TASKLET(name,func,data); /*定义及初始化tasklet*/

DECLARE_TASKLET_DISABLED(name,func,data);      /*定义及初始化后禁止该tasklet*/

void tasklet_disable(struct tasklet_struct *t) /*禁用指定tasklet*/

void tasklet_disable_nosync(struct tasklet_struct *t) /*禁用指定tasklet,但不会等待任何正在

                                                        运行的tasklet退出*/

void tasklet_enable(struct tasklet_struct *t)      /*启用先前被禁用的tasklet*/

void tasklet_schedule(struct tasklet_struct *t)    /*调度执行指定的tasklet*/

void tasklet_hi_schedule(struct tasklet_struct *t) /*调度指定的tasklet以高优先级执行*/

void tasklet_kill(struct tasklet_struct *t)        /*移除指定tasklet*/

 

简单用法:

DECLARE_TASKLET→tasklet_schedule

tasklet_kill()

 

1.6.2.   软中断

1.6.3.   工作队列

结构体:

struct work_struct

struct delayed_work

 

初始化:

    INIT_WORK(&mywork, my_work_func);

INIT_DELAYED_WORK(&mydwork, my_dwork_func);

 

调度:

内核默认队列:

schedule_work(&mywork)

schedule_delayed_work(&mydwork, 5*HZ)

 

自建队列:

struct workqueue_struct

wq = create_workqueue("tzp");

 

queue_work(wq, &mywork);

queue_delayed_work(wq, &mydwork, 3*HZ);

 

destroy_workqueue

 

1.6.4.   等待队列

结构体:

wait_queue_head_t

wait_queue_t

 

初始化:

init_waitqueue_head(wait_queue_head_t *wqh);

DECLARE_WAIT_QUEUE_HEAD(name);

init_waitqueue_entry

 

 

添加队列:

DECLARE_WAITQUEUE(name, task); // 定义并初始化一个名称为name的等待队列,task通常被设置为代表当前进程的current指针

 

add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);

remove_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);

 

等待事件:

wait_event(queue, condition); //TASK_UNINTERRUPTIBLE类型的睡眠状态,并挂在queue指定的等待队列数据链上

wait_event_interruptible(queue, condition);  //TASK_INTERRUPTIBLE类型的睡眠状态

wait_event_killable(queue, condition);// TASK_KILLABLE类型的睡眠状态

wait_event_timeout(queue, condition, timeout);

 // TASK_UNINTERRUPTIBLE类型的睡眠状态,并挂在queue指定的等待队列数据链上;当阻塞时间timeout超时后,立即返回

 

唤醒队列:

 

wake_up(wait_queue_head_t *queue);

    唤醒由queue指向的等待队列数据链中的所有睡眠类型的等待进程

wake_up_interruptible(wait_queue_head_t *queue);

    唤醒由queue指向的等待队列数据链中的所有睡眠类型为TASK_INTERRUPTIBLE的等待进程

wait_event_interruptible_timeout(queue, condition, timerout);

 

如:当前进程

wait_queue_head_t adc_wq;

init_waitqueue_head(&adc_wq);

wake_up_interruptible(&adc_wq);

wait_event_interruptible(adc_wq, condition);

 

 

 

时间: 2024-10-18 13:33:23

【linux kernel】 中断处理的相关文章

Linux kernel的中断子系统之(六):ARM中断处理过程

一.前言 本文主要以ARM体系结构下的中断处理为例,讲述整个中断处理过程中的硬件行为和软件动作.具体整个处理过程分成三个步骤来描述: 1.第二章描述了中断处理的准备过程 2.第三章描述了当发生中的时候,ARM硬件的行为 3.第四章描述了ARM的中断进入过程 4.第五章描述了ARM的中断退出过程 本文涉及的代码来自3.14内核.另外,本文注意描述ARM指令集的内容,有些source code为了简短一些,删除了THUMB相关的代码,除此之外,有些debug相关的内容也会删除. 二.中断处理的准备过

Linux Kernel - Debug Guide (Linux内核调试指南 )

http://blog.csdn.net/blizmax6/article/details/6747601 linux内核调试指南 一些前言 作者前言 知识从哪里来 为什么撰写本文档 为什么需要汇编级调试 ***第一部分:基础知识*** 总纲:内核世界的陷阱 源码阅读的陷阱 代码调试的陷阱 原理理解的陷阱 建立调试环境 发行版的选择和安装 安装交叉编译工具 bin工具集的使用 qemu的使用 initrd.img的原理与制作 x86虚拟调试环境的建立 arm虚拟调试环境的建立 arm开发板调试环

linux kernel的中断子系统之(三):IRQ number和中断描述符【转】

转自:http://www.wowotech.net/linux_kenrel/interrupt_descriptor.html 一.前言 本文主要围绕IRQ number和中断描述符(interrupt descriptor)这两个概念描述通用中断处理过程.第二章主要描述基本概念,包括什么是IRQ number,什么是中断描述符等.第三章描述中断描述符数据结构的各个成员.第四章描述了初始化中断描述符相关的接口API.第五章描述中断描述符相关的接口API. 二.基本概念 1.通用中断的代码处理

Linux kernel中断子系统之(五):驱动申请中断API

一.前言 本文主要的议题是作为一个普通的驱动工程师,在撰写自己负责的驱动的时候,如何向Linux Kernel中的中断子系统注册中断处理函数?为了理解注册中断的接口,必须了解一些中断线程化(threaded interrupt handler)的基础知识,这些在第二章描述.第三章主要描述了驱动申请 interrupt line接口API request_threaded_irq的规格.第四章是进入request_threaded_irq的实现细节,分析整个代码的执行过程. 二.和中断相关的lin

linux kernel的中断子系统之(四):High level irq event handler

一.前言 当外设触发一次中断后,一个大概的处理过程是: 1.具体CPU architecture相关的模块会进行现场保护,然后调用machine driver对应的中断处理handler 2.machine driver对应的中断处理handler中会根据硬件的信息获取HW interrupt ID,并且通过irq domain模块翻译成IRQ number 3.调用该IRQ number对应的high level irq event handler,在这个high level的handler中

linux kernel的中断子系统之(九):tasklet

一.前言 对于中断处理而言,linux将其分成了两个部分,一个叫做中断handler(top half),属于不那么紧急需要处理的事情被推迟执行,我们称之deferable task,或者叫做bottom half,.具体如何推迟执行分成下面几种情况: 1.推迟到top half执行完毕 2.推迟到某个指定的时间片(例如40ms)之后执行 3.推迟到某个内核线程被调度的时候执行 对于第一种情况,内核中的机制包括softirq机制和tasklet机制.第二种情况是属于softirq机制的一种应用场

linux kernel的中断子系统之(八):softirq

一.前言 对于中断处理而言,linux将其分成了两个部分,一个叫做中断handler(top half),是全程关闭中断的,另外一部分是deferable task(bottom half),属于不那么紧急需要处理的事情.在执行bottom half的时候,是开中断的.有多种bottom half的机制,例如:softirq.tasklet.workqueue或是直接创建一个kernel thread来执行bottom half(这在旧的kernel驱动中常见,现在,一个理智的driver厂商是

linux kernel的中断子系统之(七):GIC代码分析

一.前言 GIC(Generic Interrupt Controller)是ARM公司提供的一个通用的中断控制器,其architecture specification目前有四个版本,V1-V4(V2最多支持8个ARM core,V3/V4支持更多的ARM core,主要用于ARM64服务器系统结构).目前在ARM官方网站只能下载到Version 2的GIC architecture specification,因此,本文主要描述符合V2规范的GIC硬件及其驱动. 具体GIC硬件的实现形态有两

Linux kernel的中断子系统之(二):IRQ Domain介绍

一.概述 在linux kernel中,我们使用下面两个ID来标识一个来自外设的中断: 1.IRQ number.CPU需要为每一个外设中断编号,我们称之IRQ Number.这个IRQ number是一个虚拟的interrupt ID,和硬件无关,仅仅是被CPU用来标识一个外设中断. 2.HW interrupt ID.对于interrupt controller而言,它收集了多个外设的interrupt request line并向上传递,因此,interrupt controller需要对

linux kernel的中断子系统之(三):IRQ number和中断描述符

一.前言 本文主要围绕IRQ number和中断描述符(interrupt descriptor)这两个概念描述通用中断处理过程.第二章主要描述基本概念,包括什么是IRQ number,什么是中断描述符等.第三章描述中断描述符数据结构的各个成员.第四章描述了初始化中断描述符相关的接口API.第五章描述中断描述符相关的接口API. 二.基本概念 1.通用中断的代码处理示意图 一个关于通用中断处理的示意图如下: 在linux kernel中,对于每一个外设的IRQ都用struct irq_desc来