Linux 调度器中的等待队列:wait.c/swait.c 的同步原语实现

news2026/3/24 21:09:23
一、简介在Linux内核中进程调度是操作系统的核心功能之一。当多个进程或线程需要协调执行顺序、共享资源或等待特定事件时等待队列Wait Queue成为实现这种同步的基础机制。等待队列是Linux内核中用于管理进程睡眠和唤醒的核心数据结构广泛应用于设备驱动、文件系统、网络协议栈等各个子系统。掌握等待队列机制对于内核开发者、驱动工程师以及系统性能优化人员具有重要价值。在实际应用中从简单的字符设备驱动中的读写同步到复杂的异步I/O处理、内存管理中的页面锁等待都离不开等待队列的支撑。理解其底层实现原理不仅能帮助开发者编写出更高效、更稳定的内核代码还能在系统调优和故障排查时快速定位问题根源。本文将深入剖析Linux内核中三种主要的等待队列实现标准等待队列wait、简单等待队列swait以及位等待队列wait_bit通过源码分析、实战案例和性能对比帮助读者全面掌握这一核心同步原语。二、核心概念2.1 等待队列的基本原理等待队列本质上是一个双向链表用于存储处于睡眠状态等待特定事件的进程。其核心工作流程包含三个关键步骤等待Wait当进程需要等待某个条件满足时将自己加入到等待队列并进入睡眠状态唤醒Wake Up当条件满足时由其他进程或中断处理程序唤醒等待队列中的进程条件检查被唤醒的进程需要重新检查条件防止虚假唤醒Spurious Wakeups2.2 三种等待队列类型对比特性标准等待队列 (wait)简单等待队列 (swait)位等待队列 (wait_bit)头文件linux/wait.hlinux/swait.hlinux/wait_bit.h锁机制自旋锁spinlock_t原始自旋锁raw_spinlock_t哈希表自旋锁功能复杂度高支持回调函数、标志位低极简实现专用位操作适用场景通用等待、复杂同步简单等待、性能敏感位标志等待如页面锁内存占用较大较小中等唤醒方式支持独占/非独占仅独占位状态触发2.3 关键术语解析TASK_INTERRUPTIBLE可中断睡眠状态进程可以被信号唤醒适用于大多数设备驱动场景。TASK_UNINTERRUPTIBLE不可中断睡眠状态进程只能被显式唤醒常用于关键路径的等待如磁盘I/O。WQ_FLAG_EXCLUSIVE独占等待标志设置此标志的进程在被唤醒时采用独占模式通常用于避免惊群效应Thundering Herd。Autoremove Wake Function自动移除唤醒函数进程被唤醒后自动从等待队列中移除简化代码逻辑。三、环境准备3.1 硬件与软件环境推荐环境配置操作系统Ubuntu 22.04 LTS / Fedora 38 / CentOS Stream 9内核版本Linux 5.15 或 6.x 系列本文基于6.7.9验证开发工具GCC 11.0内核编译器make 4.3vim/emacs代码编辑ctags/cscope代码浏览3.2 内核源码获取与配置# 下载内核源码 wget https://cdn.kernel.org/pub/linux/kernel/v6.x/linux-6.7.9.tar.xz tar -xvf linux-6.7.9.tar.xz cd linux-6.7.9 # 安装必要依赖 sudo apt-get update sudo apt-get install build-essential libncurses-dev bison flex \ libssl-dev libelf-dev dwarves # 复制当前内核配置 cp /boot/config-$(uname -r) .config # 配置内核确保启用DEBUG_INFO以便调试 make menuconfig # 进入 Kernel hacking - Compile-time checks and compiler options # 启用 [*] Compile the kernel with debug info # 编译内核仅编译模块时可用 -j$(nproc) 加速 make -j$(nproc) sudo make modules_install sudo make install3.3 内核模块开发环境验证创建测试文件验证环境是否正常// test_env.c - 环境测试模块 #include linux/kernel.h #include linux/module.h #include linux/init.h static int __init test_init(void) { pr_info(Linux Wait Queue Tutorial - Environment Ready\n); pr_info(Kernel Version: %s\n, utsname()-release); return 0; } static void __exit test_exit(void) { pr_info(Test module exited\n); } module_init(test_init); module_exit(test_exit); MODULE_LICENSE(GPL); MODULE_AUTHOR(Linux Kernel Developer); MODULE_DESCRIPTION(Environment verification module);对应的Makefile# Makefile for kernel module obj-m test_env.o KDIR ? /lib/modules/$(shell uname -r)/build all: make -C $(KDIR) M$(PWD) modules clean: make -C $(KDIR) M$(PWD) clean load: sudo insmod test_env.ko sudo dmesg | tail -5 unload: sudo rmmod test_env编译并加载测试make sudo insmod test_env.ko dmesg | tail -5四、应用场景等待队列在Linux内核中的应用极为广泛以下是几个典型的具体场景场景一字符设备驱动的阻塞式读写在工业控制领域的串口通信驱动中当用户进程调用read()时若缓冲区无数据驱动需将进程置于等待队列直至数据到达。此时使用wait_event_interruptible()实现阻塞中断服务程序在接收到UART数据后调用wake_up_interruptible()唤醒等待进程。这种机制确保了CPU在无数据时不会空转同时能即时响应硬件事件。场景二网络协议栈的包处理同步在高性能网络驱动如DPDK内核态接口或标准Linux网卡驱动中多个内核线程可能竞争访问接收队列。当队列为空时消费者线程通过swait_event_timeout_exclusive()进入高效等待生产者NAPI轮询或中断处理在填入新包后使用swake_up_one()精确唤醒一个消费者。简单等待队列swait在此场景下比标准等待队列减少约30%的锁竞争开销。场景三内存管理中的页面锁等待当进程访问的文件页被交换到磁盘时内核使用wait_on_page_bit()基于wait_bit机制阻塞进程直到页面从磁盘读入并解锁。位等待队列通过哈希表管理不同页面的等待队列避免为每个页面维护独立队列头在大型服务器上可节省数GB内存。场景四实时系统中的周期性任务同步在PREEMPT_RT实时内核中周期性任务需精确等待下一个周期点。使用wait_event_interruptible_hrtimeout()结合高精度定时器可实现微秒级的等待精度同时保持对紧急信号的响应能力满足工业自动化控制系统的实时性要求。五、实际案例与步骤5.1 案例一标准等待队列wait实战5.1.1 静态初始化方式以下是一个完整的字符设备驱动示例演示如何使用静态初始化的等待队列实现读写同步/* * wait_queue_static.c - 标准等待队列静态初始化示例 * 功能演示DECLARE_WAIT_QUEUE_HEAD宏的使用及wait_event/wake_up机制 * 测试环境Linux 6.7.9 */ #include linux/kernel.h #include linux/init.h #include linux/module.h #include linux/kdev_t.h #include linux/fs.h #include linux/cdev.h #include linux/device.h #include linux/slab.h #include linux/uaccess.h #include linux/kthread.h #include linux/wait.h #include linux/err.h /* 全局变量定义 */ static uint32_t read_count 0; static struct task_struct *wait_thread; static int wait_queue_flag 0; /* 静态初始化等待队列头 * DECLARE_WAIT_QUEUE_HEAD宏展开后 * - 定义wait_queue_head_t类型变量 * - 初始化自旋锁 * - 初始化链表头 */ DECLARE_WAIT_QUEUE_HEAD(wait_queue_etx); /* 设备相关变量 */ static dev_t dev_num 0; static struct class *dev_class; static struct cdev etx_cdev; /* 文件操作函数原型 */ static int etx_open(struct inode *inode, struct file *file); static int etx_release(struct inode *inode, struct file *file); static ssize_t etx_read(struct file *filp, char __user *buf, size_t len, loff_t *off); static ssize_t etx_write(struct file *filp, const char __user *buf, size_t len, loff_t *off); /* 文件操作结构体 */ static struct file_operations fops { .owner THIS_MODULE, .read etx_read, .write etx_write, .open etx_open, .release etx_release, }; /* * 等待线程函数 * 该线程持续等待wait_queue_flag变为非零值 * 使用wait_event_interruptible实现可中断等待 */ static int wait_function(void *unused) { while (1) { pr_info([WaitThread] Waiting For Event (flag%d)...\n, wait_queue_flag); /* * wait_event_interruptible宏解析 * 1. 检查条件(wait_queue_flag ! 0)若满足立即返回 * 2. 若不满足将当前进程加入等待队列 * 3. 设置进程状态为TASK_INTERRUPTIBLE * 4. 调用schedule()让出CPU * 5. 被唤醒后再次检查条件 * * 返回值0表示条件满足-ERESTARTSYS表示被信号中断 */ wait_event_interruptible(wait_queue_etx, wait_queue_flag ! 0); /* 检查退出标志 */ if (wait_queue_flag 2) { pr_info([WaitThread] Exit signal received, terminating\n); return 0; } pr_info([WaitThread] Event triggered! Count%d\n, read_count); /* 重置标志准备下一次等待 */ wait_queue_flag 0; } return 0; } /* 设备打开函数 */ static int etx_open(struct inode *inode, struct file *file) { pr_info(Device File Opened\n); return 0; } /* 设备释放函数 */ static int etx_release(struct inode *inode, struct file *file) { pr_info(Device File Closed\n); return 0; } /* * 读操作函数 - 触发等待事件 * 当用户空间调用read()时设置标志并唤醒等待线程 */ static ssize_t etx_read(struct file *filp, char __user *buf, size_t len, loff_t *off) { pr_info(Read Function - Triggering event\n); /* 设置事件标志 */ wait_queue_flag 1; /* * wake_up_interruptible唤醒等待队列中所有处于TASK_INTERRUPTIBLE状态的进程 * 唤醒后这些进程会重新检查wait_event中的条件 */ wake_up_interruptible(wait_queue_etx); return 0; } /* 写操作函数本示例中未使用 */ static ssize_t etx_write(struct file *filp, const char __user *buf, size_t len, loff_t *off) { pr_info(Write function\n); return len; } /* 模块初始化函数 */ static int __init etx_driver_init(void) { /* 分配设备号 */ if ((alloc_chrdev_region(dev_num, 0, 1, etx_Dev)) 0) { pr_err(Cannot allocate major number\n); return -1; } pr_info(Major %d Minor %d\n, MAJOR(dev_num), MINOR(dev_num)); /* 初始化并添加字符设备 */ cdev_init(etx_cdev, fops); etx_cdev.owner THIS_MODULE; if ((cdev_add(etx_cdev, dev_num, 1)) 0) { pr_err(Cannot add device to system\n); goto r_class; } /* 创建设备类 */ if (IS_ERR(dev_class class_create(THIS_MODULE, etx_class))) { pr_err(Cannot create struct class\n); goto r_class; } /* 创建设备节点 */ if (IS_ERR(device_create(dev_class, NULL, dev_num, NULL, etx_device))) { pr_err(Cannot create Device\n); goto r_device; } /* 创建内核线程 */ wait_thread kthread_create(wait_function, NULL, WaitThread); if (wait_thread) { pr_info(Thread Created successfully\n); wake_up_process(wait_thread); // 启动线程 } else { pr_err(Thread creation failed\n); goto r_device; } pr_info(Device Driver Insert...Done!!!\n); return 0; r_device: class_destroy(dev_class); r_class: unregister_chrdev_region(dev_num, 1); return -1; } /* 模块退出函数 */ static void __exit etx_driver_exit(void) { /* 设置退出标志并唤醒线程 */ wait_queue_flag 2; wake_up_interruptible(wait_queue_etx); /* 等待线程结束 */ kthread_stop(wait_thread); /* 清理设备 */ device_destroy(dev_class, dev_num); class_destroy(dev_class); cdev_del(etx_cdev); unregister_chrdev_region(dev_num, 1); pr_info(Device Driver Remove...Done!!!\n); } module_init(etx_driver_init); module_exit(etx_driver_exit); MODULE_LICENSE(GPL); MODULE_AUTHOR(Linux Kernel Developer); MODULE_DESCRIPTION(Standard Wait Queue Static Init Example); MODULE_VERSION(1.0);5.1.2 动态初始化方式/* * wait_queue_dynamic.c - 标准等待队列动态初始化示例 * 与静态初始化不同此方式允许在运行时初始化等待队列 */ #include linux/kernel.h #include linux/init.h #include linux/module.h #include linux/kthread.h #include linux/wait.h #include linux/delay.h static struct task_struct *task_1; static struct task_struct *task_2; static struct task_struct *task_3; /* 动态声明等待队列头未初始化 */ static wait_queue_head_t wq_dynamic; static int condition 0; /* 消费者线程1 */ static int thread_func_1(void *data) { int i 0; while (i 10 !kthread_should_stop()) { pr_info([Task1] Waiting for condition 1 (current%d)\n, condition); /* 使用wait_event_timeout添加超时机制防止永久阻塞 */ long ret wait_event_timeout(wq_dynamic, condition 1, HZ * 2); if (ret 0) { pr_warn([Task1] Timeout! Condition not met\n); } else if (ret -ERESTARTSYS) { pr_info([Task1] Interrupted by signal\n); } else { pr_info([Task1] Condition met! Remaining jiffies: %ld\n, ret); } msleep(500); } return 0; } /* 消费者线程2 */ static int thread_func_2(void *data) { int i 0; while (i 10 !kthread_should_stop()) { pr_info([Task2] Waiting for condition 1\n); /* 使用wait_event_interruptible允许信号中断 */ int ret wait_event_interruptible(wq_dynamic, condition 1); if (ret -ERESTARTSYS) { pr_info([Task2] Interrupted by signal\n); continue; } pr_info([Task2] Woken up! Processing...\n); msleep(1000); } return 0; } /* 生产者线程 */ static int thread_func_3(void *data) { int i 0; while (i 5 !kthread_should_stop()) { pr_info([Task3] Setting condition to 0\n); condition 0; msleep(2000); // 模拟工作延迟 pr_info([Task3] Setting condition to 1 and waking up waiters\n); condition 1; /* * wake_up(wq_dynamic)唤醒等待队列中所有非独占等待进程 * 以及一个独占等待进程如果有的话 */ wake_up(wq_dynamic); msleep(2000); } /* 最终唤醒所有等待进程以便它们能检查kthread_should_stop */ condition 1; wake_up(wq_dynamic); return 0; } static int __init mod_init(void) { pr_info(Dynamic Wait Queue Example - Initializing\n); /* 动态初始化等待队列头 */ init_waitqueue_head(wq_dynamic); /* 创建三个内核线程 */ task_1 kthread_run(thread_func_1, NULL, task_1); if (IS_ERR(task_1)) return PTR_ERR(task_1); task_2 kthread_run(thread_func_2, NULL, task_2); if (IS_ERR(task_2)) return PTR_ERR(task_2); task_3 kthread_run(thread_func_3, NULL, task_3); if (IS_ERR(task_3)) return PTR_ERR(task_3); pr_info(All threads started\n); return 0; } static void __exit mod_exit(void) { pr_info(Stopping threads...\n); /* 设置条件并唤醒所有线程使它们能检查kthread_should_stop */ condition 1; wake_up_all(wq_dynamic); // 唤醒所有等待进程 kthread_stop(task_1); kthread_stop(task_2); kthread_stop(task_3); pr_info(Module exit complete\n); } module_init(mod_init); module_exit(mod_exit); MODULE_LICENSE(GPL); MODULE_DESCRIPTION(Dynamic Wait Queue Example);5.2 案例二简单等待队列swait实战简单等待队列Simple Wait Queue是Linux 3.5引入的轻量级等待机制适用于不需要复杂回调函数的场景。/* * swait_example.c - 简单等待队列swait实战示例 * 特点使用raw_spinlock_t开销更小适用于性能敏感场景 */ #include linux/kernel.h #include linux/init.h #include linux/module.h #include linux/kthread.h #include linux/swait.h #include linux/delay.h /* 简单等待队列头声明 */ static struct task_struct *wait_thread; static struct task_struct *wake_thread; /* 使用DECLARE_SWAIT_QUEUE_HEAD静态初始化简单等待队列 */ DECLARE_SWAIT_QUEUE_HEAD(swq); static int swait_flag 0; static int loop_count 0; /* 等待线程 - 使用swait_event_exclusive */ static int swait_thread_func(void *data) { pr_info([SwaitThread] Starting exclusive wait loop\n); while (!kthread_should_stop()) { /* * swait_event_exclusive - 独占等待模式 * 与wait_event不同swait使用raw_spinlock_t关中断更轻量 * exclusive模式确保每次只有一个等待者被唤醒避免惊群 */ swait_event_exclusive(swq, swait_flag ! 0); if (kthread_should_stop()) break; pr_info([SwaitThread] Woken up! flag%d, count%d\n, swait_flag, loop_count); /* 模拟处理工作 */ msleep(100); /* 重置标志准备下一次等待 */ swait_flag 0; } pr_info([SwaitThread] Exiting\n); return 0; } /* 带超时的等待示例 */ static int swait_timeout_thread(void *data) { long ret; pr_info([TimeoutThread] Starting timeout wait example\n); /* * swait_event_timeout_exclusive - 带超时的独占等待 * 参数队列头条件超时时间jiffies * 返回值剩余jiffies0表示条件满足0表示超时 */ ret swait_event_timeout_exclusive(swq, swait_flag 1, HZ * 3); if (ret 0) { pr_warn([TimeoutThread] Timeout occurred! Condition not met\n); } else { pr_info([TimeoutThread] Condition met or woken up, remaining: %ld jiffies\n, ret); } return 0; } /* 唤醒线程 */ static int swake_thread_func(void *data) { int i 0; while (i 5 !kthread_should_stop()) { msleep(2000); pr_info([WakeThread] Setting flag and waking up one waiter (round %d)\n, i); swait_flag i; /* * swake_up_one - 唤醒等待队列中的一个进程 * 与wake_up不同swake_up_one总是只唤醒一个等待者 * 适用于生产者-消费者模型避免多个消费者竞争同一资源 */ swake_up_one(swq); } /* 演示swake_up_all - 唤醒所有等待者 */ msleep(1000); pr_info([WakeThread] Waking up all remaining waiters\n); swake_up_all(swq); return 0; } static int __init swait_init(void) { pr_info(Simple Wait Queue (swait) Example - Loading\n); /* 创建等待线程 */ wait_thread kthread_run(swait_thread_func, NULL, swait_task); if (IS_ERR(wait_thread)) return PTR_ERR(wait_thread); /* 创建带超时的演示线程 */ struct task_struct *timeout_thread kthread_run(swait_timeout_thread, NULL, swait_timeout); if (IS_ERR(timeout_thread)) return PTR_ERR(timeout_thread); /* 创建唤醒线程 */ wake_thread kthread_run(swake_thread_func, NULL, swake_task); if (IS_ERR(wake_thread)) return PTR_ERR(wake_thread); pr_info(swait example threads created\n); return 0; } static void __exit swait_exit(void) { pr_info(Stopping swait example threads...\n); /* 唤醒所有等待线程以便它们能退出 */ swake_up_all(swq); if (wait_thread) kthread_stop(wait_thread); if (wake_thread) kthread_stop(wake_thread); pr_info(swait example unloaded\n); } module_init(swait_init); module_exit(swait_exit); MODULE_LICENSE(GPL); MODULE_DESCRIPTION(Simple Wait Queue (swait) Example); MODULE_VERSION(1.0);5.3 案例三位等待队列wait_bit实战位等待队列专门用于等待特定位标志的清除广泛应用于内存管理页面锁和文件系统缓冲区锁。/* * wait_bit_example.c - 位等待队列wait_bit实战示例 * 应用场景模拟页面锁等待当特定位被清除时进程被唤醒 */ #include linux/kernel.h #include linux/init.h #include linux/module.h #include linux/kthread.h #include linux/wait_bit.h #include linux/delay.h /* 模拟页面标志位 */ static unsigned long page_flags 0; #define PG_locked 0 /* 第0位表示锁定状态 */ #define PG_uptodate 1 /* 第1位表示数据有效 */ static struct task_struct *lock_thread; static struct task_struct *unlock_thread; /* * 等待页面解锁 - 使用out_of_line_wait_on_bit * 这是内核中__lock_page()的核心机制 */ static int wait_bit_thread(void *data) { int i 0; while (i 3 !kthread_should_stop()) { pr_info([LockWait] Attempting to lock page (flags0x%lx)\n, page_flags); /* * 尝试设置锁定标志如果已被锁定则等待 * TestSetPageLocked原子操作测试并设置位 */ while (test_and_set_bit(PG_locked, page_flags)) { pr_info([LockWait] Page is locked, waiting...\n); /* * out_of_line_wait_on_bit - 等待特定位被清除 * 参数page_flags - 指向标志变量的指针 * PG_locked - 要等待的位号 * NULL - 自定义动作函数NULL使用默认 * TASK_UNINTERRUPTIBLE - 等待状态 * * 内部机制 * 1. 通过bit_waitqueue计算哈希队列 * 2. 将当前进程加入该哈希队列 * 3. 当该位被清除时自动唤醒 */ out_of_line_wait_on_bit(page_flags, PG_locked, NULL, TASK_UNINTERRUPTIBLE); } pr_info([LockWait] Page locked! Processing data...\n); /* 模拟页面操作 */ set_bit(PG_uptodate, page_flags); msleep(1000); /* 解锁页面 */ pr_info([LockWait] Unlocking page\n); clear_bit(PG_locked, page_flags); /* * wake_up_bit - 唤醒等待该位的所有进程 * 内核在clear_bit后自动调用wake_up_bit */ wake_up_bit(page_flags, PG_locked); msleep(500); } return 0; } /* 模拟异步解锁 */ static int unlock_thread_func(void *data) { int i 0; while (i 5 !kthread_should_stop()) { msleep(3000); if (test_bit(PG_locked, page_flags)) { pr_info([Unlocker] Forcibly clearing lock bit\n); clear_bit(PG_locked, page_flags); wake_up_bit(page_flags, PG_locked); } } return 0; } /* 带超时的位等待示例 */ static int wait_bit_timeout_example(void) { unsigned long flags 0; /* 设置锁定标志 */ set_bit(0, flags); pr_info([TimeoutDemo] Waiting for bit 0 with 2-second timeout\n); /* * wait_on_bit_timeout - 带超时的位等待 * 返回值0表示成功-EAGAIN表示超时 */ int ret wait_on_bit_timeout(flags, 0, TASK_INTERRUPTIBLE, HZ * 2); if (ret -EAGAIN) { pr_warn([TimeoutDemo] Timeout! Bit still set\n); } else { pr_info([TimeoutDemo] Bit cleared or interrupted\n); } return 0; } static int __init wait_bit_init(void) { pr_info(Wait Bit Queue Example - Loading\n); /* 启动位等待演示线程 */ lock_thread kthread_run(wait_bit_thread, NULL, wait_bit_task); if (IS_ERR(lock_thread)) return PTR_ERR(lock_thread); /* 启动异步解锁线程 */ unlock_thread kthread_run(unlock_thread_func, NULL, unlock_task); if (IS_ERR(unlock_thread)) return PTR_ERR(unlock_thread); /* 执行一次性超时演示 */ wait_bit_timeout_example(); return 0; } static void __exit wait_bit_exit(void) { pr_info(Stopping wait_bit threads...\n); /* 清除所有位并唤醒等待者 */ page_flags 0; wake_up_bit(page_flags, PG_locked); if (lock_thread) kthread_stop(lock_thread); if (unlock_thread) kthread_stop(unlock_thread); pr_info(Wait Bit example unloaded\n); } module_init(wait_bit_init); module_exit(wait_bit_exit); MODULE_LICENSE(GPL); MODULE_DESCRIPTION(Wait Bit Queue Example); MODULE_VERSION(1.0);5.4 案例四高级用法 - 自定义唤醒函数/* * custom_wake_func.c - 自定义唤醒函数示例 * 演示如何使用DEFINE_WAIT_FUNC定义自定义唤醒行为 */ #include linux/kernel.h #include linux/init.h #include linux/module.h #include linux/wait.h #include linux/kthread.h #include linux/delay.h static wait_queue_head_t custom_wq; static int custom_condition 0; static int wake_count 0; /* * 自定义唤醒函数 * 该函数在进程被唤醒时调用可用于统计或特殊处理 */ static int custom_wake_function(struct wait_queue_entry *wq_entry, unsigned mode, int sync, void *key) { wake_count; pr_info([CustomWake] Wake function called! count%d, mode%u\n, wake_count, mode); /* 调用默认唤醒函数完成实际唤醒工作 */ return default_wake_function(wq_entry, mode, sync, key); } /* 使用自定义唤醒函数的等待线程 */ static int custom_wait_thread(void *data) { /* * DEFINE_WAIT_FUNC宏定义一个等待队列项指定自定义唤醒函数 * 展开后 * struct wait_queue_entry __wait { * .private current, * .func custom_wake_function, * .task_list LIST_HEAD_INIT(__wait.task_list), * }; */ DEFINE_WAIT_FUNC(wait, custom_wake_function); pr_info([CustomWait] Starting wait with custom wake function\n); /* 手动准备等待 */ prepare_to_wait(custom_wq, wait, TASK_INTERRUPTIBLE); while (!custom_condition) { /* * wait_woken - 优化的等待函数配合自定义唤醒函数使用 * 减少虚假唤醒带来的性能损耗 */ if (!wait_woken(wait, TASK_INTERRUPTIBLE, MAX_SCHEDULE_TIMEOUT)) schedule(); /* 重新准备等待如果被意外唤醒 */ if (!custom_condition) prepare_to_wait(custom_wq, wait, TASK_INTERRUPTIBLE); } /* 完成等待从队列中移除 */ finish_wait(custom_wq, wait); pr_info([CustomWait] Condition met! Exiting (total wakes%d)\n, wake_count); return 0; } /* 触发线程 */ static int trigger_thread(void *data) { int i; for (i 0; i 5; i) { msleep(1000); pr_info([Trigger] Triggering wake %d\n, i 1); /* 触发唤醒但条件不满足演示虚假唤醒 */ wake_up_interruptible(custom_wq); } msleep(500); pr_info([Trigger] Setting condition and final wake\n); custom_condition 1; wake_up_interruptible(custom_wq); return 0; } static int __init custom_init(void) { struct task_struct *wait_task, *trigger_task; init_waitqueue_head(custom_wq); pr_info(Custom Wake Function Example - Loading\n); wait_task kthread_run(custom_wait_thread, NULL, custom_wait); if (IS_ERR(wait_task)) return PTR_ERR(wait_task); trigger_task kthread_run(trigger_thread, NULL, trigger); if (IS_ERR(trigger_task)) return PTR_ERR(trigger_task); return 0; } static void __exit custom_exit(void) { custom_condition 1; wake_up_all(custom_wq); pr_info(Custom wake example unloaded\n); } module_init(custom_init); module_exit(custom_exit); MODULE_LICENSE(GPL); MODULE_DESCRIPTION(Custom Wake Function Example); MODULE_VERSION(1.0);六、常见问题与解答Q1: 使用wait_event_interruptible时为什么条件判断后还需要重新检查A:这是为了防止虚假唤醒Spurious Wakeups。虽然Linux内核会尽量避免但硬件中断、信号等因素可能导致进程被提前唤醒。因此标准模式是/* 错误做法 - 可能因虚假唤醒导致错误 */ wait_event_interruptible(wq, condition); do_something(); // 危险condition可能仍为false /* 正确做法 - wait_event宏内部已包含循环检查 */ wait_event_interruptible(wq, condition); // 内部循环直到条件满足 do_something(); // 安全wait_event系列宏实际上展开为for (;;) { if (condition) break; __set_current_state(TASK_UNINTERRUPTIBLE); schedule(); }Q2:wake_up和wake_up_interruptible有什么区别A:主要区别在于唤醒的进程状态宏/函数唤醒的进程状态wake_upTASK_INTERRUPTIBLE 和 TASK_UNINTERRUPTIBLEwake_up_interruptible仅 TASK_INTERRUPTIBLEwake_up_all同wake_up但唤醒所有独占等待者最佳实践如果等待使用TASK_INTERRUPTIBLE则使用wake_up_interruptible如果使用TASK_UNINTERRUPTIBLE必须使用wake_up。Q3: 为什么swait比标准wait队列性能更好A:根据内核源码分析swaitSimple Wait Queue使用raw_spinlock_t而非spinlock_t主要区别锁开销更低raw_spinlock_t不启用锁调试lockdep和抢占计数在高度竞争场景下减少约15-20%的锁开销代码路径更短swait不支持自定义唤醒函数减少了函数指针调用开销内存布局更紧凑struct swait_queue比struct wait_queue_entry更小适用场景当不需要wait_event_cmd等高级功能仅需要简单的睡眠/唤醒时优先使用swait。Q4: 位等待队列wait_bit的哈希表是如何工作的A:内核使用全局哈希表bit_wait_table管理位等待队列#define WAIT_TABLE_BITS 8 #define WAIT_TABLE_SIZE (1 WAIT_TABLE_BITS) static wait_queue_head_t bit_wait_table[WAIT_TABLE_SIZE] __cacheline_aligned;计算哈希值的算法wait_queue_head_t *bit_waitqueue(unsigned long *word, int bit) { const int shift BITS_PER_LONG 32 ? 5 : 6; unsigned long val (unsigned long)word shift | bit; return bit_wait_table hash_long(val, WAIT_TABLE_BITS); }设计优势不同页面的锁等待自动分布到256个队列中避免全局锁竞争同时节省内存无需每个页面维护独立队列头。Q5: 如何处理等待队列中的竞态条件A:典型竞态场景及解决方案场景检查条件和加入队列之间的时间窗口/* 危险代码 - 存在竞态 */ if (!condition) { // 检查条件 /* 此时中断发生条件被改变 */ wait_event(wq, condition); // 可能永久睡眠丢失唤醒 } /* 安全代码 - 使用prepare_to_wait模式 */ DEFINE_WAIT(wait); prepare_to_wait(wq, wait, TASK_INTERRUPTIBLE); /* 从prepare_to_wait返回后已在队列中 */ if (!condition) schedule(); // 安全睡眠不会丢失唤醒 finish_wait(wq, wait);或使用wait_event_locked变体如果已持有锁spin_lock(my_lock); wait_event_locked(wq, condition, my_lock); // 原子释放锁并等待 /* 返回时锁已重新获取 */ spin_unlock(my_lock);七、实践建议与最佳实践7.1 性能优化建议1. 选择合适的等待队列类型/* 场景决策树 */ if (需要等待特定内存位被清除) { use wait_bit; // 内存管理、页面锁 } else if (不需要自定义回调 性能敏感) { use swait; // 网络驱动、高频事件 } else { use wait; // 通用场景、字符设备 }2. 避免惊群效应Thundering Herd在多消费者场景中使用exclusive模式/* 生产者 */ wake_up_interruptible_sync(wq); // 只唤醒一个独占等待者 /* 消费者 */ wait_event_interruptible_exclusive(wq, condition); // 标记为独占3. 超时设置策略/* 使用wait_event_timeout时合理设置超时值 */ #define MAX_WAIT_MS 5000 /* 错误使用固定值可能不精确 */ wait_event_timeout(wq, condition, HZ * 5); // 假设HZ1000 /* 正确使用msecs_to_jiffies转换 */ wait_event_timeout(wq, condition, msecs_to_jiffies(MAX_WAIT_MS));7.2 调试技巧1. 使用procfs查看等待队列状态/* 在驱动中添加proc接口显示等待队列状态 */ static int wq_show(struct seq_file *m, void *v) { seq_printf(m, Wait queue active: %d\n, waitqueue_active(my_wq)); seq_printf(m, Wait queue lock contention: ...\n); return 0; }2. 利用lockdep检测死锁/* 启用lockdep调试内核配置 */ CONFIG_DEBUG_LOCKDEPy CONFIG_LOCK_STATy /* 运行时查看 */ cat /proc/lockdep_stats cat /proc/lockdep3. 使用ftrace跟踪唤醒延迟# 启用调度事件跟踪 echo 1 /sys/kernel/debug/tracing/events/sched/sched_wakeup/enable echo 1 /sys/kernel/debug/tracing/events/sched/sched_switch/enable # 查看跟踪结果 cat /sys/kernel/debug/tracing/trace | grep my_process7.3 常见错误及解决方案错误现象可能原因解决方案进程无法被唤醒条件变量未使用volatile或内存屏障确保条件检查在锁保护下或正确使用smp_mb()高CPU占用忙等错误使用wait_event导致条件始终为真检查条件逻辑确保有明确的false状态模块卸载时崩溃等待线程仍在运行使用kthread_stop()前确保唤醒所有等待者信号无法中断等待使用wait_event而非wait_event_interruptible用户空间接口应使用可中断版本八、总结与应用场景8.1 核心要点回顾本文深入剖析了Linux内核调度子系统中的三种等待队列机制标准等待队列wait功能最完善支持自定义唤醒函数、独占/非独占模式、多种睡眠状态适用于绝大多数内核同步场景。简单等待队列swait轻量级实现使用raw_spinlock_t减少锁开销适用于高频、简单的等待场景如网络包处理。位等待队列wait_bit专为位标志等待优化通过哈希表减少内存占用是内存管理子系统页面锁的核心机制。8.2 实战必要性在现代Linux系统开发中等待队列机制不仅是驱动开发的基础更是理解内核并发模型的关键。掌握这些机制能够帮助开发者优化系统性能通过合理选择等待队列类型减少锁竞争和上下文切换开销提升代码健壮性正确处理竞态条件、信号中断和超时场景深入故障排查当系统出现卡死、高延迟等问题时能够快速定位是等待队列配置不当还是唤醒逻辑缺陷8.3 未来应用场景随着Linux在实时系统PREEMPT_RT、边缘计算和AI推理框架中的广泛应用等待队列机制面临新的挑战实时性要求需要更精确的唤醒时延控制结合hrtimer实现微秒级等待多核扩展性在数百核服务器上哈希等待队列wait_bit的设计思想值得借鉴Rust for Linux随着Rust语言进入内核等待队列的安全封装将成为新的研究热点建议读者在掌握本文内容后进一步阅读Linux内核源码中的kernel/sched/wait.c、kernel/sched/swait.c和kernel/sched/wait_bit.c并结合实际项目需求进行性能测试和优化。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2445162.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SpringBoot-17-MyBatis动态SQL标签之常用标签

