谈谈对中断的理解

1.裸板中断处理过程

中断属于异常的一种
它是计算机中处理异步事件的重要机制1.1 中断的触发中断源级配置中断的触发方式  上升沿 下降沿  高 低触发中断使能 (监测到中断信号之后,能不能报上去)中断控制器级配置中断的优先级中断使能配置以irq frq 形式上报配置报给哪个核ARM core 中断的使能 I=01.2 中断的处理过程中断异常产生硬件自动做4件事 1) 备份CPSR 2) 修改CPSRMODET FI3) 保存返回地址到LR4) PC = vector_base + 0x18跳转到异常向量表执行...ldr pc, =irq_handlerirq_handler:现场保护bl c_irq_handler恢复现场c_irq_handler(){区分哪个硬件触发的IRQ异常调用该硬件的中断处理函数 hardware_isr ();清除中断pending}

2.linux中中断的处理过程

 linux中中断的处理过程和裸板中中断的处理过程相同linux将能帮你写好的中断处理代码都写好了写好的这部分代码称作 "linux中断子系统"特定硬件的中断触发方式 需要自行配置特定硬件中断处理函数需要自行编程实现 hardware_isr ();//通过这个API可以将需要做的配置,需要实现的中断处理函数"揉"到内核中去//中断注册函数request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char *name, void *dev)irq, 中断号PAD_GPIO_A +28 --->//这个是GPIO的编号而不是中断号gpio_to_irq (PAD_GPIO_A +28);返回该管脚的中断编号irq_to_gpio (IRQ_GPIO_A_START + 28);arch/arm/mach-s5p6818/include/machs5p6818_irq.h 这里面有各个设备的中断号 Physical interrupt numberIRQ_GPIO_A_START + 28handler, 要注册的中断处理函数 (hardware_isr)irq_handler_t  是typedef irqreturn_t (*irq_handler_t)(int, void *); 的别名typedef enum irqreturn irqreturn_t;
         enum irqreturn {IRQ_NONE    = (0 << 0),IRQ_HANDLED   = (1 << 0),IRQ_WAKE_THREAD   = (1 << 1),  };
         flags, 常用的取值IRQF_TRIGGER_FALLING  //下降沿触发IRQF_TRIGGER_RISING //上升沿触发IRQF_TRIGGER_HIGH //高电平触发IRQF_TRIGGER_LOW //低电平触发name, 名称dev, 当内核调用handler函数时 传递的第二个参数值函数功能:1) 约定什么情况下(上升沿...)下产生IRQ中断2) 约定IRQ号中断产生了 内核应该调用什么handler3) 约定内核调用handler 传递的第二个参数值handler (xxx, dev);free_irq (unsigned int irq, void *dev_id)函数功能: 注销中断服务程序取消request_irq中关于irq中断的约定特别注意: 注册中断服务程序时给的最后一个参数也必须是注销时给的最后一个参数电路原理图K1 捕获它按下的动作 由硬件监测GPIOA28管角上的下降沿下面展示一些 `内联代码片`。注意:模块中的 xxx_init函数返回非0 内核认为模块注册失败lsmod时 看不到该模块中断注册失败的原因:内核中自带了按键驱动程序该驱动程序中已经注册了该中断号对应的中断服务程序如果要完成实验 需要将内核中的该驱动程序裁剪cd driver/kernelmake menuconfigDevice Drivers --->Input device support --->[*] Keyboards ---><> SLsiAP push Keypad support make uImagecp arch/arm/boot/uImage /tftpboottftp 48000000 uImagemmc write 48000000 800 3000insmod btn_drv.kocat /proc/interrupts找到"up"的中断vi btn_drv.c
#include<linux/init.h>
#include<linux/module.h>
#include <linux/interrupt.h>
#include <mach/platform.h>MODULE_LICENSE("GPL");
typedef struct btn_desc
{int irq;//中断号char *name;
}btn_desc_t;
btn_desc_t buttons[] =
{{IRQ_GPIO_A_START+28,"UP"},{IRQ_GPIO_B_START+30,"DOWN"},{IRQ_GPIO_B_START+31,"LEFT"},{IRQ_GPIO_B_START+9,"RIGHT"}
};
irqreturn_t btn_isr(int irq, void * dev)
{//获取到哪个按键按下了btn_desc_t *pdata = (btn_desc_t *) dev;printk("%s key is pressed!\n", pdata->name);return IRQ_HANDLED;
}
int __init btn_drv_init (void)
{int ret = 0;int i = 0;//注册按键的中断服务程序  printk("irq num = %d\n", IRQ_GPIO_A_START + 28);for (; i <ARRAY_SIZE(buttons); i++){ret = request_irq(buttons[i].irq, btn_isrIRQF_TRIGGER_FALLING,buttons[i].name,&(buttons[i]));if (ret){printk("request_irq failed!\n");i--;while (i>=0) {free_irq (buttons[i].irq, &(buttons[i]));i--;}//返回非0值,内核认为模块注册失败return -EAGAIN;}}return 0;
}
void __exit btn_drv_exit(void)
{int i = 0;for (; i < ARRAY_SIZE (buttons); i++){free_irq(buttons[i].irq, &(buttons[i]));}
}
module_init(btn_drv_init);
module_exit(btn_drv_exit);
 挑挑问题所在,指出一下中断处理函数的问题:int buttons_isr (int num) //类似于irq_handler{float f = 100.4;f = f - 1.2;printf ("f=%f\n",f);return (int)f;}问题:1)中断处理函数中不应该有返回值2)不应该有参数3)在许多的处理器、编译器中,浮点一般都是不可重入的,需要让额外的寄存器入栈有些处理器、编译器中不允许在ISR中做浮点运算。此外ISR应该是高效而快速的。4)printf耗时,也不具有可重入性判断一个函数是否具有可重入性:看函数中是否使用了全局变量使用了全局变量的函数,不具有可重入性。16道经典嵌入式笔试题中断处理函数的特点1)要求执行的速度越快越好2) linux的中断处理函数中不能调用引起阻塞或者睡眠的函数recv/sleep 3)linux中中断处理过程使用独立的栈空间通常中断服务程序对应的栈空间较小一般物理内存页(4KB)中断处理函数中不能使用大数组4) 中断处理函数中不能做用户空间和内核空间的数据交互不能调用copy_to_user copy_from_userget_user / put_user (可能会引起阻塞)linux中希望中断处理函数执行速度尽量快但是有些硬件对应的中断服务程序执行起来就是慢针对该问题,内核提出了中断顶半部和中断低半部的机制说白了,就是为了解决中断处理程序不能很快结束的问题顶半部(top half):做紧急的工作往往就是做一些特殊功能寄存器的读写操作也包括清除中断标志位登记底半部底半部(bootom half):做不紧急的耗时的工作

3.登记底半部的方式

3.1 软中断直接修改内核源码不能以.ko文件的方式实现实现起来不方便3.2 tasklet他是基于软中断的方式实现的核心数据结构interrupt.h
 struct tasklet_struct{struct tasklet_struct *next;unsigned long state;atomic_t count;void (*func) (unsigned long);//保存底半部函数的地址unsigned long data;//当内核调用func函数时,传递的参数值}
 使用步骤:1)定义tasklet类型的变量struct tasklet_struct btn_tasklet;2)初始化tasklet变量void tasklet_init (struct tasklet_struct *t,void (*fun)(unsigned long), unsigned long data);DECLARE_TASKLET(name, func, data)等价与步骤1, 23) 使用该变量登记底半部voi tasklet_schedule(struct tasklet_struct *t);vi btn_driver.c
