驱动等待队列,poll和select编程

锺离逸春
2023-12-01

一、等待队列

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;
}

二、poll接口

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 中。

三、select函数

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;
}
 类似资料: