异常体系比较复杂,但是linux已经准备了很多的函数和框架,但是因为中断是和具体的开发板相关,所以中断需要我们自己来处理一些方面,但是这也是很少的一部分,很多公用的处理函数内核已经实现,linux内核搭建了一个非常容易扩充的中断处理体系。

中 断系统结构涉及的方面很多,而且分布在很多的函数中,这里我主要理清一些结构和流程顺序已经在哪些函数中实现,我不知道其他人怎么样?但是我自己一开始怎 是找不到linux内核是怎么把GPIO设置成中断的,我找了很久都找不到,还有我们很多的设置,初始化等等东西好像都没有实现,清除中断寄存器也不知道 是怎么实现的,只是知道使用中断,差不多用request_irq函数就差不多了。下面我就把这些涉及的方方面面的流程整理出来。

一、重要结构

/include/linux/irq.h

irq_desc 内核中记录一个irq_desc的数组,数组的每一项对应一个中断或者一组中断使用同一个中断号,一句话irq_desc几乎记录所有中断相关的东西,这个结构是中断的核心。其中包括俩个重要的结构irq_chip 和irqaction 。

irq_chip  里面基本上是一些回调函数,其中大多用于操作底层硬件,设置寄存器,其中包括设置GPIO为中断输入就是其中的一个回调函数,分析一些源代码

/include/linux/irq.h

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); 设置中断类型,其中包括设置GPIO口为中断输入

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;

};

我们可以看到这里实现的是一个框架,需要我们进一步的填充里面的函数。我们在分析另一个结构irqaction

include/linux/interrupt.h

struct irqaction {

irq_handler_t handler;  用户注册的中断处理函数

unsigned long flags;    中断标识

const char *name;       用户注册的中断名字,cat/proc/interrupts时可以看到

void *dev_id;           可以是用户传递的参数或者用来区分共享中断

struct irqaction *next; irqaction结构链,一个共享中断可以有多个中断处理函数

int irq;                中断号

struct proc_dir_entry *dir;

irq_handler_t thread_fn;

struct task_struct *thread;

unsigned long thread_flags;

};

我们用irq_request函数注册中断时,主要做俩个事情,根据中断号生成一个irqaction结构并添加到irq_desc中的 action结构链表,另一发面做一些初始化的工作,其中包括设置中断触发方式,设置一些irq_chip结构中没有初始化的函数为默认,开启中断,设置 GPIO口为中断输入模式(这里后面有详细流程分析)。

二、中断流程

整个中断可以分为几个大的流程

1. 中断初始化流程  注意这个阶段就是我非常迷惑的一个阶段,很多初始化,设置寄存器等等问题都是内核启动的时候就已经初始化了,这个阶段做很多工作,其中最重要的就是初始化 了irq_chip结构。使得其中的众多函数已经设置好了,可以被调用了。注意这里只是实现了irq_chip结构的函数,要响应中断还有很多事情要做。

2. 中断注册流程    这个流程是我们比较熟悉的,因为我们每次用中断的时候就是注册一个中断函数。request_irq首先生成一个irqaction结构,其次根据中断号 找到irq_desc数组项(还记得吧,内核中irq_desc是一个数组,没一项对应一个中断号),然后将irqaction结构添加到 irq_desc中的action链表中。当然还做一些其他的工作,注册完成后,中断函数就可以发生并被处理了。

3.中断的处理流程     这个流程主要是产生中断后调用irq_chip中的函数来屏蔽,清除中断等等,然后调用irqaction结构中用户注册的中断函数处理中断,当然还有很多其他的事情要做,不过主要流程是这样。

中断流程应该还包括中断卸载,不过内容比较简单这里就不过啰唆了,下面我们俩详细分析一下这些具体的流程。

<一>中断初始化流程

下面内容转载他人博客,原文地址  http://blog.chinaunix.net/space.php?uid=15193587&do=blog&cuid=2194431

下面我们分析内核中断初始化的过程以及如何调用到一个新平台的irq初始化函数。
这里我们以s3c2410平台为例,他的中断初始化函数定义在:

/* arch/arm/mach-s3c2410/irq.c */
void __init s3c24xx_init_irq(void)
{
……
}

在arch/arm/mach-s3c2410/mach-smdk2410.c内通过MACHINE_START宏将s3c24xx_init_irq赋值给mach_desc结构体的.init_irq成员。

