三、中断分析以及按键中断

 

在读者学习本章以及后续章节之前,最好拥有中断裸机基础,可以参考:中断编程

 

一、内核中断分析

通过裸机系列的学习,我们可以知道异常的触发分为下面几个过程:

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 #include <linux/module.h>
  2 #include <linux/fs.h>
  3 #include <linux/init.h>
  4 #include <linux/cdev.h>
  5 #include <linux/slab.h>
  6 #include <linux/device.h>
  7 #include <linux/irq.h>
  8 #include <linux/interrupt.h>
  9 #include <linux/wait.h>
 10 #include <linux/timer.h>
 11 #include <linux/gpio.h>
 12 #include <linux/sched.h>
 13 
 14 #include <asm/uaccess.h>
 15 #include <asm/irq.h>
 16 #include <asm/io.h>
 17 
 18 #include <mach/gpio.h>
 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");
View Code

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
View Code

测试文件:

 1 #include <stdio.h>
 2 #include <unistd.h>
 3 #include <sys/types.h>
 4 #include <sys/stat.h>
 5 #include <fcntl.h>
 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 }
View Code

 

 

四、中断的底半部机制

中断的顶半部(上半部)和底半部(下半部):

如果中断中需要完成大量代码,可采用上图中的分层结构。

顶半部用于完成少量紧急功能,它往往只是简单的读取寄存器,然后将底半部挂到执行队列中去。从而可以服务更多的中断请求

 

关于顶半部与底半部的差别:

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 #include <linux/workqueue.h>
 2 
 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()

 

转载于:https://www.cnblogs.com/Lioker/p/10840191.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值