文档章节

linux异常体系结构

季风_落地窗
 季风_落地窗
发布于 2014/07/22 08:59
字数 2124
阅读 139
收藏 3

以arm处理器为例, 外部中断和处理器内核异常(soc内部)都属于异常, 异常都是相对于主程序来讲的, 当soc正常执行主程序时, 中断和异常都可以打断它, 依据异常对于主程序所体现出来的"中断"性质可以区分出中断和异常的区别:

异常: 由于soc本身的内核活动产生的, 如当执行主程序时候由于arm soc预取指令/数据而产生异常等, 这个异常来自soc的内核, 所以对于soc内核来说是"同步"的.

中断: 当soc在执行主程序的时候, 各种片上外设和外部中断引脚可以产生一个异常来中断soc当前正在执行的程序, 这个异常信号来自soc的内核以外, 所以对于soc内核来说他是"异步"的


在arm架构的linux系统下, 共使用了5种异常: 1. 未定义指令异常 2. 指令预取中止异常 3. 数据访问中止异常 4. 中断异常 5. swi异常. 

(1) linux中断体系结构

linux内核在(linux)/include/asm-arm/arch-s3c2410/irqs.h 中将所有的中断统一编号, 另外 linux使用一个结构体数组来描述中断, 并且这个数组的下标就是中断号. 每个数组项对应一组中断,

file: (linux)/include/linux/irq.h 
struct irq_desc {
	irq_flow_handler_t	handle_irq;     /* 当前中断的处理函数入口 */
	struct irq_chip		*chip;          /* 底层的硬件访问 */
        ...
	struct irqaction	*action;	/* 用户提供的中断处理函数链表 */
	unsigned int		status;		/* IRQ 状态 */
        ...
	const char		*name;          /* cat /proc/interrupts 显示的中断名称 */
} ____cacheline_internodealigned_in_smp;

其中的 chip 提供了底层的硬件访问:

file: (linux)/include/linux/irq.h
struct irq_chip {
	const char	*name;	                            
	unsigned int	(*startup)(unsigned int irq);    /* 启动中断, 缺省为 "enable" */
	void		(*shutdown)(unsigned int irq);   /* 关闭中断, 缺省为 "disable" */
	void		(*enable)(unsigned int irq);     /* 使能中断, 缺省为 "unmask" */
	void		(*disable)(unsigned int irq);    /* 关闭中断, 缺省为 "mask" */
	void		(*ack)(unsigned int irq);        /* 响应中断, 通常为清除当前中断使得可以接收下一个中断 */
	void		(*mask)(unsigned int irq);       /* 屏蔽中断源 */
	void		(*mask_ack)(unsigned int irq);   /* 屏蔽和响应中断 */
	void		(*unmask)(unsigned int irq);     /* 开启中断源 */
	...
};

另外irqaction结构体为:

file: (linux)/include/linux/interrupt.h 
struct irqaction {
	irq_handler_t handler;    //用户注册的中断处理函数
	unsigned long flags;	  //中断标志
	cpumask_t mask;	          //用于smp
	const char *name;         //用户注册的名字 "cat /proc/interrupts" 可以看到
	void *dev_id;	          //用户传给上面的handler参数,还可以用来区分共享中断
	struct irqaction *next;   //指向下一个irqaction结构
	int irq;                  //中断号
	struct proc_dir_entry *dir;
};

中断执行的流程是: 中断到来时总中断入口函数 asm_do_IRQ() 根据 中断号 找到 irq_desc 结构体数组中的对应项, 并调用 irq_desc 结构体中的 handle_irq() 函数, handle_irq 函数使用 chip 结构体中的函数 清除/屏蔽/重新使能中断, 最后一一调用 action 链表中注册的中断处理函数.

(2) 中断体系结构初始化

在 (linux)/init/main.c 的 start_kernel() 函数中的 trap_init()  / init_IRQ()  来设置异常的处理函数. 

1. trap_init()  和 early_trap_init() 函数 通过下边语句将异常向量复制到 0xffff0000 地址处, 将中断服务函数复制到 0xffff0000 + 0x200地址处

