深度剖析Linux按键驱动四种访问方式:从查询到异步通知

深度剖析Linux按键驱动四种访问方式:从查询到异步通知

目录导航

  1. 开篇:一个生动的比喻
  2. 四种方式概览与核心思想
  3. 方式一:查询方式 ------ 最简单,但最累
    • 核心思想与流程
    • 代码实现详解
    • 代码调用链路分析
    • 优缺点与疏导总结
  4. 方式二:休眠-唤醒方式 ------ 高效,但可能永久等待
    • 核心思想与流程
    • 代码实现详解(含中断与等待队列)
    • 代码调用链路分析
    • 优缺点与疏导总结
  5. 方式三:poll方式 ------ 带超时机制的休眠唤醒
    • 核心思想与流程
    • 代码实现详解(驱动+应用)
    • 代码调用链路分析
    • 优缺点与疏导总结
  6. 方式四:异步通知方式 ------ 最高效,事件驱动
    • 核心思想与流程
    • 代码实现详解(信号驱动)
    • 代码调用链路分析
    • 优缺点与疏导总结
  7. 终极对比总结与选择建议
  8. 掌握度自测:一眼看穿答案的几道题

开篇:一个生动的比喻

在深入代码之前,我们先回忆一下那个经典的比喻。这个比喻能帮你建立起对这四种方式最直观的理解。

妈妈怎么知道卧室里小孩醒了?

  1. 查询方式 :妈妈每隔几分钟就推门进去看一次 。孩子没醒,就出来继续干活;醒了,就处理。简单,但妈妈累得够呛,而且可能错过孩子刚醒的瞬间。
  2. 休眠-唤醒方式 :妈妈直接躺在孩子旁边睡 。孩子一醒,肯定会把妈妈吵醒。妈妈不累,但啥活也干不了了。如果孩子一直不醒,妈妈也一直睡下去。
  3. poll方式 :妈妈定个闹钟(比如30分钟),然后睡在孩子旁边 。要么被孩子吵醒,要么被闹钟叫醒。醒了之后看看情况,如果孩子没醒,就再定个闹钟继续睡。既能休息,又能抽空干点活
  4. 异步通知方式 :妈妈在客厅安心干活 ,并告诉孩子:"你醒了就自己跑出来找我"。孩子醒了,自己就跑出来找妈妈。妈妈和孩子互不耽误,效率最高

这个比喻非常贴切,接下来我们就把这四个场景,用代码一一复现。


四种方式概览与核心思想

方式 核心机制 APP行为 驱动核心函数 适用场景
查询 主动、循环读取 一直调用read,不睡眠 read 实时性要求极高,或数据变化极快
休眠-唤醒 被动等待,事件驱动 调用read后睡眠,中断唤醒 read, 中断 事件发生不频繁,且APP可独占等待
poll/select 带超时的休眠 调用poll睡眠一段时间或被唤醒 poll, read, 中断 需要等待多个文件,或有超时要求
异步通知 信号驱动,完全异步 注册信号处理函数,继续做其他事 fasync, read, 中断 追求最高效率,事件随机性强

方式一:查询方式 ------ 最简单,但最累

这是最朴素的方式,APP就像一个勤劳的巡检员,一刻不停地去查看按键状态。

核心思想与流程

  1. APP :调用open打开设备,然后在一个死循环中不断调用read
  2. 驱动 :在read函数中,不判断是否有数据,直接读取GPIO寄存器的当前电平,并返回给APP。

代码实现详解

驱动端 (gpio_key_drv.c)

c

复制代码
// 驱动核心:read函数直接返回硬件状态
static ssize_t gpio_drv_read(struct file *file, char __user *buf,
                              size_t size, loff_t *offset)
{
    int key_value;
    // 假设我们有一个函数可以直接读取GPIO电平
    key_value = gpio_read_raw(gpio_pin); 
    
    // 直接拷贝给用户空间,不睡眠,不等待
    if (copy_to_user(buf, &key_value, sizeof(key_value)))
        return -EFAULT;
        
    return sizeof(key_value);
}

