一、基础知识
1.current在内核中的含义
current 在内核中的含义
- 在 Linux 内核中,current 是一个指向当前正在执行的进程(或线程)的 task_struct 结构体的指针。线程是特殊进程,共享进程相关资源和机制。
- 此处 current 代表当前执行的主线程(因代码上下文与主线程相关),用于将其添加到等待队列。
关于进程和线程在这种场景下的理解
- 因 Linux 内核中线程与进程联系紧密,场景中的主线程可看作轻量级进程。将 current(主线程)添加到等待队列,本质是使其暂停执行,等待子线程完成任务这一条件触发,后续被唤醒继续执行。该机制用于实现线程(或进程)间同步,确保主线程在子线程完成关键操作后推进后续工作。
2. printk与dmesg-c
dmesg 命令的基本功能
dmesg
是用于查看和控制内核环形缓冲区(kernel ring buffer)的命令,记录系统启动过程及运行时内核产生的各种信息,包括设备初始化信息、驱动程序加载消息、硬件检测结果、系统错误和警告等。dmesg -c 参数的作用
-c
选项用于清除内核环形缓冲区内容。执行dmesg -c
后,之前存储的所有内核消息会被清空。- 例如,调试时检查并处理完缓冲区旧消息后,使用
dmesg -c
清除旧消息,以便后续清晰查看新产生的内核消息。printk 函数与内核环形缓冲区的关系
- 在 Linux 内核中,
printk
函数用于在内核代码中输出信息,其输出的消息会进入内核环形缓冲区,是内核记录日志和调试信息的重要机制。- 例如,设备驱动开发中,驱动初始化函数被调用时,开发人员使用
printk
输出调试信息(如[initializing device driver]
),这些信息存储在内核环形缓冲区,可通过dmesg
命令查看。printk 消息的优先级和记录方式
printk
允许设置消息优先级,从低到高包括KERN_DEBUG
(调试信息,优先级最低)、KERN_INFO
(一般信息)、KERN_WARNING
(警告信息)、KERN_ERR
(错误信息,优先级较高)等。- 不同优先级消息在环形缓冲区的记录和显示方式不同:低优先级消息(如
KERN_DEBUG
)默认不立即显示在控制台,但会记录;高优先级消息(如KERN_ERR
)会立即显示在控制台并记录。- 例如,内核模块启动时资源分配错误,使用
printk(KERN_ERR, "Failed to allocate memory for [模块资源名称]");
输出错误信息,该消息进入环形缓冲区,且很可能在控制台显示,方便定位问题。
3. unlikely宏的作用
unlikely 不是函数,而是一个宏定义
- 作用是给编译器提供提示信息,优化代码分支执行顺序。其参数通常是条件表达式,编译器看到
unlikely
包裹的条件表达式时,会认为条件为真的概率低。- 例如代码
if (unlikely(root))
,表示编译器被提示root
非空(条件为真)的情况不太可能发生,编译器生成机器码时,可能将该分支代码放在不易被执行到的位置(如通过跳转指令减少流程转到该分支的概率),优化整体代码执行效率。unlikely 在内核代码中的应用场景
- 操作系统内核代码复杂,存在许多条件判断分支,部分分支在正常情况很少执行(如错误情况、特殊罕见的系统状态变化分支)。通过
unlikely
标记这些不常执行的分支,可让编译器更好地优化代码布局和执行顺序。- 比如内存分配失败、硬件设备出现罕见故障等场景对应的代码分支,通常用
unlikely
告知编译器这些情况相对不太可能出现,使编译器在优化代码时将更多资源(如缓存预取等优化策略)放在更可能执行的正常路径上,提升系统正常运行时的性能。
4.kthread_create和kthread_create_on_node的区别
- kthread_create 和 kthread_create_on_node 的功能相似性
kthread_create
和kthread_create_on_node
函数都用于创建内核线程,目的是在内核空间启动新执行单元(线程),且都需传入线程函数作为线程主体内容。- kthread_create 在本文章中的适用性
- 本文中使用
kthread_create
是因其为基础常用的创建内核线程方式。它会在当前节点创建线程,对于多数无特殊节点要求的情况已足够。该代码示例未涉及多核或多节点系统中指定线程创建节点需求,kthread_create
能满足创建简单内核线程要求,且代码更简洁。- kthread_create_on_node 的特点和适用场景
kthread_create_on_node
相较于kthread_create
,多了用于指定线程创建节点的参数。在多核或多节点系统架构中,若要在特定节点创建线程(如利用特定节点资源,像连接的特定硬件设备;或实现特定性能优化,如平衡不同节点负载),就需用kthread_create_on_node
。此示例未涉及多核 / 多节点复杂场景,使用kthread_create
创建内核线程更合适,可避免引入不必要的节点参数,让代码聚焦在等待队列使用和线程同步实现上。
5.Linux中创建内核线程、用户线程和LWP的API
1. 创建内核线程 API
kthread_create
- 函数原型:
struct task_struct *kthread_create(int (*threadfn)(void *data), void *data, const char namefmt[], ...)
- 参数说明:
threadfn
是线程要执行的函数;data
是传递给该函数的参数 ;namefmt
用于指定线程名称,可变参数部分可按需补充其他参数。- 功能:创建一个内核线程,但线程创建后不会立即运行,而是被放入等待队列。如需线程马上运行,需调用
wake_up_process(struct task_struct *task)
唤醒。kthread_run
- 本质:它是一个宏定义。
- 等效操作:先调用
kthread_create
创建内核线程,再调用wake_up_process
唤醒线程,即创建后立即运行。- 返回值:成功时返回线程的地址。
kthread_create_on_node
- 函数原型:
struct task_struct *kthread_create_on_node(int (*threadfn)(void *data), void *data, int node, const char namefmt[], ...)
- 参数说明:与
kthread_create
基本类似,多了node
参数,用于指定在哪个节点上创建线程,适用于多核或多节点系统架构。2. 创建用户线程 API
pthread_create
- 函数原型:
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void *), void *arg)
- 参数说明:
thread
是传出参数,用于存放新创建线程的线程 ID 。attr
用于设置线程属性,若为NULL
,则创建默认属性线程。start_routine
是函数指针,指向线程要执行的函数。arg
是传递给start_routine
函数的参数。- 功能:基于 POSIX 线程库(pthread 库)创建用户线程。Linux 下没有直接创建线程的系统调用,而是通过 pthread 库对底层接口进行封装。其底层是
clone
系统调用,通过设置不同参数实现线程创建,创建的线程共享进程的部分资源(如虚拟内存、文件系统信息等 )。编译时需链接 pthread 库(如使用-lpthread
参数 )。3. 创建轻量级进程(LWP)API
严格来说,Linux 没有专门独立用于创建 LWP 的 API。LWP 本质上是内核看待线程的方式,用户通过
pthread_create
创建用户线程时,底层实际是利用clone
系统调用创建 LWP 。
clone
函数原型:
int clone(int (*fn)(void *), void *child_stack, int flags, void *arg, ... /* pid_t *ptid, struct user_desc *tls, pid_t *ctid */ )
参数说明:
fn
是新执行流要执行的函数。child_stack
指定子进程 / 线程的栈地址。flags
是标志参数,用于控制共享资源情况,如CLONE_VM
(共享虚拟内存 )、CLONE_FS
(共享文件系统信息 )等 。arg
是传递给fn
函数的参数。功能:比
fork
更灵活,既可以创建新进程,也可用于创建新线程(通过设置共享资源相关标志参数实现 )。当使用pthread_create
创建线程时,底层最终会调用clone
并设置相应参数来创建具有共享资源特性的 LWP。
6.等待队列
一、等待队列基本概念
等待队列(wait queue)是 Linux 内核中实现进程同步与互斥的机制,使进程在等待特定事件时进入睡眠状态,释放 CPU 资源,避免忙等待的资源浪费。
二、等待队列核心函数详解
初始化函数
- init_waitqueue_head:初始化等待队列头(
wait_queue_head_t
结构体),包含链表指针等管理信息,为后续添加队列元素做准备,初始化结构体成员使其处于可用状态。- init_waitqueue_entry:初始化等待队列元素(
wait_queue_entry_t
结构体),将当前进程(current
)与元素关联,记录进程描述符(struct task_struct
),以便后续唤醒。添加元素函数
- add_waitqueue:将已初始化的队列元素添加到队列头管理的队列中,通过操作链表指针插入元素,构建完整队列,便于管理和遍历队列元素。
进程进入等待状态函数
wait_event
系列:
wait_event
/wait_event_interruptible
:配合条件判断使进程进入等待。如wait_event(head, condition)
,若条件不满足,进程加入队列睡眠,直至条件为真。- 超时机制函数:如
schedule_timeout_uninterruptible
,使进程进入不可中断睡眠,等待超时或被唤醒(如time_out = schedule_timeout_uninterruptible(2000 * 10);
)。wait_event_interruptible
:可中断等待的宏。检查条件,不满足则将进程加入队列进入可中断睡眠,直至条件满足或收到信号唤醒。例如:wait_event_interruptible(my_wait_queue_head, g_condition_variable == 1);
唤醒函数
wake_up_interruptible
:唤醒处于TASK_INTERRUPTIBLE
状态的进程。遍历等待队列,唤醒所有可中断睡眠的进程,如设备驱动中数据传输完成后唤醒等待进程。wake_up_process
:通用唤醒函数,可唤醒指定进程(无论睡眠状态,如TASK_INTERRUPTIBLE
、TASK_UNINTERRUPTIBLE
)。直接针对进程操作,传入目标进程的task_struct
指针,如父进程唤醒子进程。三、等待队列设计逻辑
以队列头设置条件的原因
- 逻辑统一性:确保所有入队进程等待相同事件,保证同步。例如,等待硬件准备好时,所有相关进程在条件满足时统一被唤醒。
- 数据结构便利性:队列头管理链表,条件满足时内核通过队列头遍历并唤醒所有进程。若针对单个进程节点操作,会增加代码复杂度和维护成本,且易出现不一致。
多节点处理机制
当队列有多个节点,wait_event_interruptible
统一管理等待与唤醒。条件不满足时,所有进程睡眠;条件满足,内核遍历队列依次唤醒进程,使其恢复就绪态,经调度继续执行,确保同步与唤醒的一致性。四、等待队列的功能与应用场景
核心功能
- 进程同步:进程等待资源可用或另一进程完成任务时,加入队列睡眠,直至被唤醒。
- 事件驱动机制:内核中事件驱动操作(如设备驱动数据传输)依赖等待队列,硬件完成操作后唤醒等待进程。
典型应用场景
- 设备驱动程序:如进程向磁盘写数据,加入磁盘设备等待队列,磁盘操作完成后中断处理程序唤醒进程。
- 进程间通信(IPC):实现信号量、条件变量等同步原语,管理因等待资源或条件阻塞的进程。
- 内核子系统交互:如网络子系统与文件系统交互时,相关进程通过等待队列同步操作(如网络数据写入文件系统)。
二、实战案例分析
1.ktconode.c
这段代码是一个 Linux 内核模块,其主要功能是创建一个新的内核线程并将其唤醒。
【ktconode.c】
#include<linux/module.h>
#include<linux/pid.h>
#include<linux/sched.h>
#include<linux/wait.h>
#include<linux/kthread.h>int MyFunc_ThreadFunc(void *argc);//新进程用来打印新进程的pid
int MyFunc_ThreadFunc(void *argc){printk("调用线程函数MyFunc_ThreadFunc(...)\n");printk("打印输出当前进程的PID值为: %d\n",current->pid);printk("退出线程函数MyFunc_ThreadFunc(...)\n");return 0;
}//配置
static int __init kthreadCreateOnNode_Func(void){struct task_struct *pts = NULL;printk("调用数MyFunc_ThreadFunc(...)\n");pts = kthread_create_on_node(MyFunc_ThreadFunc,NULL,-1,"ktconode.c");printk("打印新线程的值: %d\n",pts->pid);//唤醒刚才新创建的内核线程/*这个线程只是被创建并等待执行其任务函数(在这个例子中是MyFunc_ThreadFunc),如果没有调用wake_up_process来手动唤醒它,内核没有理由自动将其唤醒进行调度,因为它没有等待任何内核已知的、会自动触发唤醒的事件。*/wake_up_process(pts);printk("打印当前进程的PID值: %d\n",current->pid);printk("退出数MyFunc_ThreadFunc(...)\n");return 0;
}//退出
static void __exit kthreadCreateOnNode_Exit(void){printk("正常退出函数kthread_create_on_node...\n");
}MODULE_LICENSE("GPL");
module_init(kthreadCreateOnNode_Func);
module_exit(kthreadCreateOnNode_Exit);
【Makefile】
# 指定要构建的内核模块对应的目标文件
obj-m := ktconode.o# 构建所有模块的目标规则
all:$(MAKE) -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules# 清理模块的目标规则
clean:$(MAKE) -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
【编译插入卸载】
2. wait_single
这段代码是一个 Linux 内核模块,其主要功能是创建一个子线程并模拟执行任务,同时让主线程等待子线程完成任务后再继续执行,通过等待队列机制实现主线程和子线程之间的同步。
【wakeup.c】
#include<linux/kernel.h>
#include<linux/module.h>
#include<linux/init.h>
#include<linux/wait.h>
#include<linux/sched.h>
#include <linux/kthread.h>//定义一个全局变量,作为子线程任务完成的标志,初始化0表示未完成
static int task_complete = 0;//等待队列头,用于协调主线程和子线程之间的同步
static wait_queue_head_t my_wait_queue_head;//子线程函数,模拟执行一些任务,这里简单的让线程睡眠一些时间代表执行任务的过程
static int my_thread_func(void *data){//KERN_INFO表示这是一个提供一般信息(informational)的消息printk(KERN_INFO "子线程开始执行任务\n");//模拟子线程执行任务,这里简单的让线程睡眠一段时间代表执行任务的过程//set_current_state函数用于设置当前进程的状态,可中断睡眠状态:TASK_INTERRUPTIBLE是进程状态的一种,表示进程处于睡眠(等待)状态,并且可以被信号(signal)中断set_current_state(TASK_INTERRUPTIBLE);//schedule_timeout函数用于使当前进程(或线程)进入睡眠状态一段时间,主动让出 CPU,之后再被唤醒继续执行。这是一种基于时间的调度机制schedule_timeout(3 * HZ);//任务完成,修改标志变量task_complete = 1;printk(KERN_INFO "子线程执行完毕\n");//唤醒在等待队列中等待的主线程任务wake_up_interruptible(&my_wait_queue_head);printk(KERN_INFO "子线程执行完毕\n");return 0;
}//模拟初始化函数,在这里创建子线程,将主线程添加到等待队列中等待子线程完成任务
static int __init my_module_init(void){printk(KERN_INFO "模块初始化开始\n");//初始化等待队列头init_waitqueue_head(&my_wait_queue_head);//创建子线程struct task_struct *thread = kthread_create(my_thread_func,NULL,"my_subthread");if(thread){wake_up_process(thread);printk(KERN_INFO "子线程创建成功并已唤醒\n");}else{printk(KERN_INFO "子线程创建失败\n");return -1;}//将主线程添加到等待队列中等待条件满足(即子线程完成)wait_queue_entry_t wait_entry;init_waitqueue_entry(&wait_entry,current);add_wait_queue(&my_wait_queue_head,&wait_entry);//设置等到条件,等待子线程任务完成printk(KERN_INFO "主线程进入等待状态,等待子线程完成任务\n");wait_event_interruptible(my_wait_queue_head,task_complete == 1);printk(KERN_INFO "主线程被唤醒,子线程任务已完成,继续执行\n");return 0;
}// 模块退出函数,简单打印退出信息
static void __exit my_module_exit(void)
{printk(KERN_INFO "模块退出\n");
}MODULE_LICENSE("GPL");
module_init(my_module_init);
module_exit(my_module_exit);
【Makefile】
# 指定要构建的内核模块对应的目标文件
obj-m := wakeup.o# 构建所有模块的目标规则
all:$(MAKE) -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules# 清理模块的目标规则
clean:$(MAKE) -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
【编译插入卸载】
3.wait_muti
这段代码同样是一个 Linux 内核模块,其核心功能是利用等待队列实现多个线程对共享资源的同步访问。具体而言,创建了两个线程(线程 1 和线程 2),它们会等待共享资源达到特定条件。同时,存在一个模拟的资源更新函数,它会不断更新共享资源的值,当共享资源的值满足线程的等待条件时,唤醒相应的线程。
【wakeup.c】
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/kthread.h>// 定义等待队列头
wait_queue_head_t my_wait_queue_head;// 模拟共享资源
int shared_resource = 0;// 线程1等待条件
int thread1_condition = 5;
// 线程2等待条件
int thread2_condition = 10;//线程函数1
static int thread_fun_1(void *data){printk(KERN_INFO "线程1: 开始等待,等待共享资源达到 %d\n",thread1_condition);wait_event_interruptible(my_wait_queue_head,shared_resource >= thread1_condition);printk(KERN_INFO "线程1:满足条件,被唤醒,共享资源当前值为 %d\n", shared_resource);return 0;
}// 线程函数2
static int thread_func_2(void *data)
{printk(KERN_INFO "线程2:开始等待,等待共享资源达到 %d\n", thread2_condition);wait_event_interruptible(my_wait_queue_head, shared_resource >= thread2_condition);printk(KERN_INFO "线程2:满足条件,被唤醒,共享资源当前值为 %d\n", shared_resource);return 0;
}//模拟更新共享资源的函数类似一个生产者的角色
static void update_shared_resource(void){while(shared_resource < 15){shared_resource++;printk(KERN_INFO "更新共享资源,当前值为 %d\n", shared_resource);if (shared_resource >= thread1_condition) {wake_up_interruptible(&my_wait_queue_head);}if (shared_resource >= thread2_condition) {wake_up_interruptible(&my_wait_queue_head);}//简单模拟一些延迟,让过程更符合情况,schedule_timeout单位是jiffies,即几*HZ就是休眠几秒set_current_state(TASK_INTERRUPTIBLE);schedule_timeout(1 * HZ);}
}//模块初始化的函数
static int __init my_module_init(void){//初始化等待队列头init_waitqueue_head(&my_wait_queue_head);printk(KERN_INFO "模块初始化\n");//创建线程1struct task_struct *thread1;//这个函数相当于kthread_create和wake_up_process的组合thread1 = kthread_run(thread_fun_1,NULL,"thread_1");if (IS_ERR(thread1)) {printk(KERN_ERR "创建线程1失败\n");return PTR_ERR(thread1);}//手动将线程1添加到等待队列中wait_queue_entry_t wait_entry_1;init_waitqueue_entry(&wait_entry_1,thread1);add_wait_queue(&my_wait_queue_head,&wait_entry_1);//创建线程2struct task_struct *thread2;thread2 = kthread_run(thread_func_2, NULL, "thread_2");if (IS_ERR(thread2)) {printk(KERN_ERR "创建线程2失败\n");return PTR_ERR(thread2);}// 手动将线程2添加到等待队列的相关初始化及添加操作wait_queue_entry_t wait_entry_2;init_waitqueue_entry(&wait_entry_2, thread2);add_wait_queue(&my_wait_queue_head, &wait_entry_2);//启动更新共享资源函数,模拟资源更新的过程update_shared_resource();return 0;
}// 模块退出函数
static void __exit my_module_exit(void)
{printk(KERN_INFO "模块退出\n");
}module_init(my_module_init);
module_exit(my_module_exit);
MODULE_LICENSE("GPL");
【Makefile】
# 指定要构建的内核模块对应的目标文件
obj-m := wakeup.o# 构建所有模块的目标规则
all:$(MAKE) -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules# 清理模块的目标规则
clean:$(MAKE) -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
【编译插入卸载】
https://github.com/0voice