#include<linux/init.h>
#include<linux/module.h>
#include <linux/interrupt.h>
#include <mach/platform.h>
#include <linux/delay.h>MODULE_LICENSE("GPL");
typedef struct btn_desc
{int irq;//中断号char *name;
}btn_desc_t;
btn_desc_t buttons[] =
{{IRQ_GPIO_A_START+28,"UP"},{IRQ_GPIO_B_START+30,"DOWN"},{IRQ_GPIO_B_START+31,"LEFT"},{IRQ_GPIO_B_START+9,"RIGHT"}
};
//1.定义一个tasklet变量
struct tasklet_struct btn_tasklet;irqreturn_t btn_isr(int irq, void * dev)
{//获取到哪个按键按下了btn_desc_t *pdata = (btn_desc_t *) dev;printk("%s key is pressed!\n", pdata->name);//3.登记底半部printk ("register bottom half!n");tasklet_scheule(&btn_tasklet);printk("exit form top half\n");return IRQ_HANDLED;
}
int mydata = 100;
void btn_tasklet_func(unsigned long data){//tasklet登记的底半部函数中不能调用引起阻塞或者睡眠的函数int *pdata = (int *)data;//msleep(1);//会发生吐核的现象 Oops信息,将内核栈的信息输出出来printk("do bottom half work:data = %d...\n",pdata);(*pdata)++;
}
int __init btn_drv_init (void)
{int ret = 0;int i = 0;//注册按键的中断服务程序  printk("irq num = %d\n", IRQ_GPIO_A_START + 28);for (; i <ARRAY_SIZE(buttons); i++){ret = request_irq(buttons[i].irq, btn_isrIRQF_TRIGGER_FALLING,buttons[i].name,&(buttons[i]));if (ret){printk("request_irq failed!\n");i--;while (i>=0) {free_irq (buttons[i].irq, &(buttons[i]));i--;}//返回非0值,内核认为模块注册失败return -EAGAIN;}}tasklet_init (&btn_tasklet,btn_tasklet_func, (unsigned long)&mydata);return 0;
}
void __exit btn_drv_exit(void)
{int i = 0;for (; i < ARRAY_SIZE (buttons); i++){free_irq(buttons[i].irq, &(buttons[i]));}
}
module_init(btn_drv_init);
module_exit(btn_drv_exit);
     注意:tasklet基于软中断机制实现的tasklet.fun工作与中断上下文(context),其中不能调用引起阻塞操作的函数。中断上下文指的是:当异常产生之后,硬件自动做4件事,跳转到异常向量表之后,直到返回被打断的位置,这个过程当执行一个中断处理程序时,内核处于中断上下文。如果底半部函数中必须调用引起阻塞或者睡眠的函数,这时可以考虑使用工作队列机制登记底半部3.3 工作队列核心数据结构:workqueue.h:struct work_struct{work_func_t func;......};使用步骤:1)定义work变量struct work struct btn_work;2)初始化work变量INIT_WORK(&btn_work,func);DECLARE_WORK(n,f)//完成以上两步3)使用work变量登记底半部schedule_work (&btn_work);4) 卸载模块时 注意对登记未执行work的处理flush_work (struct work_struct *work);cancel_work_sync (struct work_struct *work);vi btn_drv.c
#include<linux/init.h>
#include<linux/module.h>
#include <linux/interrupt.h>
#include <mach/platform.h>
#include <linux/delay.h>MODULE_LICENSE("GPL");
typedef struct btn_desc
{int irq;//中断号char *name;
}btn_desc_t;
btn_desc_t buttons[] =
{{IRQ_GPIO_A_START+28,"UP"},{IRQ_GPIO_B_START+30,"DOWN"},{IRQ_GPIO_B_START+31,"LEFT"},{IRQ_GPIO_B_START+9,"RIGHT"}
};
//1.定义一个tasklet变量
//struct tasklet_struct btn_tasklet;
//定义一个work_struct变量
struct work_struct btn_work;irqreturn_t btn_isr(int irq, void * dev)
{//获取到哪个按键按下了btn_desc_t *pdata = (btn_desc_t *) dev;printk("%s key is pressed!\n", pdata->name);//3.登记底半部printk ("register bottom half!n");//tasklet_scheule(&btn_tasklet);schedule_work(&btn_work);printk("exit form top half\n");return IRQ_HANDLED;
}
int mydata = 100;
void btn_tasklet_func(unsigned long data){//tasklet登记的底半部函数中不能调用引起阻塞或者睡眠的函数int *pdata = (int *)data;//msleep(1);//会发生吐核的现象 Oops信息,将内核栈的信息输出出来printk("do bottom half work:data = %d...\n",pdata);(*pdata)++;
}
void btn_work_func(struct work_struct *work)
{msleep(1);//不吐核printk("do bottom half work:data...\n");
}
int __init btn_drv_init (void)
{int ret = 0;int i = 0;//注册按键的中断服务程序  printk("irq num = %d\n", IRQ_GPIO_A_START + 28);for (; i <ARRAY_SIZE(buttons); i++){ret = request_irq(buttons[i].irq, btn_isrIRQF_TRIGGER_FALLING,buttons[i].name,&(buttons[i]));if (ret){printk("request_irq failed!\n");i--;while (i>=0) {free_irq (buttons[i].irq, &(buttons[i]));i--;}//返回非0值,内核认为模块注册失败return -EAGAIN;}}//2 初始化tasklet变量//tasklet_init (&btn_tasklet,btn_tasklet_func, (unsigned long)&mydata);//2初始化work变量INIT_WORK(&btn_work,btn_work_func);return 0;
}
void __exit btn_drv_exit(void)
{int i = 0;for (; i < ARRAY_SIZE (buttons); i++){free_irq(buttons[i].irq, &(buttons[i]));}
}
module_init(btn_drv_init);
module_exit(btn_drv_exit);
 work.func函数工作于进程上下文其中对调用无限制原因是:内核中维护了system_wq,每调用一次schedule_work(&btn_work);就会在system_wq维护的队列中加入一个新的节点,内核中专门创建了一个内核线程去扫描队列,挨个执行。与tasklet的比较:1)可以调用引起阻塞睡眠的函数2)  tasklet登记的底半部函数可以更快的被执行到希望底半部函数在登记完之后 延时一段时间再执行这种情况可以考虑delayed_workstruct delayed_work {struct work_struct work;struct timer_list timer;};使用步骤:1)定义delayed_work变量struct delayed_work btn_dwork;2) 初始化INIT_DELAY_WORK (&btn_dwork, func);3)登记底半部schedule_delayed_work (&btn_dwork, unsigned long delay);4)当卸载模块时, 如果底半部函数登记了 未执行对应的处理策略:a) flush_delayed_work(struct elayed_work *dwork);//阻塞等待指定的dwork执行完毕才会返回b) cancel_delayed_work (struct delayed_work *work);//取消底半部函数的执行注意:1)连续两次登记底半部,只有第一次有效。2)登记底半部之后,没等底半部执行,就将模块卸载,则内核崩溃。由于模块的卸载 指定函数对应的内存空间释放延时时间到 跳转执行的位置存入内容不可预知vi btn_drv.c
#include<linux/init.h>
#include<linux/module.h>
#include <linux/interrupt.h>
#include <mach/platform.h>
#include <linux/delay.h>MODULE_LICENSE("GPL");
typedef struct btn_desc
{int irq;//中断号char *name;
}btn_desc_t;
btn_desc_t buttons[] =
{{IRQ_GPIO_A_START+28,"UP"},{IRQ_GPIO_B_START+30,"DOWN"},{IRQ_GPIO_B_START+31,"LEFT"},{IRQ_GPIO_B_START+9,"RIGHT"}
};//定义一个delayed_work变量
struct delayed_work btn_dwork;irqreturn_t btn_isr(int irq, void * dev)
{//获取到哪个按键按下了btn_desc_t *pdata = (btn_desc_t *) dev;printk("%s key is pressed!\n", pdata->name);//3.登记底半部printk ("register bottom half!n");schedule_delayed_work(&btn_dwork, 5*HZ);printk("exit form top half\n");return IRQ_HANDLED;
}
void btn_dwork_func (struct work_struct *work)
{printk("do bottom half work...\n");
}
int __init btn_drv_init (void)
{int ret = 0;int i = 0;//注册按键的中断服务程序  printk("irq num = %d\n", IRQ_GPIO_A_START + 28);for (; i <ARRAY_SIZE(buttons); i++){ret = request_irq(buttons[i].irq, btn_isrIRQF_TRIGGER_FALLING,buttons[i].name,&(buttons[i]));if (ret){printk("request_irq failed!\n");i--;while (i>=0) {free_irq (buttons[i].irq, &(buttons[i]));i--;}//返回非0值,内核认为模块注册失败return -EAGAIN;}}//2初始化delayed_workINIT_DELAYED_WORK(&btn_dwork,btn_work_func);return 0;
}
void __exit btn_drv_exit(void)
{int i = 0;//flush_delay_work(&btn_dwork);//cancel_delayed_work(&btn_dwork);for (; i < ARRAY_SIZE (buttons); i++){free_irq(buttons[i].irq, &(buttons[i]));}
}
module_init(btn_drv_init);
module_exit(btn_drv_exit);
 什么是中断?异常的一种异步事件的处理机制中断的触发方式:s5p6818+按键为例中断异常产生后软硬件完成的工作中断处理函数的特点Linux中可以将中断处理过程切分为两个半部登记底半部有几种方式 各自的特点5*HZ --->延时5秒之后,执行底半部函数在内核中是怎样实现的?

4.内核定时器

 系统时钟中断:周期性的,不间断的产生MACHINE_START(){....timer = &nxp_cpu_sys_timer;}struct sys_timer nxp_cpu_sys_timer = {.init = timer_initialize;}timer_event_init (){setup_irq(info->irqno, &timer_event_irqcation);}系统时钟中断处理函数:更新墙上时间检查当前进程的时间片是否耗尽(线程)jiffies++...HZ: 常数它决定了1S产生系统时钟中断的次数通过printk,知道HZ的具体值 当前系统中 HZ=1000ticktick = 1/HZ 它记录了两次系统时钟中断之间的间隔jiffies: 变量 unsigned int 它记录了自系统开机以来 经历的系统时钟中断的次数系统上电时,jiffies = 0假设HZ=1000jiffies = 10000 问开机了多长时间?10s1s : 10001min : 60 * 10001h: 60*60*10001day : 24 * 60 * 60 * 1000大约49天,jiffies就溢出了内核定时器软件上实现的定时器核心数据结构struct timer_list {//超时时间unsigned long expires;//定时时间到了之后要执行的动作function;//当调用function函数时,要传递的参数data;......};操作timer_list的API使用步骤:1) 定义一个timer_list变量struct timer_list btn_timer;2) 初始化 timer_list变量init_timer ( &btn_timer);注:一个#的作用是将变量名称转换为字符串btn_timer.expires =btn_timer.function =btn_timer.data =3) 启动定时器add_timer (&btn_timer);4)停止定时器del_timer (&btn_timer);mod_timer (...);a) 修改一个已经在计时过程中的定时器 (active);b) 启动一个定时器(deactive)并指定超时时间vi led_drv.c
#include <linux/module.h>
#include <linux/init.h>
#include <linux/gpio.h>
#include <mach/platform.h>MODULE_LICENSE ("GPL");
#define LED1 (PAD_GPIO_C+12)
struct timer_list led_timer;
void led_func(unsigned long data)
{//改变对应管脚的电平状态gpio_set_value (LED1, data);led_timer.data = !data;//从当前时间开始计时 1S后时间到mod_timer(&led_timer, jiffies + 1*HZ);
}
int __init led_drv_init(void)
{//申请GPIO管脚gpio_request(LED1,"LED1");//设置为输出模式, 默认输出高电平gpio_direction_output(LED1,1);/*初始化timer*/init_timer(&led_timer);led_timer.function = led_func;led_timer.expires = jiffies + 1*HZ;//管脚的电平要设置成的状态led_timer.data = 0;//启动定时器add_timer(&led_timer);return 0;
}
void __exit led_drv_exit(void)
{//停止定时器del_timer (&led_timer);//释放gpio管脚gpio_free(LED1);
}
module_init(led_drv_init);
module_exit(led_drv_exit);
 内核态的延时linux/delay.h睡眠延时msleep()忙等待延时 //相当于是空操作mdelayndelay

内核的竞态与并发

 实例:PC上的串口是独占式访问设备该方式的实现应该是在串口的驱动程序中实现的要解决的问题:希望按键设备只允许同时有一个进程进行访问

 vi  btn_drv.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>MODULE_LICENSE("GPL");struct cdev btn_cdev;
dev_t dev;
struct class *btn_cls = NULL;//返回非0 用户空间打开设备就是失败的
int btn_open (struct inode *inode, struct file *filp)
{printk("enter %s\n",__func__);return 0;
}int btn_release (struct inode *inode, struct file *filp)
{printk("enter %s\n",__func__);return 0;
}
struct file_operations btn_fops =
{.owner = THIS_MODULE,.open = btn_open,.release = btn_release,
};int __init btn_drv_init (void)
{//1.申请设备号alloc_chrdev_region (&dev, 0, 1, "btn");//2.初始化cdevcdev_init (&btn_cdev, &btn_fops);//3. 注册cdevcdev_add (&btn_cdev, dev, 1);//4. 自动生成设备文件btn_cls = class_create (THIS_MODULE, "mybuttons");device_create(btn_cls, NULL, dev, NULL, "buttons");return 0;
}
void __exit btn_drv_exit (void)
{//销毁设备文件device_destroy (btn_cls, dev);class_destroy (btn_cls);//注销cdevcdev_del (&btn_cdev);//注销设备号unregister_chrdev_region (dev, 1);}module_init(btn_drv_init);
module_exit(btn_drv_exit);
 vi test.c
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>int main (void)
{int fd = open("/dev/buttons", O_RDONLY);if (fd < 0){perror("open failed!\n");return -1;}printf("open successed!\n");sleep (10);close(fd);return 0;
}
 利用全局变量来解决竞态问题:这种方法看似解决了问题,但是从汇编的角度看,是存在bug的而bug存在的真正原因是两个进程都可以访问cnt这个共享资源

 vi btn_drv.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>MODULE_LICENSE("GPL");struct cdev btn_cdev;
dev_t dev;
struct class *btn_cls = NULL;int cnt = 1;//返回非0 用户空间打开设备就是失败的
int btn_open (struct inode *inode, struct file *filp)
{if (--cnt != 0){cnt++;//这个逻辑可以保证只有一个进程访问//但是从汇编的角度看,是有bug的return -EAGAIN;}return 0;
}int btn_release (struct inode *inode, struct file *filp)
{cnt++;return 0;
}
struct file_operations btn_fops =
{.owner = THIS_MODULE,.open = btn_open,.release = btn_release,
};
int __init btn_drv_init (void)
{//1.申请设备号alloc_chrdev_region (&dev, 0, 1, "btn");//2.初始化cdevcdev_init (&btn_cdev, &btn_fops);//3. 注册cdevcdev_add (&btn_cdev, dev, 1);//4. 自动生成设备文件btn_cls = class_create (THIS_MODULE, "mybuttons");device_create(btn_cls, NULL, dev, NULL, "buttons");return 0;
}void __exit btn_drv_exit (void)
{//销毁设备文件device_destroy (btn_cls, dev);class_destroy (btn_cls);//注销cdevcdev_del (&btn_cdev);//注销设备号unregister_chrdev_region (dev, 1);}
module_init(btn_drv_init);
module_exit(btn_drv_exit);
 竞态:竞争的状态竞争共享资源共享资源:硬件, uart lcd 声卡文件, 文件锁共享内存内核中的全局变量临界区:访问共享资源的代码段产生竞态的原因:1) SMP (对称多处理器)2) 多任务之间的抢占3) 任务和中断之间的抢占4) 中断和中断之间的抢占(中断优先级)linux中允许中断嵌套

 解决竞态的策略 1) 中断屏蔽2) 原子操作3) 自旋锁4) 信号量1.中断屏蔽:可以解决2,3,4三种情况local_irq_disable();//关闭中断critical section code //执行临界区代码local_irq_enable();//打开中断注意: 关中断时间要特别短,一旦过长内核直接崩溃因为linux内核中很多重要机制都是靠中断实现的写驱动程序时,不建议使用该方式注意存在的危险:.........//关中断xxxx_func(){local_irq_disable();//local_irq_save();访问共享资源local_irq_enable();//local_irq_restore();}...//该部分代码执行可能有问题.....//开中断以上情况可以使用local_irq_save()//保存原来中断的状态local_irq_restore();//恢复原来的中断状态2. 原子操作整个操作过程不可打断 不可分割2.1 位原子操作对一个bit位进行操作时,保证原子性arch/arm/include/asm/bitops.h
         set_bit(nr,addr)//将addr中的nr位设置为1 并保证原子性clear_bit(nr,addr)//将addr中的nr位清0  并保证原子性change_bit(nr,addr)//将addr中的nr位取反  并保证原子性    ......
     2.2 整型原子操作
         typedef struct {int counter;} atomic_t;
       围绕这个数据结构提供了一套APIatomic_add(i,v)//原子变量的值增加iatomic_sub(i,v)//原子变量的值减少iatomic_set(v,i)//原子变量的值设置为iatomic_read(v)//返回原子变量的值#define atomic_inc(v)     atomic_add(1, (v))//原子变量的值增加1#define atomic_dec(v)     atomic_sub(1, (v))//原子变量的值减1atomic_dec_and_test(atomic_t * v);//原子变量v自减 测试结果是否为0 如果为0返回Ture