// file_operations结构体
static struct file_operations gpio_drv_fops = {
    .owner = THIS_MODULE,
    .open  = gpio_drv_open,  // 负责配置GPIO为输入
    .read  = gpio_drv_read,  // 核心:查询读取
};

应用程序端 (app_query.c)

c

复制代码
int main(int argc, char **argv)
{
    int fd;
    int val;
    fd = open("/dev/gpio_key", O_RDWR);
    if (fd == -1) {
        printf("can not open file!\n");
        return -1;
    }

    while (1) {
        // 核心:死循环,不断调用read进行查询
        read(fd, &val, sizeof(val));
        printf("get button value: %d\n", val);
        // 注意:这里没有sleep,CPU占用率会非常高
        // 可以加个短暂的sleep来降低CPU占用,但这会降低实时性
        // usleep(10000); // 10ms
    }
    return 0;
}

代码调用链路分析

优缺点与疏导总结

  • 优点:代码实现极其简单,逻辑清晰,无需中断、无需等待队列。
  • 缺点CPU占用率极高。即使没有任何按键操作,APP也一直在疯狂占用CPU资源进行无效查询。这在资源受限的嵌入式系统中是不可接受的。
  • 疏导 :查询方式理解了,你就知道了"为什么需要其他几种方式"。它揭示了驱动程序的一个核心原则:不要让CPU做无谓的等待。当没有数据时,驱动程序应该主动"让出CPU",让其他进程有机会运行,这就是"休眠"机制的由来。

方式二:休眠-唤醒方式 ------ 高效,但可能永久等待

为了解决查询方式浪费CPU的问题,我们引入了"休眠-唤醒"机制。没有数据时,APP进入睡眠状态,交出CPU;数据到来时,由硬件中断将APP唤醒。

核心思想与流程

  1. APP :调用open,然后调用read。如果没有数据,进程在内核态被设置为"睡眠"状态。
  2. 驱动
    • open函数中配置GPIO中断。
    • read函数检查数据缓冲区,如果有数据,直接返回;如果没有,调用wait_event_interruptible让当前进程休眠。
    • 中断服务程序(ISR)被按键触发,它负责记录数据,并调用wake_up_interruptible唤醒正在睡眠的进程。

代码实现详解(含中断与等待队列)

驱动端 (gpio_drv_sleep.c)

c

复制代码
#include <linux/wait.h>  // 等待队列头

// 1. 定义并初始化一个等待队列头
static DECLARE_WAIT_QUEUE_HEAD(gpio_wait);
// 2. 定义一个环形缓冲区或变量来存放按键值
static int key_value = 0;
static int key_available = 0; // 是否有新数据标志

// 中断服务程序
static irqreturn_t gpio_key_isr(int irq, void *dev_id)
{
    // 读取按键值,假设读取GPIO后得到val
    int val = gpio_get_value(gpio_pin);
    key_value = val;
    key_available = 1; // 标记有新数据

    // 关键步骤:唤醒等待队列上的进程
    wake_up_interruptible(&gpio_wait);
    return IRQ_HANDLED;
}

// 驱动的read函数
static ssize_t gpio_drv_read(struct file *file, char __user *buf,
                              size_t size, loff_t *offset)
{
    int ret;
    // 关键步骤:如果没有数据,则休眠等待
    // 条件为假(key_available == 0)时,进程进入休眠
    wait_event_interruptible(gpio_wait, key_available != 0);
    
    // 被唤醒后,表示有数据了
    ret = copy_to_user(buf, &key_value, sizeof(key_value));
    key_available = 0; // 重置标志
    return sizeof(key_value);
}

应用程序端 (app_sleep.c)

应用程序代码与查询方式完全相同 ,只是打开设备时通常不指定O_NONBLOCK标志。

c

