本文共 10348 字,大约阅读时间需要 34 分钟。
1.1.中断统一入口
1.2.事先注册中断处理程序 1.3.根据中断源的编号处理程序linux系统irq.svc中断入口获得中断源的编号,根据中断号找到irq_desc结构,在irq_desc结构中找到action结构,执行用户注册中断处理函数
驱动程序支持中断应该做什么? 驱动程序实现中断处理程序,注册到中断号所对应irq_desc中 linux设备驱动程序中包含中断处理程序中断注册,中断处理函数实现,注销处理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)#include#include #include #include #include #include #include #include #include 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);
linux如何处理中断嵌套?
一种类型的中断发生的时候,又产生了其他的中断,其他的中断可以是同类型的,也可以是不同类型的。 不同的系统处理方式不同 慢速中断:在进行中断处理的时候,中断的总开关不关闭,允许其他类型中断产生 情况1:串口中断处理程序运行的时候,运行一段时间,又来一个网卡中断,LINUX系统暂停串口中断处理程序,转去执行网卡处理程序,执行完毕,又去执行串口中断处理程序。 情况2:串口中断处理程序运行的时候,运行一段时间,又来一个串口中断,不会执行新的串口中断,linux系统在处理同类型的中断时,不会处理新的中断,会忽略。 快速中断:在进行中断处理的时候,中断的总开关关闭,不允许其他类型中断产生 情况1:串口中断处理程序运行的时候,运行一段时间,又来一个网卡中断,LINUX系统继续串口中断处理程序,忽略网卡处理程序。 情况2:串口中断处理程序运行的时候,运行一段时间,又来一个串口中断,不会执行新的串口中断,linux系统在处理同类型的中断时,不会处理新的中断,会忽略。 4.2中断分层:方法:将中断处理程序时间减短,减小中断丢失的概率
如何将中断处理程序的时间尽量减短? 中断处理程序一般做两部分工作,一部分是和硬件相关的,另外一类部分和硬件不密切相关。于是把中断处理程序分成两部分,上半部分做与硬件相关的工作,因为硬件相关工作必须在中断处理程序上下文中进行。下半部分与硬件不相关的抛给内核处理。 上半部:当中断发生时,它进行相应地硬件读写,并“登记”该中断。通常由中断处理程序充当上半部。 下半部:在系统空闲的时候对上半部“登记”的中断进行后续处理。中断分层的方式有三种
1软中断,2tasklet,3工作队列现在最常用的方式为工作队列 工作队列是一种将任务推后执行的形式,他把推后的任务交由一个内核线程去执行。这样下半部会在进程上下文执行,它允许重新调度甚至睡眠。每个被推后的任务叫做“工作”,由这些工作组成的队列称为工作队列。 创建一个工作队列,3核的CPU,每个CPU上都会挂再一个链表,链表上连的就是工作,将下半部的工作添加到链表中买,内核为每一个链表创建一个线程,当内核空闲时,就处理下半部工作。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#include#include #include 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);
在大多数情况下, 驱动并不需要自己建立工作队列,只需定义工作, 然后将工作提交到内核已经定义好的工作队列keventd_wq中。
1. 提交工作到默认队列 schedule_work 在这里引入一个小知识点内核定时器 操作系统中,由于效率方面的原因,一般不允许for循环来等待,只能使用定时器。使用定时器可以实现按键延时去抖动,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#include #include #include #include #include #include #include #include #include MODULE_LICENSE("GPL");#define GPX1CON 0x11000C20#define GPX1DAT 0x11000C24unsigned int *gpx1con;unsigned int *gpx1dat;unsigned int key_num;struct work_struct *work1;struct work_struct *work1;struct timer_list key_timer;//定义定时器变量struct timer_listvoid 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);
当应用程序调用read读取数据时,设备没有数据提供,但以后可能会有,或者进程正在试图去写,但设备暂时没有准备好去接收数据。当以上情况发生时。驱动程序应当(缺省地)阻塞进程,使它进入等待(睡眠)状态,直到请求得到满足。内核等待队列像是候车室
1.读数据2.睡眠3.唤醒进程在哪里睡眠,就是内核等待队列
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 的进程#include#include #include #include #include #include #include #include #include #include #include MODULE_LICENSE("GPL");#define GPX1CON 0x11000C20#define GPX1DAT 0x11000C24unsigned 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#include #include 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;}
当应用程序调用驱动程序读取按键值,此时按键没有按下,则测试驱动程序使其应用程序进入等待状态,当有按键按下,则唤醒程序读取键值。
转载地址:http://lvomb.baihongyu.com/