//定义原子变量
atomic_t btn_tv;//返回非0 用户空间打开设备就是失败的
int btn_open (struct inode *inode, struct file *filp)
{//if (--cnt != 0)if (!atomic_dec_and_test(&btn_tv)){//cnt++;//这个逻辑可以保证只有一个进程访问//但是从汇编的角度看,是有bug的atomic_inc(&btn_tv);return -EAGAIN;}return 0;
}
int btn_release (struct inode *inode, struct file *filp)
{//cnt++;atomic_inc(&btn_tv);return 0;
}
int __init btn_drv_init (void)
{//1.申请设备号alloc_chrdev_region (&dev, 0, 1, "btn");//2.初始化cdevcdev_init (&btn_cdev, &btn_fops);//3. 注册cdevcdev_add (&btn_cdev, dev, 1);//4. 自动生成设备文件btn_cls = class_create (THIS_MODULE, "mybuttons");device_create(btn_cls, NULL, dev, NULL, "buttons");//初始化原子变量atomic_set (&btn_tv, 1);return 0;
}
     insmod btn_drv.ko./testtelnet 192.168.1.6./test2.3 自旋锁2.3.1 核心数据结构:struct spinlock //里面的任何一个成员变量都不需要关注2.3.2 使用方法获取自旋锁执行临界区代码 访问共享资源释放自旋锁2.3.3 特点自旋锁只能有一个持有单元如果试图获取一个已经被其他执行单元持有的自旋锁获取自旋锁不成功,原地自旋等待 直到获取成功为止2.3.4 具体使用步骤1)定义一把自旋锁struct spinlock btn_lock;2)初始化自旋锁spin_lock_init (&btn_lock);3)  获取锁spin_lock (&btn_lock);//注意获取不成功的话,原地自旋等待4) 执行临界区代码 访问共享资源5)释放自旋锁spin_unlock (&btn_lock);2.3.5 注意事项1) 获取 释放自旋锁要成对出现2) 持有自旋锁的时间要尽量短  临界区中不应该出现睡眠函数A任务获取自旋锁成功后 内核几乎不做任务调度内核希望A任务尽快执行完临界区代码 尽快释放自旋锁不要产生获取锁不成功 原地自旋的情况3)避免死锁的情况出现死锁的情况有两种:a) 连续获取同一把锁两次spin_lock(&btn_lock);spin_lock(&btn_lock);b)     A任务      B任务...             ...获取m锁 获取n锁...             ...获取n锁 获取m锁...             ...释放n锁 释放m锁...             ...释放m锁 释放n锁spin_lock改为spin_trylock //获取自旋锁不成功 立即返回错误A任务中spin_trylock 获取m锁成功再执行spin_trylock 获取n锁,一旦失败放弃m锁,过段时间重新获取m锁,获取n锁
//定义自选锁
struct spinlock btn_lock;//返回非0 用户空间打开设备就是失败的
int btn_open (struct inode *inode, struct file *filp)
{//获取自选锁spin_lock(&btn_lock);if (--cnt != 0){cnt++;spin_unlock (&btn_lock);//这个逻辑可以保证只有一个进程访问//但是从汇编的角度看,是有bug的return -EAGAIN;}//释放自选锁spin_unlock(&btn_lock);return 0;
}
int btn_release (struct inode *inode, struct file *filp)
{spin_lock (&btn_lock);cnt++;spin_unlock (&btn_lock);return 0;
}
             注意,此时B进程无法访问共享资源,并不是由于获取不到锁导致的,如果获取不到,则会原地自旋等待,而不是直接返回。那么如何出现原地自旋等待的现象呢?加睡眠函数
//返回非0 用户空间打开设备就是失败的
int btn_open (struct inode *inode, struct file *filp)
{//获取自选锁spin_lock(&btn_lock);//msleep (1000*10);mdelay (1000*10);if (--cnt != 0){cnt++;spin_unlock (&btn_lock);//这个逻辑可以保证只有一个进程访问//但是从汇编的角度看,是有bug的return -EAGAIN;}//释放自选锁spin_unlock(&btn_lock);return 0;
}
     临界区代码执行起来就是比较耗时,就考虑使用信号量来保护对共享资源的访问2.4   信号量2.4.1 核心数据结构 它是基于自旋锁机制实现的,它本质上就是一个计数器struct semaphore {raw_spinlock_t     lock;unsigned int       count;struct list_head  wait_list;};    2.4.2 使用方法获取信号量执行临界区代码 访问共享资源释放信号量2.4.3 特点信号量有多个持有单元获取信号量本质上就是count 计数器减1最小减到0,已经为0 再去获取信号量(-1)就会失败获取信号量失败 睡眠等待2.4.4 具体使用步骤1) 定义信号变量struct semaphore btn_sem;2) 完成初始化sema_init (&btn_sem,1);3) 获取信号量//成功直接返回 失败调用者进入睡眠状态down (&btn_sem);//失败 调用者进入可中断的睡眠状态down_intrruptiable (&btn_sem);down_killbale//失败直接返回错误信息down_trylockdown_timeout 临界区代码 访问共享资源释放信号量 (+1)4) 释放信号量up(&btn_sem); (+1)