复制代码
fd = open("/dev/gpio_key", O_RDWR); // 默认是阻塞方式
while (1) {
    read(fd, &val, sizeof(val)); // 此处会阻塞,直到有按键
    printf("get button: 0x%x\n", val);
}

代码调用链路分析

优缺点与疏导总结

  • 优点完美解决了CPU空转问题。没有按键时,APP不占用任何CPU资源,系统可以运行其他任务。这是现代操作系统高效运行的基础。
  • 缺点 :APP可能会永久休眠 。如果硬件损坏或者永远不会触发中断,read调用将永远不返回。这在某些场景下是不可接受的。
  • 疏导 :休眠-唤醒机制是驱动开发的基石。它引入了两个核心概念:等待队列(Wait Queue)中断上下文
    • 等待队列是实现线程安全休眠/唤醒的标准内核机制。
    • 中断服务程序 运行在特殊的中断上下文,不能调用任何可能睡眠的函数(如copy_to_user)。因此我们只在ISR中做最少量、最关键的工作(记录数据、唤醒进程),数据处理(拷贝到用户空间)留给被唤醒的进程去做。这就是"中断顶半部和底半部"思想的雏形。

方式三:poll方式 ------ 带超时机制的休眠唤醒

为了解决休眠-唤醒方式可能永久等待的问题,我们引入pollselect系统调用。它们允许APP设置一个超时时间。

核心思想与流程

  1. APP :调用poll函数,并传入一个文件描述符数组和超时时间。
  2. 驱动 :实现poll函数。
    • 驱动中的poll函数会调用poll_wait将当前进程注册到等待队列中。
    • 然后检查数据是否可用。如果可用,立即返回POLLIN标志;如果不可用,返回0。
    • 内核会循环检查,直到数据可用或超时时间到达。在此期间,进程可能多次进出休眠状态。

代码实现详解(驱动+应用)

驱动端 (gpio_drv_poll.c)

c

复制代码
// 在原有休眠-唤醒驱动基础上,增加 .poll 函数
static unsigned int gpio_drv_poll(struct file *fp, poll_table * wait)
{
    // 1. 关键步骤:将当前进程加入到等待队列gpio_wait中
    //    注意,这并不会让进程休眠,只是注册了一个"关注点"
    poll_wait(fp, &gpio_wait, wait);
    
    // 2. 检查是否有数据
    if (key_available != 0) {
        // 有数据,返回POLLIN,表示可读
        return POLLIN | POLLRDNORM;
    }
    // 3. 没有数据,返回0
    return 0;
}

static struct file_operations gpio_drv_fops = {
    .owner = THIS_MODULE,
    .open  = gpio_drv_open,
    .read  = gpio_drv_read,  // read函数实现与休眠-唤醒方式完全一样
    .poll  = gpio_drv_poll,  // 新增的poll函数
};

应用程序端 (app_poll.c)

c

复制代码
#include <poll.h>
#include <fcntl.h>

int main(int argc, char **argv)
{
    int fd;
    int val;
    int ret;
    struct pollfd fds[1];
    int timeout_ms = 5000; // 超时时间:5秒

    fd = open(argv[1], O_RDWR);
    if (fd == -1) return -1;

    fds[0].fd = fd;
    fds[0].events = POLLIN; // 关心可读事件

    while (1) {
        // 核心:使用poll代替read
        ret = poll(fds, 1, timeout_ms);
        
        if (ret == -1) {
            printf("poll error\n");
        } else if (ret == 0) {
            // poll返回0表示超时
            printf("poll: timeout\n");
        } else {
            // ret > 0 表示有事件发生
            if (fds[0].revents & POLLIN) {
                // 确认是可读事件,再调用read读取数据
                read(fd, &val, sizeof(val));
                printf("get button: 0x%x\n", val);
            }
        }
    }
    return 0;
}

代码调用链路分析

