06-阻塞IO与poll机制.md 27 KB


title: 阻塞IO与poll机制 tags: [Linux驱动, 阻塞IO, poll, select, 嵌入式, 等待队列, 异步通知] created: 2026-09-16 updated: 2026-09-17 pdf_ref:

  • "【正点原子】I.MX6U嵌入式Linux驱动开发指南V2.0.1 - 第五十二章 Linux阻塞和非阻塞IO实验"
  • "【正点原子】I.MX6U嵌入式Linux驱动开发指南V2.0.1 - 第五十三章 异步通知实验" ---

阻塞IO与poll机制

💡 关联知识: [[Linux+C+C++技术体系梳理/2. Linux系统编程/04-IO多路复用]] | [[03-Linux驱动开发核心/04-并发同步与原子操作]] | [[03-Linux驱动开发核心/05-中断与定时器]]

一、阻塞与非阻塞IO概述

1.1 什么是IO

这里的"IO"并非单片机中的GPIO引脚,而是Input/Output(输入/输出),指应用程序对驱动设备的读写操作。当应用程序对设备驱动进行操作时,可能无法立即获取到设备资源。

1.2 阻塞IO(Blocking IO)

定义:当应用程序调用read()write()时,如果设备不可用或数据未准备好,进程会进入休眠状态(阻塞),直到设备可用后被唤醒,再完成数据读写。

// 阻塞方式打开设备
int fd = open("/dev/xxx_dev", O_RDWR);  // 默认阻塞模式
ret = read(fd, &data, sizeof(data));     // 无数据时进程休眠

特点

  • 进程在等待期间不占用CPU
  • 需要在驱动中实现唤醒机制(通常在中断中唤醒)
  • CPU资源利用率高

1.3 非阻塞IO(Non-blocking IO)

定义:当设备不可用或数据未准备好时,read()/write()会立即返回一个错误码(-EAGAIN-EWOULDBLOCK),应用程序可以选择重试或放弃。

// 非阻塞方式打开设备
int fd = open("/dev/xxx_dev", O_RDWR | O_NONBLOCK);  // 添加O_NONBLOCK
ret = read(fd, &data, sizeof(data));  // 无数据时立即返回错误

特点

  • 进程不会休眠,持续轮询设备状态
  • CPU占用率高(忙等待)
  • 适合对响应时间要求高的场景

1.4 阻塞vs非阻塞对比

graph TD
    A[应用程序调用read] --> B{设备是否可用?}
    B -->|是| C[读取数据并返回]
    B -->|否| D{IO模式?}
    D -->|阻塞IO| E[进程进入休眠态]
    E --> F[等待设备就绪]
    F --> G[被唤醒]
    G --> C
    D -->|非阻塞IO| H[立即返回错误码]
    H --> I[应用程序重试或放弃]

1.5 应用程序如何设置阻塞/非阻塞

方式 代码示例 说明
阻塞(默认) open("/dev/xxx", O_RDWR) 默认行为,无O_NONBLOCK
非阻塞 open("/dev/xxx", O_RDWR \| O_NONBLOCK) 添加O_NONBLOCK标志
运行时切换 fcntl(fd, F_SETFL, flags \| O_NONBLOCK) 动态设置非阻塞

二、等待队列(Wait Queue)

2.1 等待队列原理

等待队列是Linux内核实现阻塞进程唤醒的机制。当设备不可用时,将进程添加到等待队列中使其休眠;当设备可用时(如中断发生),从队列中唤醒进程。

graph LR
    A[进程A] -->|加入| C[等待队列头]
    B[进程B] -->|加入| C
    D[中断/事件] -->|唤醒| C
    C -->|唤醒| E[进程A继续执行]
    C -->|唤醒| F[进程B继续执行]

2.2 等待队列头(wait_queue_head_t)

等待队列头是队列的入口,定义在include/linux/wait.h

struct __wait_queue_head {
    spinlock_t lock;           // 自旋锁,保护队列
    struct list_head task_list; // 链表,存放等待队列项
};
typedef struct __wait_queue_head wait_queue_head_t;

