1.相关函数
poll测试值
常量 | 说明 | 备注 |
---|---|---|
POLLIN | 数据可读 | |
POLLRDNORM | 普通数据可读 | |
POLLRDBAND | 带数据可读 | |
POLLPRI | 高优先级数据可读 | |
POLLOUT | 数据可写 | |
POLLWRNORM | 普通数据可写 | |
POLLWRBAND | 带数据可写 | |
错误常量 | ||
POLLERR | 发生错误 | |
POLLHUP | 发生挂起 | |
POLLNVAL | 描述字不是一个打开的文件 | |
rtos | ||
POLLMASK_DEFAULT | POLLIN+POLLOUT+POLLRDNORM+POLLWRNORM |
int poll(struct pollfd *fds, nfds_t nfds, int timeout)
{
int num;
struct rt_poll_table table;
poll_table_init(&table);
num = poll_do(fds, nfds, &table, timeout);
poll_teardown(&table);
return num; //返回满足条件的设备数量
}
static void poll_table_init(struct rt_poll_table *pt)
{
pt->req._proc = _poll_add; //挂接处理函数
pt->triggered = 0;
pt->nodes = RT_NULL; //记录满足条件的设备
pt->polling_thread = rt_thread_self(); //记录执行poll查询的线程(一般为本身)
}
退出条件:
- 监听的事件发生
- 超时
static int poll_do(struct pollfd *fds, nfds_t nfds, struct rt_poll_table *pt, int msec)
{
int num;
int istimeout = 0; //超时标志,也是退出查询的条件之一
int n;
struct pollfd *pf;
if (msec == 0)
{
pt->req._proc = RT_NULL;
istimeout = 1;
}
while (1)
{
pf = fds;
num = 0;
for (n = 0; n < nfds; n ++)
{
if (do_pollfd(pf, &pt->req))
{
num ++;
pt->req._proc = RT_NULL; 如果有一个设备符合监听条件,则清除之后设备的_poll_add的操作,因为poll的目的已达到
}
pf ++;
}
pt->req._proc = RT_NULL;
if (num || istimeout) //只要有一个满足条件的设备,即退出
break;
if (poll_wait_timeout(pt, msec)) //挂起
istimeout = 1;
}
return num;
}
每次查询一个设备fd的监听结果
函数内会根据fd查找相应设备,并暂时获取设备使用权
如果设备的驱动未实现poll函数,则会返回默认的POLLMASK_DEFAULT,保证使用poll的线程不会永久挂起
static int do_pollfd(struct pollfd *pollfd, rt_pollreq_t *req)
{
int mask = 0;
int fd;
fd = pollfd->fd;
if (fd >= 0)
{
struct dfs_fd *f = fd_get(fd); //根据fd查找对应设备,获取使用权
mask = POLLNVAL;
if (f)
{
mask = POLLMASK_DEFAULT; //如果没有定义poll函数,则由这里保证使用poll的线程不会永久挂起
if (f->fops->poll)
{
req->_key = pollfd->events | POLLERR | POLLHUP;
mask = f->fops->poll(f, req); //调用底层驱动实现的fops.poll()函数 //尽可能在底层使用清除而不是重新赋值
}
/* Mask out unneeded events. */
mask &= pollfd->events | POLLERR | POLLHUP;
fd_put(f); //交回设备使用权
}
}
pollfd->revents = mask;
return mask;
}
本函数一定会挂起poll线程
注意,poll的超时时间不能是-1,即不能永久阻塞!
static int poll_wait_timeout(struct rt_poll_table *pt, int msec)
{
rt_int32_t timeout;
int ret = 0;
struct rt_thread *thread;
rt_base_t level;
thread = pt->polling_thread;
timeout = rt_tick_from_millisecond(msec);
level = rt_hw_interrupt_disable();
if (timeout != 0 && !pt->triggered)
{
rt_thread_suspend(thread); //主动挂起poll的线程
if (timeout > 0)
{
rt_timer_control(&(thread->thread_timer),
RT_TIMER_CTRL_SET_TIME,
&timeout);
rt_timer_start(&(thread->thread_timer));
}
rt_hw_interrupt_enable(level);
rt_schedule();
level = rt_hw_interrupt_disable();
}
ret = !pt->triggered;
rt_hw_interrupt_enable(level);
return ret; //0,监听到事件发生/1,未监听到指定事件发生
}
总结:
通过以上函数分析可以发现:
- 应用线程在调用
poll()
时,如果do_pollfd()
监听到事件发生,则立即清除req->_proc
; - 如果没有监听到,无论是否设置了超时时间,都会在驱动中添加到等待队列中,等待异步唤醒(需要在驱动中调用
req->_proc()
); - 如果没有监听到,同时设置了超时时间,则会在所有设备轮询完成后再
poll_do()
中挂起poll线程;
@rt_timer_start()
RT_ASSERT(timer->init_tick < RT_TICK_MAX / 2);
- poll的超时时间不能设置成-1,即永久阻塞(因为poll中调用timer,timer不允许设置过大的超时时间,);
此函数需要在驱动fops->poll()
中调用,调用方式req->_proc(wq, req);
此函数的主要是挂接唤醒函数,用于异步唤醒。
static void _poll_add(rt_wqueue_t *wq, rt_pollreq_t *req)
{
struct rt_poll_table *pt;
struct rt_poll_node *node;
node = (struct rt_poll_node *)rt_malloc(sizeof(struct rt_poll_node));
if (node == RT_NULL)
return;
pt = rt_container_of(req, struct rt_poll_table, req);
node->wqn.key = req->_key; //poll的events,监听的事件,do_pollfd中首次监听时被设置
rt_list_init(&(node->wqn.list));
node->wqn.polling_thread = pt->polling_thread;
node->wqn.wakeup = __wqueue_pollwake; //挂接唤醒函数
node->next = pt->nodes; //链接到poll_table中,单向带头节点的链表
node->pt = pt;
pt->nodes = node;
rt_wqueue_add(wq, &node->wqn); //添加到队列中,用于线程间共享
}
此函数需要在驱动中调用,调用方式rt_wqueue_wakeup( &(dfs->wait_queue), (void *)POLLIN );
rt_wqueue_wakeup()
中,在函数中会调用wakeup函数
注意,rt_wqueue_wakeup()
会执行这个等待队列多有节点的wakeup函数!
//poll.c
static int __wqueue_pollwake(struct rt_wqueue_node *wait, void *key)
{
struct rt_poll_node *pn;
# if (key && !((rt_ubase_t)key & wait->key)) //key继承自rt_wqueue_wakeup()中的key参数
return -1;
pn = rt_container_of(wait, struct rt_poll_node, wqn);
pn->pt->triggered = 1; //置触发标志,用于唤醒后poll设置监听结果
return __wqueue_default_wake(wait, key); //rt源码中__wqueue_default_wake返回0
}
@waitqueue.c
int __wqueue_default_wake(struct rt_wqueue_node *wait, void *key)
{
return 0;
}
poll会为每一个没有监听到的设备创建一个rt_wqueue_node
类型的等待节点
同一个个poll下的不同设备只有key不同
struct rt_wqueue_node
{
rt_thread_t polling_thread; //未使用!
rt_list_t list; //用于链接成链表
rt_wqueue_func_t wakeup; //唤醒函数,同一类型的设备一样!
rt_uint32_t key; //监听的事件
};
static void poll_teardown(struct rt_poll_table *pt)
{
struct rt_poll_node *node, *next;
next = pt->nodes;
while (next)
{
node = next;
rt_wqueue_remove(&node->wqn);
next = node->next;
rt_free(node);
}
}
2.使用注意
1 waitqueue继承自list,初始化只是初始化链表(self.next = self);
2 poll的超时不能设置成-1,不能永久阻塞。同时超时和timer的范围一样;
3 poll的驱动中尽量对传递下来的pollreq
进行清除操作,不要重新创建,因为poll的上层会与pollreq
做掩码处理;
4 poll的驱动中不需要再创建waitqueue(poll
会创建);
- 同时wq由以下的保护机制:
- wq未被创建时,wq_wakeup会直接退出;
- wq未被创建时,wq_wait会调用wq_init;
- 并且,设备描述
rt_device.rt_wqueue
是分配了空间的,不是单纯的指针; - 当设备
close
时,需要对wq初始化,即清除;
5 尽量使用以下的流程:
-
驱动中
read
中要有wq_wait操作,尽量使用WAITING_FOREVER
,即当无数据时,挂起直到有数据; -
接收到数据时进行wq_wakeup操作(此举不需要一定和
poll
搭配,只是保证无数据时挂起,有数据时立即唤醒); -
尽量
read
前先poll
,这样效率最高; -
write
前尽量也要用poll
,如果上层每次write
都等待操作结果(需要底层配合,底层知道了结果),则不必; -
如果底层不保证上层
write
时,底层返回前已经获知了操作结果,那么必须通过poll
来监听POLLOUT
; -
POLLOUT
只有在底层完成上一次的write
操作后才能置位; -
POLLIN
表示的是有数据可读,poll
不是必须的; -
POLLOUT
表示的是写空闲,即上一次发送完成,可以进行下一次发送,poll
不是必须的;
3.理想的使用场景
-
write
前先poll
,检测发送空闲; -
write
返回前不必得知执行结果; -
read
前先poll
,保证有数据可读; -
read
设置成堵塞读取不会影响到上层人物;
通过以上方式:
- 避免
read
的堵塞; - 实现
write
和上层应用的异步(一般需要DMA加持);