在读者学习本章以及后续章节之前,最好拥有中断裸机基础,可以参考:中断编程。
一、内核中断分析
通过裸机系列的学习,我们可以知道异常的触发分为下面几个过程:
1. 在规定地址设置异常向量表
2. 保存各种寄存器的值(保存现场)
2. 执行异常处理函数(处理现场)
3. 恢复执行(恢复现场)
如u-boot中arch/arm/cpu/armv7/start.S中代码:
1 .globl _start 2 _start: b reset 3 ldr pc, _undefined_instruction 4 ldr pc, _software_interrupt 5 ldr pc, _prefetch_abort 6 ldr pc, _data_abort 7 ldr pc, _not_used 8 ldr pc, _irq 9 ldr pc, _fiq 10 11 _undefined_instruction: .word undefined_instruction 12 _software_interrupt: .word software_interrupt 13 _prefetch_abort: .word prefetch_abort 14 _data_abort: .word data_abort 15 _not_used: .word not_used 16 _irq: .word irq 17 _fiq: .word fiq 18 19 ... 20 21 irq: 22 get_irq_stack /* 设置栈 */ 23 irq_save_user_regs /* 保存寄存器的值 */ 24 bl do_irq /* 处理中断 */ 25 irq_restore_user_regs /* 恢复· */ 26 27 .align 5 28 29 ...
Linux的异常处理其实也和裸机中的流程一样,只不过Linux要对所有的异常都进行具体分析处理
Linux内核所做的中断初始化如下,其中b start_kernel代码在arch/arm/kernel/head-common.S中
b start_kernel ... local_irq_disable(); /* 关中断 */ ... setup_arch(&command_line); paging_init(mdesc); devicemaps_init(mdesc); early_trap_init(vectors); /* 设置异常向量表 */ ... trap_init(); /* 空函数 */ ... early_irq_init(); /* 初始化irq_desc数组 */ init_IRQ(); /* 芯片相关的中断的初始化 */ ... local_irq_enable(); /* 开中断 */
1. 首先关闭中断,因为异常向量表还没有设置,如果此时触发中断,程序会跑飞
2. 之后设置异常向量表
a. 申请一块内存,用异常向量表填充这块内存区域
b. 通过异常向量表的虚拟地址找到对应的物理地址,并把这块内存区域再次映射到0xFFFF 0000区域
c. 之后检查内存映射,如果不是高端映射,则映射到0地址(页表会覆盖掉前面的高端映射)
3. 初始化irq_desc数组,这个数组用于存储中断数据,如中断号、中断类型等
4. 芯片相关的中断初始化、开中断
当发生中断时,会跳转到vector_irq + offset的地址执行代码,与裸机相同,代码会进行保存现场、处理现场、恢复现场的操作
处理现场会调用asm_do_IRQ()函数,调用层次如下:
asm_do_IRQ(unsigned int irq, struct pt_regs *regs) -> handle_IRQ(irq, regs); -> generic_handle_irq(irq); -> struct irq_desc *desc = irq_to_desc(irq); /* 将中断号转化为irq_desc数组项 */ -> generic_handle_irq_desc(irq, desc); -> desc->handle_irq(irq, desc); /* 最终调用执行初始化阶段注册的通用函数 */ -> handle_level_irq(unsigned int irq, struct irq_desc *desc); -> handle_irq_event(desc); -> handle_irq_event_percpu(desc, action); -> action->handler(irq, action->dev_id) /* 我们需要实现的驱动函数 */ -> action = action->next; /* 共享中断要执行相同中断号的cation链表的所有中断 */
asm_do_IRQ()函数除此之外,还会清中断,因此我们在中断处理函数中不需要自己清中断
层次结构中的irq_desc有以下几个我们需要关注的成员:
struct irq_desc { struct irq_data irq_data; /* 每个irq和芯片数据传递给芯片功能 */ ... irq_flow_handler_t handle_irq; /* 通用中断处理函数 */ ... struct irqaction *action; /* IRQ action链表 */ ... }
代码中的struct irqaction中含有真正的中断处理函数:
struct irqaction { irq_handler_t handler; /* 中断处理函数 */ unsigned long flags; /* 标志 */ void *dev_id; /* 中断函数传入数据 */ ... int irq; /* 中断号 */ ... }
代码中的中断函数格式和中断标志位如下:
typedef irqreturn_t (*irq_handler_t)(int, void *); ... #define IRQF_TRIGGER_NONE 0x00000000 #define IRQF_TRIGGER_RISING 0x00000001 /* 上升沿触发 */ #define IRQF_TRIGGER_FALLING 0x00000002 /* 下降沿触发 */ #define IRQF_TRIGGER_HIGH 0x00000004 /* 高电平触发 */ #define IRQF_TRIGGER_LOW 0x00000008 /* 低电平触发 */ #define IRQF_TRIGGER_MASK (IRQF_TRIGGER_HIGH | IRQF_TRIGGER_LOW | \ IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING) #define IRQF_TRIGGER_PROBE 0x00000010
因此我们需要做的就是向上注册中断数据和处理函数
在内核中,注册中断函数原型为:
int request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char *name, void *dev)
函数所做的事情有:
1. 分配、设置、注册irqaction
2. 设置中断引脚
3. 使能中断
释放申请的中断函数原型为:
void free_irq(unsigned int, void *);
二、等待队列
在中断编程中,中断通常会和等待队列一起使用。等待队列的作用是防止驱动中断读或写过程浪费CPU利用率。
进程通过执行下面步骤将自己加入到一个等待队列中
1. 定义等待队列头部,如wait_queue_head_t my_queue;
2. 调用init_wait_queue_head()初始化等待队列头部
3. 调用DECLARE_WAITQUEUE()创建一个等待队列的项
4. 调用add_wait_queue()把自己加入到等待队列中,该队列会在进程等待的条件满足时唤醒它。在其他地方写相关代码,在事件发生时,对等的队列执行wake_up()操作
5. 调用set_current_state()将进程状态变更为TASK_INTERRUPTIBLE或TASK_UNINTERRUPTIBLE
6. 如果状态被置为TASK_INTERRUPTIBLE,则使用中断唤醒进程
7. 检查condition是否为真,为真则不休眠,如果为假,则调用scheduled()休眠
8. 当进程被唤醒的时候,它会再次检查条件是否为真。真就退出循环,否则再次调用scheduled()并一直重复这步操作
9. condition满足后,进程设置为TASK_RUNNING并通过remove_wait_queue()退出
需要注意的是,如果驱动程序中的read()和write()函数都实现了休眠功能,那么我们需要在read()被唤醒时调用wake_up(wait_queue_head_t *)系列函数唤醒write()函数
这样是为了防止写进程和读进程相互阻塞
wake_up()系列函数声明有:
/* 唤醒队列 */ wake_up(queue, condition); // condition为0时解除休眠 wake_up_interruptible(queue, condition); /* 等待事件 */ wait_event(queue, condition); // condition为volatile变量,为1时休眠 wait_event_interruptible(queue, condition); wait_event_timeout(queue, condition); wait_event_interruptible_timeout(queue, condition);
代码中wait_event()和wait_event_interrupt()的区别是wait_event_interrupt()设置了TASK_INTERRUPTIBLE标记,使得进程处于可中断(TASK_INTERRUPTIBLE)状态,从而睡眠进程可以通过接收信号被唤醒
本节代码中暂时使用不到wait_event()和wait_event_interrupt()函数
wait_event()和wait_event_interrupt()的差别:
wait_event()不能被Ctrl + C和kill -9命令打断,而wait_event_interrupt()都可以被打断
示例代码如下:
1 static ssize_t gm_read(struct file *filp, char __user *buf, size_t len, loff_t * loff) 2 { 3 struct gm_dev *dev = filp->private_data; 4 int count = len; // 考虑边界条件 5 int ret; 6 7 DECLARE_WAITQUEUE(wait, current); 8 mutex_lock(&dev->lock); 9 add_wait_queue(&dev->r_head, &wait); 10 11 while (dev->current_len == 0) { 12 if (filp->f_flags & O_NONBLOCK) { 13 ret = -EAGAIN; 14 goto out; 15 } 16 17 __set_current_state(TASK_INTERRUPTIBLE); 18 mutex_unlock(&dev->lock); 19 schedule(); 20 21 if (signal_pending(current)) { 22 ret = -ERESTARTSYS; 23 goto out2; 24 } 25 mutex_lock(&dev->lock); 26 } 27 28 if (count > dev->current_len) 29 count = dev->current_len; 30 31 if (copy_to_user(buf, dev->mem, count)) { 32 ret = -EFAULT; 33 goto out; 34 } 35 else { 36 // 把后面的数据放到前面 37 memcpy(dev->mem, dev->mem + count, dev->current_len - count); 38 dev->current_len -= count; 39 ret = count; 40 printk("## GM ## READ %d\n", count); 41 42 // 唤醒可能阻塞的写进程,注意是写进程 43 wake_up_interruptible(&dev->w_head); 44 } 45 46 out: 47 mutex_unlock(&dev->lock); 48 49 out2: 50 remove_wait_queue(&dev->r_head, &wait); 51 set_current_state(TASK_RUNNING); 52 53 return ret; 54 }
有了上面的基础,现在我们可以实现按键中断字符驱动程序
三、按键中断字符驱动程序
key源代码:
1 #include2 #include 3 #include 4 #include 5 #include 6 #include 7 #include 8 #include 9 #include 10 #include 11 #include 12 #include 13 14 #include 15 #include 16 #include 17 18 #include 19 20 #define KEY_MAJOR 255 21 22 struct pin_desc { 23 int gpio; 24 int val; 25 char *name; 26 }; 27 28 struct key_device { 29 struct cdev cdev; 30 wait_queue_head_t r_head; 31 wait_queue_head_t w_head; 32 }; 33 34 static struct pin_desc desc[4] = { 35 { EXYNOS4_GPX3(2), 0x01, "KEY0" }, 36 { EXYNOS4_GPX3(3), 0x02, "KEY1" }, 37 { EXYNOS4_GPX3(4), 0x03, "KEY2" }, 38 { EXYNOS4_GPX3(5), 0x04, "KEY3" }, 39 }; 40 41 static int g_major = KEY_MAJOR; 42 module_param(g_major, int, S_IRUGO); 43 44 static struct key_device* dev; 45 static struct class* scls; 46 static struct device* sdev; 47 static unsigned char key_val; 48 49 static irqreturn_t key_interrupt(int irq, void *dev_id) 50 { 51 struct pin_desc *pindesc = (struct pin_desc *)dev_id; 52 unsigned int tmp; 53 54 tmp = gpio_get_value(pindesc->gpio); 55 56 /* active low */ 57 printk(KERN_DEBUG "KEY %d: %08x\n", pindesc->val, tmp); 58 59 if (tmp) 60 key_val = pindesc->val; 61 else 62 key_val = pindesc->val | 0x80; 63 64 set_current_state(TASK_RUNNING); 65 66 return IRQ_HANDLED; 67 } 68 69 static ssize_t key_read(struct file *filp, char __user *buf, size_t len, loff_t * loff) 70 { 71 struct key_device *dev = filp->private_data; 72 73 // 声明等待队列 74 DECLARE_WAITQUEUE(rwait, current); 75 add_wait_queue(&dev->r_head, &rwait); 76 77 // 休眠 78 __set_current_state(TASK_INTERRUPTIBLE); 79 schedule(); 80 81 // 有数据 82 copy_to_user(buf, &key_val, 1); 83 84 remove_wait_queue(&dev->r_head, &rwait); 85 set_current_state(TASK_RUNNING); 86 87 return 1; 88 } 89 90 static int key_open(struct inode *nodep, struct file *filp) 91 { 92 struct key_device *dev = container_of(nodep->i_cdev, struct key_device, cdev); 93 // 放入私有数据中 94 filp->private_data = dev; 95 96 int irq; 97 int i, err = 0; 98 99 for (i = 0; i < ARRAY_SIZE(desc); i++) { 100 if (!desc[i].gpio) 101 continue; 102 103 irq = gpio_to_irq(desc[i].gpio); 104 err = request_irq(irq, key_interrupt, IRQ_TYPE_EDGE_BOTH, 105 desc[i].name, (void *)&desc[i]); 106 if (err) 107 break; 108 } 109 110 if (err) { 111 i--; 112 for (; i >= 0; i--) { 113 if (!desc[i].gpio) 114 continue; 115 116 irq = gpio_to_irq(desc[i].gpio); 117 free_irq(irq, (void *)&desc[i]); 118 } 119 return -EBUSY; 120 } 121 122 init_waitqueue_head(&dev->r_head); 123 124 return 0; 125 } 126 127 static int key_release(struct inode *nodep, struct file *filp) 128 { 129 // 释放中断 130 int irq, i; 131 132 for (i = 0; i < ARRAY_SIZE(desc); i++) { 133 if (!desc[i].gpio) 134 continue; 135 136 irq = gpio_to_irq(desc[i].gpio); 137 free_irq(irq, (void *)&desc[i]); 138 } 139 140 return 0; 141 } 142 143 static struct file_operations key_fops = { 144 .owner = THIS_MODULE, 145 .read = key_read, 146 .open = key_open, 147 .release = key_release, 148 }; 149 150 static int keys_init(void) 151 { 152 int ret; 153 int devt; 154 if (g_major) { 155 devt = MKDEV(g_major, 0); 156 ret = register_chrdev_region(devt, 1, "key"); 157 } 158 else { 159 ret = alloc_chrdev_region(&devt, 0, 1, "key"); 160 g_major = MAJOR(devt); 161 } 162 163 if (ret) 164 return ret; 165 166 dev = kzalloc(sizeof(struct key_device), GFP_KERNEL); 167 if (!dev) { 168 ret = -ENOMEM; 169 goto fail_alloc; 170 } 171 172 cdev_init(&dev->cdev, &key_fops); 173 ret = cdev_add(&dev->cdev, devt, 1); 174 if (ret) 175 return ret; 176 177 scls = class_create(THIS_MODULE, "key"); 178 sdev = device_create(scls, NULL, devt, NULL, "key"); 179 180 return 0; 181 182 fail_alloc: 183 unregister_chrdev_region(devt, 1); 184 185 return ret; 186 } 187 188 static void keys_exit(void) 189 { 190 dev_t devt = MKDEV(g_major, 0); 191 192 device_destroy(scls, devt); 193 class_destroy(scls); 194 195 cdev_del(&(dev->cdev)); 196 kfree(dev); 197 198 unregister_chrdev_region(devt, 1); 199 } 200 201 module_init(keys_init); 202 module_exit(keys_exit); 203 204 MODULE_LICENSE("GPL");
Makefile:
1 KERN_DIR = /work/tiny4412/tools/linux-3.5 2 3 all: 4 make -C $(KERN_DIR) M=`pwd` modules 5 6 clean: 7 make -C $(KERN_DIR) M=`pwd` modules clean 8 rm -rf modules.order 9 10 obj-m += key.o
测试文件:
1 #include2 #include 3 #include 4 #include 5 #include 6 #include <string.h> 7 8 int main(int argc, char** argv) 9 { 10 int fd; 11 fd = open("/dev/key", O_RDWR); 12 if (fd < 0) { 13 printf("can't open /dev/key\n"); 14 return -1; 15 } 16 17 unsigned char key_val; 18 19 while (1) { 20 read(fd, &key_val, 1); 21 printf("key_val = 0x%x\n", key_val); 22 } 23 24 close(fd); 25 26 return 0; 27 }
四、中断的底半部机制
中断的顶半部(上半部)和底半部(下半部):
如果中断中需要完成大量代码,可采用上图中的分层结构。
顶半部用于完成少量紧急功能,它往往只是简单的读取寄存器,然后将底半部挂到执行队列中去。从而可以服务更多的中断请求
关于顶半部与底半部的差别:
1. 顶半部代码短小,底半部代码较大
2. 顶半部一般不可中断,底半部可以中断
Linux实现底半部的机制主要有tasklet、工作队列、软中断和线程化irq,本章只介绍前两种。
1. tasklet
tasklet的执行上下文是软中断,属于原子上下文操作,不允许休眠,执行时机通常是顶半部返回的时候。表示tasklet的结构体为struct tasklet_struct。
使用模板如下:
1 /* 定义key_tasklet, 底部函数key_tasklet_func,绑定 */ 2 static void key_tasklet_func(unsigned long arg); 3 DECLARE_TASKLET(key_tasklet, key_tasklet_func, 0); 4 5 static void key_tasklet_func(unsigned long arg) 6 { 7 // 底半部代码 8 } 9 10 static irqreturn_t key_interrupt(int irq, void *dev_id) 11 { 12 /* 顶半部代码,如示例源码中的代码 13 * ... 14 */ 15 16 tasklet_schedule(&key_tasklet); 17 return IRQ_HANDLED; 18 }
2. 工作队列
工作队列的执行上下文是内核线程,可以调度和休眠。表示工作队列的结构体为struct work_struct。
使用模板如下:
1 #include2 3 /* 定义key_tasklet, 底部函数key_tasklet_func,绑定 */ 4 static void key_wq_func(unsigned long arg); 5 struct work_struct key_workqueue; 6 7 static void key_wq_func(unsigned long arg) 8 { 9 // 底半部代码 10 } 11 12 static irqreturn_t key_interrupt(int irq, void *dev_id) 13 { 14 /* 顶半部代码,如示例源码中的代码 15 * ... 16 */ 17 18 schedule_work(&key_workqueue); 19 return IRQ_HANDLED; 20 } 21 22 static int keys_init(void) 23 { 24 /* 其余初始化代码 */ 25 26 // 初始化工作队列 27 INIT_WORK(&key_workqueue, key_wq_func); 28 } 29 30 static void keys_exit(void) 31 { 32 // 注销工作队列 33 cancel_work_sync(&key_workqueue); 34 }
五、中断共享
中断共享就是多个设备共享一根硬件中断线的情况。在裸机编程中可以使用寄存器SUBSRCPND和EINTPEND做进一步中断判断,这里使用的就是共享中断。
共享中断的使用方式如下:
1. 共享中断的多个设备在申请中断时,都应该使用IRQF_SHARED标识,如request_irq(..., IRQF_SHAREAD | IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, ...)
2. 中断到来时,系统会遍历此中断的所有中断处理程序,直到某个函数返回IRQ_HANDLED。因此我们在中断函数中需要判断此中断是否属于本设备中断,若不是,应直接返回IRQ_NONE。
使用模板如下:
1 static irqreturn_t key_interrupt(int irq, void *dev_id) 2 { 3 /* 1. 读寄存器值或dev_id数据判断是否为本设备中断 */ 4 ; 5 /* 2. 如果不是 */ 6 if ( ) 7 return IRQ_NONE; 8 9 /* 3. 如果是,处理 */ 10 11 return IRQ_HANDLED; 12 }
下一章 四、poll()、select()和epoll()