MACHINE_START(SMDK2410,"SMDK2410")/* @TODO: request
a new identifier and switch
        * to SMDK2410 */
 /*
Maintainer: Jonas Dietsche */
 .phys_io = S3C2410_PA_UART,
 .io_pg_offst =(((u32)S3C24XX_VA_UART)>> 18)& 0xfffc,
 .boot_params = S3C2410_SDRAM_PA+ 0x100,
 .map_io = smdk2410_map_io,
 .init_irq = s3c24xx_init_irq,
 .init_machine = smdk_machine_init,
 .timer = &s3c24xx_timer,
MACHINE_END

注:MACHINE_START宏的作用是对mach_desc结构体进行初始化。mach_desc里定义了一些关键的体系架构相关的函数。Porting kernel到新平台时,这个结构体是非常关键的。

init_irq这个成员在系统初始化的时候会被赋值给init_arch_irq全局变量,如下:

/* arch/arm/kernel/setup.c */
void __init setup_arch(char**cmdline_p)
{
 ……
 cpu_init();
 /*
  * Set up various architecture-specific pointers
  */
 init_arch_irq = mdesc->init_irq;
 system_timer = mdesc->timer;
 init_machine = mdesc->init_machine;
 ……
}

注: 可以看到这里不仅初始化了init_arch_irq 全局变量,同时初始化了system_timer,init_machine等全局变量。这是kernel支持多平台的一种机制。当然这里 system_timer和init_machine我不多描述,有兴趣的可以大家自己去看。机制和init_arch_irq大同小异。

init_arch_irq函数指针定义在体系架构无关的arch/arm/kernel/irq.c内
/* arch/arm/kernel/irq.c */
void (*init_arch_irq)(void) __initdata = NULL;

并且在init_IRQ函数内会去执行它。

/* arch/arm/kernel/irq.c */
void __init init_IRQ(void)
{
 int irq;
 for (irq = 0; irq < NR_IRQS; irq++)
  irq_desc[irq].status|= IRQ_NOREQUEST| IRQ_DELAYED_DISABLE|
   IRQ_NOPROBE;
#ifdef CONFIG_SMP
 bad_irq_desc.affinity
= CPU_MASK_ALL;
 bad_irq_desc.cpu
= smp_processor_id();
#endif
 init_arch_irq();
}

那init_IRQ在哪里被调用呢? 我们猜想肯定是在系统初始化的时会调用它。

实际结果也正是,init_IRQ会在init/main.c里的start_kernel函数内被调用:
asmlinkage void __init start_kernel(void)
{
 ……
 trap_init();
 rcu_init();
 init_IRQ();
 pidhash_init();
 clockevents_init();
 init_timers();
 ……
}

上面转载的文章分析的很好,我也是看了他的问题才找到一些答案的,比如init_arch_irq 函数指针,说明的是这是个全局的函数指针,跟体系结构相关。我接着上面的继续分析一下s3c24xx_init_irq()这个函数

这个函数比较长,主要是为每一个中断号都要进行设置,主要完成几个方面的工作

1.清楚中断标志

/* first, clear all interrupts pending... */

last = 0;