初始化方法

// 方法1:动态初始化
wait_queue_head_t my_wq;
init_waitqueue_head(&my_wq);

// 方法2:静态初始化(推荐)
DECLARE_WAIT_QUEUE_HEAD(my_wq);

2.3 等待队列项(wait_queue_t)

每个等待的进程对应一个队列项:

struct __wait_queue {
    unsigned int flags;        // 标志位
    void *private;             // 私有数据,通常指向task_struct
    wait_queue_func_t func;    // 唤醒回调函数
    struct list_head task_list; // 链表节点
};
typedef struct __wait_queue wait_queue_t;

定义并初始化队列项

// 定义并初始化一个等待队列项,关联当前进程
DECLARE_WAITQUEUE(wait, current);

2.4 添加/移除等待队列

// 将等待队列项添加到队列头
void add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);

// 从队列头移除等待队列项
void remove_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);

2.5 唤醒函数

// 唤醒所有等待进程(包括TASK_INTERRUPTIBLE和TASK_UNINTERRUPTIBLE)
void wake_up(wait_queue_head_t *q);

// 只唤醒TASK_INTERRUPTIBLE状态的进程(可被信号打断)
void wake_up_interruptible(wait_queue_head_t *q);

使用场景

  • wake_up():唤醒所有类型进程
  • wake_up_interruptible():推荐使用,进程可被信号中断

2.6 等待事件API

函数 描述
wait_event(wq, condition) 无条件等待,进程状态为TASK_UNINTERRUPTIBLE
wait_event_timeout(wq, condition, timeout) 带超时等待,超时后自动唤醒
wait_event_interruptible(wq, condition) 可中断等待,进程状态为TASK_INTERRUPTIBLE
wait_event_interruptible_timeout(wq, condition, timeout) 可中断+超时

使用示例

// 等待releasekey有效(按键按下)
wait_event_interruptible(dev->r_wait, atomic_read(&dev->releasekey));

2.7 等待队列使用流程

sequenceDiagram
    participant App as 应用程序
    participant Driver as 驱动
    participant IRQ as 中断

    App->>Driver: read(fd, buf, cnt)
    Driver->>Driver: DECLARE_WAITQUEUE(wait, current)
    Driver->>Driver: add_wait_queue(&r_wait, &wait)
    Driver->>Driver: __set_current_state(TASK_INTERRUPTIBLE)
    Driver->>Driver: schedule() [进程休眠]

    Note over IRQ: 设备就绪(如按键按下)
    IRQ->>Driver: 中断处理函数
    Driver->>Driver: wake_up_interruptible(&r_wait)

    Driver->>App: 进程被唤醒,继续执行
    Driver->>Driver: remove_wait_queue(&r_wait, &wait)
    Driver->>App: 返回数据

三、poll机制

3.1 poll机制原理

当应用程序以非阻塞方式访问设备时,驱动需要提供轮询(poll)机制。应用程序通过select()poll()epoll()查询设备是否可操作,驱动中的poll()函数会被执行。

graph TD
    A[应用程序] -->|select/poll/epoll| B[内核]
    B -->|调用| C[驱动poll函数]
    C -->|返回事件| B
    B -->|通知| A
    A -->|数据可读/可写| D[read/write]

3.2 select函数

int select(int nfds,
           fd_set *readfds,
           fd_set *writefds,
           fd_set *exceptfds,
           struct timeval *timeout);

参数说明

  • nfds:最大文件描述符+1
  • readfds:监视读事件的文件描述符集合
  • writefds:监视写事件的文件描述符集合
  • exceptfds:监视异常事件的文件描述符集合
  • timeout:超时时间,NULL表示无限等待

fd_set操作宏

FD_ZERO(&readfds);        // 清空集合
FD_SET(fd, &readfds);     // 添加fd到集合
FD_CLR(fd, &readfds);     // 从集合删除fd
FD_ISSET(fd, &readfds);   // 测试fd是否在集合中

3.3 poll函数

int poll(struct pollfd *fds, nfds_t nfds, int timeout);

