linux进程管理-kthread
0、背景
kthread是指代特殊的在内核态运行的线程,其创建、管理的逻辑与普通集成有所区别,但在调度上还是与其他进程或者线程是类似的。
1、创建
内核线程的创建主要是通过kthread_create
,另外kthread_run
也可创建线程,区别是在创建的同时还会直接运行这个线程。
可以看到kthread_create
的实际原型是kthread_create_on_node
。
#define kthread_create(threadfn, data, namefmt, arg...) \
kthread_create_on_node(threadfn, data, NUMA_NO_NODE, namefmt, ##arg)
最终会调用到__kthread_create_on_node
进行创建,而实际上创建kthread的工作实际上是一个由kthreadd_task
线程完成
struct task_struct *__kthread_create_on_node(int (*threadfn)(void *data),
void *data, int node,
const char namefmt[],
va_list args)
{
DECLARE_COMPLETION_ONSTACK(done);
struct task_struct *task;
struct kthread_create_info *create = kmalloc(sizeof(*create),
GFP_KERNEL); // 创建一个ktherad_create数据上下文
if (!create)
return ERR_PTR(-ENOMEM);
/*填充create结构体*/
create->threadfn = threadfn; // kthread的实际逻辑函数指针
......
spin_lock(&kthread_create_lock);
list_add_tail(&create->list, &kthread_create_list); // 把创建好的create结构体加入到链表上
spin_unlock(&kthread_create_lock);
wake_up_process(kthreadd_task); // 唤醒创建kthread的线程
......
if (unlikely(wait_for_completion_killable(&done))) { // 使用wait_for循环等待创建工作结束
......
wait_for_completion(&done);
}
task = create->result;
free_create:
kfree(create);
return task;
}
而这个进程是在init
的创建流程中被创建的,实际是真正执行的原型是kthreadd
noinline void __ref __noreturn rest_init(void)
{
......
numa_default_policy();
pid = kernel_thread(kthreadd, NULL, NULL, CLONE_FS | CLONE_FILES);
rcu_read_lock();
kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns);
rcu_read_unlock();
......
}
我们可以看到kthreadd
的主要工作就是遍历kthread_create_list
链表创建线程。
int kthreadd(void *unused)
{
......
set_cpus_allowed_ptr(tsk, housekeeping_cpumask(HK_TYPE_KTHREAD)); // 设置cpu亲核性
set_mems_allowed(node_states[N_MEMORY]); // 设置内存节点
......
for (;;) {
set_current_state(TASK_INTERRUPTIBLE); // 先把自身设置成休眠
if (list_empty(&kthread_create_list)) // 如果没有需要创建的线程,就让出调度
schedule();
__set_current_state(TASK_RUNNING); // 有线程需要创建则重新设置成running
spin_lock(&kthread_create_lock);
while (!list_empty(&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);
spin_unlock(&kthread_create_lock);
create_kthread(create); // 创建这个线程
spin_lock(&kthread_create_lock);
}
spin_unlock(&kthread_create_lock);
}
return 0;
}
创建的实际逻辑比较简单,就是通过fork.c
里通过kernel_thread
创建线程。
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, create->full_name,
CLONE_FS | CLONE_FILES | SIGCHLD); // 通过fork.c中的kernel_thread创建线程
if (pid < 0) {
/* Release the structure when caller killed by a fatal signal. */
struct completion *done = xchg(&create->done, NULL);
kfree(create->full_name);
if (!done) {
kfree(create);
return;
}
create->result = ERR_PTR(pid);
complete(done); // 填充create->done结构体
}
}
2、kthread()
2.1-int kthread(void *_create)
kthread()
函数是由kernel_thread
在创建线程时绑定的,内核线程创建完会一直则执行ret = threadfn(data);
static int kthread(void *_create)
{
/* Copy data: it's on kthread's stack */
struct kthread_create_info *create = _create;
int (*threadfn)(void *data) = create->threadfn; // kthread实际业务逻辑
void *data = create->data;
struct kthread self; // kthread特别的数据结构
int ret;
self = to_kthread(current); // 填充kthread结构体
/* OK, tell user we're spawned, wait for stop or wakeup */
__set_current_state(TASK_UNINTERRUPTIBLE); // 先休眠
......
if (!test_bit(KTHREAD_SHOULD_STOP, &self.flags)) {
__kthread_parkme(&self);
ret = threadfn(data); // 开始执行具体ktread逻辑
}
/* we can't just return, we must preserve "self" on stack */
do_exit(ret);
}
2.2-struct kthread
而struct kthread
是一个内核线程专有的数据结果,这里会保存内核线程需要操作的基本数据结构
struct kthread {
unsigned long flags; // 线程的状态(如运行、休眠等)
unsigned int cpu; // 线程绑定的cpu
int result; // 线程的return值
int (*threadfn)(void *); // 线程实际执行逻辑函数地址
void *data; // 存储或传递给线程函数的数据
struct completion parked; // 用来记录线程暂停的结构体
struct completion exited; // 用来记录线程退出的结构体
#ifdef CONFIG_BLK_CGROUP
struct cgroup_subsys_state *blkcg_css; // 指向cgroup状态的指针,用于块设备控制组相关的功
#endif
/* To store the full name if task comm is truncated. */
char *full_name; // 线程名称
};
3、唤醒&退出
3.1-唤醒
由于很多内核线程都是阶段运行的,所以部分内核线程会主动进入休眠状态,然后等待其他线程唤醒。
唤醒的方式比较常见,就是通过wake_up_process
来唤醒进程。
3.2-退出
3.2.1-正常退出
如上述分析,kthread
正常退出后会执行kthread_exit(ret);
static int kthread(void *_create)
{
......
/* OK, tell user we're spawned, wait for stop or wakeup */
__set_current_state(TASK_UNINTERRUPTIBLE);
create->result = current;
complete(&create->done);
schedule();
ret = -EINTR;
if (!test_bit(KTHREAD_SHOULD_STOP, &self.flags)) {
__kthread_parkme(&self);
ret = threadfn(data);
}
/* we can't just return, we must preserve "self" on stack */
kthread_exit(ret);
}
而kthread_exit
中则会填充done
相关的结构体
void __noreturn kthread_exit(long result)
{
struct kthread *kthread = to_kthread(current);
kthread->result = result;
do_exit(0);
}
kthread->exited
的结构体最终是由mm_release
完成的,调用流程为:do_exit
->exit_mm
->exit_mm_release
->mm_release
,其中操作如下:
static void mm_release(struct task_struct *tsk, struct mm_struct *mm)
{
uprobe_free_utask(tsk);
......
/*
* All done, finally we can wake up parent and return this mm to him.
* Also kthread_stop() uses this completion for synchronization.
*/
if (tsk->vfork_done)
complete_vfork_done(tsk); // 通过操作vfork_done地址来填充结构体
}
上边的vfork_done
是在初始化过程中,指向exited
结构体的
bool set_kthread_struct(struct task_struct *p)
{
......
init_completion(&kthread->exited);
init_completion(&kthread->parked);
p->vfork_done = &kthread->exited;
......
}
3.2.2-kthread_stop
另外,我们也可以主动调用kthread_stop()
来终止一个循环线程,但是同时也要再线程的循环中增加kthread_should_stop()
的检查。
int kthread_stop(struct task_struct *k)
{
struct kthread *kthread;
int ret;
trace_sched_kthread_stop(k);
get_task_struct(k);
kthread = to_kthread(k); // 获取线程的kthread结构体
set_bit(KTHREAD_SHOULD_STOP, &kthread->flags); // 增加should_stop flag
kthread_unpark(k); // 取消park flag
set_tsk_thread_flag(k, TIF_NOTIFY_SIGNAL); // 提前取消signal peding
wake_up_process(k); // 唤醒线程
wait_for_completion(&kthread->exited); // 等待线程完成
ret = kthread->result; // 把return存入结构体中
put_task_struct(k); // 解引用
trace_sched_kthread_stop_ret(ret);
return ret;
}
其中比较关键的操作是wait_for_completion
,他会执行一个没有timeout的循环等待,等待进程退出。
void __sched wait_for_completion(struct completion *x)
{
wait_for_common(x, MAX_SCHEDULE_TIMEOUT, TASK_UNINTERRUPTIBLE);
}
EXPORT_SYMBOL(wait_for_completion);
static long __sched
wait_for_common(struct completion *x, long timeout, int state)
{
return __wait_for_common(x, schedule_timeout, timeout, state);
}
static inline long __sched
do_wait_for_common(struct completion *x,
long (*action)(long), long timeout, int state)
{
if (!x->done) {
DECLARE_SWAITQUEUE(wait);
do {
if (signal_pending_state(state, current)) { // 有信号排队的时候退出循环
timeout = -ERESTARTSYS;
break;
}
__prepare_to_swait(&x->wait, &wait);
__set_current_state(state); // 把本线程标记为不可中断的状态,防止误调度
raw_spin_unlock_irq(&x->wait.lock);
timeout = action(timeout); // 不会重新唤醒,应为timeout是MAX_SCHEDULE_TIMEOUT
raw_spin_lock_irq(&x->wait.lock);
} while (!x->done && timeout); // 等待kthread的done标记为置1,由do_exit完成
__finish_swait(&x->wait, &wait);
if (!x->done)
return timeout;
}
if (x->done != UINT_MAX)
x->done--;
return timeout ?: 1;
}