1.定义一个等待队列及初始化
1)动态初始化
wait_queue_head_t wq; //全局变量
init_waitqueue_head(&wq); //安装模块时候执行了初始化
2)静态初始化
DECLARE_WAIT_QUEUE_HEAD(wq); //这句等效于上面两句
功能:建立一个等待队列,并且初始化好
参数:
wq:是一个类型wait_queue_head_t的变量,就是驱动自己定义的等待队列头。
2.等待队列睡眠
wait_event(wq, condition)
//功能:建立不可以杀进程(信号不能唤醒,效果和msleep相同)。
wait_event_interruptible(wq, condition)
//功能:它可以被信号唤醒。休眠过程中,进程可以接收信号,收到后不管条件如何,直接返回。
wait_event_timeout(wq, condition, timeout)
//功能:休眠期间效果和 wait_event ,但是有一个超时时间 ,时间到不管条件如何,直接返回。
wait_event_interruptible_timeout(wq, condition, timeout)
//功能:休眠期间效果和 wait_event_interruptible相同。区别是有超时功能,时间到不管条件如何,直接返回。
参数:
wq: 是一个类型wait_queue_head_t的变量,就是驱动自己定义的等待队列头。
Condition :可以为任何类型,通常定义为整形,值为0进入休眠,值为非0直接返回。
Timeout :超时时间。
3.等待队列的唤醒
wake_up(wq) //常用
//功能:用于唤醒各种方式进入休眠的进程,只唤醒队列上的一个进程。
wake_up_all(wq)
//功能:效果和wake_up相同,只是能唤醒队列上所有的进程。
wake_up_interruptible(wq) //常用
//功能:只能用于唤醒一个 使用wait_event_interruptible*休眠的进程。
wake_up_interruptible_all(wq)
//功能:能唤醒队列所有 使用wait_event_interruptible*休眠的进程。
参数:
wq: 是一个类型wait_queue_head_t指针。
代码例子:
驱动层;
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/miscdevice.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/gpio.h>
#include <asm/uaccess.h>
#include <linux/delay.h> //msleep
//1)第一步: 添加相关头文件
#include <linux/wait.h> //等待队列相关数据结构及函数
#include <linux/sched.h> //进程状态宏定义
//2)定义一个等待队列头变量,并且初始化
static DECLARE_WAIT_QUEUE_HEAD(wq);
//按键数量
#define BTN_SIZE 4
//按键缓冲区,'0'表示没有按键,'1'表示按下了
static char keybuf[] = {"0000"};
/*把一个当成一个对象来看待,方便编程,定义一个描述按键结构*/
struct button_desc {
int gpio; //存放io口编号
int number; //存放按键编号,根据自己需要设计,
char *name; //按键名字,随便,但是要有意义
};
/* 定义4个按键的信息 */
static struct button_desc buttons[] = {
{ EXYNOS4_GPX3(2), 0, "KEY0" },
{ EXYNOS4_GPX3(3), 1, "KEY1" },
{ EXYNOS4_GPX3(4), 2, "KEY2" },
{ EXYNOS4_GPX3(5), 3, "KEY3" },
};
init_waitqueue_head
/* 按键动作标志,在中断程序中置1,read成功复制数据后清0 */
int press = 0;
//中断服务函数
irqreturn_t key_isr(int irq, void* dev)
{
//存放按键状态
int dn = 0;
int index = 0;
//这里进行还原原来的类型
struct button_desc *bdata = (struct button_desc *)dev;
//把读取到的结果取逻辑非,因为程序设计使用正逻辑。'1'表示按下。
dn = !gpio_get_value(bdata->gpio);
//取得当前中断对应 的按键编号
index = bdata->number;
//把按键状态更新到对应的按缓冲中
keybuf[index] = dn + '0';
//输出按键提示
// printk("%s %s\r\n", bdata->name, dn ? "down" : "up");
//4) 在等待条件变成真的地方调用 wake_up*函数唤醒休眠的进程
press = 1;
wake_up_interruptible(&wq) ;//通知内核到wq这个队列头上的链表去查询每一个休眠的进程 的条件是否变成了真。
//如果进程等待的条件还没有是真,则继续休眠。
return IRQ_HANDLED;
}
static ssize_t tiny4412_read (struct file *flp,char __user *buff,size_t count,loff_t * off)
{
int ret = 0;
//用户传递0,直接返回
if(!count) {
return 0;
}
//修正参数
if(count > BTN_SIZE ) {
count = BTN_SIZE;
}
/* 没有按键动作( 按下和松开时候 )*/
if (!press) {
if (flp->f_flags & O_NONBLOCK) { //调用open("/dev/button",flags) --》flags 存放在filp->f_flags = flags
return -EAGAIN;
} else {
//3)在需要休眠的地方使用wait_event*函数进行进行。
//休眠,等待有按键动作唤醒进程。
wait_event_interruptible(wq, press);
}
}
/* 清标志 */
press = 0;
//复制数据到用户空间
ret = copy_to_user(buff, keybuf, count);
if(ret) {
printk("error:copy_to_user\r\n");
return -EFAULT;
}
return count;
}
static const struct file_operations dev_fops = {
.read = tiny4412_read,
.owner = THIS_MODULE,
};
#define LEDS_MAJOR 255 //255
#define DEVICE_NAME "mybtn"
static struct miscdevice misc = {
.minor = LEDS_MAJOR, //次设备号
.name = DEVICE_NAME,//设备名
.fops = &dev_fops, //文件操作方法
};
static int __init btn_init(void)
{
int ret;
int irq;
int i;
int flags;
flags = IRQ_TYPE_EDGE_BOTH; //设置为双边触发
for ( i = 0; i < 4 ; i++ ) {
//得到中断号
irq = gpio_to_irq(buttons[i].gpio); //keyX
//注册中断
ret = request_irq(irq, key_isr, flags, buttons[i].name, (void*)&buttons[i]);
if(ret < 0) {
break;
}
}
//如果不是全部成功,则反向注销已经注册的中断
if(ret < 0) {
for ( --i; i; i-- ) {
irq = gpio_to_irq(buttons[i].gpio); //keyX
disable_irq(irq);
free_irq(irq, (void*)&buttons[i]);
}
return ret;
}
//注册杂项设备
ret = misc_register(&misc); //注册混杂设备
return ret;
}
static void __exit btn_exit(void)
{
int i = 0;
int irq;
//注销中断
for (i = 0; i < 4; i++) {
irq = gpio_to_irq(buttons[i].gpio); //keyX
disable_irq(irq);
free_irq(irq, (void*)&buttons[i]);
}
//注销杂项设备
misc_deregister(&misc);
}
module_init(btn_init);
module_exit(btn_exit);
MODULE_LICENSE("GPL");
应用层:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h> //lseek
#include <sys/ioctl.h> //ioctl
#include <poll.h> //poll
char save_buf[10] = {"0000"}; //存放数据使用
int main(void)
{
int fd; //存放文件描述符号
int ret;
int i;
struct pollfd fds[1];
//以非阻塞方式打开
//fd = open("/dev/mybtns", O_RDWR | O_NONBLOCK );
//以读写方式进行打开,默认是阻塞方式打开的
fd = open("/dev/mybtns", O_RDWR );
if(fd < 0) {
printf("open error\r\n");
return -1;
}
//实际程序需要循环读取按键动作,然后根据动作完成不同事情
while(1) {
char cur_buf[10] = {0}; //临时存放数据使用
fds[0].fd = fd;
fds[0].events = POLLIN; //要监测读事件
//ret = poll(fds, 1, -1); // 永远阻塞直到有变化
//ret = poll(fds, 1, 0); // 非阻塞
ret = poll(fds, 1, 2000); // 2秒超时
//判断查询结果
if(ret < 0) {
perror("poll");
exit(0);
} else if(ret == 0) {
printf("timeout\r\n");
continue;
} else {
//分别判断每个fd
if(fds[0].revents & POLLIN) {
//回读当前的4个灯状态
read(fd, cur_buf, 4);
for(i = 0; i < 4; i++) {
if(cur_buf[i] != save_buf[i]) {
save_buf[i] = cur_buf[i] ; //更新当前按键状态
if(save_buf[i] == '1') {
printf("K%d press\r\n", i + 1);
} else {
printf("K%d up\r\n", i + 1);
}
printf("keys:%s\r\n", save_buf);
}
}
printf("keys:%s\r\n", save_buf);
}
}
}
//关闭文件
close(fd);
return 0;
}
1.驱动层
void poll_wait(struct file * pfile, wait_queue_head_t * wait_address, poll_table *p)
参数:
pfile:由unsigned int xxxx_poll(struct file *filp,struct poll_table_struct *wait)第一个参数传递
wait_address:上面的定义并且初始化的等待队列头wq
p:由unsigned int xxxx_poll(struct file *filp,struct poll_table_struct *wait)第二个参数传递
返回值:成功:返回设备的状态掩码( 正数):可读,可写,出错掩码;失败:负数,错误码。
返回值掩码 | 含义 |
---|---|
POLLIN | 如果设备无阻塞的读,就返回该值 |
POLLRDNORM | 通常的数据已经准备好,可以读了,就返回该值。 |
POLLERR | 如果设备发生错误,就返回该值。 |
POLLOUT | 如果设备可以无阻塞地写,就返回该值 |
POLLWRNORM | 设备已经准备好,可以写了,就返回该值。 |
设备可读,通常返回: (POLLIN | POLLRDNORM)
设备可写,通常返回: (POLLOUT | POLLWRNORM)
2.应用层
int poll(struct pollfd fd[], nfds_t nfds, int timeout)
功能:可以阻塞/非阻塞地监测多个文件的可读、 可写、 错误事件发生。 poll 函数退出后, struct pollfd 变量的fd,events 值被清零,需要重新设置, revents 变量包含了监测结果。
参数:
fd:表示被监视的文件描述符(不用申明,需要定义和初始化赋值),结构如下:
struct pollfd {
int fd; //文件描述符
short events; //请求的事件
short revents; //返回的事件
};
fd:打开文件的文件描述符
events:传入需要监测事件
revents:传出返回的事件
nfds:要监视的文件描述符的数量。
timeout:大于0 :等待指定数目的毫秒数;0 :立即返回,不阻塞进程;-1 :永远等待, 直到有任何一个监测的文件描述符发生变化
返回值:
大于0 : fd 数组中准备好读,写或出错状态的那些文件描述符号的总数量( 我们要关心这种情况)
等于0 : 超时
小于0 : 调用函数失败
返回值 | 意义 |
---|---|
POLLIN | 普通或优先级带数据可读 |
POLLRDNORM | 普通数据可读 |
POLLRDBAND | 优先级带数据可读 |
POLLPRI | 高优先级数据可读 |
POLLOUT | 普通数据可写 |
POLLWRNORM | 普通数据可写 |
POLLWRBAND | 优先级带数据可写 |
POLLERR | 发生错误 |
POLLHUP | 发生挂起 |
POLLNVAL | 描述字不是一个打开的文件 |
注意:后三个只能作为描述字的返回结果存储在 revents 中,而不能作为测试条件用于 events 中。
1.应用层 (对应设备驱动的 poll接口)
int select(int nfds,fd_set *readset, fd_set *writeset,fd_set *exceptset, struct timeval*timeout)
//exceptset 三个集合中的 fd, 所以如果想检测它们, 则需要在返回后再次添加。
参数说明:
ndfs: select 监视的文件文件描述符中值最大值+1。
readset: select 监视的可读文件描述符集合。可以传入 NULL 值,表示不关心任何文件的读变化。
writeset: select 监视的可写文件描述符集合。可以传入 NULL 值,表示不关心任何文件的写变化。
exceptset: select 监视的异常文件描述符集合。
timeout:本次 select()的超时结束时间。
时间结构定义如下:
struct timeval{
long tv_sec; /*秒 */
long tv_usec; /*微秒 */
}
返回值:
大于 0:执行成功则返回文件描述符状态已改变的个数;
等于0:代表已超过 timeout 时间, 文件描述符状态还没有发生改变;
等于-1:函数有错误发生错误原因存于 errno,此时参数 readset, writeset, exceptset 和 timeout 的值变成不可预测。
代码例子:
驱动层代码:
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/miscdevice.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/gpio.h>
#include <asm/uaccess.h>
#include <linux/delay.h> //msleep
#include <linux/poll.h> //poll接口的相关定义及函数
//1)第一步: 添加相关头文件
#include <linux/wait.h> //等待队列相关数据结构及函数
#include <linux/sched.h> //进程状态宏定义
//2)定义一个等待队列头变量,并且初始化
static DECLARE_WAIT_QUEUE_HEAD(wq);
//按键数量
#define BTN_SIZE 4
//按键缓冲区,'0'表示没有按键,'1'表示按下了
static char keybuf[] = {"0000"};
/*把一个当成一个对象来看待,方便编程,定义一个描述按键结构*/
struct button_desc {
int gpio; //存放io口编号
int number; //存放按键编号,根据自己需要设计,
char *name; //按键名字,随便,但是要有意义
};
/* 定义4个按键的信息 */
static struct button_desc buttons[] = {
{ EXYNOS4_GPX3(2), 0, "KEY0" },
{ EXYNOS4_GPX3(3), 1, "KEY1" },
{ EXYNOS4_GPX3(4), 2, "KEY2" },
{ EXYNOS4_GPX3(5), 3, "KEY3" },
};
/* 按键动作标志,在中断程序中置1,read成功复制数据后清0 */
int press = 0;
//中断服务函数
irqreturn_t key_isr(int irq, void* dev)
{
//存放按键状态
int dn = 0;
int index = 0;
//这里进行还原原来的类型
struct button_desc *bdata = (struct button_desc *)dev;
//把读取到的结果取逻辑非,因为程序设计使用正逻辑。'1'表示按下。
dn = !gpio_get_value(bdata->gpio);
//取得当前中断对应 的按键编号
index = bdata->number;
//把按键状态更新到对应的按缓冲中
keybuf[index] = dn + '0';
//输出按键提示
// printk("%s %s\r\n", bdata->name, dn ? "down" : "up");
//4) 在等待条件变成真的地方调用 wake_up*函数唤醒休眠的进程
press = 1;
wake_up_interruptible(&wq) ;//通知内核到wq这个队列头上的链表去查询每一个休眠的进程 的条件是否变成了真。
//如果进程等待的条件还没有是真,则继续休眠。
return IRQ_HANDLED;
}
static ssize_t tiny4412_read (struct file *flp, char __user *buff,size_t count,loff_t * off)
{
int ret = 0;
//用户传递0,直接返回
if(!count) {
return 0;
}
//修正参数
if(count > BTN_SIZE ) {
count = BTN_SIZE;
}
/* 没有按键动作( 按下和松开时候 )*/
if (!press) {
if (flp->f_flags & O_NONBLOCK) { //调用open("/dev/button",flags) --》flags 存放在filp->f_flags = flags
return -EAGAIN;
} else {
// while(press == 0) { //这个循环中不能是独占类型代码,否则进程会死循环。 不能是while(press==0);
// msleep(5); //休眠,进程放弃CPU,这种休眠后不可被信号中断。
// }
//3)在需要休眠的地方使用wait_event*函数进行进行。
//休眠,等待有按键动作唤醒进程。
wait_event_interruptible(wq, press);
}
}
/* 清标志 */
press = 0;
//复制数据到用户空间
ret = copy_to_user(buff, keybuf, count);
if(ret) {
printk("error:copy_to_user\r\n");
return -EFAULT;
}
return count;
}
//轮询接口
//这个函数执行时候不会引起阻塞,
unsigned int tiny4412_poll (struct file *pfile, struct poll_table_struct *wait)
{
unsigned int mask = 0; //一定要初始化为0,因为mask是局部变量
//1)调用 poll_wait 把当前进程添加到等待队列中
poll_wait(pfile, &wq, wait); //这个函数不会引起进程的阻塞
//2)返回设备状态掩码(是否可读可写标志)
if(press) {
mask = POLLIN | POLLRDNORM;
}
return mask;
}
static const struct file_operations dev_fops = {
.read = tiny4412_read,
.poll = tiny4412_poll,
.owner = THIS_MODULE,
};
#define LEDS_MAJOR 255 //255
#define DEVICE_NAME "mybtns"
static struct miscdevice misc = {
.minor = LEDS_MAJOR, //次设备号
.name = DEVICE_NAME,//设备名
.fops = &dev_fops, //文件操作方法
};
static int __init btn_init(void)
{
int ret;
int irq;
int i;
int flags;
flags = IRQ_TYPE_EDGE_BOTH; //设置为双边触发
for ( i = 0; i < 4 ; i++ ) {
//得到中断号
irq = gpio_to_irq(buttons[i].gpio); //keyX
//注册中断
ret = request_irq(irq, key_isr, flags, buttons[i].name, (void*)&buttons[i]);
if(ret < 0) {
break;
}
}
//如果不是全部成功,则反向注销已经注册的中断
if(ret < 0) {
for ( --i; i; i-- ) {
irq = gpio_to_irq(buttons[i].gpio); //keyX
disable_irq(irq);
free_irq(irq, (void*)&buttons[i]);
}
return ret;
}
//注册杂项设备
ret = misc_register(&misc); //注册混杂设备
return ret;
}
static void __exit btn_exit(void)
{
int i = 0;
int irq;
//注销中断
for (i = 0; i < 4; i++) {
irq = gpio_to_irq(buttons[i].gpio); //keyX
disable_irq(irq);
free_irq(irq, (void*)&buttons[i]);
}
//注销杂项设备
misc_deregister(&misc);
}
module_init(btn_init);
module_exit(btn_exit);
MODULE_LICENSE("GPL");
应用层代码:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h> //lseek
#include <sys/ioctl.h> //ioctl
#include <poll.h> //poll
//select
#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#define poll_or_select 0//poll:1;select:0
char save_buf[10] = {"0000"}; //存放数据使用
int main(void)
{
int fd; //存放文件描述符号
int ret;
int i;
#if poll_or_select
struct pollfd fds[1];
#else
fd_set readset ; //定义监测读集合
struct timeval timeout;
#endif
//以非阻塞方式打开
//fd = open("/dev/mybtns", O_RDWR | O_NONBLOCK );
//以读写方式进行打开,默认是阻塞方式打开的
fd = open("/dev/mybtns", O_RDWR );
if(fd < 0) {
printf("open error\r\n");
return -1;
}
//实际程序需要循环读取按键动作,然后根据动作完成不同事情
while(1) {
char cur_buf[10] = {0}; //临时存放数据使用
#if poll_or_select
fds[0].fd = fd;
fds[0].events = POLLIN; //要监测读事件
//ret = poll(fds, 1, -1); // 永远阻塞直到有变化
//ret = poll(fds, 1, 0); // 非阻塞
ret = poll(fds, 1, 2000); // 2秒超时
#else
//必须每重新设备,因为每次select返回时。timeout变成值会清成为0
timeout.tv_sec = 2;
timeout.tv_usec = 0;
//一般在重新添加监测对象时候前需要前0全部fd
FD_ZERO(&readset); //清集合
//必须每次重新添加要监测的对象,因为每次select返回时。readset变成部分值会清成为0
FD_SET(fd, &readset); //添加监测对象到集合
//永远阻塞直到有文件状态发生变化
ret = select(fd+1,&readset,NULL,NULL,NULL);
//2秒超时
//ret = select(fd + 1, &readset, NULL, NULL, &timeout);
#endif
//判断查询结果
if(ret < 0) {
perror("poll"); perror("select");
exit(0);
} else if(ret == 0) {
printf("timeout\r\n");
continue;
} else {
//分别判断每个fd
#if poll_or_select
if(fds[0].revents & POLLIN)
#else
if(FD_ISSET(fd, &readset))
#endif
{
//回读当前的4个灯状态
read(fd, cur_buf, 4);
for(i = 0; i < 4; i++) {
if(cur_buf[i] != save_buf[i]) {
save_buf[i] = cur_buf[i] ; //更新当前按键状态
if(save_buf[i] == '1') {
printf("K%d press\r\n", i + 1);
} else {
printf("K%d up\r\n", i + 1);
}
printf("keys:%s\r\n", save_buf);
}
}
printf("keys:%s\r\n", save_buf);
}
}
}
//关闭文件
close(fd);
return 0;
}