file: (linux)/arch/arm/kernel 
memcpy((void *)vectors, __vectors_start, __vectors_end - __vectors_start);
memcpy((void *)vectors + 0x200, __stubs_start, __stubs_end - __stubs_start);

其中start和end如下:

.globl	__vectors_start
__vectors_start:
	swi	SYS_ERROR0
	b	vector_und + stubs_offset
	ldr	pc, .LCvswi + stubs_offset
	b	vector_pabt + stubs_offset
	b	vector_dabt + stubs_offset
	b	vector_addrexcptn + stubs_offset
	b	vector_irq + stubs_offset
	b	vector_fiq + stubs_offset

	.globl	__vectors_end
__vectors_end:

负责初始化的为 init_IRQ,因其与具体开发板密切相关,所以需要单独列出来.  它用来初始化中断的框架,设置各个中断的默认处理函数. 当发生中断时候进入中断总入口 asm_do_IRQ()  调用init_IRQ()设置的函数

file: (linux)/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_NOPROBE;	
	init_arch_irq();
}

1 for循环中将 irq_desc[] 数组中每一项的状态都设置为 IRQ_NOREQUEST | IRQ_NOPROBE (未请求 | 未探测)

2 init_arch_irq 其实是一个函数指针, 定义如下: 

file: (linux)/arch/arm/kernel/irq.c 
void (*init_arch_irq)(void) __initdata = NULL;

2440移植好的linux系统在启动的时候通过(linux)/arch/arm/kernel/setup.c 的 setup_arch() 获取到 machine_desc 结构体, 然后将 init_arch_irq 这个函数指针初始化为 s3c24xx_init_irq() . 

file: (linux/arch/plat-s3c24xx/irq.c 的 s3c24xx_init_irq)
void __init s3c24xx_init_irq(void)
{
    ...
    /* first, clear all interrupts pending... */
    ...
    /* register the main interrupts */
    ...
    /* setup the cascade irq handlers */
    set_irq_chained_handler(IRQ_EINT4t7, s3c_irq_demux_extint4t7);
    set_irq_chained_handler(IRQ_EINT8t23, s3c_irq_demux_extint8);
    ...
    /* external interrupts */
    //外部中断 0 ~ 3
    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);
    }
    //外部中断4 ~ 23
    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);
    }
    /* register the uart interrupts */
    ...
}

1. 清除所有中断的 pending 位

2. 进一步设置 irq_desc[] 中元素的 chip , handle_irq字段.

set_irq_chip(irqno, &s3c_irqext_chip)  结果是: irq_desc[irqno]. chip = &s3c_irqext_chip; 之后就可以使用 irq_desc[irqno].chip 结构体的成员来设置触发方式/使能中断/禁止中断等

set_irq_handler(irqno, handle_edge_irq) 结果是: irq_desc[irqno].handle_irq = handle_edge_irq; 这是该中断号对应的中断函数入口

set_irq_flags(irqno, IRQF_VALID) 结果是: 消除 irqno 对应的IRQ_NOREQUEST标志, 告诉系统该中断被可申请使用了.中断申请的时候会查看该标志如果设置了表示中断尚不可用

执行完 init_IRQ之后 irq_desc[] 中各个数组项的chip, handle_irq字段都被设置好了.

(3) 用户注册中断处理函数流程

驱动程序使用 request_irq 来向内核注册中断处理函数, request_irq 根据中断号找到 irq_desc[] 数组项, 在数组项的 action 链表中添加一个表项.

file: (linux)/kernel/irq/manage.c
int request_irq(unsigned int irq, irq_handle_t handler, unsinged long irqflags, const char *devname, void *dev_id)
{
    struct irqaction *action;             //创建 irqaction 结构体指针   
    ...
    action = kmalloc(sizeof(struct irqaction), GFP_ATOMIC);//填充 irqaction 结构体各个元素
    action->handler = handler;    
    action->flags = irqflags;
    cpus_clear(action->mask);
    action->name = devname;
    action->next = NULL;
    action->dev_id = dev_id;
    
    retval = setup_irq(irq, action);      
    ...
}