优缺点与疏导总结

  • 优点 :解决了永久阻塞的问题,提供了超时机制 。同时poll可以同时监控多个文件描述符,这是构建复杂事件驱动应用的基石(例如同时等待按键、触摸屏和网络数据)。
  • 缺点 :实现比单纯的休眠-唤醒稍复杂,但非常标准。poll函数内部仍然可能发生多次进程切换,有一定开销。
  • 疏导poll机制的精髓在于将"等待"和"读取"两个动作分离poll只负责告诉你"数据准备好了没有",而不负责传输数据。这符合Unix"做一件事,并做好 "的设计哲学。poll_wait函数非常重要,它只是注册,不是睡眠。真正的睡眠是由内核的poll实现循环调度的。理解这一点,你就明白了为什么在poll函数中不能直接schedule()

方式四:异步通知方式 ------ 最高效,事件驱动

这是最高级的方式,它让驱动程序变成了一个"主动汇报者",而不是被动等待APP来查询。它使用了Unix信号(Signal)机制。

核心思想与流程

  1. APP
    • 注册SIGIO信号的处理函数。
    • 调用fcntl设置FASYNC标志,告诉内核"我想收到这个文件的异步通知"。
    • 然后APP就可以去做其他任何事了。
  2. 驱动
    • 实现.fasync函数,用于记录和释放发送信号的进程信息。
    • 在中断服务程序中,当数据准备好后,调用kill_fasync函数向之前记录的所有进程发送SIGIO信号。
    • APP收到信号后,暂停当前工作,去执行信号处理函数,在信号处理函数中调用read读取数据。

代码实现详解(信号驱动)

驱动端 (gpio_drv_async.c)

c

复制代码
#include <linux/fs.h>   // for fasync_struct

// 1. 定义一个fasync结构体指针
static struct fasync_struct *button_fasync;

// 2. 实现fasync函数
static int gpio_drv_fasync(int fd, struct file *filp, int on)
{
    // 核心:调用标准函数 fasync_helper 来管理 fasync_struct
    return fasync_helper(fd, filp, on, &button_fasync);
}

// 中断服务程序(在原有基础上增加发送信号的代码)
static irqreturn_t gpio_key_isr(int irq, void *dev_id)
{
    // ... 读取按键值,存入缓冲区 ...
    val = gpio_get_value(gpio_pin);
    put_key_into_buffer(val);
    
    // 核心:唤醒等待队列(给poll/read用)
    wake_up_interruptible(&gpio_wait);
    
    // 核心:向应用程序发送SIGIO信号
    kill_fasync(&button_fasync, SIGIO, POLL_IN);
    
    return IRQ_HANDLED;
}

static struct file_operations gpio_drv_fops = {
    .owner   = THIS_MODULE,
    .open    = gpio_drv_open,
    .read    = gpio_drv_read,  // read实现与休眠-唤醒类似,但可由信号触发
    .poll    = gpio_drv_poll,  // 依然可以实现poll以兼容
    .fasync  = gpio_drv_fasync, // 新增异步通知函数
};

应用程序端 (app_async.c)

c

复制代码
#include <signal.h>
#include <unistd.h>
#include <fcntl.h>

int fd; // 全局变量,以便在信号处理函数中使用

// 信号处理函数
void my_signal_fun(int sig)
{
    int val;
    if (sig == SIGIO) {
        // 在信号处理函数中读取数据
        read(fd, &val, sizeof(val));
        printf("Async get button: 0x%x\n", val);
        // 注意:信号处理函数中应避免调用非异步信号安全的函数,但read通常可以
    }
}