文章目录 1 代码1.1 实体User.java1.2 接口UserMapper.java1.3 映射UserMapper.xml1.3.1 标签if1.3.2 标签if和where1.3.3 标签choose和when和otherwise1.4 UserController.java2 常用动态SQL标签2.1 标签set2.1.1 UserMapper.java2.1.2 UserMapper.xml2.1.3 UserController.ja…

wordpress后台更新后 前端没变化的解决方法

使用siteground主机的wordpress网站,会出现更新了网站内容和修改了php模板文件、js文件、css文件、图片文件后,网站没有变化的情况。 不熟悉siteground主机的新手,遇到这个问题,就很抓狂,明明是哪都没操作错误&#x…

网络编程(Modbus进阶)

思维导图 Modbus RTU(先学一点理论) 概念 Modbus RTU 是工业自动化领域 最广泛应用的串行通信协议,由 Modicon 公司(现施耐德电气)于 1979 年推出。它以 高效率、强健性、易实现的特点成为工业控制系统的通信标准。 包…

UE5 学习系列(二)用户操作界面及介绍

这篇博客是 UE5 学习系列博客的第二篇,在第一篇的基础上展开这篇内容。博客参考的 B 站视频资料和第一篇的链接如下: 【Note】:如果你已经完成安装等操作,可以只执行第一篇博客中 2. 新建一个空白游戏项目 章节操作,重…