setup_irq函数有几个功能: 

1. 将 action 连接入 irq_desc[irq]的action链表中

2. irq_desc[].chip 结构体中没有被 init_irq() 设置的元素设置为默认值

3. 使用传入的 irqflags 作为参数 调用 chip->set_type来设置中断: 设置为外部中断, 设置触发方式(高电平/低电平/上升沿/下降沿)

4. 启动中断, 调用chip->startup 或 chip->enable 使能中断, 

总之, 调用了request_irq之后: irq_desc[]的action结构体已链入action链表, 中断触发方式也设置好了, 中断已被使能. 现在中断已经可以发生并处理了

(4) 中断具体的执行流程

中断的总入口是 asm_do_IRQ()

file: (linux)/arch/arm/kernel/irq.c 
asmlinkage void __exception asm_do_IRQ(unsigned int irq, struct pt_regs *regs){
        ...
	struct irq_desc *desc = irq_desc + irq;
        ...
        desc_handle_irq(irq, desc);
        ...
}

file: (linux)/include/asm-arm/mach/irq.h
static inline void desc_handle_irq(unsigned int irq, struct irq_desc *desc){
	desc->handle_irq(irq, desc);
}

irq 取值范围: IRQ0_EINT0~(IRQ_EINT0+31).  因为2440的INTPND共32bit, 但是irq_desc[]却远不止32个元素, 因为有些个中断是共用同一个位, 如 EINT8~23. 所以当 asm_do_IRQ 的 irq 是对应于"一组"子中断的时候, irq_desc[irq].handle_irq 函数还需要判断出到底是哪个子中断申请的中断, 假设该子中断号为irqno 那么继续调用 irq_decs[irqno].handle_irq 来处理

  以外部中断EINT8~23为例来讲解中断调用的详细过程:

1 当EINT8~23 中任意一个中断触发的时候, INTOFFSET 寄存器的值都是 5.  asm_do_IRQ 的 irq 就是IRQ_EINT0+5 即 IRQ_EINT8t23, 然后调用 irq_desc[IRQ_EINT0t23].handle_irq 来处理

2 irq_desc[IRQ_EINT8t23].handle_irq 设置是在中断初始化的时候完成的: init_IRQ -> init_arch_irq() 该函数指针在linux启动的时候被赋值为 s3c24xx_init_irq ->

file: (linux)/arch/plat-s3c24xx/irq.c
    set_irq_chained_handler(IRQ_EINT4t7, s3c_irq_demux_extint4t7);
    set_irq_chained_handler(IRQ_EINT8t23, s3c_irq_demux_extint8); //irq_desc[IRQ_EINT8t23].handle_irq 设置为 s3c_irq_demux_extint8
    set_irq_chained_handler(IRQ_UART0, s3c_irq_demux_uart0);
    set_irq_chained_handler(IRQ_UART1, s3c_irq_demux_uart1);
    set_irq_chained_handler(IRQ_UART2, s3c_irq_demux_uart2);
    set_irq_chained_handler(IRQ_ADCPARENT, s3c_irq_demux_adc);

接下来看看 s3c_irq_demux_extint8 做了些什么

static void s3c_irq_demux_extint8(unsigned int irq, struct irq_desc *desc){
	unsigned long eintpnd = __raw_readl(S3C24XX_EINTPEND);
	unsigned long eintmsk = __raw_readl(S3C24XX_EINTMASK);

	eintpnd &= ~eintmsk;
	eintpnd &= ~0xff;	/* ignore lower irqs */

	/* we may as well handle all the pending IRQs here */
	while (eintpnd) {
		irq = __ffs(eintpnd);
		eintpnd &= ~(1<<irq);

		irq += (IRQ_EINT4 - 4);
		desc_handle_irq(irq, irq_desc + irq);
	}
}

他首先读取了EINTPEND/EINTMASK寄存器, 查找出具体发生的中断源, 然后重新计算中断号, 最后使用新计算出的这个中断号调用 irq_desc[新中断号].handle_irq

 s3c_irq_demux_extint8 与 handle_edge_irq / handle_level_irq 是什么关系?