int main(int argc, char **argv)
{
    int flags;
    
    // 1. 打开设备
    fd = open(argv[1], O_RDWR);
    if (fd == -1) return -1;
    
    // 2. 注册SIGIO信号的处理函数
    signal(SIGIO, my_signal_fun);
    
    // 3. 设置本进程为设备文件的"所有者",这样驱动才知道把信号发给谁
    fcntl(fd, F_SETOWN, getpid());
    
    // 4. 获取当前文件状态标志,并添加FASYNC标志,触发驱动的 .fasync
    flags = fcntl(fd, F_GETFL);
    fcntl(fd, F_SETFL, flags | FASYNC);
    
    // 5. 主程序可以安心去做其他事了
    while (1) {
        printf("APP is doing other important work...\n");
        sleep(2);
    }
    return 0;
}

代码调用链路分析

优缺点与疏导总结

  • 优点效率最高,实现了解耦 。APP无需主动查询或等待,可以完全专注于自己的主要任务。驱动程序在事件发生时主动通知。这是典型的"好莱坞原则"(Don't call us, we'll call you)。
  • 缺点 :实现相对复杂,涉及信号、进程间通信等概念。信号处理函数中有很多限制(比如不能调用printf等非异步信号安全的函数,示例中为简化使用了printf)。
  • 疏导 :异步通知是Linux下实现事件驱动编程的经典模式 。其核心在于fasync_struct结构体和kill_fasync函数。它本质上是一个"观察者模式 "的内核实现:驱动程序是被观察者,APP是观察者。当事件发生时,驱动程序通知所有注册过的观察者。fasync_helper负责维护这个观察者列表。这种方式不仅用于按键,在socket、串口等需要高实时性异步通知的场景中也被广泛使用。

终极对比总结与选择建议

特性 查询 休眠-唤醒 poll/select 异步通知
CPU占用 极高 极低 极低
实时性 最好(取决于查询频率) 最好
代码复杂度 非常简单 简单 中等 复杂
单/多文件 单文件 单文件 多文件 单/多文件
超时支持 需手动实现
适用场景 极简单调试,或数据变化极快 单任务,事件不频繁且可接受永久等待 需要等待多个事件,或有超时要求 高并发,事件驱动的高效应用,如GUI、网络服务

选择建议

  • 教学/调试:查询方式。
  • 简单驱动,专用任务:休眠-唤醒。
  • 需要同时监控多个设备(如按键+触摸屏)或有超时需求poll/select
  • 构建大型、高效、非阻塞的应用程序(如Qt/GTK应用、网络服务器) :异步通知(常与epoll等配合,SIGIOepoll的底层机制之一)。

深度理解自测:四种驱动访问方式

1. 在休眠-唤醒方式的驱动中,如果中断服务程序(ISR)里直接调用 copy_to_user 将按键值传回用户空间,会引发什么问题?为什么?

答案:

会引发内核崩溃(oops)或系统死锁。原因如下:

  • copy_to_user 可能引起缺页异常 (用户空间内存未映射或换出),内核需要睡眠来换入页面。
  • 中断服务程序运行在中断上下文 中,不允许睡眠(in_interrupt() 为真)。睡眠会导致调度器无法运行,因为中断上下文不关联任何进程,无法被唤醒。
  • 正确做法:ISR 只做最少的事(读硬件、存数据、唤醒等待队列),数据拷贝交给被唤醒的进程在进程上下文 中完成(即 read 函数内)。

2. poll 机制中,驱动层的 .poll 函数内部调用了 poll_wait,这个调用会让当前进程立即进入睡眠吗?如果不是,真正的睡眠发生在哪里?

答案:
不会立即睡眠poll_wait 的作用是:

  • 将当前进程(current)添加到 wait_queue_head_t 中,但不主动调度
  • 它只是注册一个"等待器",告诉内核:如果将来有事件(中断),请唤醒这个进程。
  • 真正的睡眠 发生在内核的 do_poll 循环中:
    1. 内核调用驱动 .poll 获取状态。
    2. 如果返回非零(有数据),则立即返回。
    3. 如果返回 0(无数据),内核调用 schedule_timeout() 让进程进入有限时间睡眠。
    4. 超时或被 wake_up 唤醒后,内核再次调用 .poll 检查状态,直到有数据或超时。
  • 理解这一点就明白了:.poll轻量级查询函数 ,可以被反复调用;poll_wait 只负责建立"唤醒路径",不负责睡眠。