pollfd结构体

struct pollfd {
    int fd;           // 文件描述符
    short events;     // 请求的事件(输入)
    short revents;    // 返回的事件(输出)
};

事件标志

事件 描述
POLLIN 有数据可以读取
POLLPRI 有紧急数据需要读取
POLLOUT 可以写数据
POLLERR 发生错误
POLLHUP 文件描述符挂起
POLLNVAL 无效的请求
POLLRDNORM 等同于POLLIN,普通数据可读

3.4 epoll机制

epoll是为处理大并发设计的,适合文件描述符数量多的场景:

// 创建epoll句柄
int epoll_create(int size);

// 控制epoll(添加/修改/删除监视)
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

// 等待事件
int epoll_wait(int epfd, struct epoll_event *events,
               int maxevents, int timeout);

epoll事件

事件 描述
EPOLLIN 有数据可以读取
EPOLLOUT 可以写数据
EPOLLPRI 有紧急数据需要读取
EPOLLERR 发生错误
EPOLLHUP 挂起
EPOLLET 边沿触发(默认水平触发)
EPOLLONESHOT 一次性监视

3.5 驱动中的poll函数

unsigned int (*poll)(struct file *filp, struct poll_table_struct *wait);

实现要点

  1. 调用poll_wait()将进程添加到等待队列
  2. 根据设备状态返回相应事件

    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);
    
    // 根据设备状态设置返回事件
    if (atomic_read(&dev->data_ready)) {
        mask |= POLLIN | POLLRDNORM;  // 可读
    }
    
    if (!atomic_read(&dev->buffer_full)) {
        mask |= POLLOUT | POLLWRNORM;  // 可写
    }
    
    return mask;
    }
    

3.6 poll_wait函数

void poll_wait(struct file *filp, wait_queue_head_t *wait_address, poll_table *p);

作用:将当前进程添加到poll_table对应的等待队列中,不会引起阻塞

3.7 poll机制流程图

sequenceDiagram
    participant App as 应用程序
    participant Kernel as 内核
    participant Driver as 驱动

    App->>Kernel: select/poll/epoll
    Kernel->>Driver: 调用file_operations.poll
    Driver->>Driver: poll_wait()添加到等待队列
    Driver->>Kernel: 返回事件掩码
    Kernel->>App: 返回就绪的fd

    alt 有数据可读
        App->>Kernel: read(fd, buf, cnt)
        Kernel->>Driver: 调用read函数
        Driver->>App: 返回数据
    else 超时
        App->>App: 处理超时
    end

四、异步通知(Fasync)

4.1 异步通知原理

异步通知类似于硬件中断,驱动主动向应用程序发送信号(如SIGIO),通知数据就绪。应用程序无需轮询,等待信号即可。

graph LR
    A[驱动程序] -->|kill_fasync| B[发送SIGIO信号]
    B --> C[应用程序]
    C -->|信号处理函数| D[读取数据]

4.2 fasync_struct结构体

struct fasync_struct {
    spinlock_t fa_lock;           // 自旋锁
    int magic;                    // 魔数
    int fa_fd;                    // 文件描述符
    struct fasync_struct *fa_next; // 链表下一个节点
    struct file *fa_file;         // 关联的文件
    struct rcu_head fa_rcu;       // RCU回调
};

在设备结构体中定义

struct xxx_dev {
    // ... 其他成员
    struct fasync_struct *async_queue; // 异步通知队列
};

4.3 驱动中的fasync函数

int (*fasync)(int fd, struct file *filp, int on);

实现示例

static int xxx_fasync(int fd, struct file *filp, int on) {
    struct xxx_dev *dev = filp->private_data;

    // 调用fasync_helper初始化fasync_struct
    if (fasync_helper(fd, filp, on, &dev->async_queue) < 0)
        return -EIO;
    return 0;
}

// 在file_operations中注册
static struct file_operations xxx_ops = {
    .fasync = xxx_fasync,
    .release = xxx_release,  // 关闭时释放
};

4.4 释放fasync_struct

