Linux select/poll/epoll 原理(三)poll 实现

针对 select 系统调用的三个不足,poll 解决的是第一个、最多 1024 个 FD 限制的问题。

其实现思路是:
1. 不再使用位图来传递事件和结果,而是使用 pollfd 。 结构体数组来传递。
2. 在内部实现时,以 poll_list 链表的形式来分批次保存 pollfd 。不像 select 那样一次申请完整的一大块内存。
3. 通过从进程的信号量里获取能打开的最大文件数量,解决 1024 个限制的问题。

0. 基本数据结构

// 源码位置:include/uapi/asm-generic/poll.h
struct pollfd {
    int fd;         // FD
    short events;   // 输入的敢兴趣事件
    short revents;  // 输出的结果
};

// 源码位置:fs/select.c
struct poll_list {
    struct poll_list *next;

    // entries 指向的数组里 pollfd 的数量
    int len;

    // 指向 pollfd 数组的指针
    struct pollfd entries[0];
};

pollfd 结构体用来传递单个FD的输入事件、输出结果。

poll_list 是一个链表,其节点指向 pollfd 结构体的数组,这个数组要么是在栈上预分配、要么是按内存页分配(保持页对齐)。

继续阅读

Linux select/poll/epoll 原理(一)实现基础

本序列涉及的 Linux 源码都是基于 linux-4.14.143 。

1. 文件抽象 与 poll 操作

1.1 文件抽象

在 Linux 内核里,文件是一个抽象,设备是个文件,网络套接字也是个文件。

文件抽象必须支持的能力定义在 file_operations 结构体里。

在 Linux 里,一个打开的文件对应一个文件描述符 file descriptor/FD,FD 其实是一个整数,内核把进程打开的文件维护在一个数组里,FD 对应的是数组的下标。

文件抽象的能力定义:

// 源码位置:include/linux/fs.h
struct file_operations {
    struct module *owner;
    loff_t (*llseek) (struct file *, loff_t, int);
    ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
    ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
    ssize_t (*read_iter) (struct kiocb *, struct iov_iter *);
    ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);
    int (*iterate) (struct file *, struct dir_context *);
    int (*iterate_shared) (struct file *, struct dir_context *);

    // 对于 select/poll/epoll 最重要的实现基础
    // 非阻塞的轮询文件状态的函数
    unsigned int (*poll) (struct file *, struct poll_table_struct *);

    // 省略其他函数指针
} __randomize_layout;


// 源码位置:include/linux/poll.h
typedef struct poll_table_struct {
    // 文件的 file_operations.poll 实现一定会调用的队列处理函数
    poll_queue_proc _qproc;

    // poll 操作敢兴趣的事件
    unsigned long _key;
} poll_table;

// poll 队列处理函数
typedef void (*poll_queue_proc)(struct file *, wait_queue_head_t *, struct poll_table_struct *);

继续阅读