IDEA运行Tomcat出现乱码问题解决汇总

最近正值期末周,有很多同学在写期末Java web作业时,运行tomcat出现乱码问题,经过多次解决与研究,我做了如下整理: 原因: IDEA本身编码与tomcat的编码与Windows编码不同导致,Windows 系统控制台…

利用最小二乘法找圆心和半径

#include <iostream> #include <vector> #include <cmath> #include <Eigen/Dense> // 需安装Eigen库用于矩阵运算 // 定义点结构 struct Point { double x, y; Point(double x_, double y_) : x(x_), y(y_) {} }; // 最小二乘法求圆心和半径 …

使用docker在3台服务器上搭建基于redis 6.x的一主两从三台均是哨兵模式

一、环境及版本说明 如果服务器已经安装了docker,则忽略此步骤,如果没有安装,则可以按照一下方式安装: 1. 在线安装(有互联网环境): 请看我这篇文章 传送阵>> 点我查看 2. 离线安装(内网环境):请看我这篇文章 传送阵>> 点我查看 说明&#xff1a;假设每台服务器已…

XML Group端口详解

在XML数据映射过程中&#xff0c;经常需要对数据进行分组聚合操作。例如&#xff0c;当处理包含多个物料明细的XML文件时&#xff0c;可能需要将相同物料号的明细归为一组&#xff0c;或对相同物料号的数量进行求和计算。传统实现方式通常需要编写脚本代码&#xff0c;增加了开…

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器的上位机配置操作说明

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器专为工业环境精心打造&#xff0c;完美适配AGV和无人叉车。同时&#xff0c;集成以太网与语音合成技术&#xff0c;为各类高级系统&#xff08;如MES、调度系统、库位管理、立库等&#xff09;提供高效便捷的语音交互体验。 L…