3. 异步通知方式中,应用程序的信号处理函数里直接调用 printfmalloc 可能存在什么风险?正确的做法是什么?

答案:
风险printfmalloc 等函数不是异步信号安全的 (async-signal-safe)。信号处理函数执行时会打断主程序任意位置,如果主程序正持有 malloc 的堆锁,信号处理函数中再调用 malloc 会导致死锁printf 内部可能涉及 stdout 锁,同样危险。

正确做法

  • 在信号处理函数中只调用异步信号安全的函数 (如 writeread_exitsignal)。
  • 典型模式:信号处理函数中调用 read(fd, buf, size) 读取数据(read 是异步信号安全的),然后将数据放入无锁环形缓冲区 ,再设置一个全局标志。主循环检查该标志并安全地处理数据(可调用 printf)。
  • 更现代的方法:使用 signalfd 将信号转换为文件描述符,用 poll/epoll 统一处理,避免在信号处理函数中做复杂操作。

4. 如果一个驱动同时实现了 .poll.fasync,并且中断中既调用了 wake_up_interruptible 又调用了 kill_fasync。应用程序同时使用 poll 和异步通知(比如先 poll 等待,又注册了 SIGIO),可能产生什么竞争条件?如何解决?

答案:
竞争条件

  1. 按键中断发生 → 驱动唤醒等待队列并发送信号。
  2. poll 返回 POLLIN,应用程序准备调用 read
  3. 但在调用 read 之前,信号处理函数可能被调度执行,它调用了 read 提前取走了数据
  4. 随后主程序中的 read 调用可能因为缓冲区空而阻塞 (如果驱动未正确处理)或返回 -EAGAIN

解决方案

  • 驱动侧read 函数必须实现为:如果非阻塞且无数据,返回 -EAGAIN;如果阻塞且无数据,休眠。这样可以容忍"虚假唤醒"。
  • 应用侧 :采用两种策略之一:
    • 统一使用异步通知 :不在主循环中调用 poll,完全依赖信号处理函数读取数据。
    • 信号处理函数仅设置标志 :不在信号处理函数中直接 read,而是设置 volatile sig_atomic_t flag = 1;主循环检查标志后调用 poll(或直接 read)并清除标志。这样数据读取由主循环统一控制,避免竞争。

5. 查询方式虽然低效,但在某些场景下反而是最佳选择。请举一个具体的嵌入式例子,并解释为什么不能用休眠-唤醒或异步通知。

答案:
例子 :读取旋转编码器高频方波信号 (比如频率 > 10kHz)。
原因

  • 休眠-唤醒依赖中断,而高频率中断会导致系统响应延迟剧增,甚至丢失中断(因为中断处理本身有开销)。
  • 异步通知同样依赖中断,信号处理函数频繁触发会使系统负载极高,且用户态信号处理有延迟。
  • 查询方式可以在一个死循环中连续读取GPIO电平 ,配合 CPU 直连的快速 I/O缓存一致性,能准确捕获每一个电平变化。
  • 优化:查询循环中关闭中断、使用 udelay 去抖,甚至直接用 内存映射 I/O 配合 while 循环,实现微秒级采样。

结论 :查询方式适用于高实时性、低延迟、信号频率高于中断处理能力的场景,代价是占用单核 CPU 100%,但有时这是唯一可行的方案(例如软件实现 SPI 时序)。


6. selectpollepoll 都是等待多个文件描述符的机制。为什么在按键驱动示例中通常使用 poll 而不是 epollepoll 有什么缺点?

