一、poll机制的作用
1.poll机制的作用
在前面的使用中断的的方式来读取按键值(linux 中断管理(四))。使用这种方式读取按键,如果按键没有按下的时候,应用程序会一直处于睡眠的状态。如果想要即使按键没有按下,在一定的时间后也能返回,要实现这种功能,可以使用
poll机制
。(select IO复用
和epoll
也可以实现这种功能,这里只写poll机制)
二、poll机制的应用编程
1.应用层函数接口
1).API:
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
2).Paramet:
fds | nfds | timeout |
---|---|---|
参数类型: struct pollfd { int fd; /* 文件描述符 / short events; / 等待的发生事件类型 / short revents; / 实际返回的事件类型 */ }; 参数描述: fds是一个结构体指针,也就是poll函数可以同时等待一个或者多个文件描述符的事件 |
参数类型: nfds_t,其实就是int型 参数描述: 用来说明poll同时监听fds的个数 |
参数类型: int 参数描述: 等于-1:永久等待 等于0:立即返回 大于0:等待超时时间,以毫秒为单位 |
3).Return:
返回值 | 描述 |
---|---|
<0 | 错误返回 |
=0 | 超时返回 |
>0 | 返回结构体中 revents 域不为 0 的文件描述符个数 |
2.应用程序
应用程序主要使用poll的方式读取按键的值,并且设置5000ms超时等待
#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <stdio.h> #include <poll.h> /* poll机制测试 */ int main(int argc, char **argv) { int fd; unsigned char key_val; int ret; /* 定义一个 struct pollfd 结构体数组*/ struct pollfd fds[1]; /* 打开一个设备文件 */ fd = open("/dev/my_button", O_RDWR); if (fd < 0) { printf("can't open!\n"); } /**************** 初始化 struct pollfd 结构体 *************/ /* 初始化文件描述符 */ fds[0].fd = fd; /* 初始化监听的事件事件 */ fds[0].events = POLLIN; while (1) { /* 调用poll函数,超时时间是5000ms */ ret = poll(fds, 1, 5000); if (ret == 0){ /* 超时返回 */ printf("time out\n"); }else if(ret<0){ /* 出错返回 */ printf("poll error\n"); }else{ /* 有数据可读,读取数据 */ /* 这里为了简单就不对返回的事件revents,做判断和重置了 */ read(fd, &key_val, 1); printf("key_val = %d\n", key_val); } } return 0; }
三、驱动程序
驱动程序的编写主要在
file_operations
的poll
成员添加一个函数接口button_poll
。下面的程序是在linux中断管理(四)更改的(不过本章节只使用了一个按键)。更改的代码如下:
#include <linux/device.h> #include <linux/interrupt.h> #include <linux/module.h> #include <linux/kernel.h> #include <linux/fs.h> #include <linux/init.h> #include <linux/delay.h> #include <linux/irq.h> #include <asm/uaccess.h> #include <asm/irq.h> #include <asm/io.h> #include <asm/arch/regs-gpio.h> #include <linux/cdev.h> #include <linux/poll.h> /* 申请一个等待队列头 */ DECLARE_WAIT_QUEUE_HEAD (button_waitq); /* 事件触发标志 */ int ev_press; /* 用一个结构体来描述一个按键,抽象的或许不是很好 */ struct but_irq_desc{ /* 中断名称 */ char *button_nane; /* 按键的状态,1松开,0按下 */ unsigned char key_state; /* 按键所接的GPIO口 */ unsigned long pin; /* 按键的中断号 */ unsigned int irq; }; struct but_irq_desc but_irq_descs={ .button_nane="button1", .key_state = 1, .pin = S3C2410_GPF0, .irq=IRQ_EINT0, }; static struct cdev *button_cdev; static struct class *button_class; struct file_operations *button_fops; /* 中断处理函数 */ static irqreturn_t button_irq(int irq, void * dev_id) { /* 获取这个按键的结构体 */ struct but_irq_desc *btndesc = (struct but_irq_desc *)dev_id; unsigned int pinval; /* 读取按键的电平 */ pinval = s3c2410_gpio_getpin(btndesc->pin); /* 如果是高电平*/ if(pinval){ /* 按键松开 */ btndesc->key_state = 1; }else{ btndesc->key_state = 0; /* 按键按下 */ } /* 唤醒该等待队列里的进程 */ wake_up_interruptible(&button_waitq); /* 将标志置1 */ ev_press = 1; return IRQ_HANDLED; } int button_open (struct inode * inode, struct file *file){ /* 注册驱动,中断为上升沿和下降沿触发 */ request_irq(but_irq_descs.irq, button_irq,\ IRQF_TRIGGER_RISING| IRQF_TRIGGER_FALLING,\ but_irq_descs.button_nane,(void*)(&but_irq_descs)); return 0; } int button_release (struct inode *inode, struct file * file){ /* 释放中断 */ free_irq(but_irq_descs.irq,(void*)(&but_irq_descs)); return 0; } /* 驱动读函数 */ ssize_t button_read(struct file *file, char __user *buf, size_t size, loff_t * offset){ unsigned char key_val; if(size!=1){ return -EINVAL; } /* 等待按键按下,如果按键按下,则这个进程会被唤醒(以后有时间系一章等待队列的源码分析) */ /* 如果ev_press等于1的时候则这个进程不会被挂起,等于0的时候这个进程才会被挂起 */ wait_event_interruptible(button_waitq, ev_press); key_val = but_irq_descs.key_state; /* 将按键值返回给应用层 */ copy_to_user(buf, &key_val, 1); /* 将标志置0将进程挂起,等待下一次唤醒 */ ev_press = 0; return 1; } /*********************本章中增加的函数****************************/ static unsigned int button_poll(struct file *file, poll_table *wait) { unsigned int res = 0; poll_wait(file, &button_waitq, wait); /************************ poll_wait函数定义如下 **********************/ /* static inline void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p) * { * if (p && wait_address) * p->qproc(filp, wait_address, p); * //本质调用的是__pollwait(filp, wait_address, p);这个函数 * } */ /***********************************************************************/ /* p->qproc 是一个函数指针,指向 __pollwait函数。 * __pollwait 做的事情也就是将当前的进程添加到 button_waitq 等待队列中。 * 这里只是添加到等待队列而已,并不会立即休眠。 * (下一章分析poll机制源码的时候会详细分析) */ /* 如果当前有按键按下,则返回POLLIN 和 POLLRDNORM事件, * 否则返回0 * 返回非0则当前的进程不会休眠,返回0当前的进程会休眠 */ if (ev_press) res = POLLIN | POLLRDNORM; /* 返回res */ return res; } /*********************本章中增加的函数****************************/ static dev_t dev_id; /* 模块入口函数 */ static int __init my_button_init(void){ /* 分配一个file_operations 结构体 */ button_fops = kmalloc(sizeof(struct file_operations), GFP_KERNEL); /* 初始化接口函数 */ button_fops->open = button_open; button_fops->release = button_release; button_fops->read = button_read; button_fops->poll = button_poll; /* 动态分配一个设备号 */ alloc_chrdev_region(&dev_id, 0, 1, "my_button"); /* 分配一个cdev结构体 */ button_cdev = cdev_alloc(); /* 将cdev结构体和 fops结构体绑定*/ cdev_init(button_cdev, button_fops); /* 将驱动注册到内核中 */ cdev_add(button_cdev, dev_id,1); /* 创建一个class */ button_class = class_create(THIS_MODULE, "my_button"); /* 根据class内容创建一个设备节点 my_button*/ class_device_create(button_class, NULL, dev_id, NULL,"my_button"); return 0; } /* 模块出口函数 */ static void __exit my_button_exit(void){ /* 销毁设别节点 */ class_device_destroy(button_class, dev_id); /* 销毁设备节点 */ class_destroy(button_class); /* 释放cdev结构体空间 */ cdev_del(button_cdev); /* 注销设备号 */ unregister_chrdev_region(dev_id,1); /* 释放fops空间 */ kfree(button_fops); } /* 声明模块入口 */ module_init(my_button_init); /* 声明模块出口 */ module_exit(my_button_exit); /* 遵循GPL协议 */ MODULE_LICENSE("GPL");
四:实验现象
1.按照前面的几章的步骤,编译驱动程序和应用程序。然后将编译好的内核模块和应用程序copy到文件系统。然后使用
insmod xxx.ko
插入内核模块,执行应用程序。
2.观察实验现象(如下图):
- 按键按下时打印:
key_val=0
- 按键松开时打印:
key_val=1
- 如果5000毫秒后,没有按键按下也没有按键松开则打印
time out
来源:https://www.cnblogs.com/gulan-zmc/p/12229159.html