s3c_irq_demux_extint8最后调用 irq_desc[新中断号].handle_irq 这个入口函数就是在 s3c24xx_init_irq 中定义的中断入口函数 set_irq_handler(irqno, handle_edge_irq); 中的 handle_edge_irq 



本文转载自:http://hi.baidu.com/sun_yfs/item/0402824d8dec900ce83504a6

季风_落地窗
粉丝 4
博文 16
码字总数 16829
作品 0
杨浦
程序员
私信 提问
迅为4412开发板Linux驱动教程之内核开发基础

视频教程:http://v.youku.com/v_show/id_XMTMwNjAwMDc0OA==.html 主要内容 • Linux体系结构 • Linux内核结构 • Linux内核源码目录结构 Linux体系结构 从上图可知,Linux体系结构由用户空...

topeet
2015/08/10
303
0
Linux 内核剖析

简介: Linux® 内核是一个庞大而复杂的操作系统的核心,不过尽管庞大,但是却采用子系统和分层的概念很好地进行了组织。在本文中,您将探索 Linux 内核的总体结构,并学习一些主要的子系统和...

晨曦之光
2012/03/02
203
0
字符设备驱动程序之中断方式的按键驱动_Linux异常处理结构(六)

字符设备驱动程序之中断方式的按键驱动_Linux异常处理结构 中断方式获取按键值(单片机) 1、有按键按下 2、CPU发生中断 强制调到异常向量入口执行(中断是异常的一种) 3、入口函数 跳转指令...

xiaodingqq
2018/05/12
0
0
笔试面试—Linux操作系统

1. Linux 架构 2. 常见名词: Bootloader:在操作系统内核运行之前运行的一段小程序。通过这段小程序,我们可以初始化硬件设备、建立内存空间的映射图,从而将系统的软硬件环境带到一个合适的状...

晨曦之光
2012/03/09
280
0
Linux 内核剖析

简介: Linux® 内核是一个庞大而复杂的操作系统的核心,不过尽管庞大,但是却采用子系统和分层的概念很好地进行了组织。在本文中,您将探索 Linux 内核的总体结构,并学习一些主要的子系统和...

长平狐
2013/01/06
148
0

没有更多内容

加载失败,请刷新页面

加载更多

领域驱动中的“贫血症和失忆症” --实践领域驱动--原文

贫血症严重危害着人类健康,并且伴随有危险的副作用。当贫血领域对象被首次提出来时,它并不是一个博得赞美的词汇,它描述的是一个缺少内在行为领域对象。奇怪的是,人们对于贫血领域对象的态...

还仙
24分钟前
5
0
条码打印软件中标签预览正常打印无反应怎么解决

在使用条码打印软件制作标签时,有客户反馈,标签打印预览正常的,但是打印无反应,咨询是怎么回事?今天针对这个情况,可以参考以下方法进行解决。 一、预览正常情况下,打印没反应 (1)在条码...

中琅软件
34分钟前
5
0
判断字符串的时候

判断字符串的时候一定把常量房前边, //报警程度 String leve = vo.getDeviceAlertDeal().getWarnLevel(); if(("0").equals(leve)) { row.add("无报警"); }else if(("1").equals(leve)) { ro......

简小姐
34分钟前
7
0
Linux maven3.6.2 install

PS:安装 maven 之前请先安装 jdk 1.安装 wget 命令(安装过就不用了) yum -y install wget 2.寻找需要的 maven 版本 https://maven.apache.org/download.cgi 3.进入 /var/local 文件夹 cd...

东方神祇
36分钟前
5
0
Tomcat源码分析二:先看看Tomcat的整体架构

Tomcat源码分析二:先看看Tomcat的整体架构 Tomcat架构图 我们先来看一张比较经典的Tomcat架构图: 从这张图中,我们可以看出Tomcat中含有Server、Service、Connector、Container等组件,接下...

flygrk
39分钟前
6
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部