struct semaphore btn_sem;//返回非0 用户空间打开设备就是失败的
int btn_open (struct inode *inode, struct file *filp)
{//获取信号量down(&btn_sem);//mdelay(1000*10);msleep(3*1000);if (--cnt != 0){cnt++;//这个逻辑可以保证只有一个进程访问//但是从汇编的角度看,是有bug的return -EAGAIN;}//释放信号量up(&btn_sem);return 0;
}
int btn_release (struct inode *inode, struct file *filp)
{down(&btn_sem);cnt++;up(&btn_sem);return 0;
}//初始化信号量sema_init (&btn_sem,1);
     自旋锁和信号量的区别:1) 信号量保护的临界区 对执行时间长短无要求自旋锁保护的临界区 要求执行速度尽量快执行时间一旦过长 很容易造成另一个进程获取锁不成功原地自旋 系统性能下降2)获取自旋锁不成功 原地自旋获取信号量不成功 睡眠等待
struct semaphore btn_sem;
//返回非0 用户空间打开设备就是失败的
int btn_open (struct inode *inode, struct file *filp)
{//获取信号量 -1操作//down(&btn_sem);if(down_interruptible(&btn_sem))//被信号打断{return -EAGAIN;}return 0;
}int btn_release (struct inode *inode, struct file *filp)
{//释放信号量 +1up(&btn_sem);return 0;
}
//初始化信号量
sema_init (&btn_sem,1);

 1.设备的阻塞方式访问//有数据直接返回//无数据睡眠等待 一旦有了数据立即返回回顾recv函数:recv (sd, buf, len, flags)希望按键设备也能实现以上效果:内核中的等待队列核心数据结构:wait_queue_head_t使用步骤:1)定义等待队列头变量wait_queue_head_t  btn_wqh;2) 初始化等待队列头变量init_waitqueue_head(&btn_wqh)以上两步可以使用DECLARE_WAIT_QUEUE_HEAD(btn_wqh)替换3)无数据供用户空间读, 需要阻塞睡眠的位置调用/*condition,条件该表达式为False 执行睡眠动作该表达式为True 立即返回*/wait_event(btn_wqh, condition)wait_event_interruptible(wq, condition)4) 一旦有数据供用户空间读 唤醒睡眠的进程wake_up(&btn_wqh)wake_up_interruptible(&btn_wqh) __wait_event_interruptible{//定义并初始化了等待队列 //(链表中的一个节点,节点中记录了当前进程的新为了将来唤醒时使用)DEFINE_WAIT(__wait);//将当前进程状态设置为TASK_INTERRUPTIBLE(可中断)状态prepare_to_wait(&wq, &__wait, TASK_INTERRUPTIBLE);  //任务调度 从就绪状态的进程集合中选出一个放入cpu中执行schedule();}

vi test.c

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>int main (void)
{char val = 0;int fd = open("/dev/buttons", O_RDONLY);if (fd < 0){perror("open failed!\n");return -1;}printf("open successed!\n");while (1) {read(fd, &val, 1);printf("val = %#x\n",val);}close(fd);return 0;
}
vi btn_drv.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/delay.h>#include <linux/interrupt.h>
#include <mach/platform.h>
#include <linux/sched.h>
#include <linux/uaccess.h>
MODULE_LICENSE("GPL");typedef struct btn_desc {int irq;  //中断号char *name;//名称char val ;//按键值
}btn_desc_t;btn_desc_t buttons[]=
{{IRQ_GPIO_A_START+28, "up", 0x10},{IRQ_GPIO_B_START+30, "down", 0x20},{IRQ_GPIO_B_START+31, "left", 0x30},{IRQ_GPIO_B_START+9,  "right", 0x40},
};/*按键缓冲区*/
char key_buf = 0;
/**记录按键缓冲区是否有按键值的* =0, 无键值* =1, 有键值* */
volatile int ev_press = 0;struct cdev btn_cdev;
dev_t dev;
struct class *btn_cls = NULL;struct semaphore btn_sem;/*定义并初始化等待队列头变量*/
DECLARE_WAIT_QUEUE_HEAD(btn_wqh);
irqreturn_t btn_isr(int irq, void *dev)
{/*获取哪个按键触发的中断*/btn_desc_t *pdata = dev;/*保存键值*/key_buf = pdata->val;ev_press = 1;/*唤醒因无数据可读而睡眠的进程*/wake_up_interruptible(&btn_wqh);return IRQ_HANDLED;
}//返回非0 用户空间打开设备就是失败的
int btn_open (struct inode *inode, struct file *filp)
{//获取信号量 -1操作if (down_interruptible(&btn_sem)) {return -EAGAIN;}return 0;
}
int btn_release (struct inode *inode, struct file *filp)
{//释放信号量 +1up(&btn_sem);return 0;
}ssize_t btn_read(struct file *filp,char __user *buf, size_t len,loff_t *offset)
{int ret = 0;/**ev_press =0 意味着按键缓冲区无键值 进程睡眠=1                 有     进程继续执行* */wait_event_interruptible(btn_wqh, ev_press);ret = copy_to_user(buf, &key_buf, len);ev_press = 0;return len;}
struct file_operations btn_fops =
{.owner = THIS_MODULE,.open = btn_open,.release = btn_release,.read = btn_read,
};
int __init btn_drv_init (void)
{int i;int ret;//1.申请设备号alloc_chrdev_region (&dev, 0, 1, "btn");//2.初始化cdevcdev_init (&btn_cdev, &btn_fops);//3. 注册cdevcdev_add (&btn_cdev, dev, 1);//4. 自动生成设备文件btn_cls = class_create (THIS_MODULE, "mybuttons");device_create(btn_cls, NULL, dev, NULL, "buttons");for(i=0; i<ARRAY_SIZE(buttons); i++){ret = request_irq(buttons[i].irq,btn_isr,IRQF_TRIGGER_FALLING,buttons[i].name, &(buttons[i]));if(ret){printk("<2>"  "request irq failed!");while(i){i--;free_irq(buttons[i].irq, &(buttons[i]));}return -EAGAIN;}}//初始化信号量sema_init (&btn_sem,1);return 0;
}
void __exit btn_drv_exit (void)
{//销毁设备文件device_destroy (btn_cls, dev);class_destroy (btn_cls);//注销cdevcdev_del (&btn_cdev);//注销设备号unregister_chrdev_region (dev, 1);}module_init(btn_drv_init);
module_exit(btn_drv_exit);
 2、按键抖动1) 定时器检测每个下降沿的触发的时间,如果一个下降沿的时间超过10ms,才认为确实按下了按键。

 vi btn_drv.c
struct timer_list btn_timer;
irqreturn_t btn_isr(int irq, void *dev)
{//传递按键的参数信息btn_timer.data = (unsigned long)dev;mod_timer(&btn_timer, jiffies+HZ/10);return IRQ_HANDLED;
}
void btn_timer_func(unsigned long data)
{/*获取哪个按键触发的中断*/btn_desc_t *pdata = (btn_desc_t *)data;/*保存键值*/key_buf = pdata->val;ev_press = 1;//按键缓冲区中有键值/*唤醒因无数据可读而睡眠的进程*/wake_up_interruptible(&btn_wqh);
}init_timer (&btn_timer);
btn_timer.function = btn_timer_func;
 3. 按下和释放都关注问题1:希望按下和释放都监测 触发中断datasheet :让gpio控制器检测上升沿下降沿触发,在GPIOxDETMOD0,1,EX这三个寄存器中规定了检测的模式request_irq (xxx, btn_isr, IRQF_TRIGGER_FALLING...);request_irq (xxx, btn_isr, IRQF_TRIGGER_RISING...);//以上这种写法不可以,因为是同一个中断号request_irq (xxx, btn_isr, IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING );问题2:如何判断该键是按下触发还是释放触发的?可以在保存按键值之前, 通过读取管脚的电平状态或者是按下触发还是释放触发,gpio库函数问题3:驱动程序中判断出是哪个按键触发的而且可以判断出是按下触发的还是释放触发的。这个信息如何反馈用户空间去传不同的值到用户空间