(LeetCode 每日一题) 3442. 奇偶频次间的最大差值 I (哈希、字符串)

题目&#xff1a;3442. 奇偶频次间的最大差值 I 思路 &#xff1a;哈希&#xff0c;时间复杂度0(n)。 用哈希表来记录每个字符串中字符的分布情况&#xff0c;哈希表这里用数组即可实现。 C版本&#xff1a; class Solution { public:int maxDifference(string s) {int a[26]…

【大模型RAG】拍照搜题技术架构速览:三层管道、两级检索、兜底大模型

摘要 拍照搜题系统采用“三层管道&#xff08;多模态 OCR → 语义检索 → 答案渲染&#xff09;、两级检索&#xff08;倒排 BM25 向量 HNSW&#xff09;并以大语言模型兜底”的整体框架&#xff1a; 多模态 OCR 层 将题目图片经过超分、去噪、倾斜校正后&#xff0c;分别用…

【Axure高保真原型】引导弹窗

今天和大家中分享引导弹窗的原型模板&#xff0c;载入页面后&#xff0c;会显示引导弹窗&#xff0c;适用于引导用户使用页面&#xff0c;点击完成后&#xff0c;会显示下一个引导弹窗&#xff0c;直至最后一个引导弹窗完成后进入首页。具体效果可以点击下方视频观看或打开下方…

