博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
linux设备驱动中断程序深度完全解析
阅读量:2433 次
发布时间:2019-05-10

本文共 10348 字,大约阅读时间需要 34 分钟。

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
#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);

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
#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循环来等待,只能使用定时器。使用定时器可以实现按键延时去抖动,

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
#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);

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
#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/

你可能感兴趣的文章
MySQL 5.7权限的介绍
查看>>
MySQL各种日志的简介和设置方法
查看>>
Oracle Rman 数据库的不完全恢复
查看>>
MySQL 5.5 源码安装流程
查看>>
MySQL 5.5 配置文件设置
查看>>
Oracle 11g ASH报告的生成方法
查看>>
Mysql 5.5 设置数据库参数
查看>>
MySQL 5.5 Slave节点备份脚本(mysqldump)
查看>>
Mysql 5.5 数据库启动关闭命令
查看>>
Oracle 11g PL/SQL 用户自定义 Exception
查看>>
MySQL 5.5半同步复制的配置与监控
查看>>
Mysql 5.5 重置root密码
查看>>
MySQL 5.7 延迟复制配置
查看>>
MySQL 5.5更改用户密码方法
查看>>
MySQL 5.5级联复制配置流程
查看>>
MySQL 5.7 mysqldumpslow工具介绍
查看>>
Oracle 11g RMAN 异机恢复
查看>>
MySQL 5.5 SHOW PROFILE、SHOW PROFILES语句介绍
查看>>
Oracle 11g RMAN multiplexing 备份脚本
查看>>
MySQL 5.5 主主复制搭建流程
查看>>