static int xxx_release(struct inode *inode, struct file *filp) {
    // 调用fasync函数,on=0表示删除异步通知
    return xxx_fasync(-1, filp, 0);
}

4.5 kill_fasync函数

void kill_fasync(struct fasync_struct **fp, int sig, int band);

参数

  • fp:fasync_struct指针
  • sig:要发送的信号(如SIGIO
  • band:可读设为POLL_IN,可写设为POLL_OUT

使用示例

// 在中断处理或定时器中发送信号
if (atomic_read(&dev->releasekey)) {
    if (dev->async_queue)
        kill_fasync(&dev->async_queue, SIGIO, POLL_IN);
}

4.6 应用程序处理异步通知

三步设置

// 1. 注册信号处理函数
signal(SIGIO, sigio_signal_func);

// 2. 将进程号告诉内核
fcntl(fd, F_SETOWN, getpid());

// 3. 开启异步通知
int flags = fcntl(fd, F_GETFL);
fcntl(fd, F_SETFL, flags | FASYNC);

信号处理函数示例

static void sigio_signal_func(int signum) {
    int err;
    unsigned int keyvalue;

    err = read(fd, &keyvalue, sizeof(keyvalue));
    if (err < 0) {
        // 读取错误处理
    } else {
        printf("SIGIO signal! key value=%d\r\n", keyvalue);
    }
}

4.7 异步通知流程图

sequenceDiagram
    participant App as 应用程序
    participant Driver as 驱动
    participant IRQ as 中断/定时器

    App->>Driver: open("/dev/xxx")
    App->>App: signal(SIGIO, handler)
    App->>App: fcntl(F_SETOWN, getpid())
    App->>App: fcntl(F_SETFL, FASYNC)
    Driver->>Driver: fasync()被调用

    Note over IRQ: 设备就绪
    IRQ->>Driver: 中断处理函数
    Driver->>App: kill_fasync(SIGIO)
    Driver->>App: 信号处理函数执行
    App->>Driver: read(fd, buf, cnt)
    Driver->>App: 返回数据

五、完整源码分析

5.1 阻塞IO实验源码(blockio.c)

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/cdev.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/device.h>
#include <linux/of_irq.h>
#include <linux/atomic.h>
#include <linux/timer.h>
#include <linux/interrupt.h>
#include <linux/wait.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>

#define IMX6UIRQ_CNT        1
#define IMX6UIRQ_NAME       "blockio"
#define KEY0VALUE           0X01
#define INVAKEY             0XFF
#define KEY_NUM             1

struct irq_keydesc {
    int gpio;
    int irqnum;
    unsigned char value;
    char name[10];
    irqreturn_t (*handler)(int, void *);
};

struct imx6uirq_dev {
    dev_t devid;
    struct cdev cdev;
    struct class *class;
    struct device *device;
    int major;
    int minor;
    struct device_node *nd;
    atomic_t keyvalue;
    atomic_t releasekey;
    struct timer_list timer;
    struct irq_keydesc irqkeydesc[KEY_NUM];
    unsigned char curkeynum;
    wait_queue_head_t r_wait;  /* 读等待队列头 */
};

struct imx6uirq_dev imx6uirq;

/* 中断服务函数,开启定时器消抖 */
static irqreturn_t key0_handler(int irq, void *dev_id) {
    struct imx6uirq_dev *dev = (struct imx6uirq_dev *)dev_id;
    dev->curkeynum = 0;
    dev->timer.data = (volatile long)dev_id;
    mod_timer(&dev->timer, jiffies + msecs_to_jiffies(10));
    return IRQ_RETVAL(IRQ_HANDLED);
}

/* 定时器服务函数,按键消抖 */
void timer_function(unsigned long arg) {
    unsigned char value;
    unsigned char num;
    struct irq_keydesc *keydesc;
    struct imx6uirq_dev *dev = (struct imx6uirq_dev *)arg;

    num = dev->curkeynum;
    keydesc = &dev->irqkeydesc[num];
    value = gpio_get_value(keydesc->gpio);

    if (value == 0) {  /* 按下按键 */
        atomic_set(&dev->keyvalue, keydesc->value);
    } else {  /* 按键松开 */
        atomic_set(&dev->keyvalue, 0x80 | keydesc->value);
        atomic_set(&dev->releasekey, 1);
    }

    /* 唤醒进程 */
    if (atomic_read(&dev->releasekey)) {
        wake_up_interruptible(&dev->r_wait);
    }
}

/* 按键IO初始化 */
static int keyio_init(void) {
    // ... GPIO和中断初始化代码 ...

    /* 初始化等待队列头 */
    init_waitqueue_head(&imx6uirq.r_wait);
    return 0;
}

/* 从设备读取数据(阻塞方式) */
static ssize_t imx6uirq_read(struct file *filp, char __user *buf,
                             size_t cnt, loff_t *offt) {
    int ret = 0;
    unsigned char keyvalue = 0;
    unsigned char releasekey = 0;
    struct imx6uirq_dev *dev = (struct imx6uirq_dev *)filp->private_data;

    /* 等待按键按下 */
    ret = wait_event_interruptible(dev->r_wait,
                                   atomic_read(&dev->releasekey));
    if (ret) {
        goto wait_error;
    }

    keyvalue = atomic_read(&dev->keyvalue);
    releasekey = atomic_read(&dev->releasekey);

    if (releasekey) {
        if (keyvalue & 0x80) {  /* 松开按键 */
            keyvalue &= ~0x80;
            ret = copy_to_user(buf, &keyvalue, sizeof(keyvalue));
        } else {
            goto data_error;
        }
    } else {
        goto data_error;
    }
    return 0;

wait_error:
    set_current_state(TASK_RUNNING);
    remove_wait_queue(&dev->r_wait, &wait);
    return ret;

data_error:
    return -EINVAL;
}

static struct file_operations imx6uirq_fops = {
    .owner = THIS_MODULE,
    .open = imx6uirq_open,
    .read = imx6uirq_read,
};

5.2 非阻塞IO+poll实验源码(noblockio.c)

/* 读取函数(支持阻塞和非阻塞) */
static ssize_t imx6uirq_read(struct file *filp, char __user *buf,
                             size_t cnt, loff_t *offt) {
    int ret = 0;
    unsigned char keyvalue = 0;
    unsigned char releasekey = 0;
    struct imx6uirq_dev *dev = (struct imx6uirq_dev *)filp->private_data;

    if (filp->f_flags & O_NONBLOCK) {  /* 非阻塞访问 */
        if (atomic_read(&dev->releasekey) == 0)
            return -EAGAIN;
    } else {  /* 阻塞访问 */
        ret = wait_event_interruptible(dev->r_wait,
                                       atomic_read(&dev->releasekey));
        if (ret) {
            goto wait_error;
        }
    }

    keyvalue = atomic_read(&dev->keyvalue);
    releasekey = atomic_read(&dev->releasekey);

    if (releasekey) {
        if (keyvalue & 0x80) {
            keyvalue &= ~0x80;
            ret = copy_to_user(buf, &keyvalue, sizeof(keyvalue));
        } else {
            goto data_error;
        }
    } else {
        goto data_error;
    }
    return 0;

wait_error:
    return ret;

data_error:
    return -EINVAL;
}

/* poll函数 */
static unsigned int imx6uirq_poll(struct file *filp,
                                  struct poll_table_struct *wait) {
    unsigned int mask = 0;
    struct imx6uirq_dev *dev = (struct imx6uirq_dev *)filp->private_data;

    poll_wait(filp, &dev->r_wait, wait);

    if (atomic_read(&dev->releasekey)) {  /* 按键按下 */
        mask = POLLIN | POLLRDNORM;  /* 返回POLLIN */
    }

    return mask;
}

static struct file_operations imx6uirq_fops = {
    .owner = THIS_MODULE,
    .open = imx6uirq_open,
    .read = imx6uirq_read,
    .poll = imx6uirq_poll,
};

5.3 异步通知实验源码(asyncnoti.c)

/* 设备结构体 */
struct imx6uirq_dev {
    // ... 其他成员
    struct fasync_struct *async_queue;  /* 异步相关结构体 */
};

/* fasync函数 */
static int imx6uirq_fasync(int fd, struct file *filp, int on) {
    struct imx6uirq_dev *dev = (struct imx6uirq_dev *)filp->private_data;
    return fasync_helper(fd, filp, on, &dev->async_queue);
}

/* release函数 */
static int imx6uirq_release(struct inode *inode, struct file *filp) {
    return imx6uirq_fasync(-1, filp, 0);
}

/* 定时器服务函数中发送信号 */
void timer_function(unsigned long arg) {
    // ... 按键消抖处理 ...

    if (atomic_read(&dev->releasekey)) {
        if (dev->async_queue)
            kill_fasync(&dev->async_queue, SIGIO, POLL_IN);
    }
}

static struct file_operations imx6uirq_fops = {
    .owner = THIS_MODULE,
    .open = imx6uirq_open,
    .read = imx6uirq_read,
    .poll = imx6uirq_poll,
    .fasync = imx6uirq_fasync,
    .release = imx6uirq_release,
};

5.4 异步通知测试APP(asyncnotiApp.c)

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>

static int fd = 0;

static void sigio_signal_func(int signum) {
    int err = 0;
    unsigned int keyvalue = 0;

    err = read(fd, &keyvalue, sizeof(keyvalue));
    if (err < 0) {
        printf("Read error!\r\n");
    } else {
        printf("SIGIO signal! key value=%d\r\n", keyvalue);
    }
}

int main(int argc, char *argv[]) {
    int flags = 0;
    char *filename;

    if (argc != 2) {
        printf("Error Usage!\r\n");
        return -1;
    }

    filename = argv[1];
    fd = open(filename, O_RDWR);
    if (fd < 0) {
        printf("Can't open file %s\r\n", filename);
        return -1;
    }

    signal(SIGIO, sigio_signal_func);
    fcntl(fd, F_SETOWN, getpid());
    flags = fcntl(fd, F_GETFL);
    fcntl(fd, F_SETFL, flags | FASYNC);

    while (1) {
        sleep(2);
    }

    close(fd);
    return 0;
}

六、实验验证

6.1 编译方法

驱动编译(Makefile)

KERNELDIR := /home/zuozhongkai/linux/IMX6ULL/linux/temp/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek
CURRENT_PATH := $(shell pwd)
obj-m := blockio.o  # 或 noblockio.o, asyncnoti.o

build: kernel_modules
kernel_modules:
    $(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules

clean:
    $(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean

编译命令

# 编译驱动模块
make -j32

# 交叉编译测试APP
arm-linux-gnueabihf-gcc blockioApp.c -o blockioApp
arm-linux-gnueabihf-gcc noblockioApp.c -o noblockioApp
arm-linux-gnueabihf-gcc asyncnotiApp.c -o asyncnotiApp

6.2 测试步骤

# 1. 拷贝文件到开发板
cp blockio.ko blockioApp /rootfs/lib/modules/4.1.15/

# 2. 加载驱动
cd /lib/modules/4.1.15
depmod
modprobe blockio.ko

# 3. 后台运行测试APP
./blockioApp /dev/blockio &

# 4. 按下KEY0按键测试
# 观察输出按键值

# 5. 查看CPU使用率
top

6.3 非阻塞IO测试

# 使用select方式测试
./noblockioApp /dev/noblockio &

# 或使用poll方式(修改源码中#if 0为#if 1)
./noblockioApp /dev/noblockio &

6.4 异步通知测试

# 加载异步通知驱动
modprobe asyncnoti.ko

# 运行测试APP
./asyncnotiApp /dev/asyncnoti

# 按下KEY0按键,观察输出
# SIGIO signal! key value=1

七、IO模型对比

模型 机制 CPU占用 响应延迟 实现复杂度 适用场景
阻塞IO 等待队列 简单 低频设备
非阻塞IO 轮询 简单 实时性要求高
IO多路复用 select/poll/epoll 中等 多设备监控
异步通知 信号(SIGIO) 复杂 事件驱动

选择建议

  • 单设备、低频操作:阻塞IO
  • 多设备、需要并发:IO多路复用(epoll)
  • 事件驱动、低延迟:异步通知
  • 实时性要求极高:非阻塞IO + 轮询

八、跨平台对比

特性 IMX6ULL (Linux) STM32 (FreeRTOS) RK3568 (Linux)
阻塞IO 等待队列 任务通知/信号量 等待队列
poll机制 select/poll/epoll 无原生支持 select/poll/epoll
异步通知 SIGIO信号 无原生支持 SIGIO信号
中断机制 request_irq xTaskNotifyFromISR request_irq
任务调度 内核调度器 FreeRTOS调度器 内核调度器

嵌入式差异

  • Linux系统:完整的IO模型支持,等待队列、poll、异步通知
  • RTOS系统:通常使用任务通知、事件组、信号量实现类似功能
  • 裸机系统:轮询+中断,无阻塞机制

九、面试精选

题目1:阻塞IO和非阻塞IO有什么区别?

考察点:IO模型理解

参考答案

  • 阻塞IO:进程在设备不可用时进入休眠状态,不占用CPU,等待设备就绪后被唤醒
  • 非阻塞IO:进程在设备不可用时立即返回错误码(-EAGAIN),应用程序需要重试
  • 核心区别:阻塞IO让出CPU,非阻塞IO占用CPU轮询
  • 应用场景:阻塞IO适合低频操作,非阻塞IO适合实时性要求高的场景

题目2:如何实现设备的可poll功能?

考察点:poll机制实现

参考答案

  1. 在驱动中实现file_operationspoll函数
  2. poll函数中调用poll_wait()将进程添加到等待队列
  3. 根据设备状态设置返回事件掩码(POLLIN/POLLOUT等)
  4. 用户空间使用select()/poll()/epoll()监控
  5. 当设备就绪时,驱动返回相应事件,用户空间进行读写操作

题目3:等待队列的使用流程是什么?

考察点:等待队列机制

参考答案

  1. 定义并初始化等待队列头DECLARE_WAIT_QUEUE_HEAD(r_wait)init_waitqueue_head()
  2. 在read函数中等待
    • 定义等待队列项:DECLARE_WAITQUEUE(wait, current)
    • 添加到队列:add_wait_queue(&r_wait, &wait)
    • 设置进程状态:__set_current_state(TASK_INTERRUPTIBLE)
    • 让出CPU:schedule()
  3. 在中断中唤醒wake_up_interruptible(&r_wait)
  4. 清理工作remove_wait_queue(&r_wait, &wait)

题目4:异步通知和信号有什么关系?

考察点:异步通知机制

参考答案

  • 异步通知基于信号机制:驱动通过kill_fasync()向应用程序发送信号(如SIGIO
  • 应用程序处理
    1. 使用signal()注册信号处理函数
    2. 使用fcntl(F_SETOWN)将进程号告诉内核
    3. 使用fcntl(F_SETFL, FASYNC)开启异步通知
  • 优势:无需轮询,驱动主动通知,CPU占用低
  • 限制:仅支持SIGIO等特定信号,不适合高频事件

题目5:select、poll、epoll有什么区别?

考察点:IO多路复用对比

参考答案

特性 select poll epoll
最大fd数 1024(FD_SETSIZE) 无限制 无限制
数据结构 fd_set位图 pollfd数组 红黑树+就绪链表
触发方式 水平触发 水平触发 水平/边沿触发
效率 O(n)遍历 O(n)遍历 O(1)事件回调
内核实现 遍历所有fd 遍历所有fd 回调通知就绪fd
适用场景 少量fd 中等fd 大量fd(高并发)

选择建议

  • 少量fd(<1024):select
  • 中等fd:poll
  • 大量fd、高并发:epoll

代码来源: Linux驱动例程 14_blockio, 15_noblockio, 16_asyncnoti 最后更新: 2026-09-17