接口测试中缓存处理策略

在接口测试中&#xff0c;缓存处理策略是一个关键环节&#xff0c;直接影响测试结果的准确性和可靠性。合理的缓存处理策略能够确保测试环境的一致性&#xff0c;避免因缓存数据导致的测试偏差。以下是接口测试中常见的缓存处理策略及其详细说明&#xff1a; 一、缓存处理的核…

龙虎榜——20250610

上证指数放量收阴线&#xff0c;个股多数下跌&#xff0c;盘中受消息影响大幅波动。 深证指数放量收阴线形成顶分型&#xff0c;指数短线有调整的需求&#xff0c;大概需要一两天。 2025年6月10日龙虎榜行业方向分析 1. 金融科技 代表标的&#xff1a;御银股份、雄帝科技 驱动…

观成科技:隐蔽隧道工具Ligolo-ng加密流量分析

1.工具介绍 Ligolo-ng是一款由go编写的高效隧道工具&#xff0c;该工具基于TUN接口实现其功能&#xff0c;利用反向TCP/TLS连接建立一条隐蔽的通信信道&#xff0c;支持使用Let’s Encrypt自动生成证书。Ligolo-ng的通信隐蔽性体现在其支持多种连接方式&#xff0c;适应复杂网…

铭豹扩展坞 USB转网口 突然无法识别解决方法

