目录
POLL机制
poll机制的底层实现原理:基于等待队列来实现。
poll()函数,poll()函数底层函数接口有一个对应的函数指针。当调用poll()函数的时候,会找到对应的file_operations的成员变量poll,最终会调用poll成员变量指向的函数指针。
同步阻塞IO和异步阻塞IO
同步阻塞IO
应用层调用一个 read / write 对一个文件进行读写操作时, read / write可能使当前的进程或线程进入一个休眠态(进程 / 线程阻塞在一个文件的读写操作上),陷入休眠态的进程 / 线程只能通过对应的文件设备驱动唤醒自己。
异步阻塞IO
在应用层调用poll()函数时,poll()函数会对多个文件进行轮询操作,去查看每一个文件是否有特定的事件。若所有文件都没有发生特定的事件,那么poll()函数会阻塞当前进程 / 线程。
poll()函数文件操作涉及多个文件的轮询,所以poll()函数引起的进程 / 线程休眠,可以会被多个设备驱动唤醒。
多个文件对应多个设备驱动。阻塞在多个文件的轮询操作上(poll),可以被多个设备驱动唤醒。
文件I/O事件类型
事件类型:可读、可写、异常...
poll()函数的目的是查询每个文件的I/O事件,没查到就会引起休眠。这些事件是由设备驱动产生,产生后会唤醒由poll()函数导致的进程 / 线程休眠,poll()函数也能把发生的事件返回给用户空间。
poll()函数
头文件
cpp
#include <poll.h>
函数定义
cpp
/*
* 监视多个文件描述符的指定事件(注意:不是所有事件)
* 事件发生时(设备驱动唤醒poll函数导致休眠的进程/线程),把发生的具体事件通知给用户程序
* fds:数组类型详见下
* nfds:pollfd数组的元素个数,要监控的文件描述符数量
* timeout:超时时间(ms),不希望进程线程一直休眠在poll函数里
*/
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
/*
* 返回值:
* 成功:发生事件的文件数量,超时返回0
* 失败:-1
*/
struct pollfd
{
int fd; // 要监视的文件描述符
short events; // 指定要监视的请求事件类型,通过设置一系列的宏来描述
short revents; // 返回的事件类型,内核设置具体的返回事件,记录实际发生的事件
};
events监视的事件:
POLLIN:系统内核通知应用层指定数据已经准备好,读数据不会被阻塞
POLLPRI:有紧急的数据需要被读取
POLLOUT:系统内核通知应用层IO缓冲区已经准备好,写数据不会被阻塞
POLLERR:指定的文件描述符发生错误,必须先解决错误才能对文件做其他的处理
POLLNVAL:无效的请求
...
实验环节
app.c文件
cpp
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <poll.h>
int main(int argc, char *argv[])
{
struct pollfd fds = {0};
fds.fd = 0; //标准输入
fds.events = POLLIN;
int ret = poll(&fds, 1, 5000); //监视1个数组,5s超时
if(ret == -1){
printf("poll error!\n");
}else if(ret){
printf("data is ready!\n");
}else if(ret == 0){
printf("time out!\n");
}
return 0;
}
执行过程
gcc app.c -o App
sudo ./App(回车等待5s)
sudo ./App &
cat
2
poll函数底层机制
SYSCALL_DEFINE3函数
cpp
// 此宏是如何一步一步推导到sys_poll见下
SYSCALL_DEFINE3(poll, struct pollfd __user *, ufds, unsigned int, nfds,int, timeout_msecs)
{
struct timespec64 end_time, *to = NULL;
int ret;
if (timeout_msecs >= 0) {
to = &end_time;
poll_select_set_timeout(to, timeout_msecs / MSEC_PER_SEC,
NSEC_PER_MSEC * (timeout_msecs % MSEC_PER_SEC));
}
// 关键
ret = do_sys_poll(ufds, nfds, to);
...
return ret;
}
do_sys_poll()函数
该函数保存在内核/fs/select.c文件中。
函数执行过程:
复制用户空间pollfd数组到内核空间
分配静态数组内存(一个poll_list结构体)
动态分配内存(一组poll_list结构体)
调用do_poll()函数
返回修改后的pollfd数组到用户空间(主要是返回修改后的revents值)
cpp
static int do_sys_poll(struct pollfd __user *ufds, unsigned int nfds, struct timespec64 *end_time)
{
struct poll_wqueues table;
int err = -EFAULT, fdcount, len, size;
// 此宏定义详见下
long stack_pps[POLL_STACK_ALLOC/sizeof(long)];
struct poll_list *const head = (struct poll_list *)stack_pps;
struct poll_list *walk = head;
// 用户空间调用poll函数的第二个参数,数组长度
unsigned long todo = nfds;
...
// 获取静态分配的数组大小,取最小值
// 判断可以存放多少个用户空间传进来的pollfd结构体
len = min_t(unsigned int, nfds, N_STACK_PPS);
for (;;) {
walk->next = NULL;
walk->len = len;
if (!len)
break;
if (copy_from_user(walk->entries, ufds + nfds-todo,
sizeof(struct pollfd) * walk->len))
goto out_fds;
todo -= walk->len;
if (!todo)
break;
// 计算剩下的文件描述符所需空间大小,最大为一个页
len = min(todo, POLLFD_PER_PAGE);
size = sizeof(struct poll_list) + sizeof(struct pollfd) * len;
walk = walk->next = kmalloc(size, GFP_KERNEL);
if (!walk) {
err = -ENOMEM;
goto out_fds;
}
}
//
poll_initwait(&table);
// 详见下,fdcount为所有发生事件的文件数量
// 为了返回修改后的pollfd数组到用户空间
fdcount = do_poll(head, &table, end_time);
poll_freewait(&table);
// 在此遍历poll_list(链表)
for (walk = head; walk; walk = walk->next) {
struct pollfd *fds = walk->entries;
int j;
// 遍历每个poll_list元素的pollfd数组
for (j = 0; j < walk->len; j++, ufds++)
// 返回事件值给用户空间
if (__put_user(fds[j].revents, &ufds->revents))
goto out_fds;
}
...
}
do_poll()函数
该函数保存在内核/fs/select.c文件中。
函数里面的三重循环:
第一重:确保线程 / 进程被唤醒后,继续执行一次循环体内容
第二重:遍历一组poll_list
第三重:遍历每一组poll_list的一组pollfd
cpp
static int do_poll(struct poll_list *list, struct poll_wqueues *wait,
struct timespec64 *end_time)
{
poll_table* pt = &wait->pt;
ktime_t expire, *to = NULL;
int timed_out = 0, count = 0;
u64 slack = 0;
__poll_t busy_flag = net_busy_loop_on() ? POLL_BUSY_LOOP : 0;
unsigned long busy_start = 0;
...
for (;;) {
struct poll_list *walk;
bool can_busy_loop = false;
// 二重:遍历 poll_list 结构体(链表)
for (walk = list; walk != NULL; walk = walk->next) {
struct pollfd * pfd, * pfd_end;
pfd = walk->entries;
pfd_end = pfd + walk->len;
// 三重:遍历 pollfd 结构体(数组)
for (; pfd != pfd_end; pfd++) {
if (do_pollfd(pfd, pt, &can_busy_loop,
busy_flag)) {
// 返回值不为0,count++,表示有返回事件的文件数量
// 返回值不为0,文件没有发生任何事件
// 返回值为0,发生了某(些)事件
count++;
pt->_qproc = NULL;
/* found something, stop busy polling */
busy_flag = 0;
can_busy_loop = false;
}
}
}
pt->_qproc = NULL;
if (!count) {
count = wait->error;
// 检查当前进程或者线程是否有信号处理
if (signal_pending(current))
count = -EINTR;
}
// 下面的break会跳出最外层循环
if (count || timed_out)
break;
...
// 真正使当前进程或者线程休眠的函数
// 若timeout为1,下一次最外层循环将会从上面的break跳出
// 此函数是阻塞的,当等待的事件发生时,会从此处继续向下执行
if (!poll_schedule_timeout(wait, TASK_INTERRUPTIBLE, to, slack))
timed_out = 1;
}
return count;
}
poll函数指针
cpp
// 参数2的数据类型其实是 poll_table 结构体的别名
/*
* filp:要打开的设备文件
* wait:结构图poll_table_struct类型指针
*/
__poll_t (*poll) (struct file *filp, struct poll_table_struct *wait);
//返回值:文件可用事件类型
poll驱动模板
cpp
static __poll_t xxx_poll(struct file *filp, struct poll_table_struct *wait)
{
unsigned int mask = 0;
// 详见下,注意第二个参数就是等待队列头
poll_wait(filp, &yyy, wait);
// 判断驱动程序里面发生了哪些条件,这些条件对应哪些文件的事件,设置好事件后,return回去
if(...)
{
mask |= POLLOUT | ...;
}
return mask;
}
// 参数2:等待队列头
static inline void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p)
{
if (p && p->_qproc && wait_address)
// 其实就 上面初始化为 __pollwait 函数
// 参数2指定等待队列头
p->_qproc(filp, wait_address, p);
}
相关结构体框图
实验环节:poll实验
实验设计
App应用程序调用poll()函数检测/dev/rgb_led设备文件的可写事件
无可写事件,进程一直休眠(poll)
有可写事件,写入字符"1",点亮rgb红灯
file_operations->poll
监视write_data全局变量值(0-无事件发送,1-返回可写事件)
指定唤醒App进程的等待队列头(poll_wait)
file_operations->write
判断write_data全局变量值(0-点亮rgb红灯,1-唤醒poll函数引起休眠的App进程)
dts_led.c文件
App.c文件
cpp
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <poll.h>
int main(int argc, char *argv[])
{
struct pollfd fds = {0};
if(argc != 2){
printf("commend error!\n");
return -1;
}
int fd = open("/dev/rgb_led", O_RDWR);
if(fd < 0){
printf("open file:/dev/rgb_led failed!!!\n");
return -1;
}
fds.fd = fd;
fds.events = POLLOUT;
if(poll(&fds, 1, -1) < 0) //一直休眠
printf("poll error!\n");
if(fds.revents & POLLOUT){
int error = write(fd, argv[1], sizeof(argv[1]));
if(error < 0){
printf("write file error!\n");
close(fd);
}
}
error = close(fd);
if(error < 0){
printf("close file error!\n");
}
return 0;
}