1、内核等待队列
recv (...) // 网卡收到数据该函数立即返回
// 未收到数据阻塞睡眠等待,一旦有数据立即读取
在
linux驱动程序中,可以使用等待队列来实现进程阻塞。
等待队列可以看作保存进程的容器,在阻塞进程时,将进程放入等待队列,当进程被唤醒时,从等待队列中取出进程。实际上,信号量等对进程的阻塞在内核中也依赖等待队列来实现。
核心数据结构:
'wait_queue_head_t' - 是一个线性链式存储结构
typedef struct __wait_queue_head
wait_queue_head_t;
使用步骤:
1) 定义一个等待队列头变量:
wait_queue_head_t
btn_wqh; // 定义变量
2) 初始化等待队列头:
init_waitqueue_head (&btn_wqh); // 初始化变量
init_waitqueue_head (wait_queue_head_t *q);
/* 定义并初始化等待队列头 */
#define DECLARE_WAIT_QUEUE_HEAD(name) ...
/* 定义和初始化等待队列 */
DECLARE_WAITQUEUE(name, task);
// 定义并初始化一个名称为name的等待队列,task通常被设置为代表当前进程的current指针
3) 使用:(有按键事件就返回,没有按键事件就睡眠)
/* 进入睡眠状态 */
wait_event(wq, condition);
// 当condition为真时,立即返回;否则进程进入TASK_UNINTERRUPTIBLE类型的睡眠状态,并挂在queue指定的等待队列数据链上
wait_event_interruptible(wq, condition);
// 当condition为真时,立即返回;否则进程进入TASK_INTERRUPTIBLE类型的睡眠状态,并挂在queue指定的等待队列数据链上
wait_event_timeout(wq, condition, timeout);
// 当condition为真时,立即返回;否则进程进入TASK_KILLABLE类型的睡眠状态,并挂在queue指定的等待队列数据链上
@wq:<==> btn_wqh,等待队列头变量;
@condition:条件,该表达式为true,不睡眠直接跳过该函数;
该表达式为false,让进程进入睡眠状态。
/* 唤醒 */
wake_up(x);
// 唤醒由queue指向的等待队列数据链中的所有睡眠类型的等待进程
wake_up_interruptible(x);
// 唤醒由queue指向的等待队列数据链中的所有睡眠类型为TASK_INTERRUPTIBLE的等待进程
内核中:
'current:指针,指向CPU正在执行的进程。
struct task_struct: 内核中关于进程的数据结构。
有一个学生,
数据库中就有一条学生记录;
有一个进程,内核中就有一条进程的记录;
TASK_INTERRUPTIBLE: 可中断的睡眠状态
schedule: 内核进行重新调度
/** 代码演示 - 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 <linux/uaccess.h>
#include <mach/platform.h>
#include <linux/sched.h>
MODULE_LICENSE ("GPL");
dev_t dev;
struct cdev btn_cdev;
struct class *cls = NULL;
/* 定义信号量 */
struct semaphore btn_sem;
/* 定义等待队列头 */
wait_queue_head_t btn_wqh;
int ev_PRess = 0; // 如果有按键值供用户空间读 = 1,没有值可读 = 0
char key_buf; // 按键缓冲区 == 1个字节
/* 中断数据结构 */
typedef struct btn_desc {
char key_val; // 键值
int irq; // 中断号
char* name; // 名称
} btn_desc_t;
btn_desc_t buttons[] =
{
{0x10, IRQ_GPIO_A_START + 28, "Key1"},
{0x20, IRQ_GPIO_B_START + 30, "Key2"},
{0x30, IRQ_GPIO_B_START + 31, "Key3"},
{0x40, IRQ_GPIO_B_START + 9, "Key4"},
};
int btn_open (struct inode* inode, struct file* filp)
{
/* 获取信号量 */
// 1. 获取信号量成功,返回0
// 2. 被信号打断,返回非0
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) // 新增
{
/* 有数据供用户空间读,返回数据给用户空间
无数据供用户空间读,调用者进程进入睡眠 */
wait_event_interruptible (btn_wqh, ev_press);
if ( copy_to_user (buf, &key_buf, len) )
printk ("copy_to_user failed...\n");
ev_press = 0; // 读走缓冲数据后的清0标记
return len; // 读取成功的字节个数
}
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)
{
btn_desc_t* pdata = (btn_desc_t*)dev;
/* 保存按键值 */
key_buf = pdata->key_val;
ev_press = 1;
/* 唤醒因按键值不足而睡眠的进程 */
wake_up_interruptible (&btn_wqh);
return IRQ_HANDLED;
}
int __init btn_drv_init (void)
{
int i = 0;
/* 1.申请设备号 */
alloc_chrdev_region (&dev, 0, 1, "mybtns");
/* 2.初始化cdev */
cdev_init (&btn_cdev, &btn_fops); // btn_fops ()操作函数
/* 3.注册cdev */
cdev_add (&btn_cdev, dev, 1);
/* 4.创建设备文件 */
cls = class_create (THIS_MODULE, "mybtns");
device_create (cls, NULL, dev, NULL, "btns");
/* 初始化信号量 - 只允许1个持有者 */
sema_init (&btn_sem, 1);
/* 初始化等待队列头 */
init_waitqueue_head (&btn_wqh);
/* 注册中断处理函数 - 可以处理4个按键中断事件 */
for (; i < ARRAY_SIZE (buttons); i++) {
if (request_irq (buttons[i].irq, btn_isr, IRQF_TRIGGER_FALLING,
buttons[i].name, &(buttons[i]))) {
printk ("request_irq failed!\n");
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]));
}
/* 销毁设备文件 */
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);
/** 测试代码 - test.c **/
#include <stdio.h>
#include <fcntl.h>
int main (void)
{
int fd = 0;
char key = 0;
fd = open ("/dev/btns", O_RDWR);
if (fd < 0) {
perror ("open /dev/btns failed");
return -1;
}
printf ("open /dev/btns success...\n");
/* 新增 */
while (1) {
read (fd, &key, sizeof (char));
printf ("user space key = %#x\n", key);
}
close (fd);
return 0;
}
2、延时去抖
按键抖动的延时间隔,可以用'示波器'抓波形得出。
如果小于10ms,在定时器设置就延时10ms;
假如抓出15ms,在定时器设置就延时20ms,以此类推。
/** 代码演示 - 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 <linux/uaccess.h>
#include <mach/platform.h>
#include <linux/sched.h>
MODULE_LICENSE ("GPL");
dev_t dev;
struct cdev btn_cdev;
struct class *cls = NULL;
/* 定义信号量 */
struct semaphore btn_sem;
/* 定义等待队列头 */
wait_queue_head_t btn_wqh;
int ev_press = 0; // 如果有按键值供用户空间读 = 1,没有值可读 = 0
char key_buf; // 按键缓冲区 == 1个字节
/* 定义定时器变量 */
struct timer_list btn_timer;
/* 中断数据结构 */
typedef struct btn_desc {
char key_val; // 键值
int irq; // 中断号
char* name; // 名称
} btn_desc_t;
btn_desc_t buttons[] =
{
{0x10, IRQ_GPIO_A_START + 28, "Key1"},
{0x20, IRQ_GPIO_B_START + 30, "Key2"},
{0x30, IRQ_GPIO_B_START + 31, "Key3"},
{0x40, IRQ_GPIO_B_START + 9, "Key4"},
};
int btn_open (struct inode* inode, struct file* filp)
{
/* 获取信号量 */
// 1. 获取信号量成功,返回0
// 2. 被信号打断,返回非0
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) // 新增
{
/* 有数据供用户空间读,返回数据给用户空间
无数据供用户空间读,调用者进程进入睡眠 */
wait_event_interruptible (btn_wqh, ev_press);
if ( copy_to_user (buf, &key_buf, len) )
printk ("copy_to_user failed...\n");
ev_press = 0; // 读走缓冲数据后的清0标记
return len; // 读取成功的字节个数
}
struct file_operations btn_fops =
{
.owner = THIS_MODULE,
.open = btn_open,
.release = btn_release,
.read = btn_read,
};
int interval = 100;
module_param (interval, int, 0644);
/* 中断处理函数 */
irqreturn_t btn_isr (int irq, void* dev)
{
btn_timer.data = (unsigned long)dev;
mod_timer (&btn_timer, jiffies + HZ/interval);
return IRQ_HANDLED;
}
void btn_timer_func (unsigned long data)
{
btn_desc_t* pdata = (btn_desc_t*)data; // 这里写成dev了
/* 保存按键值 */
key_buf = pdata->key_val;
ev_press = 1;
/* 唤醒因按键值不足而睡眠的进程 */
wake_up_interruptible (&btn_wqh);
}
int __init btn_drv_init (void)
{
int i = 0;
/* 1.申请设备号 */
alloc_chrdev_region (&dev, 0, 1, "mybtns");
/* 2.初始化cdev */
cdev_init (&btn_cdev, &btn_fops); // btn_fops ()操作函数
/* 3.注册cdev */
cdev_add (&btn_cdev, dev, 1);
/* 4.创建设备文件 */
cls = class_create (THIS_MODULE, "mybtns");
device_create (cls, NULL, dev, NULL, "buttons");
/* 初始化信号量 - 只允许1个持有者 */
sema_init (&btn_sem, 1);
/* 初始化等待队列头 */
init_waitqueue_head (&btn_wqh);
/* 注册中断处理函数 - 可以处理4个按键中断事件 */
for (; i < ARRAY_SIZE (buttons); i++) {
if (request_irq (buttons[i].irq, btn_isr, IRQF_TRIGGER_FALLING,
buttons[i].name, &(buttons[i]))) {
printk ("request_irq failed!\n");
return -EAGAIN;
}
}
/* 初始化timer */
init_timer (&btn_timer);
btn_timer.function = btn_timer_func;
return 0;
}
void __exit btn_drv_exit (void)
{
int i = 0;
/* 取消timer */
del_timer (&btn_timer);
/* 注销中断处理 */
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);
/** 测试代码 - test.c **/
#include <stdio.h>
#include <fcntl.h>
int main (void)
{
int fd = 0;
char key = 0;
fd = open ("/dev/buttons", O_RDWR);
if (fd < 0) {
perror ("open /dev/buttons failed");
return -1;
}
printf ("open /dev/btns success...\n");
/* 新增 */
while (1) {
read (fd, &key, sizeof (char));
printf ("user space key = %#x\n", key);
}
close (fd);
return 0;
}
3、按下和释放去抖触发中断
'IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING'
request_irq () // 可以指定双沿触发
保存按键前要判断按下触发还是释放触发
通过管脚状态来判断。
/** 代码演示 **/
#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 <linux/uaccess.h>
#include <mach/platform.h>
#include <linux/sched.h>
#include <linux/gpio.h>
MODULE_LICENSE ("GPL");
dev_t dev;
struct cdev btn_cdev;
struct class *cls = NULL;
/* 定义信号量 */
struct semaphore btn_sem;
/* 定义等待队列头 */
wait_queue_head_t btn_wqh;
int ev_press = 0; // 如果有按键值供用户空间读 = 1,没有值可读 = 0
char key_buf; // 按键缓冲区 == 1个字节
/* 定义定时器变量 */
struct timer_list btn_timer;
/* 中断数据结构 */
typedef struct btn_desc {
char key_val; // 键值
int irq; // 中断号
int gpio; // 管脚编号
char* name; // 名称
} btn_desc_t;
btn_desc_t buttons[] =
{
{0x10, IRQ_GPIO_A_START + 28, PAD_GPIO_A + 28, "Key1"},
{0x20, IRQ_GPIO_B_START + 30, PAD_GPIO_B + 30, "Key2"},
{0x30, IRQ_GPIO_B_START + 31, PAD_GPIO_B + 31, "Key3"},
{0x40, IRQ_GPIO_B_START + 9, PAD_GPIO_B + 9, "Key4"},
};
int btn_open (struct inode* inode, struct file* filp)
{
/* 获取信号量 */
// 1. 获取信号量成功,返回0
// 2. 被信号打断,返回非0
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) // 新增
{
/* 有数据供用户空间读,返回数据给用户空间
无数据供用户空间读,调用者进程进入睡眠 */
wait_event_interruptible (btn_wqh, ev_press);
if ( copy_to_user (buf, &key_buf, len) )
printk ("copy_to_user failed...\n");
ev_press = 0; // 读走缓冲数据后的清0标记
return len; // 读取成功的字节个数
}
struct file_operations btn_fops =
{
.owner = THIS_MODULE,
.open = btn_open,
.release = btn_release,
.read = btn_read,
};
int interval = 100;
module_param (interval, int, 0644);
/* 中断处理函数 */
irqreturn_t btn_isr (int irq, void* dev)
{
btn_timer.data = (unsigned long)dev;
mod_timer (&btn_timer, jiffies + HZ/interval);
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); // 获取管脚上的电平状态:0低非0高
/* 保存按键值 stat==100 ---> !stat==0 ---> !!stat==1 */
key_buf = pdata->key_val + !!stat;
ev_press = 1;
/* 唤醒因按键值不足而睡眠的进程 */
wake_up_interruptible (&btn_wqh);
}
int __init btn_drv_init (void)
{
int i = 0;
/* 1.申请设备号 */
alloc_chrdev_region (&dev, 0, 1, "mybtns");
/* 2.初始化cdev */
cdev_init (&btn_cdev, &btn_fops); // btn_fops ()操作函数
/* 3.注册cdev */
cdev_add (&btn_cdev, dev, 1);
/* 4.创建设备文件 */
cls = class_create (THIS_MODULE, "mybtns");
device_create (cls, NULL, dev, NULL, "buttons");
/* 初始化信号量 - 只允许1个持有者 */
sema_init (&btn_sem, 1);
/* 初始化等待队列头 */
init_waitqueue_head (&btn_wqh);
/* 注册中断处理函数 - 可以处理4个按键中断事件 */
for (; i < ARRAY_SIZE (buttons); i++) {
if (request_irq (buttons[i].irq, btn_isr,
IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,
buttons[i].name, &(buttons[i]))) {
printk ("request_irq failed!\n");
return -EAGAIN;
}
/* GPIO管脚的申请 */
gpio_request (buttons[i].gpio, buttons[i].name);
}
/* 初始化timer */
init_timer (&btn_timer);
btn_timer.function = btn_timer_func;
return 0;
}
void __exit btn_drv_exit (void)
{
int i = 0;
/* 取消timer */
del_timer (&btn_timer);
/* 注销中断处理 */
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);
4、用户态对设备的非阻塞方式访问
实际上,应用程序并不关心驱动里面read/write具体实现,只管调用并获取返回值。
如果设备没有准备好数据给应用程序读或者没有准备好接收用户程序写,驱动程序应当阻塞进程,使它进入睡眠,直到请求可以得到满足。
open ("/dev/xxx", O_RDWR);
// 默认是阻塞方式访问
open ("/dev/xxx", O_RDWR |
O_NONBLOCK); // 非阻塞方式进行访问。
struct file {
// open函数的系统调用从f_op指针指向了file_operations里面的open函数
const struct file_operations *f_op;
// 记录了open(..., flag)时flag的绝大多数信息
unsigned int f_flags;
};
'简言之:f_flags 记录了文件open时的打开方式。'
#ifndef O_NONBLOCK
#define O_NONBLOCK 00004000
#endif
/** 代码演示 - 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 <linux/uaccess.h>
#include <mach/platform.h>
#include <linux/sched.h>
#include <linux/gpio.h>
MODULE_LICENSE ("GPL");
dev_t dev;
struct cdev btn_cdev;
struct class *cls = NULL;
/* 定义信号量 */
struct semaphore btn_sem;
/* 定义等待队列头 */
wait_queue_head_t btn_wqh;
int ev_press = 0; // 如果有按键值供用户空间读 = 1,没有值可读 = 0
char key_buf; // 按键缓冲区 == 1个字节
/* 定义定时器变量 */
struct timer_list btn_timer;
/* 中断数据结构 */
typedef struct btn_desc {
char key_val; // 键值
int irq; // 中断号
int gpio; // 管脚编号
char* name; // 名称
} btn_desc_t;
btn_desc_t buttons[] =
{
{0x10, IRQ_GPIO_A_START + 28, PAD_GPIO_A + 28, "Key1"},
{0x20, IRQ_GPIO_B_START + 30, PAD_GPIO_B + 30, "Key2"},
{0x30, IRQ_GPIO_B_START + 31, PAD_GPIO_B + 31, "Key3"},
{0x40, IRQ_GPIO_B_START + 9, PAD_GPIO_B + 9, "Key4"},
};
int btn_open (struct inode* inode, struct file* filp)
{
/* 获取信号量 */
// 1. 获取信号量成功,返回0
// 2. 被信号打断,返回非0
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) // 新增
{
/* 判断open文件时是否加了O_NONBLOCK的打开方式 */
if ( (filp->f_flags & O_NONBLOCK) && ev_press == 0)
return -EAGAIN;
/* 有数据供用户空间读,返回数据给用户空间
无数据供用户空间读,调用者进程进入睡眠 */
wait_event_interruptible (btn_wqh, ev_press);
if ( copy_to_user (buf, &key_buf, len) )
printk ("copy_to_user failed...\n");
ev_press = 0; // 读走缓冲数据后的清0标记
return len; // 读取成功的字节个数
}
struct file_operations btn_fops =
{
.owner = THIS_MODULE,
.open = btn_open,
.release = btn_release,
.read = btn_read,
};
int interval = 100;
module_param (interval, int, 0644);
/* 中断处理函数 */
irqreturn_t btn_isr (int irq, void* dev)
{
btn_timer.data = (unsigned long)dev;
mod_timer (&btn_timer, jiffies + HZ/interval);
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); // 获取管脚上的电平状态:0低非0高
/* 保存按键值 stat==100 ---> !stat==0 ---> !!stat==1 */
key_buf = pdata->key_val + !!stat;
ev_press = 1;
/* 唤醒因按键值不足而睡眠的进程 */
wake_up_interruptible (&btn_wqh);
}
int __init btn_drv_init (void)
{
int i = 0;
/* 1.申请设备号 */
alloc_chrdev_region (&dev, 0, 1, "mybtns");
/* 2.初始化cdev */
cdev_init (&btn_cdev, &btn_fops); // btn_fops ()操作函数
/* 3.注册cdev */
cdev_add (&btn_cdev, dev, 1);
/* 4.创建设备文件 */
cls = class_create (THIS_MODULE, "mybtns");
device_create (cls, NULL, dev, NULL, "buttons");
/* 初始化信号量 - 只允许1个持有者 */
sema_init (&btn_sem, 1);
/* 初始化等待队列头 */
init_waitqueue_head (&btn_wqh);
/* 注册中断处理函数 - 可以处理4个按键中断事件 */
for (; i < ARRAY_SIZE (buttons); i++) {
if (request_irq (buttons[i].irq, btn_isr,
IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,
buttons[i].name, &(buttons[i]))) {
printk ("request_irq failed!\n");
return -EAGAIN;
}
/* GPIO管脚的申请 */
gpio_request (buttons[i].gpio, buttons[i].name);
}
/* 初始化timer */
init_timer (&btn_timer);
btn_timer.function = btn_timer_func;
return 0;
}
void __exit btn_drv_exit (void)
{
int i = 0;
/* 取消timer */
del_timer (&btn_timer);
/* 注销中断处理 */
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);
/** 测试代码 - test.c **/
#include <stdio.h>
#include <fcntl.h>
int main (void)
{
int fd = 0;
char key = 0;
/* 非阻塞方式+可读可写方式打开 */
fd = open ("/dev/buttons", O_RDWR | O_NONBLOCK);
if (fd < 0) {
perror ("open /dev/buttons failed");
return -1;
}
printf ("open /dev/btns success...\n");
while (1) {
if (read (fd, &key, sizeof (char)) > 0)
printf ("user space key = %#x\n", key);
else
perror ("read failed");
sleep (3);
}
close (fd);
return 0;
}