#include <linux/init.h>
#include <linux/module.h>#include <linux/fs.h>
#include <linux/device.h>
#include <linux/cdev.h>
#include <linux/interrupt.h>
#include <mach/platform.h>
#include <linux/delay.h>
#include <linux/sched.h>
#include <linux/uaccess.h>
#include <linux/gpio.h>MODULE_LICENSE("GPL");struct timer_list btn_timer;typedef struct btn_desc{int gpio; //管脚编号int irq;  //中断号char *name;//名称char val ;//按键值
}btn_desc_t;btn_desc_t buttons[]=
{{PAD_GPIO_A + 28, IRQ_GPIO_A_START +28 ,"up", 0x10},{PAD_GPIO_B + 30, IRQ_GPIO_B_START +30,"down", 0x20},{PAD_GPIO_B + 31, IRQ_GPIO_B_START +31,"left", 0x30},{PAD_GPIO_B + 9,  IRQ_GPIO_B_START +9,"right", 0x40},
};
/*按键缓冲区*/
char key_buf = 0;
/**记录按键缓冲区是否有按键值的* =0, 无键值* =1, 有键值* */
volatile int ev_press = 0;
struct cdev btn_cdev;
struct class *cls = NULL;
dev_t  dev = 0;struct semaphore btn_sem;/*定义并初始化等待队列头变量*/
DECLARE_WAIT_QUEUE_HEAD(btn_wqh);irqreturn_t btn_isr(int irq, void *dev)
{/*传递按键描述信息*/btn_timer.data = (unsigned long)dev;/*启动定时器 从当前时间开始延时10ms*/mod_timer(&btn_timer, jiffies+HZ/100);return IRQ_HANDLED;
}void btn_timer_func(unsigned long data)
{int status = 0;/*获取哪个按键触发的中断*/btn_desc_t *pdata = (btn_desc_t *)data;//判断是按下触发还是释放触发status = gpio_get_value (pdata->gpio);/*保存键值*/key_buf = pdata->val + !!status;//两个逻辑取反保证值只有0,1ev_press = 1;/*唤醒因无数据可读而睡眠的进程*/wake_up_interruptible(&btn_wqh);
}
int btn_open(struct inode *inode, struct file *filp)
{/*获取信号量*///down(&btn_sem);if(down_interruptible(&btn_sem))//被信号打断{return -EAGAIN;}return 0;
}
int btn_release(struct inode *inode, struct file *filp)
{up(&btn_sem);return 0;
}
ssize_t btn_read(struct file *filp,char __user *buf, size_t len,loff_t *offset)
{int ret = 0;/**ev_press =0 意味着按键缓冲区无键值 进程睡眠=1                 有     进程继续执行* */wait_event_interruptible(btn_wqh, ev_press);/*将按键值返回到用户空间*/ret = copy_to_user(buf, &key_buf, len);ev_press = 0;return len;
}
struct file_operations btn_fops =
{.owner = THIS_MODULE,.open = btn_open,.release = btn_release,.read = btn_read,
};
int __init btn_drv_init(void)
{int ret = 0;int i = 0;/*申请注册设备号*/alloc_chrdev_region(&dev, 0, 1, "buttons");/*初始化cdev*/cdev_init(&btn_cdev, &btn_fops);/*注册cdev*/cdev_add(&btn_cdev, dev, 1);/*自动生成设备文件*/cls = class_create(THIS_MODULE, "buttons");device_create(cls, NULL, dev, NULL, "mybuttons");gpio_request (buttons[i].gpio, buttons[i].name);for(i=0; i<ARRAY_SIZE(buttons); i++){ret = request_irq(buttons[i].irq,btn_isr,IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,buttons[i].name, &(buttons[i]));if(ret){printk("<2>"  "request irq failed!");while(i){i--;free_irq(buttons[i].irq, &(buttons[i]));}return -EAGAIN;}}sema_init(&btn_sem, 1);init_timer(&btn_timer);btn_timer.function = btn_timer_func;return 0;
}
void __exit btn_drv_exit(void)
{int i = 0;for(; i<ARRAY_SIZE(buttons); i++){free_irq(buttons[i].irq, buttons+i);gpio_free(buttons[i].gpio);}/*销毁设备文件*/device_destroy(cls, dev);class_destroy(cls);/*注销cdev*/cdev_del(&btn_cdev);/*注销设备号*/unregister_chrdev_region(dev, 1);
}
module_init(btn_drv_init);
module_exit(btn_drv_exit);
 vi test.c
#include <stdio.h>
#include <fcntl.h>int main(void)
{int fd = 0;char val = 0;fd = open("/dev/mybuttons", O_RDONLY);if(fd < 0){perror("open failed");return -1;}printf("open successed!\n");while(1){read(fd, &val, 1);printf("val = %#x\n", val);}close(fd);return 0;
}
 4.设备的非阻塞方式访问open()----> O_NONBLOCK//默认以阻塞方式访问fd = open("/dev/mybuttons", O_RDONLY);//以非阻塞方式打开设备 后续读写未就绪时 直接返回错误,不阻塞fd = open("/dev/mybuttons", O_RDONLY|O_NONBLOCK);struct file //fs.h{const struct file_operations   *f_op;unsigned int      f_flags;}

 vi  btn_drv.c
#include <linux/init.h>
#include <linux/module.h>#include <linux/fs.h>
#include <linux/device.h>
#include <linux/cdev.h>
#include <linux/interrupt.h>
#include <mach/platform.h>
#include <linux/delay.h>
#include <linux/sched.h>
#include <linux/uaccess.h>
#include <linux/gpio.h>MODULE_LICENSE("GPL");struct timer_list btn_timer;typedef struct btn_desc{int gpio; //管脚编号int irq;  //中断号char *name;//名称char val ;//按键值
}btn_desc_t;btn_desc_t buttons[]=
{{PAD_GPIO_A + 28, IRQ_GPIO_A_START +28 ,"up", 0x10},{PAD_GPIO_B + 30, IRQ_GPIO_B_START +30,"down", 0x20},{PAD_GPIO_B + 31, IRQ_GPIO_B_START +31,"left", 0x30},{PAD_GPIO_B + 9,  IRQ_GPIO_B_START +9,"right", 0x40},
};
/*按键缓冲区*/
char key_buf = 0;
/**记录按键缓冲区是否有按键值的* =0, 无键值* =1, 有键值* */
volatile int ev_press = 0;
struct cdev btn_cdev;
struct class *cls = NULL;
dev_t  dev = 0;struct semaphore btn_sem;/*定义并初始化等待队列头变量*/
DECLARE_WAIT_QUEUE_HEAD(btn_wqh);irqreturn_t btn_isr(int irq, void *dev)
{/*传递按键描述信息*/btn_timer.data = (unsigned long)dev;/*启动定时器 从当前时间开始延时10ms*/mod_timer(&btn_timer, jiffies+HZ/100);return IRQ_HANDLED;
}void btn_timer_func(unsigned long data)
{int status = 0;/*获取哪个按键触发的中断*/btn_desc_t *pdata = (btn_desc_t *)data;//判断是按下触发还是释放触发status = gpio_get_value (pdata->gpio);/*保存键值*/key_buf = pdata->val + !!status;//两个逻辑取反保证值只有0,1ev_press = 1;/*唤醒因无数据可读而睡眠的进程*/wake_up_interruptible(&btn_wqh);
}int btn_open(struct inode *inode, struct file *filp)
{/*获取信号量*///down(&btn_sem);if(down_interruptible(&btn_sem))//被信号打断{return -EAGAIN;}return 0;
}
int btn_release(struct inode *inode, struct file *filp)
{up(&btn_sem);return 0;
}
ssize_t btn_read(struct file *filp,char __user *buf, size_t len,loff_t *offset)
{int ret = 0;//if (ev_press == 0 && 非阻塞方式访问)//       return -EAGAIN;////是不是非阻塞的方式访问的,其实上是用户空间的flag把filp->f_flags中//的某一位给清0或者置1了if (ev_press == 0 && filp->f_flags&O_NONBLOCK){return -EAGAIN;}/**ev_press =0 意味着按键缓冲区无键值 进程睡眠=1                 有     进程继续执行* */wait_event_interruptible(btn_wqh, ev_press);/*将按键值返回到用户空间*/ret = copy_to_user(buf, &key_buf, len);ev_press = 0;//按键缓冲区中没有值return len;
}
struct file_operations btn_fops =
{.owner = THIS_MODULE,.open = btn_open,.release = btn_release,.read = btn_read,
};
int __init btn_drv_init(void)
{int ret = 0;int i = 0;/*申请注册设备号*/alloc_chrdev_region(&dev, 0, 1, "buttons");/*初始化cdev*/cdev_init(&btn_cdev, &btn_fops);/*注册cdev*/cdev_add(&btn_cdev, dev, 1);/*自动生成设备文件*/cls = class_create(THIS_MODULE, "buttons");device_create(cls, NULL, dev, NULL, "mybuttons");gpio_request (buttons[i].gpio, buttons[i].name);for(i=0; i<ARRAY_SIZE(buttons); i++){ret = request_irq(buttons[i].irq,btn_isr,IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,buttons[i].name, &(buttons[i]));if(ret){printk("<2>"  "request irq failed!");while(i){i--;free_irq(buttons[i].irq, &(buttons[i]));}return -EAGAIN;}}sema_init(&btn_sem, 1);init_timer(&btn_timer);btn_timer.function = btn_timer_func;return 0;
}
void __exit btn_drv_exit(void)
{int i = 0;for(; i<ARRAY_SIZE(buttons); i++){free_irq(buttons[i].irq, buttons+i);gpio_free(buttons[i].gpio);}/*销毁设备文件*/device_destroy(cls, dev);class_destroy(cls);/*注销cdev*/cdev_del(&btn_cdev);/*注销设备号*/unregister_chrdev_region(dev, 1);
}
module_init(btn_drv_init);
module_exit(btn_drv_exit);
 vi test.c
#include <stdio.h>
#include <fcntl.h>int main(void)
{int fd = 0;char val = 0;//以非阻塞的方式打开fd = open("/dev/mybuttons", O_RDONLY|O_NONBLOCK);if(fd < 0){perror("open failed");return -1;}printf("open successed!\n");while(1){if(read(fd, &val, 1) == -1){perror("read failed!\n");sleep(1);continue;}printf("val = %#x\n", val);}close(fd);return 0;
}

按键驱动程序:

上述“上升沿下降沿都关注的代码”可以认为时按键驱动程序的第一版
1.第一版存在的问题:1.1 按键值标准化的问题:up   0x10/0x11down 0x20/0x21...1.2 按键缓冲区的问题:之前的版本,按键缓冲区只有一个字节,之所以没有暴露出问题,是因为测试程序写得好,因为应用程序只是在不断的读取键值。但是时间间隔一旦拉长,会出现丢键值的现象。在test.c的while循环中可以加延时,之后可以看到效果。本质上是一个循环队列 链式存储考虑竞态的问题


 1.3 按住不放的问题检测到下降沿之后保存键值开启另外一个新的定时器该定时器每隔一段时间就记录一次按键值直到该按键产生了释放动作为止ioctl ();//调节按键的灵敏度2. input子系统2.1 什么是input子系统就是内核中的一部分代码其中最核心的文件drivers/input/input.c2.2 input子系统的作用:linux 内核中支持了大量的硬件驱动程序发现其中有一部分硬件,只有输入没有输出例如,按键,键盘,鼠标, 触摸屏,   游戏摇杆这类硬件在实现驱动时设备号申请注册cdev的初始化注册设备文件的创建操作函数集合read使用等待队列 实现阻塞方式访问键值缓冲区 (循环等待队列)按住不放的问题linux内核的设计者将输入设备驱动程序的通用代码在内核中实现好(表现为drivers/input目录下的一系列文件)当驱动程序在实现输入设备驱动程序的时候,可以复用这部分代码驱动工程师只需要在该输入设备特有的硬件相关代码实现驱动即可完成。其作用:简化输入设备的驱动编程工作2.3 input子系统的使用方式核心数据结构:struct input_dev {const char *name;//evbit: bitmap of types of events supported by the deviceunsigned long evbit[BITS_TO_LONGS(EV_CNT)];//keybit: bitmap of keys/buttons this device hasunsigned long keybit[BITS_TO_LONGS(KEY_CNT)];                };使用步骤:1) 定义一个struct input_dev类型的变量struct input_dev *input_allocate_device(void);2) 初始化input_dev 变量3) 注册input_dev变量4) 硬件操作注册中断延时去抖保存按键值5)报告事件保存键值唤醒睡眠进程void input_event(struct input_dev *dev,unsigned int type, unsigned int code, int value)dev, 哪个输入设备报告的事件type, 报告的事件类型code, 当type=EV_KEY时,按键的编码值value, 当type = EV_KEY时, = 1 ,按下触发= 0,释放触发= 2,  按住不放6) 注销input_dev变量void input_unregister_device(struct input_dev *dev);7) 释放input_dev变量void input_free_device(struct input_dev *dev);input core 模块中支持多种事件:#define EV_SYN         0x00 //同步事件#define EV_KEY           0x01 //按键事件#define EV_REL           0x02 //相对坐标(鼠标)。。。。。。
#include <linux/module.h>
#include <linux/init.h>
#include <linux/input.h>
#include <linux/gpio.h>
#include <mach/platform.h>
#include <linux/interrupt.h>MODULE_LICENSE("GPL");
struct input_dev * btn_input = NULL;
typedef struct btn_desc
{int gpio;int irq;char *name;char code;//按键编码
}btn_desc_t;btn_desc_t buttons[] =
{{PAD_GPIO_A + 28, IRQ_GPIO_A_START + 28, "up", KEY_UP},{PAD_GPIO_B + 30, IRQ_GPIO_B_START + 30, "down", KEY_DOWN},{PAD_GPIO_B + 31, IRQ_GPIO_B_START + 31, "left", KEY_LEFT},{PAD_GPIO_B + 9,  IRQ_GPIO_B_START + 9, "right", KEY_RIGHT},};
struct timer_list btn_timer;
irqreturn_t btn_isr(int irq, void *dev)
{//传递按键信息btn_timer.data = (unsigned long)dev;//延时去除抖动mod_timer (&btn_timer, jiffies + HZ/100);return IRQ_HANDLED;
}
void btn_timer_func(unsigned long data)
{int stat = 0;//获取哪个按键触发的btn_desc_t * pdata = (btn_desc_t *)data;//判断是按下触发还是释放触发stat = gpio_get_value (pdata->gpio);//保存按键值,唤醒睡眠的进程//5. 报告事件input_event (btn_input, EV_KEY, pdata->code, !stat);//加上一个同步事件,代表报告完毕    input_event (btn_input, EV_SYN, 0, 0);
}
int __init btn_drv_init(void)
{int i = 0;int ret = 0;//1.申请input_dev变量空间btn_input = input_allocate_device ();//2.初始化input_dev变量//2.1 name赋值btn_input->name = "mybuttons";//strcpy(btn_input->name, "mybuttons");//2.2 evbit赋值 设置硬件将来要产生的事件类型set_bit (EV_SYN, btn_input->evbit);set_bit (EV_KEY, btn_input->evbit);//可以解决按住不放的问题set_bit (EV_REP, btn_input->evbit);//2.3 设置EV_KEY事件时会报告的按键的编码for (; i < ARRAY_SIZE(buttons);i++){set_bit (buttons[i].code, btn_input->keybit);}//3 注册input_dev变量ret = input_register_device (btn_input);//4 硬件操作for (i = 0; i < ARRAY_SIZE(buttons);i++){ret = request_irq (buttons[i].irq,btn_isr,IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING,buttons[i].name,&(buttons[i]));}init_timer(&btn_timer);btn_timer.function = btn_timer_func;return 0;
}
void __exit btn_drv_exit(void)
{int i = 0;//停止定时器del_timer(&btn_timer);for (;i < ARRAY_SIZE(buttons);i++){free_irq(buttons[i].irq, &(buttons[i]));}//6. 注销input_devinput_unregister_device (btn_input);//7. 释放input_dev空间input_free_device (btn_input);
}
module_init(btn_drv_init);
module_exit(btn_drv_exit);
 ls /dev/input/event*/dev/input/event0  /dev/input/event2  /dev/input/event4/dev/input/event1  /dev/input/event3insmod btn_drv.ko/dev/input/event0  /dev/input/event2  /dev/input/event4/dev/input/event1  /dev/input/event3  /dev/input/event5evnt5是自动创建出来的hexdump序号        秒      微秒    type  code   value00001c0 5ee7 54a5 3ac6 000a 0001 0067 0001 000000001d0 5ee7 54a5 3aca 000a 0000 0000 0000 000000001e0 5ee7 54a5 2700 000c 0001 0067 0000 000000001f0 5ee7 54a5 2704 000c 0000 0000 0000 0000如何确认哪个对应的是触摸屏的设备文件?hexdump /dev/input/event*cat /proc/bus/input/devices通过name = "xxx" 来判断vi test.c