答案
原因

  • 复杂度poll API 简单,适合描述符数量少(< 10)的场景,按键驱动通常只监控 1~4 个按键。
  • 触发模式epoll 默认是边缘触发(ET),需要应用程序一次性读光数据,否则会丢失事件。按键驱动数据量小(每次一个值),水平触发(LT)更自然,而 poll 天然是水平触发。
  • 开销epoll 需要创建 epoll 实例、注册、等待等步骤,对于少量描述符,其内存开销和调用开销 大于 poll
  • 可移植性poll 是 POSIX 标准,epoll 是 Linux 特有,教学示例追求通用性。

epoll 的缺点

  • 不适用于普通文件(普通文件总是可读,导致 epoll 一直返回事件)。
  • 边缘触发模式下编程容易出错(需要非阻塞 read 直到 EAGAIN)。
  • 在描述符很少时,性能并不比 poll 高。

7. 驱动设计原则"提供能力,不提供策略"在这四种访问方式中是如何体现的?请结合代码举例说明。

答案

该原则意味着:驱动应该实现所有可能的访问机制 (查询、休眠、poll、异步通知),但不限制或强制 APP 使用哪一种。APP 根据自己的需求(实时性、CPU负载、复杂度)选择合适的机制。

代码体现

  • 驱动提供 .read(支持阻塞/非阻塞)、.poll(支持超时等待)、.fasync(支持信号驱动)。

  • 没有策略 :驱动不会规定"你必须用 poll 否则效率低",也不会禁止查询方式。APP 可以通过 open 时是否带 O_NONBLOCK 来选择阻塞/非阻塞;可以通过是否调用 poll 来选择超时等待;可以通过是否设置 FASYNC 来选择异步通知。

  • 示例

    c

    复制代码
    // APP 可以选择查询(非阻塞)
    fd = open("/dev/button", O_RDWR | O_NONBLOCK);
    while (1) { ret = read(fd, &val, 4); if (ret != -EAGAIN) break; }
    
    // APP 也可以选择阻塞休眠
    fd = open("/dev/button", O_RDWR); // 默认阻塞
    read(fd, &val, 4);
    
    // APP 还可以选择 poll
    poll(fds, 1, 2000);
    
    // APP 甚至选择异步通知
    signal(SIGIO, handler);
    fcntl(fd, F_SETOWN, getpid());
    fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | FASYNC);
  • 驱动内部通过检查 file->f_flags & O_NONBLOCK 来决定 read 是否立即返回 -EAGAIN;通过 poll_table 支持 poll;通过 fasync_helper 支持异步通知。所有这些能力都同时提供,选择权完全交给 APP

相关推荐
凉、介2 小时前
从设备树到驱动源码:揭秘嵌入式 Linux 中 MMC 子系统的统一与差异
linux·驱动开发·笔记·学习·嵌入式·sd·emmc
@insist1232 小时前
网络工程师-动态路由协议(二):BGP 协议与路由引入技术详解
运维·网络·网络工程师·软考·软件水平考试
Full Stack Developme2 小时前
Linux 软连接与硬连接比较
linux·运维·服务器
云边有个稻草人2 小时前
【Linux系统】第九节—进程状态续集+进程优先级+进程切换
linux·进程状态·进程优先级·linux进程调度算法·linux进程切换·死循环进程如何运行·pri and ni
草莓熊Lotso2 小时前
Linux 线程同步与互斥(二):线程同步从条件变量到生产者消费者模型全解,原理 + 源码彻底吃透
linux·运维·服务器·c语言·开发语言·数据库·c++
eEKI DAND2 小时前
一个比 Nginx 还简单的 Web 服务器
服务器·前端·nginx
小郑加油2 小时前
python学习Day6-7天:条件判断与基本综合应用
java·服务器·apache
程序员老邢2 小时前
【技术底稿 17】DevOps 监控告警实战踩坑复盘 —— 企微机器人告警 + Milvus 向量库监控全流程验证
运维·机器人·企业微信·devops·milvus
ITOWARE_SAPer10 小时前
选择SAP实施公司能否兼得官方授权与高性价比?
运维·能源·制造·零售