[TOC]
问题描述:
锁用于保护共享资源,避免多线程 / 中断同时修改导致的数据不一致。常见锁包括:自旋锁(spinlock)、互斥体(mutex)、信号量(semaphore)、读写锁(rwlock)、等待队列等。
如何选择:锁 vs. 原子操作?
遵循以下原则:
首选原子操作:如果你要保护的只是一个整型变量上的简单操作(设置、递增、递减、位操作等),永远首选原子操作。性能优势巨大。
必须使用锁:如果你要保护的是一个逻辑临界区,其中包含:
操作多个变量。
操作链表、树等复杂数据结构。
有一系列必须连续执行、不能被打断的步骤。
日志
添加打印日志信息分析步骤
第1步:
第2步:
...代码片段
- 自旋锁(spinlock) 应用场景:短时间持有锁,不允许睡眠(中断上下文或原子操作),适合多核环境。 特点:获取不到锁时会循环等待(自旋),不释放 CPU,因此持有时间必须短
自旋锁的核心设计初衷是在多CPU系统中保护共享数据。它的工作方式是:如果一个CPU上的进程试图获取一个已被其他CPU持有的锁,它就在那里“自旋”等待
为什么自旋锁需要禁止本地中断?
简单回答:为了防止死锁(deadlock)。
详细解释:
自旋锁是一种“忙等待”的锁。当一个CPU(我们称之为CPU A)试图获取一个已被其他CPU持有的锁时,它会在一个循环里不停地检查锁的状态,直到锁被释放。
现在考虑这样一种情况,假设我们没有禁止中断:
一个进程运行在 CPU A 上,它获取了一个自旋锁 L。
在持有锁 L 的期间,一个硬件中断发生了(例如,网卡收到一个数据包)。
CPU A 会立即暂停当前进程的执行,转去执行与这个中断对应的中断处理程序(Interrupt Handler)。
巧合的是,这个中断处理程序的代码路径也需要获取同一个自旋锁 L。
中断处理程序开始执行,并尝试获取锁 L。但它发现锁 L 已经被持有(正是被它在第一步中断的那个进程持有)。
于是,中断处理程序也开始“自旋”,在原地空转,等待锁 L 被释放。
然而,锁 L 永远也不会被释放。因为原来持有锁的进程只有在中断处理程序执行完毕后才能被恢复执行,从而才有机会释放锁。这就形成了一个永恒的等待环,即死锁。
如何解决?
答案就是在获取自旋锁之前,禁止当前CPU(本地CPU)的中断。
spin_lock_irq(&lock): 在获取锁的同时,禁用本地CPU的中断。
spin_unlock_irq(&lock): 在释放锁的同时,恢复本地CPU的中断。
这样,在上面的场景中,第2步就不会发生。或者更准确地说,即使硬件发出了中断信号,CPU也会暂时不响应它,直到锁被释放、中断被重新启用。这就彻底杜绝了中断处理程序和进程之间因为竞争同一把锁而导致的死锁。
总结:禁止本地中断是为了防止中断处理程序(它需要获取同一个锁)打断当前正持有锁的进程,从而引发死锁。
对于单个CPU,自旋锁的处理是什么?
简单回答:在单CPU系统上,自旋锁的“自旋”行为是没有意义的,内核会在编译时将其替换为“禁止抢占”的操作。
详细解释:
自旋锁的核心设计初衷是在多CPU系统中保护共享数据。它的工作方式是:如果一个CPU上的进程试图获取一个已被其他CPU持有的锁,它就在那里“自旋”等待。
但在单CPU(UP, Uniprocessor) 系统中:
不存在“其他CPU”。只有一个CPU。
如果一个进程在持有锁时被调度器抢占了,那么下一个被调度运行的进程如果也尝试获取这个锁,它就会陷入“自旋”。
然而,它的“自旋”是徒劳的,因为持有锁的进程此刻根本无法运行(它已经被抢占了),所以也就没有机会释放锁。这个等待的进程会永远自旋下去,导致系统卡死。
因此,Linux内核非常聪明地处理了这种情况。在编译内核时,如果配置为单CPU系统(CONFIG_SMP=n),自旋锁的实现会被大幅简化:
spin_lock() 操作本质上退化为 preempt_disable()(禁止内核抢占)。
这意味着,当一个进程持有锁时,它不会被其他进程抢占,从而保证了临界区的执行不会被打断。
spin_unlock() 操作则退化为 preempt_enable()(启用内核抢占)。
禁止抢占(preemption disable)指的就是当前正在CPU上运行的进程(或线程)不会被调度器(scheduler)切换出去。
让我为您详细解释一下:
什么是内核抢占?
在现代Linux内核中,即使一个进程正在内核态执行(例如,正在执行系统调用),内核本身也是可以被抢占的。这意味着:
进程A正在内核态运行。
一个更高优先级的进程B变得可以运行了(例如,它所等待的I/O操作完成了)。
调度器可以中断进程A的内核执行路径,立即切换到进程B去执行。
这个特性被称为内核抢占(Kernel Preemption)。它对于提高系统的交互性和实时响应能力非常重要。
禁用CPU中断和禁用CPU抢占,这是2个维度, 禁用CPU中断: 禁止CPU处理中断,硬件中断 禁用CPU抢占: 禁止CPU调度其他进程
#include <linux/irqflags.h>
// 禁止本地CPU中断(不保存状态)
local_irq_disable();
// 使能本地CPU中断
local_irq_enable();
// 禁止本地CPU中断并保存之前的状态
local_irq_save(flags);
// 恢复本地CPU中断到之前保存的状态
local_irq_restore(flags);
#include <linux/preempt.h>
// 禁用内核抢占
preempt_disable();
// 使能内核抢占
preempt_enable();
// 禁用内核抢占并检查是否有调度请求
preempt_disable_notrace();
// 使能内核抢占并检查是否有调度请求
preempt_enable_notrace();
选择正确的锁:
如果共享数据只会被进程上下文访问(例如,多个用户线程通过系统调用访问),使用 spin_lock() 就足够了。
如果共享数据也会被中断处理程序访问,必须使用 spin_lock_irq() 或 spin_lock_irqsave()。
在驱动程序中,由于经常需要在中段处理程序中和进程上下文中访问相同的设备寄存器,因此最常用、最安全的是 spin_lock_irqsave()。
/*
* @Author: your name
* @Date: 2025-09-02 17:21:14
* @LastEditTime: 2025-09-02 17:24:18
* @LastEditors: Please set LastEditors
* @Description: In User Settings Edit
* @FilePath: \linux-4.14.143\drivers\gpu\drm\test\spin_lock.c
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/spinlock.h>
#include <linux/kthread.h>
#include <linux/delay.h>
static spinlock_t my_spinlock;
static int shared_data = 0; // 共享资源
static struct task_struct *thread1, *thread2;
// 线程1:修改共享资源
static int thread1_func(void *data) {
while (!kthread_should_stop()) {
unsigned long flags;
// 获取自旋锁(禁止本地中断,避免死锁)
spin_lock_irqsave(&my_spinlock, flags);
shared_data++;
printk(KERN_INFO "线程1:shared_data = %d\n", shared_data);
// 释放自旋锁
spin_unlock_irqrestore(&my_spinlock, flags);
msleep(1000);
}
return 0;
}
// 线程2:修改共享资源
static int thread2_func(void *data) {
while (!kthread_should_stop()) {
unsigned long flags;
spin_lock_irqsave(&my_spinlock, flags);
shared_data--;
printk(KERN_INFO "线程2:shared_data = %d\n", shared_data);
spin_unlock_irqrestore(&my_spinlock, flags);
msleep(1000);
}
return 0;
}
static int __init spinlock_demo_init(void) {
spin_lock_init(&my_spinlock); // 初始化自旋锁
// 创建线程
thread1 = kthread_run(thread1_func, NULL, "spin_thread1");
thread2 = kthread_run(thread2_func, NULL, "spin_thread2");
printk(KERN_INFO "自旋锁示例初始化完成\n");
return 0;
}
static void __exit spinlock_demo_exit(void) {
kthread_stop(thread1);
kthread_stop(thread2);
printk(KERN_INFO "自旋锁示例卸载\n");
}
module_init(spinlock_demo_init);
module_exit(spinlock_demo_exit);
MODULE_LICENSE("GPL");
- 互斥体(mutex) 应用场景:长时间持有锁,允许睡眠(进程上下文),适合保护复杂资源(如设备状态)。 特点:获取不到锁时会阻塞并释放 CPU,CPU会去执行其他程序,不像自旋锁那样会一直占用CPU,适合持有锁时间较长的场景。
/*
* @Author: your name
* @Date: 2025-09-02 18:08:31
* @LastEditTime: 2025-09-02 18:09:10
* @LastEditors: Please set LastEditors
* @Description: In User Settings Edit
* @FilePath: \linux-4.14.143\drivers\gpu\drm\test\mutex.c
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/kthread.h>
#include <linux/delay.h>
static struct mutex my_mutex;
static int shared_data = 0;
static struct task_struct *thread1, *thread2;
// 线程1:修改共享资源
static int thread1_func(void *data) {
while (!kthread_should_stop()) {
mutex_lock(&my_mutex); // 获取互斥体(可睡眠)
shared_data += 10;
printk(KERN_INFO "线程1:shared_data = %d\n", shared_data);
msleep(500); // 模拟耗时操作(允许睡眠)
mutex_unlock(&my_mutex); // 释放互斥体
msleep(1000);
}
return 0;
}
// 线程2:修改共享资源
static int thread2_func(void *data) {
while (!kthread_should_stop()) {
mutex_lock(&my_mutex);
shared_data -= 5;
printk(KERN_INFO "线程2:shared_data = %d\n", shared_data);
msleep(500);
mutex_unlock(&my_mutex);
msleep(1000);
}
return 0;
}
static int __init mutex_demo_init(void) {
mutex_init(&my_mutex); // 初始化互斥体
thread1 = kthread_run(thread1_func, NULL, "mutex_thread1");
thread2 = kthread_run(thread2_func, NULL, "mutex_thread2");
printk(KERN_INFO "互斥体示例初始化完成\n");
return 0;
}
static void __exit mutex_demo_exit(void) {
kthread_stop(thread1);
kthread_stop(thread2);
printk(KERN_INFO "互斥体示例卸载\n");
}
module_init(mutex_demo_init);
module_exit(mutex_demo_exit);
MODULE_LICENSE("GPL");
- 读写锁(rwlock) 应用场景:读操作远多于写操作的场景(如配置数据读取),允许多个读者同时访问,写者独占。 特点:读锁可共享,写锁排他,提升读操作效率。
/*
* @Author: your name
* @Date: 2025-09-02 18:14:38
* @LastEditTime: 2025-09-02 18:18:31
* @LastEditors: Please set LastEditors
* @Description: In User Settings Edit
* @FilePath: \linux-4.14.143\drivers\gpu\drm\test\rwlock.c
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/rwlock.h>
#include <linux/kthread.h>
#include <linux/delay.h>
static rwlock_t my_rwlock;
static int config_data = 100; // 配置数据(读多写少)
static struct task_struct *reader1, *reader2, *writer;
// 读者线程:读取数据
static int reader_func(void *data) {
while (!kthread_should_stop()) {
read_lock(&my_rwlock); // 获取读锁(可共享)
printk(KERN_INFO "读者%d config_data = %d\n", (int)data, config_data);
read_unlock(&my_rwlock);
msleep(1000);
printk(KERN_INFO "读者%d finish\n", (int)data);
}
return 0;
}
// 写者线程:修改数据
static int writer_func(void *data) {
while (!kthread_should_stop()) {
write_lock(&my_rwlock); // 获取写锁(排他)
config_data += 10;
printk(KERN_INFO "写者%d config_data 更新为 %d\n", (int)data, config_data);
write_unlock(&my_rwlock);
msleep(3000); // 写操作频率低
}
return 0;
}
static int __init rwlock_demo_init(void) {
rwlock_init(&my_rwlock); // 初始化读写锁
reader1 = kthread_run(reader_func, (void *)1, "reader1");
reader2 = kthread_run(reader_func, (void *)2, "reader2");
writer = kthread_run(writer_func, NULL, "writer");
printk(KERN_INFO "读写锁示例初始化完成\n");
return 0;
}
static void __exit rwlock_demo_exit(void) {
kthread_stop(reader1);
kthread_stop(reader2);
kthread_stop(writer);
printk(KERN_INFO "读写锁示例卸载\n");
}
module_init(rwlock_demo_init);
module_exit(rwlock_demo_exit);
MODULE_LICENSE("GPL");
- 信号量核心功能说明
信号量初始化
使用 sema_init(&sem, 1) 初始化信号量,第二个参数为初始值(这里设为 1,实现互斥锁效果)
P 操作(获取信号量)
down(&sem):尝试获取信号量,若信号量值大于 0 则减 1 并继续执行;若为 0 则阻塞等待
其他变体:down_interruptible(&sem)(可被信号中断)、down_trylock(&sem)(非阻塞,获取失败立即返回)
V 操作(释放信号量)
up(&sem):释放信号量,将信号量值加 1,唤醒等待队列中的一个线程
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kthread.h>
#include <linux/semaphore.h>
#include <linux/delay.h>
// 共享资源:一个简单的计数器
static int shared_counter = 0;
// 定义信号量
static struct semaphore sem;
// 线程退出标志
static bool thread_exit = false;
// 线程1:对共享资源进行加操作
static int increment_thread(void *data)
{
while (!thread_exit && !kthread_should_stop()) {
// P操作:获取信号量(若无法获取则阻塞等待)
down(&sem);
// 临界区:操作共享资源
shared_counter++;
pr_info("线程1: 增加后计数器 = %d拥有信号量\n", shared_counter);
// 模拟处理耗时
msleep(500);
// V操作:释放信号量
up(&sem);
pr_info("线程1: 释放信号量\n");
// 让出CPU,给其他线程运行机会
msleep(500);
}
return 0;
}
// 线程2:对共享资源进行减操作
static int decrement_thread(void *data)
{
while (!thread_exit && !kthread_should_stop()) {
// P操作:获取信号量(若无法获取则阻塞等待)
down(&sem);
// 临界区:操作共享资源
shared_counter--;
pr_info("线程2: 减少后计数器 = %d拥有信号量\n", shared_counter);
// 模拟处理耗时
msleep(500);
// V操作:释放信号量
up(&sem);
pr_info("线程2: 释放信号量\n");
// 让出CPU,给其他线程运行机会
msleep(500);
}
return 0;
}
// 线程指针
static struct task_struct *inc_thread, *dec_thread;
static int __init semaphore_demo_init(void)
{
// 初始化信号量(设置初始值为1,表示互斥访问)
sema_init(&sem, 1);
pr_info("信号量初始化完成,初始值为1\n");
// 创建两个线程
inc_thread = kthread_run(increment_thread, NULL, "inc_thread");
dec_thread = kthread_run(decrement_thread, NULL, "dec_thread");
if (IS_ERR(inc_thread) || IS_ERR(dec_thread)) {
pr_err("线程创建失败\n");
return PTR_ERR(inc_thread);
}
pr_info("信号量演示驱动加载成功\n");
return 0;
}
static void __exit semaphore_demo_exit(void)
{
// 通知线程退出
thread_exit = true;
// 等待线程结束
if (!IS_ERR(inc_thread))
kthread_stop(inc_thread);
if (!IS_ERR(dec_thread))
kthread_stop(dec_thread);
pr_info("信号量演示驱动卸载完成,最终计数器值 = %d\n", shared_counter);
}
module_init(semaphore_demo_init);
module_exit(semaphore_demo_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Kernel Semaphore Basic Usage Demo");
- 等待队列
第1种方式:wait_event_interruptible + wake_up
#include <linux/init.h>
#include <linux/module.h>
#include <linux/wait.h>
#include <linux/kthread.h>
#include <linux/delay.h>
#include <linux/slab.h>
// 共享缓冲区(生产者写入,消费者读取)
static int *buffer;
static int buffer_size = 0;
static int buffer_full = 0; // 缓冲区状态:0=空,1=满
// 等待队列头(消费者等待缓冲区有数据)
static wait_queue_head_t wq;
// 生产者线程:往缓冲区写入数据,唤醒消费者
static int producer_thread(void *data)
{
int count = 0;
while (!kthread_should_stop()) {
// 生产数据(填充缓冲区)
buffer_size = 1;
buffer[0] = count++; // 写入数据
buffer_full = 1; // 标记缓冲区满
printk(KERN_INFO "[生产者] 写入数据:%d(缓冲区状态:满)\n", buffer[0]);
// 唤醒等待队列中的消费者(条件已满足)
wake_up(&wq);
// 消费者已处理完成,生产者可以继续下一轮生产
msleep(1000); // 短暂延迟,降低CPU占用
}
return 0;
}
// 消费者线程:等待缓冲区有数据,读取并处理
static int consumer_thread(void *data)
{
while (!kthread_should_stop()) {
printk("消费者线程等待中...\n");
// 阻塞等待:缓冲区满(buffer_full == 1),可被信号中断
// 若条件不满足,进程进入休眠;条件满足或被中断时唤醒
wait_event_interruptible(wq, buffer_full == 1);
// 检查是否被要求退出(避免虚假唤醒后继续循环)
if (kthread_should_stop())
break;
// 消费数据
printk(KERN_INFO "[消费者] 读取数据:%d(缓冲区状态:空)\n", buffer[0]);
buffer_size = 0;
buffer_full = 0; // 消费者处理完成后,重置缓冲区状态(核心修复)
}
return 0;
}
static struct task_struct *producer, *consumer;
static int __init waitqueue_demo_init(void)
{
// 初始化等待队列头
init_waitqueue_head(&wq);
// 分配缓冲区
buffer = kmalloc(sizeof(int), GFP_KERNEL);
if (!buffer) {
printk(KERN_ERR "缓冲区分配失败\n");
return -ENOMEM;
}
// 创建生产者和消费者线程
producer = kthread_run(producer_thread, NULL, "producer");
consumer = kthread_run(consumer_thread, NULL, "consumer");
if (IS_ERR(producer) || IS_ERR(consumer)) {
printk(KERN_ERR "线程创建失败\n");
kfree(buffer);
return PTR_ERR(producer);
}
printk(KERN_INFO "等待队列示例初始化完成(已修复状态同步问题)\n");
return 0;
}
static void __exit waitqueue_demo_exit(void)
{
// 停止线程
kthread_stop(producer);
kthread_stop(consumer);
// 释放资源
kfree(buffer);
printk(KERN_INFO "等待队列示例卸载完成\n");
}
module_init(waitqueue_demo_init);
module_exit(waitqueue_demo_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Fixed Waitqueue Demo (Producer-Consumer)");
第2种方式:wait_event + wake_up
/*
* @Author: your name
* @Date: 2025-09-03 10:42:51
* @LastEditTime: 2025-09-03 10:45:34
* @LastEditors: Please set LastEditors
* @Description: In User Settings Edit
* @FilePath: \linux-4.14.143\drivers\gpu\drm\test\wait_queue1.c
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/wait.h>
#include <linux/kthread.h>
#include <linux/delay.h>
#include <linux/slab.h>
// 共享缓冲区
static int *buffer;
static int buffer_full = 0; // 0=空,1=满
static wait_queue_head_t wq_cons; // 消费者等待队列(等满)
// 生产者线程(无等待,满了就跳过生产)
static int producer_thread(void *data)
{
int count = 0;
while (!kthread_should_stop()) {
// 缓冲区未满时才生产(无主动等待,满了就跳过)
if (buffer_full == 0) {
buffer[0] = count++;
buffer_full = 1;
printk(KERN_INFO "[生产者] 写入数据:%d(缓冲区:满)\n", buffer[0]);
wake_up(&wq_cons); // 唤醒消费者
} else {
// 缓冲区满时跳过生产(可添加日志监控)
// printk(KERN_INFO "[生产者] 缓冲区满,跳过生产\n");
}
msleep(1000); // 固定生产间隔,不等待消费者
}
return 0;
}
// 消费者线程(不可中断等待缓冲区满)
static int consumer_thread(void *data)
{
while (!kthread_should_stop()) {
printk(KERN_INFO "[消费者] 等待缓冲区满...\n");
// 不可中断等待:缓冲区为满(不会被信号中断)
wait_event(wq_cons, buffer_full == 1 || kthread_should_stop());
if (kthread_should_stop())
break;
// 消费数据
printk(KERN_INFO "[消费者] 读取数据:%d(缓冲区:空)\n", buffer[0]);
buffer_full = 0; // 标记为空,允许生产者继续生产
}
return 0;
}
static struct task_struct *producer, *consumer;
static int __init waitqueue_noprod_init(void)
{
init_waitqueue_head(&wq_cons);
buffer = kmalloc(sizeof(int), GFP_KERNEL);
if (!buffer) {
printk(KERN_ERR "缓冲区分配失败\n");
return -ENOMEM;
}
producer = kthread_run(producer_thread, NULL, "producer");
consumer = kthread_run(consumer_thread, NULL, "consumer");
if (IS_ERR(producer) || IS_ERR(consumer)) {
printk(KERN_ERR "线程创建失败\n");
kfree(buffer);
return PTR_ERR(producer);
}
printk(KERN_INFO "无生产者等待的不可中断队列初始化完成\n");
return 0;
}
static void __exit waitqueue_noprod_exit(void)
{
kthread_stop(producer);
kthread_stop(consumer);
kfree(buffer);
printk(KERN_INFO "驱动卸载完成\n");
}
module_init(waitqueue_noprod_init);
module_exit(waitqueue_noprod_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Uninterruptible Waitqueue (No Producer Wait)");
第3种方式:wait_event_timeout + wake_up
/*
* @Author: your name
* @Date: 2025-09-03 10:50:02
* @LastEditTime: 2025-09-03 10:54:25
* @LastEditors: Please set LastEditors
* @Description: In User Settings Edit
* @FilePath: \linux-4.14.143\drivers\gpu\drm\test\wait_queue2.c
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/wait.h>
#include <linux/kthread.h>
#include <linux/delay.h>
#include <linux/slab.h>
#include <linux/jiffies.h>
// 共享缓冲区
static int *buffer;
static int buffer_full = 0; // 0=空,1=满
static wait_queue_head_t wq_cons; // 消费者等待队列
// 生产者线程(无等待,满了就跳过生产)
static int producer_thread(void *data)
{
int count = 0;
while (!kthread_should_stop()) {
// 缓冲区未满时才生产
if (buffer_full == 0) {
buffer[0] = count++;
buffer_full = 1;
printk(KERN_INFO "[生产者] 写入数据:%d(缓冲区:满)\n", buffer[0]);
wake_up(&wq_cons); // 唤醒消费者
}
msleep(8000); // 生产间隔(快于消费者处理速度,模拟数据积压风险)
}
return 0;
}
// 消费者线程(带超时等待)
static int consumer_thread(void *data)
{
while (!kthread_should_stop()) {
// 最多等待1秒,超时后自动唤醒
unsigned long timeout = 1 * HZ;
long remaining = wait_event_timeout(wq_cons,
buffer_full == 1 || kthread_should_stop(),
timeout);
if (kthread_should_stop())
break;
if (remaining == 0) {
// 超时:未等到新数据
printk(KERN_WARNING "[消费者] 等待超时1秒无数据\n");
continue;
}
// 正常消费数据
printk(KERN_INFO "[消费者] 读取数据:%d剩余等待时间:%ldms\n",
buffer[0], remaining * 10); // 假设1jiffy≈10ms
buffer_full = 0; // 允许生产者继续生产
msleep(1500); // 消费耗时(慢于生产速度)
}
return 0;
}
static struct task_struct *producer, *consumer;
static int __init waitqueue_timeout_noprod_init(void)
{
init_waitqueue_head(&wq_cons);
buffer = kmalloc(sizeof(int), GFP_KERNEL);
if (!buffer) {
printk(KERN_ERR "缓冲区分配失败\n");
return -ENOMEM;
}
producer = kthread_run(producer_thread, NULL, "producer");
consumer = kthread_run(consumer_thread, NULL, "consumer");
if (IS_ERR(producer) || IS_ERR(consumer)) {
printk(KERN_ERR "线程创建失败\n");
kfree(buffer);
return PTR_ERR(producer);
}
printk(KERN_INFO "无生产者等待的超时队列初始化完成\n");
return 0;
}
static void __exit waitqueue_timeout_noprod_exit(void)
{
kthread_stop(producer);
kthread_stop(consumer);
kfree(buffer);
printk(KERN_INFO "驱动卸载完成\n");
}
module_init(waitqueue_timeout_noprod_init);
module_exit(waitqueue_timeout_noprod_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Timeout Waitqueue (No Producer Wait)");
第4种方式
:wait_event_interruptible_timeout + wake_up
#include <linux/init.h>
#include <linux/module.h>
#include <linux/wait.h>
#include <linux/kthread.h>
#include <linux/sched/signal.h> // 信号处理头文件
#include <linux/delay.h>
// 等待队列头
static wait_queue_head_t wq;
// 等待条件(示例中未使用实际条件,仅演示信号唤醒)
static int dummy_condition = 0;
// 内核线程结构体
static struct task_struct *signal_thread;
// 内核线程函数:等待并响应外部信号
static int signal_thread_func(void *data)
{
// 关键:允许内核线程接收指定信号,如果在这里没有允许信号,那么线程就不会收到信号,即使发送信号也不会接收到
allow_signal(SIGINT); // 允许处理Ctrl+C产生的SIGINT
allow_signal(SIGTERM); // 允许处理终止信号SIGTERM
allow_signal(SIGUSR1); // 允许处理用户自定义信号SIGUSR1
pr_info("信号等待线程启动,PID: %d\n", current->pid);
pr_info("发送信号示例:kill -SIGINT %d 或 kill -SIGUSR1 %d\n", current->pid, current->pid);
while (!kthread_should_stop()) {
unsigned long timeout = 10 * HZ; // 超时时间:10秒
long remaining;
pr_info("\n进入等待状态(10秒超时),等待信号...\n");
// 等待队列超时等待:
// 1. 超时后自动唤醒
// 2. 被wake_up唤醒(本示例未使用)
// 3. 收到允许的信号时唤醒
remaining = wait_event_interruptible_timeout(wq,
dummy_condition || kthread_should_stop(),
timeout);
printk("remaining: %ld\n", remaining);
// 检查是否需要退出线程
if (kthread_should_stop())
break;
// 判断唤醒原因
if (remaining == 0) {
// 超时唤醒(未收到信号)
pr_info("等待超时(10秒),重新等待...\n");
} else if (signal_pending(current)) {
// 信号唤醒(处理外部kill发送的信号)
struct siginfo info;
int signo;
// 从pending队列中取出信号
signo = dequeue_signal(current, ¤t->blocked, &info);
// 处理不同信号
switch (signo) {
case SIGINT:
pr_info("收到SIGINT信号Ctrl+C,唤醒成功!\n");
break;
case SIGTERM:
pr_info("收到SIGTERM信号终止请求,唤醒成功!\n");
break;
case SIGUSR1:
pr_info("收到SIGUSR1信号用户自定义,唤醒成功!\n");
break;
default:
pr_info("收到未知信号 %d,唤醒成功!\n", signo);
}
} else {
// 条件满足唤醒(本示例未使用)
pr_info("条件满足唤醒(剩余等待时间:%ld秒),唤醒成功!\n", remaining / HZ);
dummy_condition = 0; // 重置条件
}
}
pr_info("信号等待线程退出\n");
return 0;
}
static int __init wait_signal_kill_init(void)
{
// 初始化等待队列
init_waitqueue_head(&wq);
// 创建内核线程
signal_thread = kthread_run(signal_thread_func, NULL, "signal_wait_thread");
if (IS_ERR(signal_thread)) {
pr_err("内核线程创建失败\n");
return PTR_ERR(signal_thread);
}
pr_info("驱动加载成功,信号等待线程PID: %d\n", signal_thread->pid);
return 0;
}
static void __exit wait_signal_kill_exit(void)
{
// 停止内核线程
if (!IS_ERR(signal_thread)) {
kthread_stop(signal_thread);
}
pr_info("驱动卸载完成\n");
}
module_init(wait_signal_kill_init);
module_exit(wait_signal_kill_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("wait_event_timeout w/ external kill signal wakeup");
- schedule函数+wake_up函数的使用
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kthread.h>
#include <linux/wait.h>
#include <linux/delay.h>
#include <linux/sched.h>
// 等待队列头
static wait_queue_head_t wq;
// 线程退出标志
static int thread_quit = 0;
// 唤醒条件
static int wakeup_flag = 0;
// 工作线程:正确使用等待队列项类型
static int worker_thread(void *data)
{
// 正确类型:wait_queue_entry_t(而非wait_queue)
wait_queue_entry_t wait;
pr_info("工作线程启动 (PID: %d)\n", current->pid);
// 初始化等待队列项,绑定当前线程
init_waitqueue_entry(&wait, current);
while (!thread_quit) {
pr_info("工作线程:准备进入等待状态\n");
// 将线程加入等待队列
add_wait_queue(&wq, &wait);
// 设置线程状态为可中断睡眠
set_current_state(TASK_INTERRUPTIBLE);
// 检查条件,不满足则让出CPU
if (!wakeup_flag && !thread_quit) {
pr_info("工作线程:条件不满足,调用schedule()让出CPU\n");
schedule(); // 此时线程已在等待队列中,可被唤醒
}
// 恢复线程状态为运行态
set_current_state(TASK_RUNNING);
// 将线程从等待队列移除
remove_wait_queue(&wq, &wait);
// 处理唤醒后的逻辑
if (thread_quit) {
pr_info("工作线程:收到退出信号\n");
break;
}
if (wakeup_flag) {
pr_info("工作线程:被唤醒,处理任务...\n");
msleep(1000); // 模拟任务处理
wakeup_flag = 0; // 重置条件
} else {
pr_info("工作线程:被虚假唤醒(未满足条件)\n");
}
}
pr_info("工作线程:退出\n");
return 0;
}
// 唤醒线程:负责触发唤醒
static int wakeup_thread(void *data)
{
int count = 0;
pr_info("唤醒线程启动 (PID: %d)\n", current->pid);
while (!thread_quit && count < 3) {
msleep(2000); // 每隔2秒尝试唤醒
if (thread_quit)
break;
pr_info("唤醒线程:发送唤醒信号\n");
wakeup_flag = 1; // 设置唤醒条件
wake_up(&wq); // 唤醒wq等待队列中的线程
count++;
}
// 通知工作线程退出
thread_quit = 1;
wake_up(&wq); // 确保工作线程被唤醒以退出
pr_info("唤醒线程:退出\n");
return 0;
}
static struct task_struct *worker_tsk, *wakeup_tsk;
static int __init correct_schedule_init(void)
{
init_waitqueue_head(&wq); // 初始化等待队列
// 创建工作线程
worker_tsk = kthread_run(worker_thread, NULL, "worker_thread");
if (IS_ERR(worker_tsk)) {
pr_err("创建工作线程失败\n");
return PTR_ERR(worker_tsk);
}
// 创建唤醒线程
wakeup_tsk = kthread_run(wakeup_thread, NULL, "wakeup_thread");
if (IS_ERR(wakeup_tsk)) {
pr_err("创建唤醒线程失败\n");
kthread_stop(worker_tsk);
return PTR_ERR(wakeup_tsk);
}
pr_info("驱动加载成功\n");
return 0;
}
static void __exit correct_schedule_exit(void)
{
thread_quit = 1;
wake_up(&wq); // 唤醒可能在等待的线程
if (!IS_ERR(worker_tsk))
kthread_stop(worker_tsk);
if (!IS_ERR(wakeup_tsk))
kthread_stop(wakeup_tsk);
pr_info("驱动卸载完成\n");
}
module_init(correct_schedule_init);
module_exit(correct_schedule_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Correct waitqueue entry type demo");
图片
结论
输出结论
待查资料问题
- 问题 1:?
- 问题 2:?
参考链接
- 官方文档
