1、设备的阻塞方式访问
应用编程
sd=socke(...)
recv(sd, ....)//无数据阻塞等待 有数据立即返回
希望按键设备 也能达到在用户空间阻塞方式访问
内核中为了实现设备的阻塞方式访问,提供了一套机制: 等待队列
核心数据结构
wait_queue_head_t
实验步骤:
1)定义一个等待队列头变量
wait_queue_head_t btn_wqh;
2) 初始化等待队列头变量
init_waitqueue_head(&btn_wqh);
//等价于步骤1) 2)
DECLARE_WAIT_QUEUE_HEAD(btn_wqh)
3) 驱动程序中,对设备执行读写操作时
如果设备I/O为就绪 可以调用以下函数,
实现进程的阻塞
//该函数会使得调用者进程进入睡眠状态
wait_event(btn_wqh, condition)
//进入的是可中断的睡眠状态
wait_event_interruptible(btn_wqh, condition)
condition,为TRUE 直接返回 不睡眠
为FALSE,进程进入睡眠状态
4)当设备I/O就绪时
唤醒因I/O未就绪而进入睡眠状态的进程
wake_up(&btn_wqh);
wake_up_interruptible(&btn_wqh);
实现原理:
1)内核中管理进程,会为每个进程建立PCB(进程控制块)
linux中PCB在内核中对应的数据结构
struct task_struct
{
//代表了进程的当前状态
volatile long state
...
}
linux内核会为每个进程创建一个task_struct变量
该变量在内核软件中代表着该进程
2)current
始终执行正在CPU中执行的那个进程的数据结构
3) wait_event_interruptible
__wait_event_interruptible(btn_wqh, ....)
{
//创建了一个新的节点
//该节点中保存了当前进程信息(task_struct变量地址)
DEFINE_WAIT(__wait);
prepare_to_wait(&wq, &__wait, TASK_INTERRUPTIBLE)
{
//把前边创建的新节点 插入btn_wqh指向的链表中去
__add_wait_queue(q, wait);
//将当前进程的状态改为TASK_INTERRUPTIBLE(可中断的睡眠状态)
set_current_state(state);
}
//从处于running的集合中选出一个进程放入CPU中执行
//当前任务真的就放弃了CPU 进入睡眠状态
schedule();
}
4)唤醒
//btn_wqh指向的链表中有多个节点
//每个节点中记录了因某类事件而睡眠的进程
//当该类事件条件满足时
//内核会唤醒多个节点对应的多个进程
wake_up_interruptible(&btn_wqh)
2、延时去抖
1)timer_list
2) delayed_work
3、按键的按下和释放都关注
1)如何使得上升沿和下降沿都触发中断
requst_irq(..., btn_isr
IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING,
...)
2)btn_timer_func
哪个按键触发的
按下触发还是释放触发 通过读取管脚的电平状态获取
gpio_get_value(...)
练习:
./test
up 按下 输出 0x10
释放 0x11
4、设备的非阻塞方式访问
//默认为阻塞方式访问
fd = open("/dev/mybuttons", O_RDONLY);
//实现设备的非阻塞方式访问
fd = open("/dev/mybuttons", O_RDONLY|O_NONBLOCK);
btn_read(filp, buf, len, offset)
{
要实现非阻塞,关键在于要知道open设备时是否
使用了O_NONBLOCK参数
该参数信息存储在了filp->f_flags
}
struct file//文件表
{
//其中存储了用户空间open设备时的绝大部分参数信息
unsigned int f_flags;
//记录对应的操作函数集合地址
const struct file_operations *f_op;
...
}
#include <stdio.h> #include <fcntl.h> int main(void) { unsigned char key=0; int fd = open("/dev/mybuttons", O_RDONLY); if(fd < 0) { perror("open failed:"); return -1; } printf("open successed!\n"); while(1) { read(fd, &key, sizeof(key)); printf("key=%#x\n", key); } close(fd); return 0; }
#include "../../global.h" #include <linux/cdev.h> #include <linux/fs.h> #include <linux/device.h> #include <linux/delay.h> #include <linux/uaccess.h> #include <linux/interrupt.h> #include <mach/platform.h> #include <linux/sched.h> dev_t dev = 0; struct cdev btn_cdev; struct class *cls = NULL; /*定义信号量*/ struct semaphore btn_sem; /*按键键值缓冲区*/ unsigned char key_buf; /*记录按键缓冲区中是否有键值 *0,无键值 *1, 有键值 * */ volatile int ev_press = 0; /*定义等待队列头变量*/ wait_queue_head_t btn_wqh; static int btn_open(struct inode *inode, struct file *filp) { int ret = 0; //down(&btn_sem); ret = down_interruptible(&btn_sem); if(ret) //被信号打断 { return -EAGAIN; } return 0; } static int btn_release(struct inode *inode, struct file *filp) { up(&btn_sem); return 0; } static ssize_t btn_read(struct file *filp, char __user *buf, size_t len, loff_t *off) { int ret = 0; /* *按键缓冲区中有键值 直接返回 *如果无键值 让test进程进入睡眠状态 * */ wait_event_interruptible(btn_wqh, ev_press); //*buf = key_buf;//有风险 ret = copy_to_user(buf, &key_buf, len); if(ret) { return -EFAULT; } ev_press = 0; return ret; } struct file_operations btn_fops = { .owner = THIS_MODULE, .open = btn_open, .release = btn_release, .read = btn_read, }; irqreturn_t btn_isr(int irq, void *dev) { /*保存按键值*/ key_buf = 0x10; ev_press = 1;//有键值 /*唤醒因无键值而睡眠的进程*/ wake_up_interruptible(&btn_wqh); return IRQ_HANDLED; //中断处理完毕 } int __init btn_drv_init(void) { if(request_irq(IRQ_GPIO_A_START+28, btn_isr, IRQF_TRIGGER_FALLING, "up", NULL)) { printk("<1>" "request_irq failed!"); return -EAGAIN; } /*申请注册设备号*/ 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"); /*初始化信号量*/ /*该设备可以同时被一个进程访问*/ sema_init(&btn_sem, 1); /*初始化等待队列头*/ init_waitqueue_head(&btn_wqh); return 0; } void __exit btn_drv_exit(void) { /*销毁设备文件*/ device_destroy(cls, dev); class_destroy(cls); /*注销cdev*/ cdev_del(&btn_cdev); /*注销设备号*/ unregister_chrdev_region(dev, 1); free_irq(IRQ_GPIO_A_START+28, NULL); } module_init(btn_drv_init); module_exit(btn_drv_exit);
#include "../../global.h" #include <linux/cdev.h> #include <linux/fs.h> #include <linux/device.h> #include <linux/delay.h> #include <linux/uaccess.h> #include <linux/interrupt.h> #include <mach/platform.h> #include <linux/sched.h> dev_t dev = 0; struct cdev btn_cdev; struct class *cls = NULL; /*定义信号量*/ struct semaphore btn_sem; /*按键键值缓冲区*/ unsigned char key_buf; /*记录按键缓冲区中是否有键值 *0,无键值 *1, 有键值 * */ volatile int ev_press = 0; /*定义等待队列头变量*/ wait_queue_head_t btn_wqh; struct timer_list btn_timer; typedef struct btn_desc { unsigned char code;// 按键编码 int irq; char *name; }btn_desc_t; btn_desc_t buttons[]= { {0x10, IRQ_GPIO_A_START+28, "up"}, {0x20, IRQ_GPIO_B_START+30, "down"}, {0x30, IRQ_GPIO_B_START+31, "left"}, {0x40, IRQ_GPIO_B_START+9, "right"}, }; static int btn_open(struct inode *inode, struct file *filp) { int ret = 0; //down(&btn_sem); ret = down_interruptible(&btn_sem); if(ret) //被信号打断 { return -EAGAIN; } return 0; } static int btn_release(struct inode *inode, struct file *filp) { up(&btn_sem); return 0; } static ssize_t btn_read(struct file *filp, char __user *buf, size_t len, loff_t *off) { int ret = 0; /* *按键缓冲区中有键值 直接返回 *如果无键值 让test进程进入睡眠状态 * */ wait_event_interruptible(btn_wqh, ev_press); //*buf = key_buf;//有风险 ret = copy_to_user(buf, &key_buf, len); if(ret) { return -EFAULT; } ev_press = 0; return ret; } struct file_operations btn_fops = { .owner = THIS_MODULE, .open = btn_open, .release = btn_release, .read = btn_read, }; void btn_timer_func(unsigned long data) { /*获取按键的描述信息*/ btn_desc_t *pdata = (btn_desc_t *)data; /*保存按键值*/ key_buf = pdata->code; ev_press = 1;//有键值 /*唤醒因无键值而睡眠的进程*/ wake_up_interruptible(&btn_wqh); } irqreturn_t btn_isr(int irq, void *dev) { /*传递按键的描述信息*/ btn_timer.data = (unsigned long)dev; mod_timer(&btn_timer, jiffies+HZ/100); return IRQ_HANDLED; //中断处理完毕 } int __init btn_drv_init(void) { int i = 0; int ret = 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"); /*初始化信号量*/ /*该设备可以同时被一个进程访问*/ sema_init(&btn_sem, 1); /*初始化等待队列头*/ init_waitqueue_head(&btn_wqh); for(; i<ARRAY_SIZE(buttons);i++) { ret = request_irq(buttons[i].irq, btn_isr, IRQF_TRIGGER_FALLING, 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; for(; i<ARRAY_SIZE(buttons); i++) { free_irq(buttons[i].irq, buttons+i); } /*销毁设备文件*/ 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);
#include "../../global.h" #include <linux/cdev.h> #include <linux/fs.h> #include <linux/device.h> #include <linux/delay.h> #include <linux/uaccess.h> #include <linux/interrupt.h> #include <mach/platform.h> #include <linux/sched.h> #include <linux/gpio.h> dev_t dev = 0; struct cdev btn_cdev; struct class *cls = NULL; /*定义信号量*/ struct semaphore btn_sem; /*按键键值缓冲区*/ unsigned char key_buf; /*记录按键缓冲区中是否有键值 *0,无键值 *1, 有键值 * */ volatile int ev_press = 0; /*定义等待队列头变量*/ wait_queue_head_t btn_wqh; struct timer_list btn_timer; typedef struct btn_desc { unsigned char code;// 按键编码 int irq; char *name; int gpio; //管脚编号 }btn_desc_t; btn_desc_t buttons[]= { {0x10, IRQ_GPIO_A_START+28, "up",PAD_GPIO_A+28}, {0x20, IRQ_GPIO_B_START+30, "down",PAD_GPIO_B+30}, {0x30, IRQ_GPIO_B_START+31, "left",PAD_GPIO_B+31}, {0x40, IRQ_GPIO_B_START+9, "right",PAD_GPIO_B+9}, }; static int btn_open(struct inode *inode, struct file *filp) { int ret = 0; //down(&btn_sem); ret = down_interruptible(&btn_sem); if(ret) //被信号打断 { return -EAGAIN; } return 0; } static int btn_release(struct inode *inode, struct file *filp) { up(&btn_sem); return 0; } static ssize_t btn_read(struct file *filp, char __user *buf, size_t len, loff_t *off) { int ret = 0; /* *按键缓冲区中有键值 直接返回 *如果无键值 让test进程进入睡眠状态 * */ wait_event_interruptible(btn_wqh, ev_press); //*buf = key_buf;//有风险 ret = copy_to_user(buf, &key_buf, len); if(ret) { return -EFAULT; } ev_press = 0; return ret; } struct file_operations btn_fops = { .owner = THIS_MODULE, .open = btn_open, .release = btn_release, .read = btn_read, }; void btn_timer_func(unsigned long data) { int stat = 0; /*获取按键的描述信息*/ btn_desc_t *pdata = (btn_desc_t *)data; /*判断是按下触发还是释放触发 *按下 返回0 *释放 返回1 * */ stat = gpio_get_value(pdata->gpio); /*保存按键值*/ key_buf = pdata->code + stat; ev_press = 1;//有键值 /*唤醒因无键值而睡眠的进程*/ wake_up_interruptible(&btn_wqh); } irqreturn_t btn_isr(int irq, void *dev) { /*传递按键的描述信息*/ btn_timer.data = (unsigned long)dev; mod_timer(&btn_timer, jiffies+HZ/100); return IRQ_HANDLED; //中断处理完毕 } int __init btn_drv_init(void) { int i = 0; int ret = 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"); /*初始化信号量*/ /*该设备可以同时被一个进程访问*/ sema_init(&btn_sem, 1); /*初始化等待队列头*/ init_waitqueue_head(&btn_wqh); for(; 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; for(; i<ARRAY_SIZE(buttons); i++) { free_irq(buttons[i].irq, buttons+i); } /*销毁设备文件*/ 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);
#include "../../global.h" #include <linux/cdev.h> #include <linux/fs.h> #include <linux/device.h> #include <linux/delay.h> #include <linux/uaccess.h> #include <linux/interrupt.h> #include <mach/platform.h> #include <linux/sched.h> #include <linux/gpio.h> dev_t dev = 0; struct cdev btn_cdev; struct class *cls = NULL; /*定义信号量*/ struct semaphore btn_sem; /*按键键值缓冲区*/ unsigned char key_buf; /*记录按键缓冲区中是否有键值 *0,无键值 *1, 有键值 * */ volatile int ev_press = 0; /*定义等待队列头变量*/ wait_queue_head_t btn_wqh; struct timer_list btn_timer; typedef struct btn_desc { unsigned char code;// 按键编码 int irq; char *name; int gpio; //管脚编号 }btn_desc_t; btn_desc_t buttons[]= { {0x10, IRQ_GPIO_A_START+28, "up",PAD_GPIO_A+28}, {0x20, IRQ_GPIO_B_START+30, "down",PAD_GPIO_B+30}, {0x30, IRQ_GPIO_B_START+31, "left",PAD_GPIO_B+31}, {0x40, IRQ_GPIO_B_START+9, "right",PAD_GPIO_B+9}, }; static int btn_open(struct inode *inode, struct file *filp) { int ret = 0; //down(&btn_sem); ret = down_interruptible(&btn_sem); if(ret) //被信号打断 { return -EAGAIN; } return 0; } static int btn_release(struct inode *inode, struct file *filp) { up(&btn_sem); return 0; } static ssize_t btn_read(struct file *filp, char __user *buf, size_t len, loff_t *off) { int ret = 0; //if(用户非阻塞方式访问设备 && ev_press==0) if((filp->f_flags&O_NONBLOCK) && ev_press==0) { return -EAGAIN; } /* *按键缓冲区中有键值 直接返回 *如果无键值 让test进程进入睡眠状态 * */ wait_event_interruptible(btn_wqh, ev_press); //*buf = key_buf;//有风险 ret = copy_to_user(buf, &key_buf, len); if(ret) { return -EFAULT; } ev_press = 0; return ret; } struct file_operations btn_fops = { .owner = THIS_MODULE, .open = btn_open, .release = btn_release, .read = btn_read, }; void btn_timer_func(unsigned long data) { int stat = 0; /*获取按键的描述信息*/ btn_desc_t *pdata = (btn_desc_t *)data; /*判断是按下触发还是释放触发 *按下 返回0 *释放 返回1 * */ stat = gpio_get_value(pdata->gpio); /*保存按键值*/ key_buf = pdata->code + stat; ev_press = 1;//有键值 /*唤醒因无键值而睡眠的进程*/ wake_up_interruptible(&btn_wqh); } irqreturn_t btn_isr(int irq, void *dev) { /*传递按键的描述信息*/ btn_timer.data = (unsigned long)dev; mod_timer(&btn_timer, jiffies+HZ/100); return IRQ_HANDLED; //中断处理完毕 } int __init btn_drv_init(void) { int i = 0; int ret = 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"); /*初始化信号量*/ /*该设备可以同时被一个进程访问*/ sema_init(&btn_sem, 1); /*初始化等待队列头*/ init_waitqueue_head(&btn_wqh); for(; 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; for(; i<ARRAY_SIZE(buttons); i++) { free_irq(buttons[i].irq, buttons+i); } /*销毁设备文件*/ 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);