#include <stdio.h>
#include <fcntl.h>
#include <linux/input.h>struct key
{unsigned long sec;//秒unsigned long usec;//微秒short  type; //事件类型short code;//按键编码unsigned int val;
}keyval;int main(void)
{int fd = open("/dev/input/event5",O_RDONLY);while (1) {read(fd, &keyval, sizeof(keyval));if (keyval.type == EV_KEY){   printf("type = %d code = %d value = %d \n", keyval.type, keyval.code, keyval.val);}   }close (fd);return 0;
}
3. 研究input子系统的代码


input.c

         input_init(void){   //只要主设备号为13,其对应的操作函数集合就是    input_fopsregister_chrdev_region(INPUT_MAJOR, "input",&input_fops);}  open ("/dev/input/event5",......);                ---------------------------------------------------struct  file xxx_filexxx_file.f_op = &input_fopssys_open    {major = 13-----> input_fopsinput_fops.open (){//根据次设备号找到该设备匹配的handlerhandler = input_table[iminor(inode) >> 5];//取得handler中的操作函数集合evdev_fopsnew_fops = fops_get(handler->fops);//xxx_file.f_op = &evdev_fops }}

linux 中的中断处理相关推荐

  1. Linux中的中断处理

    与Linux设备驱动中中断处理相关的首先是申请与释放IRQ的API request_irq()和free_irq(), request_irq()的原型为: int request_irq(unsig ...

  2. Linux 中断之中断处理浅析

    1. 中断的概念 中断是指在CPU正常运行期间,由于内外部事件或由程序预先安排的事件引起的 CPU 暂时停止正在运行的程序,转而为该内部或外部事件或预先安排的事件服务的程序中去,服务完毕后再返回去继续 ...

  3. linux中常用的头文件

    #include <linux/***.h> 是在linux-2.6.29/include/linux下面寻找源文件. #include <asm/***.h> 是在linux ...

  4. Linux 中的各种栈:进程栈 线程栈 内核栈 中断栈

    栈是什么?栈有什么作用? 首先,栈 (stack) 是一种串列形式的 数据结构.这种数据结构的特点是 后入先出 (LIFO, Last In First Out),数据只能在串列的一端 (称为:栈顶 ...

  5. linux中进程的用户管理

    linux中进程的用户管理 每个进程都拥有真实的用户.组(uid.gid),有效的用户.组(euid.egid),保存的设置用户.组(suid.sgid),还有linux中专门用于文件存储存取的用户. ...

  6. Linux中的中断管理机制

    1.中断相关基础知识介绍 1.1.中断产生背景 假设现在CPU需要去获取一个键盘的时间,如果处理器发出一个请求信号之后一直在轮询键盘的响应,由于键盘响应速度比处理器慢得多并且需要等待用户输入,这对于C ...

  7. c linux time微秒_Linux基础知识(Linux系统、Linux中的链表)

    Linux系统简介 Linux系统的结构及特点 Linux系统的结构图如下图所示: 从上图可以看出,Linux是一个典型的宏内核(一体化内核)结构.硬件系统上面时硬件抽象层,在硬件抽象层上面时内核服务 ...

  8. linux中_Linux基础知识(Linux系统、Linux中的链表)

    Linux系统简介 Linux系统的结构及特点 Linux系统的结构图如下图所示: 从上图可以看出,Linux是一个典型的宏内核(一体化内核)结构.硬件系统上面时硬件抽象层,在硬件抽象层上面时内核服务 ...

  9. 一文读懂 | Linux 中的各种栈:进程栈 线程栈 内核栈 中断栈

    点击蓝字 关注我们 因公众号更改推送规则,请点"在看"并加"星标"第一时间获取精彩技术分享 来源于网络,侵删 栈是什么?栈有什么作用? 首先,栈 (stack) ...

最新文章

  1. 2022-2028年中国密胺塑料制品行业市场研究及前瞻分析报告
  2. CMD 一条命令 执行 多条命令
  3. jieba分词_从语言模型原理分析如何jieba更细粒度的分词
  4. 第十、十一周项目五 - 摩托车继承自行车和机动车
  5. WebBrowser(超文本浏览框)控件默认使用IE9,IE10的方法
  6. hibernate 悲观锁乐观锁
  7. 我对“POST和GET的区别”的理解
  8. 【ARM】ARM汇编程序设计(四) 选择结构
  9. 2011年数据库大会纪行
  10. Linux命令gitview,使用linux的gitview命令查看文件内容
  11. 对比linux终端模式和图形模式,Linux知识-2. Linux初学(CnetOS Linux7)之切换命令模式和图形模式...
  12. 浅谈javascript函数劫持
  13. matlab中的g2(t)是什么,matlab实验1-8带答案,,
  14. 一位 90 后程序员的自述:如何从年薪 3W 到 30W
  15. matlab如何让图更清晰,matlab图片清晰度调整
  16. Mac有些网址打不开问题解决办法
  17. 普及组noip2015年问题求解——重新排列1234和根节点数为2015的二叉树最多有__个叶子节点
  18. google Map API实现地址解析
  19. 简述什么是静态测试、动态测试、黑盒测试、白盒测试、α测试、 β测试?
  20. phpstorm破解后,运行一段时间后突然有提示没有破解.

热门文章

  1. 《用两天学习光线追踪》1.项目介绍和ppm图片输出
  2. Google Earth Engine(GEE)——几何图形ee.Geometry
  3. Linux 音频开发之入门篇
  4. bison解析中lookahead前瞻工作原理
  5. linux静态网页搭建
  6. Fineplus(QQ完美助手) v0.39SP3
  7. CSS3弹性盒布局方式
  8. 网易100件事任务清单html,人生必做的100件事清单
  9. elasticsearch学习——spring整合
  10. 可快速部署的轻量级运维监控系统——WGCLOUD