1.ARM裸机中断程序流程分析 1.1.中断统一入口
1.2.事先注册中断处理程序
1.3.根据中断源的编号处理程序 2.linux系统中断处理流程分析2.1中断流程概述Linux系统irq.svc中断入口获得中断源的编号,根据中断号找到irq_desc结构,在irq_desc结构中找到action结构,执行用户注册中断处理函数
驱动程序支持中断应该做什么?
驱动程序实现中断处理程序,注册到中断号所对应irq_desc中
linux设备驱动程序中包含中断处理程序中断注册,中断处理函数实现,注销处理 2.2linux系统中断处理实现2.2.1中断注册使用request_irq函数用于中断注册
int request_irq(unsigned int irq,void (handler)(int, void, structpt_regs *),unsigned long flags,const char *devname,void *dev_id)
返回0表示成功,或者返回一个错误码
参数:
unsigned int irq
中断号。
void (handler)(int,void )
中断处理函数。
unsigned long flags
与中断管理有关的各种选项。
const char * devname
设备名
void *dev_id
共享中断时使用
在flags参数中,可以选择一些与中断管理有关的选项,如:
IRQF_DISABLED(SA_INTERRUPT)
如果设置该位,表示是一个“快速”中断处理程序;如果没有设置这位,那么是一个“慢速”中断处理程
序。
IRQF_SHARED(SA_SHIRQ)
该位表明该中断号是多个设备共享的。
也可选择为中断触发标志:IRQF_TRIGGER_FALLING(下降沿)
快/慢速中断的主要区别在于:快速中断保证中断处理的原子性(不被打断),而慢速中断则不保证。换句话说,也就是“开启中断”标志位(处理器IF)
在运行快速中断处理程序时是关闭的,因此在服务该中断时,不会被其他类型的中断打断;而调用慢速中断处理时,其它类型的中断仍可以得到服务。
2.2.2中断处理程序
中断处理程序的特别之处是在中断上下文中运行的,它的行为受到某些限制:1.不能使用可能引起阻塞的函数,2.不能使用肯呢过引起调度的函数
中断处理程序流程
1.检查设备是否产生了中断
2.清除中断产生标志
3.相应的硬件操作
2.2.3.注销中断
void free_irq(unsigned int irq,void *dev_id) 3.按键中断处理框架代码实现- #include<linux/module.h>
- #include<linux/init.h>
- #include<linux/miscdevice.h>
- #include<linux/fs.h>
- #include<linux/interrupt.h>
- #include<linux/irq.h>
- #include<linux/io.h>
- #include<linux/slab.h>
- #include<linux/vmalloc.h>
- MODULE_LICENSE("GPL");
- #define GPX1CON 0x11000C20
- //home按键的物理基地址
- unsigned int *gpx1con;
- irqreturn_t key_int(int irq,void *dev_id)
- {
- //检测是否发生了按键中断
- //清除按键中断
- //打印键值
- printk("press the key\n");
- return 0;
- }
- void keyinit()
- {
- gpx1con = ioremap(GPX1CON,4);//物理地址映射为虚拟地址
- writel(readl(gpx1con)&~(0xf<<4)|(0xf<<4),gpx1con);//将GPIO功能设置为输入
- }
- int key_open(struct inode *node,struct file *filp)
- {
- return 0;
- }
- struct file_operations key_fops =
- {
- .open = key_open,
- };
- struct miscdevice key_miscdev = {
- .minor = 200,
- .name = "mykey",
- .fops = &key_fops,
- };
- static int key_init()
- {
- misc_register(&key_miscdev);//注册杂项设备
- keyinit();按键的硬件初始化
- request_irq(IRQ_EINT(9),key_int,IRQF_TRIGGER_FALLING,"mykey",0);//注册中断
- return 0;
- }
- static void key_exit()
- {
- misc_deregister(&key_miscdev);//注销杂项设备
- free_irq(IRQ_EINT(9),0);//释放中断
- }
- module_init(key_init);
- module_exit(key_exit);
复制代码 4.中断分层4.1.中断嵌套4.2.中断分层方式4.3.使用工作队列实现分层4.1中断嵌套linux如何处理中断嵌套?
一种类型的中断发生的时候,又产生了其他的中断,其他的中断可以是同类型的,也可以是不同类型的。
不同的系统处理方式不同
慢速中断:在进行中断处理的时候,中断的总开关不关闭,允许其他类型中断产生
情况1:串口中断处理程序运行的时候,运行一段时间,又来一个网卡中断,LINUX系统暂停串口中断处理程序,转去执行网卡处理程序,执行完毕,又去执行串口中断处理程序。
情况2:串口中断处理程序运行的时候,运行一段时间,又来一个串口中断,不会执行新的串口中断,linux系统在处理同类型的中断时,不会处理新的中断,会忽略。
快速中断:在进行中断处理的时候,中断的总开关关闭,不允许其他类型中断产生
情况1:串口中断处理程序运行的时候,运行一段时间,又来一个网卡中断,LINUX系统继续串口中断处理程序,忽略网卡处理程序。
情况2:串口中断处理程序运行的时候,运行一段时间,又来一个串口中断,不会执行新的串口中断,linux系统在处理同类型的中断时,不会处理新的中断,会忽略。
4.2中断分层: 4.2.1linux如何处理中断丢失的问题?方法:将中断处理程序时间减短,减小中断丢失的概率
如何将中断处理程序的时间尽量减短?
中断处理程序一般做两部分工作,一部分是和硬件相关的,另外一类部分和硬件不密切相关。于是把中断处理程序分成两部分,上半部分做与硬件相关的工作,因为硬件相关工作必须在中断处理程序上下文中进行。下半部分与硬件不相关的抛给内核处理。
上半部:当中断发生时,它进行相应地硬件读写,并“登记”该中断。通常由中断处理程序充当上半部。
下半部:在系统空闲的时候对上半部“登记”的中断进行后续处理。 4.2.2中断分层方式中断分层的方式有三种
1软中断,2tasklet,3工作队列现在最常用的方式为工作队列
工作队列是一种将任务推后执行的形式,他把推后的任务交由一个内核线程去执行。这样下半部会在进程上下文执行,它允许重新调度甚至睡眠。每个被推后的任务叫做“工作”,由这些工作组成的队列称为工作队列。
创建一个工作队列,3核的CPU,每个CPU上都会挂再一个链表,链表上连的就是工作,将下半部的工作添加到链表中买,内核为每一个链表创建一个线程,当内核空闲时,就处理下半部工作。 4.3工作队列的使用Linux内核使用workqueue_struct来描述一个工作队列:
struct workqueue_struct {
struct cpu_workqueue_struct *cpu_wq;
struct list_head list;
const char name; /*workqueue name/
int singlethread;
int freezeable; /* Freeze threads during suspend */
int rt;
};
Linux内核使用struct work_struct来描述一个工作项:
struct work_struct {
atomic_long_t data;
struct list_head entry;
work_func_t func;
};
typedef void (*work_func_t)(struct work_struct *work);
工作队列使用流程:
step1. 创建工作队列
create_workqueue
step2. 创建工作
INIT_WORK
step3. 提交工作
queue_work 4.4工作队列代码实现- #include<linux/init.h>
- #include<linux/module.h>
- #include<linux/slab.h>
- MODULE_LICENSE("GPL");
- struct workqueue_struct *my_wq;
- struct work_struct *work1;
- struct work_struct *work2;
- static void work1_fun(struct work_struct *work)
- {
- printk("this is work1\n");
- }
- static void work2_fun(struct work_struct *work)
- {
- printk("this is work2\n");
- }
- MODULE_LICENSE("GPL");
- int init_queue(void)
- {
- //创建工作队列
- //my_wq = create_workqueue("mywq");
- //创建工作
- work1 = kmalloc(sizeof(struct work_struct),GFP_KERNEL);
- INIT_WORK(work1,work1_fun);
- //挂载工作
- //queue_work(my_wq,work1);
- schedule_work(work1);
- //创建工作
- work2 = kmalloc(sizeof(struct work_struct),GFP_KERNEL);
- INIT_WORK(work2,work2_fun);
- //挂载工作
- //queue_work(my_wq,work2);
- schedule_work(work2);
- }
- void clean_queue(void)
- {
- }
- module_init(init_queue);
- module_exit(clean_queue);
复制代码 4.5内核定时器linux内核使用struct timer_list来描述定时器: - struct timer_list {
- /*
- * All fields that change during normal runtime grouped to the
- * same cacheline
- */
- struct list_head entry;
- unsigned long expires;
- struct tvec_base *base;
- void (*function)(unsigned long);
- unsigned long data;
- };
复制代码定时器使用流程
1.定义定时器变量struct timer_list
2初始化定时器
2.1init_timer的初始化
2.2设置超时函数
3.add_timer注册定时器
4.mod_timer启动定时器 按键驱动的分层实现 - #include<linux/module.h>
- #include<linux/init.h>
- #include<linux/miscdevice.h>
- #include<linux/fs.h>
- #include<linux/interrupt.h>
- #include<linux/io.h>
- #include<linux/irq.h>
- #include<linux/slab.h>
- #include<linux/vmalloc.h>
- #include<linux/uaccess.h>
- MODULE_LICENSE("GPL");
- #define GPX1CON 0x11000C20
- #define GPX1DAT 0x11000C24
- unsigned int *gpx1con;
- unsigned int *gpx1dat;
- unsigned int key_num;
- struct work_struct *work1;
- struct work_struct *work1;
- struct timer_list key_timer;//定义定时器变量struct timer_list
- void work1_fun(struct work_struct *work)
- {
- mod_timer(&key_timer,jiffies+HZ/10);//mod_timer启动定时器
- // printk("press the key\n");
- }
- void key_timer_func(unsigned long data)//超时函数
- { unsigned int key_val;
- key_val = readl(gpx1dat)&(0x1<<1);
- if(key_val==0)
- {
- key_num = 1;
- }
- //printk("\nhome key down\n");
- key_val = readl(gpx1dat)&(0x1<<2);
- if(key_val==0)
- {
- key_num = 2;
- }
- //printk("\n back key down\n");
- }
- irqreturn_t key_int(int irq,void *dev_id)
- {
- //检测是否发生了按键中断
- //清除按键中断
- //提交下半部分
- schedule_work(work1);
- //printk("press the key\n");
- return 0;
- }
- void keyinit()
- {
- gpx1con = ioremap(GPX1CON,4);
- writel(readl(gpx1con)&~(0xff<<4)|(0xff<<4),gpx1con);
- gpx1dat = ioremap(GPX1DAT,4);
- }
- ssize_t key_read(struct file *filp, char __user *buf, size_t size, loff_t *pos)
- {
- printk("in kernel :key num is %d\n",key_num);
- copy_to_user(buf, &key_num, 4);
- return 4;
- }
- int key_open(struct inode *node,struct file *filp)
- {
- return 0;
- }
- struct file_operations key_fops =
- {
- .open = key_open,
- .read = key_read,
- };
- struct miscdevice key_miscdev = {
- .minor = 200,
- .name = "mykey",
- .fops = &key_fops,
- };
- static int key_init()
- {
- misc_register(&key_miscdev);
- keyinit();
- request_irq(IRQ_EINT(9),key_int,IRQF_TRIGGER_FALLING,"mykey",0);
- request_irq(IRQ_EINT(10),key_int,IRQF_TRIGGER_FALLING,"mykey",0);
- //创建工作
- work1 = kmalloc(sizeof(struct work_struct),GFP_KERNEL);
- INIT_WORK(work1,work1_fun);
- init_timer(&key_timer);//init_timer的初始化
- key_timer.function = key_timer_func;//设置超时函数
- add_timer(&key_timer);//add_timer注册定时器
- return 0;
- }
- static void key_exit()
- {
- misc_deregister(&key_miscdev);
- free_irq(IRQ_EINT(9),0);
- free_irq(IRQ_EINT(10),0);
- }
- module_init(key_init);
- module_exit(key_exit);
复制代码 5.阻塞型驱动设计5.1.阻塞的必要性当应用程序调用read读取数据时,设备没有数据提供,但以后可能会有,或者进程正在试图去写,但设备暂时没有准备好去接收数据。当以上情况发生时。驱动程序应当(缺省地)阻塞进程,使它进入等待(睡眠)状态,直到请求得到满足。内核等待队列像是候车室
1.读数据2.睡眠3.唤醒 5.2.内核等待队列进程在哪里睡眠,就是内核等待队列
5.2.1、定义等待队列
wait_queue_head_t my_queue
5.2.2、初始化等待队列
init_waitqueue_head(&my_queue)
5.2.3、定义+初始化等待队列
DECLARE_WAIT_QUEUE_HEAD(my_queue)
5.2.4、进入等待队列,睡眠
wait_event(queue,condition)
当condition(布尔表达式)为真时,立即返回;否则让进程进入TASK_UNINTERRUPTIBLE模式的睡眠,并挂在queue参数所指定的等待队列上。
wait_event_interruptible(queue,condition)当condition(布尔表达式)为真时,立即返回;否则让进程进入TASK_INTERRUPTIBLE的睡眠,并挂在queue参数所指定的等待队列上。
5.2.5从等待队列中唤醒进程
wake_up(wait_queue_t *q)
从等待队列q中唤醒状态为TASK_UNINTERRUPTIBLE,TASK_INTERRUPTIBLE,TASK_KILLABLE 的所有进程。wake_up_interruptible(wait_queue_t *q)从等待队列q中唤醒状态为TASK_INTERRUPTIBLE 的进程 5.3.阻塞优化驱动- #include<linux/module.h>
- #include<linux/init.h>
- #include<linux/miscdevice.h>
- #include<linux/fs.h>
- #include<linux/interrupt.h>
- #include<linux/io.h>
- #include<linux/irq.h>
- #include<linux/slab.h>
- #include<linux/vmalloc.h>
- #include<asm/uaccess.h>
- #include <linux/sched.h>
- MODULE_LICENSE("GPL");
- #define GPX1CON 0x11000C20
- #define GPX1DAT 0x11000C24
- unsigned int *gpx1con;
- unsigned int *gpx1dat;
- unsigned int key_num=0;
- wait_queue_head_t key_queue;//定义等待队列
- struct work_struct *work1;
- struct timer_list key_timer;
- void work1_fun(struct work_struct *work)
- {
- mod_timer(&key_timer,jiffies+HZ/10);
- // printk("press the key\n");
- }
- void key_timer_func(unsigned long data)
- { unsigned int key_val;
- key_val = readl(gpx1dat)&(0x1<<1);
- if(key_val==0)
- {
- key_num = 1;
- }
- //printk("\nhome key down\n");
- key_val = readl(gpx1dat)&(0x1<<2);
- if(key_val==0)
- {
- key_num = 2;
- }
- wake_up(&key_queue);//按键按下则唤醒进程
- //printk("\n back key down\n");
- }
- irqreturn_t key_int(int irq,void *dev_id)
- {
- //检测是否发生了按键中断
- //清除按键中断
- //提交下半部分
- schedule_work(work1);
- //printk("press the key\n");
- return 0;
- }
- void keyinit()
- {
- gpx1con = ioremap(GPX1CON,4);
- writel(readl(gpx1con)&~(0xff<<4)|(0xff<<4),gpx1con);
- gpx1dat = ioremap(GPX1DAT,4);
- }
- ssize_t key_read(struct file *filp, char __user *buf, size_t size, loff_t *pos)
- {
- wait_event(key_queue,key_num);//进入等待队列,key_num是进入等待队列的条件
- printk("in kernel :key num is %d\n",key_num);
- copy_to_user(buf, &key_num, 4);
- key_num = 0;
- return 4;
- }
- int key_open(struct inode *node,struct file *filp)
- {
- return 0;
- }
- struct file_operations key_fops =
- {
- .open = key_open,
- .read = key_read,
- };
- struct miscdevice key_miscdev = {
- .minor = 200,
- .name = "mykey",
- .fops = &key_fops,
- };
- static int mykey_init()
- {
- misc_register(&key_miscdev);
- keyinit();
- request_irq(IRQ_EINT(9),key_int,IRQF_TRIGGER_FALLING,"mykey",0);
- request_irq(IRQ_EINT(10),key_int,IRQF_TRIGGER_FALLING,"mykey",0);
- //创建工作
- work1 = kmalloc(sizeof(struct work_struct),GFP_KERNEL);
- INIT_WORK(work1,work1_fun);
- //初始化定时器
- init_timer(&key_timer);
- //添加超时函数
- key_timer.function = key_timer_func;
- //向内核注册
- add_timer(&key_timer);
- //初始化等待队列
- init_waitqueue_head(&key_queue);
- return 0;
- }
- static void mykey_exit()
- {
- misc_deregister(&key_miscdev);
- free_irq(IRQ_EINT(9),0);
- free_irq(IRQ_EINT(10),0);
- }
- module_init(mykey_init);
- module_exit(mykey_exit);
复制代码 应用程序调用- #include<stdio.h>
- #include<stdlib.h>
- #include <errno.h>
- int main(int argc,char **argv)
- {
- int fd;
- int key_num;
- //1.打开设备
- fd = open("/dev/mykey",0);
- if(fd<0)
- printf("open device fail\n");
- //读取设备
- read(fd,&key_num,4);
- printf("key is %d\n",key_num);
- //关闭设备
- close(fd);
- return 0;
- }
复制代码 当应用程序调用驱动程序读取按键值,此时按键没有按下,则测试驱动程序使其应用程序进入等待状态,当有按键按下,则唤醒程序读取键值。
|