linux编程过程中需要对多个文件描述符进行读,写,异常等进行监听。目前提供的方法有select,poll,epoll等。
1.select的原理
把需要进行监听文件描述符fd进行分类,并形成一个集合叫做文件描述符集合,文件描述符一般分为读文件描述符集合,写文件描述符集合,异常文件描述符集合,然后再对其进行监听。select会对监听文件描述符集合中的进行轮询查找符合该类文件描述符集合的操作并且返回,否则轮询一遍后没有找打可以进行操作的fd便休眠阻塞等待。例如有slect对如下文件描述符集合进行了监听:
读文件描述符集合:fd1,fd2
写文件描述符集合:fd3,fd4
异常文件描述符集合:fd5,fd6
那么当fd1,fd2文件描述符任意一个fd可以读时都会返回,或者当fd3,fd4文件描述符任意一个fd可以写时都会返回,又或者当fd1,fd2文件描述符任意一个fd发生异常时都会返回.但是fd1,fd2文件描述符任意一个发生可以写时并不会返回,因为fd1,fd2并没有添加到写文件描述符集合中去,那么select也不会对其进行写监听,所以就不会返回。
借助宋宝华老师驱动设备编程中的生动形象图:
2.select方法
api:
int select(int numfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds,struct timeval *timeout);
//numfds:为监听的文件描述符中fd的最高值+1
//readfds: 监听的读文件描述符集合(也就是所有读所有fd集合)
//writefds:监听的写文件描述符集合(也就是所有写fd集合)
//exceptfds:监听的异常文件描述符集合(也就是所有异常fd集合
//timeout:监听过程中,等待的监听的读写,异常文件描述符fd超时时间,单位为毫秒,当在timeout的时间内监听的fd没有可读写,异常发生的话便休眠等待,等超过timeout依然没有,那么久返回。
/*返回值
0:就绪描述字的正数目,即有多少个fd文件描述符可以操作
-1:出错
0 :超时
*/
FD_ZERO(fd_set *set)//清除一个文件描述符集合
FD_SET(int fd,fd_set *set)//将一个文件描述符添加到一个文件描述符集合中
FD_CLEAR(int fd,fd_set *set)//将一个文件描述符从一个文件描述符集合中移除
FD_ISSET(int fd,fd_set *set);//判断文件描述集合set中的文件描述符是否被置位,置位代表可以读,或者写,或者异常操作。
3.设备驱动的轮询编程
为了配合系统调用中的select轮询操作,我们需要对我们的设备进行轮询编程,而对应的是我们文件操作集合中的poll接口。
其中的原理是L:
当系统调用的select时,会通过系统陷入内核调用
(1)先依次调用fd对应的struct file.f_op->poll()方法(如果有提供实现的话),尝试检查每个提供待检测IO的fd是否已经有IO事件就绪
(2)如果已经有IO事件就绪,则直接所收集到的IO事件返回,本次调用结束
(3)如果暂时没有IO事件就绪,则根据所给定的超时参数,选择性地进入等待
(4)如果超时参数指示不等待,则本次调用结束,无IO事件返回
(5)如果超时参数指示等待(等待一段时间或持续等待),则将当前select/poll/epoll的调用任务挂起
(6)当所检测的fd任何一个有新的IO事件发生时,会将上述的处于等待的任务唤醒。任务被唤醒之后,重新执行1中的IO事件收集过程,将此时收集到的IO事件返回,本次的调用过程结束。
因此一般我们的设备驱动的轮询编程模板如下:
static unsigned int xxx_poll(sturct file *filp,poll_table *wait)
{
unsigned int mask = 0;
struct xxx_dev *dev = filp->private_data;
...
poll_wait(filp,&dev->r_wart,wait);
poll_wait(filp,&dev->w_wait,wait);
if(r_condition)
mask |=POLLIN |POLLRDNORM;//可读
if(w_condition)
mask |=POLLOUT|POLLWRNORM;//可写
...
ruturn mask;
}
poll_wait作用主要是将两个作用:
(1)将select调用的本进程添加到&dev->r_wait(读队列头)中。
(2)将dev->r_wait(读队列头)等进程信息添加到poll_table类型的wait中,主要是管理每个进程 的状态,好让等设备驱动iIO就绪时可以通过队列头可以唤醒在队列头上的所有进程,从上面得知,因select调用而休眠的进程会将其本身添加到队列头中,因此调用select的进程会被唤醒返回。
(3)注意poll_wait并不会阻塞,而是直接返回该设备驱动的状态是否可读可写,好让select系统调用的时候轮询所有fd设备驱动的io状态进行轮询判断,当所有的设备驱动io都没有就绪的时候才会进行阻塞,阻塞的时候应该在系统调用的时候,而不是在poll调用的时候。
(4)设备驱动中,poll各种宏定义
include/uapi/asm-generic/poll.h
#define POLLIN 0x0001
#define POLLPRI 0x0002
#define POLLOUT 0x0004
#define POLLERR 0x0008
#define POLLHUP 0x0010
#define POLLNVAL 0x0020
/* The rest seem to be more-or-less nonstandard. Check them! */
#define POLLRDNORM 0x0040
#define POLLRDBAND 0x0080
#ifndef POLLWRNORM
#define POLLWRNORM 0x0100
#endif
#ifndef POLLWRBAND
#define POLLWRBAND 0x0200
#endif
#ifndef POLLMSG
#define POLLMSG 0x0400
#endif
#ifndef POLLREMOVE
#define POLLREMOVE 0x1000
#endif
#ifndef POLLRDHUP
4.实例
(1)设备驱动的轮询
参考宋宝华老师的linux设备驱动编程中/dev/globalmem实例编写了设备驱动的轮询xxx_poll
#include "globalmem.h"
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/types.h>
#include <linux/completion.h>
#include <linux/wait.h>
#include <linux/signal.h>
#include <linux/sched/signal.h>
//#include <asm/poll.h>
#include <linux/poll.h>
#define GLOBALMEM_BUFFER_SIZE 50
#define GLOBALMEM_MAJOR 255
struct globalmem_dev
{
dev_t devno;
struct cdev dev;
unsigned char globalmem_buf[GLOBALMEM_BUFFER_SIZE];
struct mutex globalmem_mutex;
wait_queue_head_t r_wait;
wait_queue_head_t w_wait;
unsigned long current_len;
//struct completion globalmem_complet;
};
struct globalmem_dev *globalmem_devp = NULL;
static int globalmem_open(struct inode *inode, struct file *filp)
{
if(!globalmem_devp)
return -ENODEV;
filp->private_data = globalmem_devp;
return 0;
}
static ssize_t globalmem_read(struct file *filp, char __user *buf, size_t size, loff_t *ppos)
{
int ret = 0;
unsigned long count = size;
struct globalmem_dev *dev = filp->private_data;
mutex_lock(&dev->globalmem_mutex);
if(dev->current_len == 0){
//判断上层访问是否使用不阻塞的标志,不阻塞直接返回再次访问错误。
if(filp->f_flags & O_NONBLOCK){
ret = -EAGAIN;
goto out;
}
//解除互斥量是因为在写进程的时候也会有用到&dev->globalmem_mutex,假如读线程阻塞的时候,不释放dev->globalmem_mutex,
//那么写进程无法持有dev->globalmem_mutex进入写进程资源访问,那么无法访问写进程资源,current_len一直为空,就无法唤醒读进程。
//这样会进入死锁状态。
mutex_unlock(&dev->globalmem_mutex);
wait_event_interruptible(dev->r_wait,dev->current_len!=0);//当currentcurrent!=0不满足阻塞
mutex_lock(&dev->globalmem_mutex);
//判断是否条件满足,不满足被唤醒可能是信号意外唤醒
if(dev->current_len == 0){
ret = -ERESTARTSYS;
goto out;
}
}
//开始访问设备资源
if(count > dev->current_len)
count = dev->current_len;
if(copy_to_user(buf,dev->globalmem_buf,count)){
ret = -EFAULT;
goto out;
}else{
memcpy(&dev->globalmem_buf,&dev->globalmem_buf+count,GLOBALMEM_BUFFER_SIZE-count);
dev->current_len -= count;
wake_up_interruptible(&dev->w_wait);
ret = count;
}
//访问设备资源结束
out:
mutex_unlock(&dev->globalmem_mutex);
return ret;
}
static ssize_t globalmem_write(struct file *filp, const char __user *buf, size_t size, loff_t *ppos)
{
int ret = 0;
unsigned long count = size;
struct globalmem_dev *dev = filp->private_data;
DECLARE_WAITQUEUE(wait,current);
mutex_lock(&dev->globalmem_mutex);
add_wait_queue(&dev->w_wait,&wait);
while(dev->current_len == GLOBALMEM_BUFFER_SIZE){
if(filp->f_flags & O_NONBLOCK){
ret = -EAGAIN;
goto out;
}
__set_current_state(TASK_INTERRUPTIBLE);
mutex_unlock(&dev->globalmem_mutex);
schedule();
if(signal_pending(current)){
ret = -ERESTARTSYS;
goto out2;
}
mutex_lock(&dev->globalmem_mutex);
}
if(count > GLOBALMEM_BUFFER_SIZE -dev->current_len)
count = GLOBALMEM_BUFFER_SIZE - dev->current_len;
if(copy_from_user(dev->globalmem_buf+dev->current_len,buf,count)){
ret = -EFAULT;
}else{
dev->current_len += count;
wake_up_interruptible(&dev->r_wait);
ret = count;
}
out:
mutex_unlock(&dev->globalmem_mutex);
out2:
remove_wait_queue(&dev->w_wait,&wait);
__set_current_state(TASK_RUNNING);
return ret;
}
static unsigned int globalmem_poll(struct file *filp, struct poll_table_struct *poll_table){
struct globalmem_dev *dev = filp->private_data;
unsigned int mask = 0;
if(!dev){
return -ENODEV;
}
mutex_lock(&dev->globalmem_mutex);
poll_wait(filp,&dev->r_wait,poll_table);
poll_wait(filp,&dev->w_wait,poll_table);
if(dev->current_len != 0)
mask |= POLLIN | POLLRDNORM;
if(dev->current_len != GLOBALMEM_BUFFER_SIZE)
mask |= POLLOUT | POLLWRNORM;
mutex_unlock(&dev->globalmem_mutex);
return mask;
}
struct file_operations globalmem_fops = {
.open = globalmem_open,
.read = globalmem_read,
.write = globalmem_write,
.poll = globalmem_poll
};
static int __init globalmem_init(void)
{
int ret ;
globalmem_devp = (struct globalmem_dev *)kzalloc(sizeof(struct globalmem_dev),GFP_KERNEL);
if(!globalmem_devp)
return -ENOMEM;
globalmem_devp->devno = MKDEV(GLOBALMEM_MAJOR,0);
if(GLOBALMEM_MAJOR)
ret = register_chrdev_region(globalmem_devp->devno,1,"globalmem");
else
ret = alloc_chrdev_region(&globalmem_devp->devno,0,1,"globalmem");
if(ret < 0)
return ret;
cdev_init(&globalmem_devp->dev,&globalmem_fops);
globalmem_devp->dev.owner = THIS_MODULE;
ret = cdev_add(&globalmem_devp->dev,globalmem_devp->devno,1);
if(ret < 0 ){
unregister_chrdev_region(globalmem_devp->devno,1);
kfree(globalmem_devp);
return ret;
}
globalmem_devp->current_len = 0;
mutex_init(&globalmem_devp->globalmem_mutex);
init_waitqueue_head(&globalmem_devp->r_wait);
init_waitqueue_head(&globalmem_devp->w_wait);
//init_completion(&globalmem_devp->globalmem_complet);
return 0;
}
static void __exit globalmem_exit(void)
{
cdev_del(&globalmem_devp->dev);
unregister_chrdev_region(globalmem_devp->devno,1);
kfree(globalmem_devp);
}
module_init(globalmem_init);
module_exit(globalmem_exit);
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("gentle");
(2)用户空间的调用
#include <stdio.h>
#include <sys/select.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/types.h>
#include <unistd.h>
#define DEV_NAME "/dev/globalmem"
int main(int argc, char const *argv[])
{
int fd;
int ret;
int count = 100;
fd_set w_set,r_set;
//(1)open fd
fd = open(DEV_NAME,O_RDWR |O_NONBLOCK);
if(fd < 0){
printf("open %s failed.\n",DEV_NAME );
return -1;
}
//(2)clear and set fd_set
FD_ZERO(&w_set);
FD_ZERO(&r_set);
FD_SET(fd,&w_set);
FD_SET(fd,&r_set);
while(count--){
//(3)monitor fd_set by select
ret = select(fd+1,&r_set,&w_set,NULL,NULL);
//(4)judge what fd happened
if(ret > 0){
if(FD_ISSET(fd,&r_set))
printf("dev can be read\n");
if(FD_ISSET(fd,&w_set))
printf("dev can be written\n");
}
sleep(1);
}
close(fd);
return 0;
}