在字符設備驅動中實現 select/poll 支持,讓用戶空間應用能夠通過非阻塞方式監聽設備就緒事件。本文詳細介紹 fd_set 操作宏、select 系統調用接口,以及驅動中 file_operations.poll 的實現模式。
fd_set 本質是文件描述符位圖,標準操作宏:
FD_ZERO(&set):清零集合FD_SET(fd, &set):添加 fdFD_CLR(fd, &set):移除 fdFD_ISSET(fd, &set):檢測 fd 是否在集合中select 原型:
int select(int nfds, fd_set *readset, fd_set *writeset,
fd_set *exceptset, struct timeval *timeout);
nfds:最大 fd 值 + 1timeout:NULL 永久等待;tv_sec=tv_usec=0 非阻塞輪詢在 file_operations 中註冊 poll 回調:
static struct file_operations fops = {
.owner = THIS_MODULE,
.open = my_open,
.release = my_release,
.poll = my_poll,
// ...
};
static __poll_t my_poll(struct file *filp, poll_table *wait)
{
struct my_device *dev = filp->private_data;
__poll_t mask = 0;
// 1. 將當前進程加入等待隊列
poll_wait(filp, &dev->wq, wait);
// 2. 檢查設備狀態,設置返回掩碼
if (dev->data_ready)
mask |= POLLIN | POLLRDNORM; // 可讀
if (dev->buffer_space)
mask |= POLLOUT | POLLWRNORM; // 可寫
return mask;
}
wait_queue_head_t),不會立即睡眠。內核會在適當時機調用隊列喚醒函數POLLIN/POLLRDNORM(可讀)、POLLOUT/POLLWRNORM(可寫)、POLLERR(錯誤)、POLLHUP(掛起)、POLLPRI(高優先級數據)wake_up_interruptible(&dev->wq)static __poll_t my_poll(struct file *filp, poll_table *wait)
{
struct my_dev *dev = filp->private_data;
__poll_t mask = 0;
poll_wait(filp, &dev->read_q, wait);
poll_wait(filp, &dev->write_q, wait);
if (dev->rp != dev->wp) // 環形緩衝區有數據
mask |= POLLIN | POLLRDNORM;
if (space_available(dev)) // 有寫入空間
mask |= POLLOUT | POLLWRNORM;
return mask;
}
// 中斷處理或數據接收路徑
static void data_received(struct my_dev *dev)
{
// ... 處理數據 ...
wake_up_interruptible(&dev->read_q); // 喚醒等待讀的進程
}
timeout 語義差異:Linux 修改 timeout 返回剩餘時間(System V 風格),BSD 不修改。移植代碼需重置 timeoutepoll(用戶空間)或 fasync(信號驅動 I/O)poll_wait 的並發安全,通常配合自旋鎖保護等待隊列實現 poll 回調是字符設備驅動支持非阻塞 I/O 的標準方式。核心在於:poll_wait 註冊等待隊列,業務邏輯檢查條件返回事件掩碼,數據路徑調用 wake_up 喚醒。這讓 select/poll/epoll 能夠統一監聽設備就緒事件。