Linux进程管理 (篇外)内核线程简要介绍
关键词:kthread、irq、ksoftirqd、kworker、workqueues
在使用ps查看线程的时候,会有不少[...]名称的线程,这些有别于其它线程,都是内核线程。
其中多数内核线程从名称看,就知道其主要功能。
比如给中断线程化使用的irq内核线程,软中断使用的内核线程ksoftirqd,以及work使用的kworker内核线程。
本文首先概览一下Linux都有哪些内核线程,然后分析创建内核线程的API。
在介绍内核线程和普通线程都有哪些区别?
最后介绍主要内核线程(irq/ksoftirqd/kworker/)的创建过程及其作用。
1. ps下初步认识Linux内核线程
在ps -a会显示如下,可以看出内核线程都用[...]标注。
并且pid=1的init进程是所有用户空间进程的父进程;pid=2的kthreadd内核线程是所有内核线程的父线程。
内核线程分为几大类:softirq、kworker、irq及其他。
PID USER TIME COMMAND 1 0 0:01 {linuxrc} init 2 0 0:00 [kthreadd] 3 0 0:00 [ksoftirqd/0] 4 0 0:00 [kworker/0:0] 5 0 0:00 [kworker/0:0H] 6 0 0:00 [kworker/u8:0] 7 0 0:00 [rcu_sched] 8 0 0:00 [rcu_bh] 9 0 0:00 [migration/0] 10 0 0:00 [migration/1] 11 0 0:00 [ksoftirqd/1] 12 0 0:00 [kworker/1:0] 13 0 0:00 [kworker/1:0H] 14 0 0:00 [migration/2] 15 0 0:00 [ksoftirqd/2] 16 0 0:00 [kworker/2:0] 17 0 0:00 [kworker/2:0H] 18 0 0:00 [migration/3] 19 0 0:00 [ksoftirqd/3] 20 0 0:00 [kworker/3:0] 21 0 0:00 [kworker/3:0H] 22 0 0:00 [khelper] 23 0 0:00 [kdevtmpfs] 24 0 0:00 [perf] 25 0 0:00 [kworker/u8:1] 279 0 0:00 [khungtaskd] 280 0 0:00 [writeback] 281 0 0:00 [kintegrityd] 282 0 0:00 [kworker/0:1] 284 0 0:00 [bioset] 286 0 0:00 [kblockd] 294 0 0:00 [ata_sff] 408 0 0:00 [rpciod] 409 0 0:00 [kworker/2:1] 410 0 0:00 [kworker/1:1] 412 0 0:00 [kswapd0] 416 0 0:00 [fsnotify_mark] 429 0 0:00 [nfsiod] 449 0 0:00 [kworker/3:1] 527 0 0:00 [kpsmoused] 537 0 0:00 [kworker/1:2] 613 0 0:00 [deferwq]
2. kthreadd以及创建内核线程API
2.1 kthreadd:kthreadd内核线程的创建
内核其他线程的创立,要基于kthreadd。kthreadd线程是其他线程的父线程。
start_kernel-->rest_init如下:
static noinline void __init_refok rest_init(void) { int pid; rcu_scheduler_starting(); /* * We need to spawn init first so that it obtains pid 1, however * the init task will end up wanting to create kthreads, which, if * we schedule it before we create kthreadd, will OOPS. */ kernel_thread(kernel_init, NULL, CLONE_FS);--------------------------------创建第一个用户空间线程init numa_default_policy(); pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);---------------创建第一个内核线程kthreadd rcu_read_lock(); kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns);--------------------kthreadd_task指向kthreadd的task_strcut结构体 rcu_read_unlock(); complete(&kthreadd_done);--------------------------------------------------在init进程kernel_init-->kernel_init_freeable中等待kthreadd_done释放 /* * The boot idle thread must execute schedule() * at least once to get things moving: */ init_idle_bootup_task(current); schedule_preempt_disabled(); /* Call into cpu_idle with preempt disabled */ cpu_startup_entry(CPUHP_ONLINE); }
kernel_init在kthreadd之前启动,但是kernel_init的很多任务需要基于kthreadd。所以在kernel_init的开头等待reset_init的kthreadd_done完成量。
因为kernel_init-->kernel_init_freeable-->do_basic_setup-->do_initcalls中很多初始化需要kthread_create支援。
kernel_init-->kernel_init_freeable: static noinline void __init kernel_init_freeable(void) { /* * Wait until kthreadd is all set-up. */ wait_for_completion(&kthreadd_done);-------------------等待kthreadd_done完成量 ...
do_basic_setup();---------------------------------------很多初始化需要kthread_create支持
...
}
内核中有一个线程kthreadd_task负责创建其他内核线程,这个线程的函数为kthreadd()。
int kthreadd(void *unused) { struct task_struct *tsk = current; /* Setup a clean context for our children to inherit. */ set_task_comm(tsk, "kthreadd"); ignore_signals(tsk); set_cpus_allowed_ptr(tsk, cpu_all_mask); set_mems_allowed(node_states[N_MEMORY]); current->flags |= PF_NOFREEZE; for (;;) { set_current_state(TASK_INTERRUPTIBLE); if (list_empty(&kthread_create_list)) schedule();----------------------------------------------如果kthread_create_list为空,让出CPU,进入休眠状态。在kthread_create_on_node()中会将要创建进程节点加入到kthread_create_list中,然后唤醒此进程。 __set_current_state(TASK_RUNNING); spin_lock(&kthread_create_lock); while (!list_empty(&kthread_create_list)) {------------------只要kthread_create_list不为空,遍历kthread_create_list链表 struct kthread_create_info *create; create = list_entry(kthread_create_list.next, struct kthread_create_info, list); list_del_init(&create->list);----------------------------从kthread_create_list中摘除当前create spin_unlock(&kthread_create_lock); create_kthread(create);----------------------------------创建线程 spin_lock(&kthread_create_lock); } spin_unlock(&kthread_create_lock); } return 0; } static void create_kthread(struct kthread_create_info *create) { int pid; #ifdef CONFIG_NUMA current->pref_node_fork = create->node; #endif /* We want our own signal handler (we take no signals by default). */ pid = kernel_thread(kthread, create, CLONE_FS | CLONE_FILES | SIGCHLD);----调用do_fork()创建线程 if (pid < 0) { /* If user was SIGKILLed, I release the structure. */ struct completion *done = xchg(&create->done, NULL); if (!done) { kfree(create); return; } create->result = ERR_PTR(pid); complete(done);--------------------------------------------------------触发complete事件 } } pid_t kernel_thread(int (*fn)(void *), void *arg, unsigned long flags) { return do_fork(flags|CLONE_VM|CLONE_UNTRACED, (unsigned long)fn, (unsigned long)arg, NULL, NULL); }
2.2 创建内核线程接口:kthread_create等
kthread_create()是最常见的创建内核线程的接口。
kthread_create_on_cpu()相对于kthread_create多了个cpu,但都基于kthread_create_on_node()。
kthread_run基于kthreadd_create,所以这些函数都基于kthread_create_on_node。
#define kthread_create(threadfn, data, namefmt, arg...) \ kthread_create_on_node(threadfn, data, -1, namefmt, ##arg) struct task_struct *kthread_create_on_cpu(int (*threadfn)(void *data), void *data, unsigned int cpu, const char *namefmt); /** * kthread_run - create and wake a thread. * @threadfn: the function to run until signal_pending(current). * @data: data ptr for @threadfn. * @namefmt: printf-style name for the thread. * * Description: Convenient wrapper for kthread_create() followed by * wake_up_process(). Returns the kthread or ERR_PTR(-ENOMEM). */ #define kthread_run(threadfn, data, namefmt, ...) \ ({ \ struct task_struct *__k \ = kthread_create(threadfn, data, namefmt, ## __VA_ARGS__); \ if (!IS_ERR(__k)) \--------------------------如果kthread_create()正确创建了一个进程,调用wake_up_process()唤醒它。 wake_up_process(__k); \ __k; \ })
kthread_create_on_node()负责创建一个线程,填充一个kthread_create_info结构体;然后将此结构体作为一个节点插入kthread_create_list队尾。
然后唤醒kthreadd_task进行处理,创建线程。
struct task_struct *kthread_create_on_node(int (*threadfn)(void *data), void *data, int node, const char namefmt[], ...) { DECLARE_COMPLETION_ONSTACK(done); struct task_struct *task; struct kthread_create_info *create = kmalloc(sizeof(*create), GFP_KERNEL);---------------------------------创建插入kthread_create_list的节点。 if (!create) return ERR_PTR(-ENOMEM); create->threadfn = threadfn; create->data = data; create->node = node; create->done = &done; spin_lock(&kthread_create_lock); list_add_tail(&create->list, &kthread_create_list);-------------------将填充的节点插入kthread_create_list中。 spin_unlock(&kthread_create_lock); wake_up_process(kthreadd_task);---------------------------------------唤醒kthread_task处理kthread_create_list链表,创建相应的线程。 /* * Wait for completion in killable state, for I might be chosen by * the OOM killer while kthreadd is trying to allocate memory for * new kernel thread. */ if (unlikely(wait_for_completion_killable(&done))) {------------------等待complete事件触发,在create_kthread()中触发。 /* * If I was SIGKILLed before kthreadd (or new kernel thread) * calls complete(), leave the cleanup of this structure to * that thread. */ if (xchg(&create->done, NULL)) return ERR_PTR(-EINTR); /* * kthreadd (or new kernel thread) will call complete() * shortly. */ wait_for_completion(&done);---------------------------------------等待complete事件触发。 } task = create->result;------------------------------------------------创建的结果为task_struct结构体。 if (!IS_ERR(task)) { static const struct sched_param param = { .sched_priority = 0 }; va_list args; va_start(args, namefmt); vsnprintf(task->comm, sizeof(task->comm), namefmt, args);---------配置进程名称。 va_end(args); /* * root may have changed our (kthreadd's) priority or CPU mask. * The kernel thread should not inherit these properties. */ sched_setscheduler_nocheck(task, SCHED_NORMAL, ¶m);-----------设置进程调度策略为NORMAL,优先级为0。 set_cpus_allowed_ptr(task, cpu_all_mask); } kfree(create);--------------------------------------------------------释放kthread_create_info。 return task; }
3. 内核线程和普通线程的区别
内核线程没有地址空间,所以task_struct->mm指针为NULL。内核线程没有用户上下文。
内核线程只工作在内核空间,不会切换至用户空间。但内核线程同样是可调度且可抢占的。
普通线程即可工作在内核空间,也可工作在用户空间。
内核线程只能访问3GB以上地址,而普通线程可访问所有4GB地址空间。
4. irq、softirq、woker内核线程
irq、softirq、worker都可能创建对应的内核线程,有线程就有优先级。
下面从优先来来看看它们的重要性。
可以看出中断内核线程优先级很高,为49,并且使用了实时调度策略。softirq和worker都是普通内核线程。
prio | policy | |
irq | 49 | SCHED_FIFO |
softirq | 120 | SCHED_NORMAL |
worker | 120 | SCHED_NORMAL |
init | 120 | SCHED_NORMAL |
kthreadd | 120 | SCHED_NORMAL |
cfinteractive | 0 | SCHED_FIFO |
其它特殊内核线程init优先级为120,kthreadd优先级为120.
cfinteractive优先级最高,主要处理CPU Frequency负载更新。
4.1 irq/xx-xx:创建处理线程化中断的线程
irq/...: 中断处理线程。对于配置为线程化处理的中断请求(IRQ),内核会为每个这样的IRQ创建一个专门的线程来处理其中断服务例程(ISR)。名称格式通常是irq/<irq_number>-<irq_name>(如 irq/178-pwrkey_irq 表示处理第178号中断,该中断是电源按键中断)。
request_threaded_irq-->__setup_irq,可见如果设置了thread_fn,并且不允许中断嵌套,则创建一个类似"irq/中断号-终端名称"的线程。
线程函数是irq_thread,
/* * Internal function to register an irqaction - typically used to * allocate special interrupts that are part of the architecture. */ static int __setup_irq(unsigned int irq, struct irq_desc *desc, struct irqaction *new) { ... if (new->thread_fn && !nested) { struct task_struct *t; static const struct sched_param param = { .sched_priority = MAX_USER_RT_PRIO/2, }; t = kthread_create(irq_thread, new, "irq/%d-%s", irq,----------------在irq_thread中调用irq_thread_fn,进而调用action->thread_fn,request_threaded_irq参数thread_fn。 new->name); ... } ... }
request_irq是对request_threaded_irq的封装,创建中断线程的工作交给__setup_irq()。
static inline int __must_check request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char *name, void *dev) { return request_threaded_irq(irq, handler, NULL, flags, name, dev); }
更详细信息参考:《Linux中断管理 (1)Linux中断管理机制》中关于request_irq()介绍。
4.2 ksoftirqd/xx:创建处理软中断线程
ksoftirqd/...: 软中断守护线程。每个CPU核心通常有一个。当软中断(softirq)处理在中断上下文中无法完成(或数量过多)时,由该线程在进程上下文中继续处理。软中断是内核中处理高优先级、可延迟任务的重要机制(如网络数据包接收)。
软中断线程通过smpboot_register_percpu_thread注册softirq_threads创建。
static struct smp_hotplug_thread softirq_threads = { .store = &ksoftirqd, .thread_should_run = ksoftirqd_should_run, .thread_fn = run_ksoftirqd, .thread_comm = "ksoftirqd/%u", }; static __init int spawn_ksoftirqd(void) { register_cpu_notifier(&cpu_nfb); BUG_ON(smpboot_register_percpu_thread(&softirq_threads)); return 0; }
smpboot_register_percpu_thread-->__smpboot_create_thread,最终也还是调用kthread_create_on_cpu,创建了类似"ksoftirqd/xx"的内核线程,xx为cpuid号。
从ps -a中可以看出创建的结果如下,可以看出每个CPU创建了一个ksoftirqd内核线程。
3 0 0:03 [ksoftirqd/0] 11 0 0:03 [ksoftirqd/1] 15 0 0:00 [ksoftirqd/2] 19 0 0:00 [ksoftirqd/3]
更详细信息参考: 《Linux中断管理 (2)软中断和tasklet》
4.3 kworker:创建work的工作线程
kwoker线程是处理work的工作线程,详细参考《Linux中断管理 (3)workqueue工作队列》。
每个CPU都会创建自己的workqueue,用以集中处理内核kworker。
workquuue就是把一些任务(work)推迟到一个或一组内核线程中去执行,那个内核线程被称为worker_thread。
首先看看创建结果,可以看出在init_workqueues中创建了绑定CPU0的两个kworker,分别是nice=0和nice=-20。
apply_workqueue_attrs创建unbund worker,即kworker/u8:0。
然后在每个CPU_UP_PREPARE回调中创建两个不同nice的kworker。所以四个CPU一共9个内核线程。
PID USER TIME COMMAND 1 0 0:01 {linuxrc} init 2 0 0:00 [kthreadd] 3 0 0:00 [ksoftirqd/0] 4 0 0:00 [kworker/0:0] 5 0 0:00 [kworker/0:0H]---------------init_workqueues-->create_worker 6 0 0:00 [kworker/u8:0]---------------apply_workqueue_attrs-->alloc_unbound_pwq-->create_worker 7 0 0:00 [rcu_sched] 8 0 0:00 [rcu_bh] 9 0 0:00 [migration/0] 10 0 0:00 [migration/1] 11 0 0:00 [ksoftirqd/1] 12 0 0:00 [kworker/1:0]---------------workqueue_cpu_up_callback-->create_worker 13 0 0:00 [kworker/1:0H] 14 0 0:00 [migration/2] 15 0 0:00 [ksoftirqd/2] 16 0 0:00 [kworker/2:0] 17 0 0:00 [kworker/2:0H]--------------workqueue_cpu_up_callback-->create_worker 18 0 0:00 [migration/3] 19 0 0:00 [ksoftirqd/3] 20 0 0:00 [kworker/3:0] 21 0 0:00 [kworker/3:0H]--------------workqueue_cpu_up_callback-->create_worker 22 0 0:00 [khelper] 23 0 0:00 [kdevtmpfs] 24 0 0:00 [perf] 25 0 0:00 [kworker/u8:1]--------------worker_thread-->create_worker 279 0 0:00 [khungtaskd] 280 0 0:00 [writeback] 281 0 0:00 [kintegrityd] 282 0 0:00 [kworker/0:1]---------------worker_thread-->create_worker 284 0 0:00 [bioset] 286 0 0:00 [kblockd] 294 0 0:00 [ata_sff] 408 0 0:00 [rpciod] 409 0 0:00 [kworker/2:1]---------------worker_thread-->create_worker 410 0 0:00 [kworker/1:1]---------------worker_thread-->create_worker 412 0 0:00 [kswapd0] 416 0 0:00 [fsnotify_mark] 429 0 0:00 [nfsiod] 449 0 0:00 [kworker/3:1]---------------worker_thread-->create_worker 527 0 0:00 [kpsmoused] 537 0 0:00 [kworker/1:2]---------------worker_thread-->create_worker 613 0 0:00 [deferwq]
init_workqueues-->create_worker-->kthread_create_on_node,创建"kworker/xx:xxH"内核线程。
static int __init init_workqueues(void) { int std_nice[NR_STD_WORKER_POOLS] = { 0, HIGHPRI_NICE_LEVEL }; int i, cpu; ... /* create the initial worker */ for_each_online_cpu(cpu) {---------------------------------遍历CPU[0~3] struct worker_pool *pool; for_each_cpu_worker_pool(pool, cpu) {------------------NR_STD_WORKER_POOLS=2,所以每个CPU有两个pool pool->flags &= ~POOL_DISASSOCIATED; BUG_ON(!create_worker(pool)); } } ... system_wq = alloc_workqueue("events", 0, 0); system_highpri_wq = alloc_workqueue("events_highpri", WQ_HIGHPRI, 0); system_long_wq = alloc_workqueue("events_long", 0, 0); system_unbound_wq = alloc_workqueue("events_unbound", WQ_UNBOUND, WQ_UNBOUND_MAX_ACTIVE); system_freezable_wq = alloc_workqueue("events_freezable", WQ_FREEZABLE, 0); system_power_efficient_wq = alloc_workqueue("events_power_efficient", WQ_POWER_EFFICIENT, 0); system_freezable_power_efficient_wq = alloc_workqueue("events_freezable_power_efficient", WQ_FREEZABLE | WQ_POWER_EFFICIENT, 0); BUG_ON(!system_wq || !system_highpri_wq || !system_long_wq || !system_unbound_wq || !system_freezable_wq || !system_power_efficient_wq || !system_freezable_power_efficient_wq); return 0; }
create_worker()函数创建工作线程。
static struct worker *create_worker(struct worker_pool *pool) { ... if (pool->cpu >= 0) snprintf(id_buf, sizeof(id_buf), "%d:%d%s", pool->cpu, id,-------------cpuid和id,区分cpu和cpu内kworker。 pool->attrs->nice < 0 ? "H" : ""); else snprintf(id_buf, sizeof(id_buf), "u%d:%d", pool->id, id);--------------u表示不指定cpu。 worker->task = kthread_create_on_node(worker_thread, worker, pool->node, "kworker/%s", id_buf); ... }
更详细信息参考:《Linux中断管理 (3)workqueue工作队列》、《Linux workqueue工作原理》、《Concurrency Managed Workqueue之(一):workqueue的基本概念》
20250620补充:
工作线程 (kworker) 的详细分解 (kworker/[属性]:[ID]-[池名/描述符]):
- [属性]: 表示工作线程所属工作队列的类型或属性。这是最关键的部分之一。
- u: 表示该线程属于 Unbound Workqueue (通用工作队列)。这类工作队列的线程不绑定到特定CPU核心,调度器可以将其迁移到任何核心运行。适用于不需要CPU亲和性的任务。如 kworker/u2:0..., kworker/u3:0...。
- R: 表示该线程属于 Per-CPU Workqueue (CPU绑定的工作队列)。这类工作队列为每个CPU核心(或每个CPU组)创建专用的线程池。线程名中的数字(如R0, R1)通常表示CPU ID或CPU组ID。线程被严格绑定到指定的CPU核心上运行,适用于需要CPU亲和性(如利用CPU缓存)或避免跨核同步开销的任务。如 kworker/R-slub, kworker/R-mm佩 (注意"佩"可能是乱码或特定模块缩写), kworker/R-inet_。
- H: 表示该线程属于 High Priority Workqueue (高优先级工作队列)。这类工作队列创建的线程具有更高的调度优先级(SCHED_FIFO),用于执行需要快速响应的关键任务。如 kworker/0:0H-kblockd (绑定在CPU 0上的高优先级kworker)。
- [ID]: 一个数字(有时带冒号),用于区分同一属性/CPU上的不同工作线程实例。如 kworker/0:0..., kworker/0:1... 都是在CPU 0上的不同线程。
- [池名/描述符]: 这是工作线程所属工作队列池(worker pool)的名称或描述符。它通常反映了该池主要服务的子系统、模块或任务类型。这是理解线程功能的核心部分。常见的有:
- events: 通用事件处理池(非常常见)。处理各种内核事件。
- events_unbound: 通用事件处理池(未绑定版本)。
- kblockd: 块设备子系统相关任务(如请求队列处理)。
- slub: SLUB内存分配器相关任务(如缓存回收)。
- mm_...: 内存管理 (Memory Management) 相关任务(如 mm_percpu_wq, 你列表中的 mm佩 可能是 mm_percpu_wq 的截断或显示问题)。
- writeback: 文件系统写回 (writeback) 任务。
- dm_...: 设备映射器 (Device Mapper) 相关任务(如 dm_bufio, dm-thin, 你列表中的 dm_bu, dmld 可能是其缩写)。
- sdhc1: 指向特定SD Host Controller驱动(SD卡/MMC控制器驱动)的任务。表明该工作队列用于处理这个特定硬件控制器的中断下半部或其他异步操作。
- stmm: 可能指向特定网络驱动(如 stmmac - Synopsys Ethernet MAC 驱动)的任务。
- cfg80...: 可能指向特定无线网络驱动(如 cfg80211 或 mac80211 子系统)的任务。
- mlid: 可能指向特定硬件驱动(如 InfiniBand 或 RDMA 相关)的任务。需要具体上下文。
- pm: 电源管理 (Power Management) 相关任务。
- inet_...: 网络协议栈(如 TCP/IP)相关任务(可能是 inet_frag_wq 处理分片重组等)。
- - 后跟其他字符串:通常是更具体的描述符或模块名缩写。
5. 其他内核线程
其他常见内核线程解释如下:
- kthreadd:内核线程管理器,负责创建和调度其他内核线程(PID 2)。
- pool_workqueue_release:工作队列池清理线程,释放不再使用的动态工作队列资源。
- kdevtmpfs:自动创建设备节点的守护线程,管理`/dev`目录下的设备文件。
- oom_reaper:内存耗尽(OOM)时回收被杀死进程内存的线程。
- kcompactd0:内存压缩守护线程,减少内存碎片以支持大页分配。
- kswapd0:内存交换守护线程,将不活跃内存页换出到磁盘以释放空间。
- spi0:SPI控制器驱动线程(通常为特定硬件驱动创建,如串行外设接口)。
- ubi_bgt0d/1d/2d/3d:UBI闪存存储的后台线程,分别处理擦除、磨损均衡等操作。
- ubifs_bgt0_1:UBIFS文件系统的后台线程,负责垃圾回收和损耗均衡。
- rcu_sched:RCU(读-复制-更新)机制的计划任务线程,处理锁-free同步回调。
- rcu_bh:RCU机制的Bottom-half处理线程,用于软中断上下文回调。
- migration:SMP系统中进程迁移线程,负责在CPU间平衡任务负载。
- khelper:内核辅助线程(旧版),用于模块热插拔等事件通知(现代内核已弃用)。
- perf:性能分析事件处理线程,支持`perf`工具的数据采集。
- writeback:文件系统脏页回写线程,将缓存数据同步到磁盘。
- kintegrityd:块设备数据完整性检查线程,验证I/O操作的完整性标签。
- bioset:块I/O子系统线程,管理`bio`结构的内存池回收。
- kblockd:块设备层通用任务线程,处理块设备请求队列。
- ata_sff:ATA控制器驱动线程,管理PATA/SATA设备的命令队列。
- rpciod:RPC(远程过程调用)I/O守护线程,处理NFS等网络文件系统的异步操作。
- nfsiod:NFS客户端I/O线程,管理NFS文件读写的异步请求。
- kpsmpused:电源管理SMP协调线程(通常与CPU热插拔或节能状态切换相关)。
- deferwq:延迟任务工作队列,处理设备驱动中延迟初始化的操作。