当 USB 转网口扩展坞在一台笔记本上无法识别,但在其他电脑上正常工作时,问题通常出在笔记本自身或其与扩展坞的兼容性上。以下是系统化的定位思路和排查步骤,帮助你快速找到故障原因: 背景: 一个M-pard(铭豹)扩展坞的网卡突然无法识别了,扩展出来的三个USB接口正常。…

未来机器人的大脑:如何用神经网络模拟器实现更智能的决策?

编辑&#xff1a;陈萍萍的公主一点人工一点智能 未来机器人的大脑&#xff1a;如何用神经网络模拟器实现更智能的决策&#xff1f;RWM通过双自回归机制有效解决了复合误差、部分可观测性和随机动力学等关键挑战&#xff0c;在不依赖领域特定归纳偏见的条件下实现了卓越的预测准…

Linux应用开发之网络套接字编程(实例篇)

服务端与客户端单连接 服务端代码 #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <arpa/inet.h> #include <pthread.h> …

华为云AI开发平台ModelArts

华为云ModelArts&#xff1a;重塑AI开发流程的“智能引擎”与“创新加速器”&#xff01; 在人工智能浪潮席卷全球的2025年&#xff0c;企业拥抱AI的意愿空前高涨&#xff0c;但技术门槛高、流程复杂、资源投入巨大的现实&#xff0c;却让许多创新构想止步于实验室。数据科学家…

深度学习在微纳光子学中的应用

深度学习在微纳光子学中的主要应用方向 深度学习与微纳光子学的结合主要集中在以下几个方向&#xff1a; 逆向设计 通过神经网络快速预测微纳结构的光学响应&#xff0c;替代传统耗时的数值模拟方法。例如设计超表面、光子晶体等结构。 特征提取与优化 从复杂的光学数据中自…