for (i = 0;
i < 4; i++) {

pend
= __raw_readl(S3C24XX_EINTPEND);

if
(pend == 0 || pend == last)

break;

__raw_writel(pend,
S3C24XX_EINTPEND);

printk("irq:
clearing pending ext status %08x\n", (int)pend);

last
= pend;

}

这里有个问题没有弄明白,要是有人知道很感谢告诉我一声,就是上面的for循环,循环清除中断标识,而且设置了一个last变量,不知道是不是为了防止一次擦除中断失败。

2. 设置irq_chip结构,中断的处理函数入口和中断标识

for (irqno = IRQ_EINT4; irqno <= IRQ_EINT23; irqno++)
{

irqdbf("registering
irq %d (extended s3c irq)\n", irqno);

set_irq_chip(irqno,
&s3c_irqext_chip);

set_irq_handler(irqno,
handle_edge_irq);

set_irq_flags(irqno,
IRQF_VALID);

}

首先是填充irq_chip结构,然后是设置中断的处理函数入口,这个响应中断的时候就通过函数入口调用用户注册的中断处理函数,中断标识设置为可以使用。

<二>中断注册处理流程

requesrt_irq函数

request_irq(unsigned int irq, irq_handler_t handler,
unsigned long flags,

const char *name, void *dev)

{

return
request_threaded_irq(irq, handler, NULL, flags, name, dev);

}

里面就一个request_threaded_irq函数,参数的话irq为中断号,handler为用户的中断函数,flags中断标 识,name中断名称,dev可以是传递的参数可以是作为区分共享中断。对应irqaction结构中的dev_id,其实request_irq主要是 把这些参数来构造一个irqaction结构添加到链表中。

继续看request_threaded_irq函数

/kernel/irq/manage.c

action = kzalloc(sizeof(struct irqaction), GFP_KERNEL);

if (!action)

return
-ENOMEM;

action->handler
= handler;

action->thread_fn
= thread_fn;

action->flags
= irqflags;

action->name
= devname;

action->dev_id
= dev_id;

chip_bus_lock(irq,
desc);

retval =
__setup_irq(irq, desc, action);

chip_bus_sync_unlock(irq,
desc);

代码比较多,但是主要是利用用户传递的参数构造一个irqaction结构,并调用__setup_irq函数

__setup_irq内容比较多,主要完成几个方面的工作

1.添加irqaction结构到irq_desc的action链表中,需要判断是否为共享中断,只有共享中断可以添加多个中断处理函数,如果是共享中断,则要检查中断处理函数是否和链表中其他函数的触发方式等是否相同,只有一致才可以添加到链表中。

2.设置一些irq_chip结构中的函数指针指向默认函数

3.设置中断的触发方式和启动中断

完成request_irq后中断就可以被接收和处理了。下面我在整理一下设置GPIO口为中断输入的流程

request_irq() --> request_threaded_irq -->__setup_irq() -->
__irq_set_trigger() (定义在kernel/irq/manage.c里) -->set_type

还 有印象没,set_type是irq_chip结构中的一个回调函数,在arch/arm/plat-s3c24xx/irq.c具体的中断的 irq_chip结构中set_type即对应s3c_irqext_type() (定义在arch/arm/plat-s3c24xx/irq.c里),看一个外部中断的代码

arch/arm/plat-s3c24xx/irq.c

static struct irq_chip s3c_irqext_chip = {

.name          = "s3c-ext",

.mask          = s3c_irqext_mask,

.unmask        = s3c_irqext_unmask,

.ack           = s3c_irqext_ack,

.set_type      = s3c_irqext_type,

.set_wake      = s3c_irqext_wake

};

再看s3c_irqext_type() 函数,其中就设置了GPIO端口为中断输入模式

if ((irq
>= IRQ_EINT0) && (irq <= IRQ_EINT3))

{

gpcon_reg
= S3C2410_GPFCON;

extint_reg
= S3C24XX_EXTINT0;

gpcon_offset
= (irq - IRQ_EINT0) * 2;

extint_offset
= (irq - IRQ_EINT0) * 4;

}

<三>中断的处理流程

中断处理流程又是一个比较复杂的过程,要牵涉到ARM的工作模式,异常,异常向量,还有一堆汇编代码。这些我不怎么懂,我只是看了其他一些人的分析,大概整个流程出来吧。

首先异常向量表,保存一条跳转指令,一般存放在0x00000000或者0xffff000地址,linux使用后者,中断发生后CPU进入异常模式,将跳转到相应的异常向量表处执行,异常向量表保存跳转指令,经过一段汇编,最后跳转到中断的入口asm_do_IRQ

arch/arm/kernel/irq.c

asmlinkage void __exception asm_do_IRQ(unsigned int irq,
struct pt_regs *regs)

{

struct
pt_regs *old_regs = set_irq_regs(regs);

irq_enter();

/*

* Some hardware gives randomly wrong
interrupts.  Rather

* than crashing, do something sensible.

*/

if
(unlikely(irq >= NR_IRQS)) {

if
(printk_ratelimit())

printk(KERN_WARNING
"Bad IRQ%u\n", irq);

ack_bad_irq(irq);

} else {

generic_handle_irq(irq);

}

/* AT91 specific
workaround */

irq_finish(irq);

irq_exit();

set_irq_regs(old_regs);

}

主要调用generic_handle_irq(irq)

include/linux/irq.h

static inline void generic_handle_irq_desc(unsigned int
irq, struct irq_desc *desc)

{

#ifdef CONFIG_GENERIC_HARDIRQS_NO__DO_IRQ

desc->handle_irq(irq,
desc);

#else

if
(likely(desc->handle_irq))

desc->handle_irq(irq,
desc);

else

__do_IRQ(irq);

#endif

}

static inline void generic_handle_irq(unsigned int irq)

{

generic_handle_irq_desc(irq,
irq_to_desc(irq));

}

generic_handle_irq调用前面定义的generic_handle_irq_desc,而
generic_handle_irq_desc也没做什么,调用desc——>handle_irq,这个函数就是irq_desc中的成员,我 们在s3c24xx_init_irq()中不是设置了一个中断函数入口的吗?(set_irq_handler() ),这里就用到了。
这里调用desc->handle_irq分为俩种情况,一是单独的中断号的,一是共享中断号的,俩者的区别在于后者需要先判断是共享中断的中的哪一个然后再真正的去调用handle_irq,所以我这里分析一下单独中断号的处理流程,共享中断也是一样可以分析。

我们在回到s3c24xx_init_irq()中,分析一个具体的,以外部中断为例

for (irqno =
IRQ_EINT0; irqno <= IRQ_EINT3; irqno++) {

irqdbf("registering
irq %d (ext int)\n", irqno);

set_irq_chip(irqno,
&s3c_irq_eint0t4);

set_irq_handler(irqno,
handle_edge_irq);

set_irq_flags(irqno,
IRQF_VALID);

}

上面代码我们看到,set_irq_handler的值是handler_edge_irq
,这里是处理边沿触发的中断函数,当然还有电平触发方式的中断(handler_level_irq),继续看代码

kernel/irq/chip.c

void

handle_edge_irq(unsigned int irq, struct irq_desc *desc)

{

spin_lock(&desc->lock);         上锁

desc->status
&= ~(IRQ_REPLAY | IRQ_WAITING);

/*

* If we're currently running this IRQ, or its
disabled,

* we shouldn't process the IRQ. Mark it
pending, handle

* the necessary masking and go out

*/

if
(unlikely((desc->status & (IRQ_INPROGRESS | IRQ_DISABLED)) ||   判断

!desc->action)) {

desc->status
|= (IRQ_PENDING | IRQ_MASKED);

mask_ack_irq(desc,
irq);   屏蔽并清除中断

goto
out_unlock;

}

kstat_incr_irqs_this_cpu(irq,
desc); 中断统计计数

/* Start
handling the irq */

if (desc->chip->ack)      应答中断

desc->chip->ack(irq);

/* Mark the
IRQ currently in progress.*/

desc->status
|= IRQ_INPROGRESS;   标记中断状态

do {

struct
irqaction *action = desc->action;

irqreturn_t
action_ret;

if
(unlikely(!action)) {

desc->chip->mask(irq);

goto
out_unlock;

}

/*

* When another irq arrived while we were
handling

* one, we could have masked the irq.

* Renable it, if it was not disabled in
meantime.

*/

if
(unlikely((desc->status &

(IRQ_PENDING | IRQ_MASKED |
IRQ_DISABLED)) ==

(IRQ_PENDING | IRQ_MASKED))) {

desc->chip->unmask(irq);

desc->status
&= ~IRQ_MASKED;

}

desc->status
&= ~IRQ_PENDING;

spin_unlock(&desc->lock);

action_ret
= handle_IRQ_event(irq, action);  处理中断,最重要的函数,注意参数,action这个参数将联系到我们的用户中断处理函数

if
(!noirqdebug)

note_interrupt(irq,
desc, action_ret);

spin_lock(&desc->lock);

} while
((desc->status & (IRQ_PENDING | IRQ_DISABLED)) == IRQ_PENDING);

desc->status
&= ~IRQ_INPROGRESS;

out_unlock:

spin_unlock(&desc->lock);

}

进行追踪handle_IRQ_event()

kernel/irq/handle.c

trace_irq_handler_entry(irq,
action);

ret =
action->handler(irq, action->dev_id);

trace_irq_handler_exit(irq,
action, ret);

调用action中的handler,我们注册中断的时候注意任务就是构造一个irqaction结构并添加到irq_desc中的irqaction链表中的指针action下面。现在处理中断中我们就看到了调用了我们自己的中断处理函数来处理中断了。

至此中断处理流程就结束了,如有不足之处,或者不对的地方,欢迎大家指出,转载请标明出处。

2011-09-23 19:48

linux中断流程详解的更多相关文章

  1. Linux启动流程详解【转载】

    在BIOS阶段,计算机的行为基本上被写死了,可以做的事情并不多:一般就是通电.BIOS.主引导记录.操作系统这四步.所以我们一般认为加载内核是linux启动流程的第一步. 第一步.加载内核 操作系统接 ...

  2. Linux启动流程详解

    在BIOS阶段,计算机的行为基本上被写死了,可以做的事情并不多:一般就是通电.BIOS.主引导记录.操作系统这四步.所以我们一般认为加载内核是linux启动流程的第一步. 第一步.加载内核 操作系统接 ...

  3. 驱动: 中断【1】linux中断流程

    通常情况下,当一个给定的中断处理程序正在执行时,所有其他的中断都是打开的,所以这些不同中断线上的其他中断都能被处理,但当前中断总是被禁止的. 将中断处理切为两个部分或两半.中断处理程序上半部(top ...

  4. linux驱动由浅入深系列:高通sensor架构实例分析之三(adsp上报数据详解、校准流程详解)【转】

    本文转载自:https://blog.csdn.net/radianceblau/article/details/76180915 本系列导航: linux驱动由浅入深系列:高通sensor架构实例分 ...

  5. 迅为4412开发板Linux驱动教程——总线_设备_驱动注册流程详解

    本文转自:http://www.topeetboard.com 视频下载地址: 驱动注册:http://pan.baidu.com/s/1i34HcDB 设备注册:http://pan.baidu.c ...

  6. linux awk命令详解

    linux awk命令详解 简介 awk是一个强大的文本分析工具,相对于grep的查找,sed的编辑,awk在其对数据分析并生成报告时,显得尤为强大.简单来说awk就是把文件逐行的读入,以空格为默认分 ...

  7. Linux /dev目录详解和Linux系统各个目录的作用

    Linux /dev目录详解(转http://blog.csdn.net/maopig/article/details/7195048) 在linux下,/dev目录是很重要的,各种设备都在下面.下面 ...

  8. [nRF51822] 5、 霸屏了——详解nRF51 SDK中的GPIOTE(从GPIO电平变化到产生中断事件的流程详解)

    :由于在大多数情况下GPIO的状态变化都会触发应用程序执行一些动作.为了方便nRF51官方把该流程封装成了GPIOTE,全称:The GPIO Tasks and Events (GPIOTE) . ...

  9. linux lsof命令详解

    linux lsof命令详解 简介 lsof(list open files)是一个列出当前系统打开文件的工具.在linux环境下,任何事物都以文件的形式存在,通过文件不仅仅可以访问常规数据,还可以访 ...

随机推荐

  1. 查询 IDE 的版本 方便安装第三方的时候选择

    TMD 很多第三方软件 的版本 定义 与 delphi是不一致的. 1.有的是以这个为准.

  2. z-index失效的情况

    1.父标签 position属性为relative: 2.问题标签无position属性(不包括static): 3.问题标签含有浮动(float)属性. 4.问题标签的祖先标签的z-index值比较 ...

  3. LeetCode解题报告—— Unique Binary Search Trees & Binary Tree Level Order Traversal & Binary Tree Zigzag Level Order Traversal

    1. Unique Binary Search Trees Given n, how many structurally unique BST's (binary search trees) that ...

  4. Oracle中的case when then else end 应用

    Case when 的用法,简单Case函数 简单CASE表达式,使用表达式确定返回值. 语法: CASE search_expression WHEN expression1 THEN result ...

  5. Distinct Subsequences ——动态规划

    Given a string S and a string T, count the number of distinct subsequences of T in S. A subsequence ...

  6. Linux下使进程在后台运行

    怎么样使程序在后台执行 ///////////////////  nohup  ./nn > nn.log  2 > &1  &   //////////// 方法有很多, ...

  7. 论文笔记-SPP_NET中提到的金字塔

    时隔这么久终于考完试放假了,现在终于有时间开始研究spp net的相关内容了,看了几篇网上的博客,发现看完之后还是不是很懂,于是乎下载了spp net的原始论文<Spatial Pyramid ...

  8. java 的Calendar类的可视化日历示例

    import java.text.DateFormat; import java.text.ParseException; import java.text.SimpleDateFormat; imp ...

  9. python之并发编程(线程\进程\协程)

    一.进程和线程 1.进程 假如有两个程序A和B,程序A在执行到一半的过程中,需要读取大量的数据输入(I/O操作),而此时CPU只能静静地等待任务A读取完数据才能继续执行,这样就白白浪费了CPU资源.是 ...

  10. Web会话安全分析工具ProxyStrike

    Web会话安全分析工具ProxyStrike   在Web应用中,客户端发出一次请求,服务器响应一次.这构成一个完整的会话.通过分析请求和响应的数据,可以发现Web应用存在的漏洞.Kali Linux ...