select系统调用
#include <sys/select.h>
#include <sys/time.h>
int select(int maxfdp1, fd_set *readset, fd_set *writeset, fd_set *exceptset, const struct timeval *timeout);
返回:就绪描述字的正数目,0——超时,-1——出错
struct timeval {
long tv_sec;
long tv_usec;
};
这个参数有3个可能:
1. 永远等待下去,仅在有一个描述字准备好I/O时才返回,为此我们该把参数设置为空指针.
2. 等待一段固定时间,在有一个描述字准备好I/O时返回,但是不超过由该参数所指向的timeval结构中指定的秒数和微妙数.
3. 根本不等待,检查描述字后立即返回,这称为轮询,为此,该参数必须指向一个timeval结构,而且其中的定时器必须为0.
从select/poll系统调用到内核调用poll函数流程
系统调用select和poll最终会引发设备驱动中的poll函数的调用,调用流程查看源码fs/select.c
SYSCALL_DEFINE5(select, int, n, fd_set __user *, inp, fd_set __user *, outp,
d_set __user *, exp, struct timeval __user *, tvp)
{
...
ret = core_sys_select(n, inp, outp, exp, to);
...
}
core_sys_select接着调用do_select
int do_select(int n, fd_set_bits *fds, struct timespec *end_time)
{
...
poll_initwait(&table);
for (;;) {
...
for (i = 0; i < n; ++rinp, ++routp, ++rexp) {
...
for (j = 0; j < __NFDBITS; ++j, ++i, bit <<= 1) {
...
if (file) {
f_op = file->f_op;
mask = DEFAULT_POLLMASK;
if (f_op && f_op->poll) {
wait_key_set(wait, in, out, bit);
mask = (*f_op->poll)(file, wait);
}
fput_light(file, fput_needed);
if ((mask & POLLIN_SET) && (in & bit)) {
res_in |= bit;
retval++;
wait = NULL;
}
if ((mask & POLLOUT_SET) && (out & bit)) {
res_out |= bit;
retval++;
wait = NULL;
}
if ((mask & POLLEX_SET) && (ex & bit)) {
res_ex |= bit;
retval++;
wait = NULL;
}
}
}
if (res_in)
*rinp = res_in;
if (res_out)
*routp = res_out;
if (res_ex)
*rexp = res_ex;
cond_resched();
}
wait = NULL;
if (retval || timed_out || signal_pending(current))
break;
if (table.error) {
retval = table.error;
break;
}
if (end_time && !to) {
expire = timespec_to_ktime(*end_time);
to = &expire;
}
if (!poll_schedule_timeout(&table, TASK_INTERRUPTIBLE,
to, slack))
timed_out = 1;
}
poll_freewait(&table);
return retval;
}
在do_select函数中首先会调用poll_initwait,poll_initwait函数中通过init_poll_funcptr(&pwq->pt, __pollwait);给结构体struct poll_table设置qproc回调函数为__pollwait,这样当驱动中调用poll_wait函数的时候就会调用到qproc对应的回调函数__pollwait,来看看__pollwait,他其实是把wait_queue_head_t添加到poll_table对应的wait_queue_t中.
/* Add a new entry */
static void __pollwait(struct file *filp, wait_queue_head_t *wait_address,
poll_table *p)
{
struct poll_wqueues *pwq = container_of(p, struct poll_wqueues, pt);
struct poll_table_entry *entry = poll_get_entry(pwq);
if (!entry)
return;
get_file(filp);
entry->filp = filp;
entry->wait_address = wait_address;
entry->key = p->key;
init_waitqueue_func_entry(&entry->wait, pollwake);
entry->wait.private = pwq;
add_wait_queue(wait_address, &entry->wait);
}
poll_initwait之后进入一个for循环,
执行mask = (*f_op->poll)(file, wait);
这个poll需要在设备驱动中实现,具体怎么实现见后面一个小节。
并返回对应的mask表示可读、可写或其他。
在这个for循环的最后会调用poll_schedule_timeout,这里就对应到select系统调用的最后一个参数了,
如果struct timeval为NULL,那么只有当fd可操作才能被唤醒返回,否则永远阻塞在这里
如果struct timeval为某个值,fd可操作时或者超时会返回
如果struct timeval为0,那么执行完一遍poll函数便退出了for循环
执行到poll_schedule_timeout,会进入休眠状态,直到超时或者wake_up将其唤醒,这个wake_up唤醒的是之前通过poll_wait添加的wait_queue_head_t。
内核驱动中的poll接口实现
设备驱动中poll函数的原型是:
unsigned int (*poll)(struct file *filp, struct poll_table *wait);
设备驱动中poll函数的典型模板:
static unsigned int xxx_poll(struct file *filp, poll_table *wait)
{
unsigned int mask = 0;
struct xxx_dev *dev = filp->private_data;
poll_wait(filp, &dev->r_wait, wait);//加读等待队列头
poll_wait(filp, &dev->w_wait, wait);//加写等待队列头
if (...)//可读
mask = POLLIN | POLLRDNORM;//标志数据可读
if (...)//可写
mask = POLLOUT | POLLWRNORM;//标志数据可写
return mask;
}
然后在其他地方可以调用wake_up(&dev->r_wait)唤醒poll_schedule_timeout,这个对于poll_schedule_timeout中time参数为NULL即一直阻塞在内核中的情况特别管用,而对于time为某个值或者0的poll_schedule_timeout,既可以被wake_up唤醒,也可以超时自己返回。
实例
还没自己验证过
1、 非阻塞内核按键驱动。
//“irq_drv”,"irq_","irq"
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/poll.h>
#include <linux/delay.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/irq.h>
#include <asm/uaccess.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>
static DECLARE_WAIT_QUEUE_HEAD(button_waitq);//定义结构体名称为button_waitq
static struct class *irq_class;
static struct class_device *irq_class_dev;
static int ev_press = 0;
static unsigned char key_val;
struct pin_desc{
unsigned int pin;
unsigned int key_val;
};
struct pin_desc pins_desc[3] = {
{S3C2410_GPF0, 0x01},
{S3C2410_GPF2, 0x02},
{S3C2410_GPG3, 0x03},
};
static irqreturn_t irq_handle(int irq, void *dev__id){
//printk("irq = %d\n", irq);
int pinval;
struct pin_desc *pindesc = (struct pin_desc *)dev__id;
pinval = s3c2410_gpio_getpin(pindesc->pin);
if(!pinval){//按下
key_val = pindesc->key_val;
}else{//松开
key_val = 0x80 | pindesc->key_val;
}
ev_press = 1;
wake_up_interruptible(&button_waitq);
return IRQ_RETVAL(IRQ_HANDLED);//warn:返回IRQ_HANDLED
}
static unsigned irq_drv_poll(struct file *file, poll_table *wait)
{
unsigned int mask = 0;
poll_wait(file, &button_waitq, wait); // 不会立即休眠
if (ev_press)
mask |= POLLIN | POLLRDNORM;
return mask;
}
static int irq_drv_open(struct inode *inode, struct file *file)
{
printk("irq_dev read\n");
// request_irq(unsigned int irq, irq_handler_t handler, unsigned long irqflags, const char * devname, void * dev_id); dev_id随意
request_irq(IRQ_EINT0, irq_handle, IRQ_TYPE_EDGE_BOTH, "s2", &pins_desc[0]);
request_irq(IRQ_EINT2, irq_handle, IRQ_TYPE_EDGE_BOTH, "s3", &pins_desc[1]);
request_irq(IRQ_EINT11, irq_handle, IRQ_TYPE_EDGE_BOTH, "s4", &pins_desc[2]);
return 0;
}
static ssize_t irq_drv_read (struct file *file, char __user *buf, size_t count, loff_t *ppos){
if(count != 1)return -EINVAL;
wait_event_interruptible(button_waitq, ev_press);//ev_press标志(if!(ev_press)),那么一直休眠
copy_to_user(buf, &key_val, 1);//一个 char 0xff
ev_press = 0;
return 1;//warn :return the size of val
}
static ssize_t irq_drv_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)
{
printk("irq_dev write\n");
return 0;
}
static ssize_t irq_drv_release(struct inode *inode, struct file *file){
free_irq(IRQ_EINT0, &pins_desc[0]);
free_irq(IRQ_EINT2, &pins_desc[1]);
free_irq(IRQ_EINT11, &pins_desc[2]);
return 0;
}
static struct file_operations irq_drv_fops = {
.owner = THIS_MODULE, /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */
.open = irq_drv_open,
.write = irq_drv_write,
.read = irq_drv_read,
.release = irq_drv_release,
.poll = irq_drv_poll,
};
int major;
static int irq_drv_init(void)
{
major = register_chrdev(0, "irq_drv", &irq_drv_fops); // 注册, 告诉内核
if (major < 0) {
printk(" can't register major number\n");
return major;
}
irq_class = class_create(THIS_MODULE, "irq_drv");
if (IS_ERR(irq_class))
return PTR_ERR(irq_class);
irq_class_dev = class_device_create(irq_class, NULL, MKDEV(major, 0), NULL, "irq"); /* /dev/xyz */
if (IS_ERR(irq_class_dev))
return PTR_ERR(irq_class_dev);
return 0;
}
static void irq_drv_exit(void)
{
unregister_chrdev(major, "irq_drv"); // 卸载
class_device_unregister(irq_class_dev);
class_destroy(irq_class);
}
module_init(irq_drv_init);
module_exit(irq_drv_exit);
MODULE_LICENSE("GPL");
2、测试应用程序
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <poll.h>
/* irq
*/
int main(int argc, char **argv)
{
int fd;
unsigned char key_val;
int cnt = 0;
int ret;
struct pollfd fds[1];
fd = open("/dev/irq", O_RDWR);
if (fd < 0)
{
printf("can't open!\n");
exit(1);
}
fds[0].fd = fd;
fds[0].events = POLLIN;
while (1)
{
ret = poll(fds, 1, 5000);
if(ret == 0){
printf("time out!\n");
}else{
read(fd, &key_val, 1);
printf("key_Vals = 0x%x\n", key_val);
}
}
return 0;
}
Makefile
#myirq.bin
objs := $(patsubst %c, %o, $(shell ls *.c))
myarmgcc := /workspacearm/armlinuxgcc2626/bin/arm-linux-gcc
myirq.bin:$(objs)
$(myarmgcc) -o $@ $^
cp *.bin /opt/fsmini/
%.o:%.c
$(myarmgcc) -c -o $@ $<
clean:
rm -f *.bin *.o
参考文章
- Linux poll机制分析(基于内核3.10.0)
- [arm驱动]Linux内核开发之阻塞非阻塞IO—-轮询操作
- 3.