一、linux 异常结构
1、start_kernel 会调用trap_init 和init_IRQ设置异常处理函数
2、trap_init (位于arch/arm/kernel/traps.c中),linux异常向量表位于0xffff_0000,异常处理程序位于0xffff_0000+0x200(一级处理)
void __init trap_init(void)
{
unsigned long vectors = CONFIG_VECTORS_BASE;
extern char __stubs_start[], __stubs_end[];
extern char __vectors_start[], __vectors_end[];
extern char __kuser_helper_start[], __kuser_helper_end[];
int kuser_sz = __kuser_helper_end - __kuser_helper_start;
/*
* Copy the vectors, stubs and kuser helpers (in entry-armv.S)
* into the vector page, mapped at 0xffff0000, and ensure these
* are visible to the instruction stream.
*/
memcpy((void *)vectors, __vectors_start, __vectors_end - __vectors_start);
memcpy((void *)vectors + 0x200, __stubs_start, __stubs_end - __stubs_start);
memcpy((void *)vectors + 0x1000 - kuser_sz, __kuser_helper_start, kuser_sz);
/*
* Copy signal return handlers into the vector page, and
* set sigreturn to be a pointer to these.
*/
memcpy((void *)KERN_SIGRETURN_CODE, sigreturn_codes,
sizeof(sigreturn_codes));
flush_icache_range(vectors, vectors + PAGE_SIZE);
modify_domain(DOMAIN_USER, DOMAIN_CLIENT);
}
3、异常向量、其跳去执行的代码位于arch/arm/kernel/entry-armv.S
//异常向量表,如发生未定义异常,则执行b vector_und + stubs_offset
.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:
//异常处理(一级处理),如未定义异常,确定是那种模式下的异常,ARM有7中模式,4位表示没有用到的定义为__und_invalid
vector_stub und, UND_MODE
.long __und_usr @ 0 (USR_26 / USR_32)
.long __und_invalid @ 1 (FIQ_26 / FIQ_32)
.long __und_invalid @ 2 (IRQ_26 / IRQ_32)
.long __und_svc @ 3 (SVC_26 / SVC_32)
.long __und_invalid @ 4
.long __und_invalid @ 5
.long __und_invalid @ 6
.long __und_invalid @ 7
.long __und_invalid @ 8
.long __und_invalid @ 9
.long __und_invalid @ a
.long __und_invalid @ b
.long __und_invalid @ c
.long __und_invalid @ d
.long __und_invalid @ e
.long __und_invalid @ f
3、各种异常处理具体代码(二级处理)
(1)do_undefinstr (arch/arm/kernel/traps.c)
(2)do_DataAbort, do_PerfetchAbort (arch/arm/mm/fault.c)
(3)中断处理函数总入口asm_do_IRQ (arch/arm/mm/irq.c)
(4)swi处理函数指针被处理成一个表格,swi指令机器码位[23:0]作为索引,处理函数为系统调用以sys开头,如sys_open ……。swi处理与具体开发板无关
二、linux中断体系
1、数据结构
Linux内核把所有中断统一编号,使用irq_desc (include/linux/irq.h)结构数组描述
struct irq_desc {
//这个或这组(可能多个中断共享一个中断号)中断处理函数的入口。发生中断时总入口为asm_do_IRQ 再根据中断号调用相应的handle_irq
irq_flow_handler_t handle_irq;
//结构chip中存放清除、使能中断的函数
struct irq_chip *chip;
struct msi_desc *msi_desc;
void *handler_data;
void *chip_data;
第二级中断处理函数组成的链表(如果有多个的话)
struct irqaction *action; /* IRQ action list */
unsigned int status; /* IRQ status */
unsigned int depth; /* nested irq disables */
unsigned int wake_depth; /* nested wake enables */
unsigned int irq_count; /* For detecting broken IRQs */
unsigned int irqs_unhandled;
spinlock_t lock;
#ifdef CONFIG_SMP
cpumask_t affinity;
unsigned int cpu;
#endif
#if defined(CONFIG_GENERIC_PENDING_IRQ) || defined(CONFIG_IRQBALANCE)
cpumask_t pending_mask;
#endif
#ifdef CONFIG_PROC_FS
struct proc_dir_entry *dir;
#endif
const char *name;
} ____cacheline_internodealigned_in_smp;
2、中断处理流程
(1)发生中断,CPU执行一次向量vector_irq中代码
(2)一些操作后,调用中断总入口asm_do_IRQ
(3)根据中断号调用irq_desc 结构中的 handle_irq
(4)handle_irq 使用chip里面函数设置硬件
(5)handle_irq 逐个调用用户在action中注册的处理函数。
所以中断初始化就是构造数据结构:irq_desc数组中的 handle_irq 、chip;注册中断就是构造action 链表,卸载中断就是去除action 中的项
3、代码分析
(1)开发板中断初始化
void __init init_IRQ(void)
{
int irq;
for (irq = 0; irq < NR_IRQS; irq++)
irq_desc[irq].status |= IRQ_NOREQUEST | IRQ_NOPROBE;
#ifdef CONFIG_SMP
bad_irq_desc.affinity = CPU_MASK_ALL;
bad_irq_desc.cpu = smp_processor_id();
#endif
//如,s3c24xx_init_irq就指向init_arch_irq()
init_arch_irq();
}
s3c24xx_init_irq在 arch/arm/plat-s3c24xx/irq.c, 为所有中断设置相关数据结构,如外部中断
for (irqno = IRQ_EINT4; irqno <= IRQ_EINT23; irqno++) {
irqdbf("registering irq %d (extended s3c irq)\n", irqno);
//设置chip成员
set_irq_chip(irqno, &s3c_irqext_chip);
//设置该中断号入口处理函数
set_irq_handler(irqno, handle_edge_irq);
//是否使用
set_irq_flags(irqno, IRQF_VALID);
}
使用request_irq 注册中断处理函数,该函数在kernel/irq/manage.c中定义
//根据中断号找到irq_desc数组项, 在它的action链表中加入一个表项。
int request_irq(unsigned int irq, irq_handler_t handler,
unsigned long irqflags, const char *devname, void *dev_id)
action->handler = handler;
action->flags = irqflags;
cpus_clear(action->mask);
action->name = devname;
action->next = NULL;
action->dev_id = dev_id;
(2)初始化后的实际中断过程
中断C语言总入口
asmlinkage void __exception asm_do_IRQ(unsigned int irq, struct pt_regs *regs)
{
struct pt_regs *old_regs = set_irq_regs(regs);
struct irq_desc *desc = irq_desc + irq;
/*
* Some hardware gives randomly wrong interrupts. Rather
* than crashing, do something sensible.
*/
if (irq >= NR_IRQS)
desc = &bad_irq_desc;
irq_enter();
//根据中断号调用irq_desc相关结构的处理函数
desc_handle_irq(irq, desc);
/* AT91 specific workaround */
irq_finish(irq);
irq_exit();
set_irq_regs(old_regs);
}
//asm_do_IRQ根据中断号调用irq_desc[irq].handle_irq,这是中断处理函数入口。对于电平触发中断通常是handle_level_irq, 边沿触发中断是handle_edge_irq, 它们都会逐个调用用户在irq_desc[irq].action中注册的中断处理函数。
4、例子:按键中断,除了应用程序,其他都在s3c24xx_buttons.c 中
(1)加载和卸载,即从内核中注册或注销
// 执行“insmod s3c24xx_buttons”加载一个模块命令时就会调用这个函数
static int __init s3c24xx_buttons_init(void)
{
int ret;
/* 注册字符设备驱动程序
* 参数为主设备号、设备名字、file_operations结构;
* BUTTON_MAJOR可以设为0,表示由内核自动分配主设备号
*/
ret = register_chrdev(BUTTON_MAJOR, DEVICE_NAME, &s3c24xx_buttons_fops);
if (ret < 0) {
printk(DEVICE_NAME " can't register major number\n");
return ret;
}
printk(DEVICE_NAME " initialized\n");
return 0;
}
/*
* 执行”rmmod s3c24xx_buttons”卸载命令时就会调用这个函数
*/
static void __exit s3c24xx_buttons_exit(void)
{
/* 卸载驱动程序 */
unregister_chrdev(BUTTON_MAJOR, DEVICE_NAME);
}
/* 这两行指定驱动程序的初始化函数和卸载函数 */
module_init(s3c24xx_buttons_init);
module_exit(s3c24xx_buttons_exit);
(2)定义设备描述结构file_operations
static struct file_operations s3c24xx_buttons_fops = {
.owner = THIS_MODULE, /* 这是一个宏,指向编译模块时自动创建的__this_module变量 */
.open = s3c24xx_buttons_open,
.release = s3c24xx_buttons_close,
.read = s3c24xx_buttons_read,
};
(3)open注册中断、 relaese 释放已经注册的中断、 read读按键次数数据
static int s3c24xx_buttons_open(struct inode *inode, struct file *file)
{
int i;
int err;
for (i = 0; i < sizeof(button_irqs)/sizeof(button_irqs[0]); i++) {
// 注册中断处理函数,参数:中断号、中断处理函数、中断触发方式、中断名称、计数数组
err = request_irq(button_irqs[i].irq, buttons_interrupt, button_irqs[i].flags, button_irqs[i].name, (void *)&press_cnt[i]);
if (err)
break;
}
if (err) {
// 释放已经注册的中断
i--;
for (; i >= 0; i--)
free_irq(button_irqs[i].irq, (void *)&press_cnt[i]);
return -EBUSY;
}
return 0;
}
/* 应用程序对设备文件/dev/buttons执行close(...)时,
* 就会调用s3c24xx_buttons_close函数
*/
static int s3c24xx_buttons_close(struct inode *inode, struct file *file)
{
int i;
for (i = 0; i < sizeof(button_irqs)/sizeof(button_irqs[0]); i++) {
// 释放已经注册的中断
free_irq(button_irqs[i].irq, (void *)&press_cnt[i]);
}
return 0;
}
/* 应用程序对设备文件/dev/buttons执行read(...)时,
* 就会调用s3c24xx_buttons_read函数
buff存读到的数据,count是数据长度*/
static int s3c24xx_buttons_read(struct file *filp, char __user *buff,
size_t count, loff_t *offp)
{
unsigned long err;
/* 如果ev_press等于0,休眠 */
wait_event_interruptible(button_waitq, ev_press);
/* 执行到这里时,ev_press等于1,将它清0 */
ev_press = 0;
/* 将按键状态复制给用户,并清0 */
err = copy_to_user(buff, (const void *)press_cnt, min(sizeof(press_cnt), count));
memset((void *)press_cnt, 0, sizeof(press_cnt));
return err ? -EFAULT : 0;
}
(4)中断处理函数,dev_id是前面数组press_cnt,按键按一次对应数据加一
static irqreturn_t buttons_interrupt(int irq, void *dev_id)
{
volatile int *press_cnt = (volatile int *)dev_id;
*press_cnt = *press_cnt + 1; /* 按键计数加1 */
ev_press = 1; /* 表示中断发生了 */
wake_up_interruptible(&button_waitq); /* 唤醒休眠的进程 */
return IRQ_RETVAL(IRQ_HANDLED);
}
(5)把s3c24xx_buttons放到源码目录drivers/char下,在同目录Makefile中加一行
obj-m += s3c24xx_buttons.o。 在内核根目录下执行 make modules 则可在drivers/char下生成s3c24xx_buttons.ko。把其放到开发板根文件系统/lib/modules/2.6.22.6/下就可以使用insmod s3c24xx_buttons 进行加载了。
加载之间先的在开发板中建立设备文件 mknod /dev/buttons c 232 0
cat /proc/devices 可以看到内核中已有的设备
(6)测试文件,输入button_test &让它在后台运行
int main(int argc, char **argv)
{
int i;
int ret;
int fd;
int press_cnt[4];
fd = open("/dev/buttons", 0); // 打开设备
if (fd < 0) {
printf("Can't open /dev/buttons\n");
return -1;
}
// 这是个无限循环,进程有可能在read函数中休眠,当有按键被按下时,它才返回
while (1) {
// 读出按键被按下的次数
ret = read(fd, press_cnt, sizeof(press_cnt));
if (ret < 0) {
printf("read err!\n");
continue;
}
for (i = 0; i < sizeof(press_cnt)/sizeof(press_cnt[0]); i++) {
// 如果被按下的次数不为0,打印出来
if (press_cnt[i])
printf("K%d has been pressed %d times!\n", i+1, press_cnt[i]);
}
}
close(fd);
return 0;
}