全部博文(573)
分类: LINUX
2015-12-09 14:38:58
这个话题乍一听貌似比较大,其实线程创建本身就是一件很平常的事情。
下面将要介绍的是,新版linux中创建内核线程的机制做了一些变化(其实本质没变,最终还是调用do_fork()来实现),和控制线程的时候需要注意的地方。
本文引用的几个源码文件:
@ kernel/kernel/kthread.c
@ kernel/include/linux/kthread.h
@ kernel/include/linux/wait.h
@ kernel/kernel/workqueue.c
新版linux中将创建内核线程的工作交给了一个专门的内核线程kthreadd去做了,该线程会检查全局链表kthread_create_list,如果为NULL,就会调schedule()放弃cpu进入睡眠状态,否则就取下该链表中的一项出来创建对应的线程。
kthreadd线程何时创建?
kthreadd线程在系统启动阶段被创建,创建代码如下:
start_kernel() @ kernel/init/main.c
--> rest_init()
pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);
kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns);
struct task_struct *kthreadd_task; @ kernel/kernel/kthread.c
-- 记录这线程kthreadd的task_struct结构体
该线程的线程函数是kthreadd() @ kernel/kernel/kthread.c -- 见代码
我们平时使用的创建接口?
@ kernel/include/linux/kthread.h
#define kthread_run(threadfn, data, namefmt, ...) /
({ /
struct task_struct *__k /
= kthread_create(threadfn, data, namefmt, ## __VA_ARGS__); /
if (!IS_ERR(__k)) /
wake_up_process(__k); /
__k; /
})
或者直接使用kthread_create()函数来创建,值得注意的是,该函数创建线程ok返回时,新建线程是休眠的。代码里可以看到休眠的位置。所以如果需要创建线程后并马上运行,kthread_run()是个不错的接口。
kthread_create()函数?
先看两个相关的数据结构:
struct kthread_create_info
{
int (*threadfn)(void *data);
void *data;
struct task_struct *result;
struct completion done;
struct list_head list;
};
struct kthread {
int should_stop;
struct completion exited;
};
该函数的实现见代码:kernel/kernel/kthread.c
create_kthread()函数时线程kthreadd被唤醒之后调用的函数,该函数调用函数:
pid = kernel_thread(kthread, create, CLONE_FS | CLONE_FILES | SIGCHLD);
其中kthread()是创建的内核线程的共用线程函数,我们在上层接口中传递下来的线程函数和参数都包含在参数create中。在函数kthread()才会调用我们传递进来的函数。
详见代码:kernel/kernel/kthread.c
如何停止某个线程?
使用函数kthread_stop(struct task_struct *k)即可停止一个指定的线程。但是有时候停止某线程的时候需要满足一定条件才可以成功让线程停止并销毁资源,这个稍后会提到。这里先看该函数源码: kernel/kernel/kthread.c
注意事项
这个问题是在停止线程的时候需要注意的。
如果你的线程没事做的时候是睡眠的,而起睡眠在某个等待队列头上等某个条件满足,那么在停止该线程之前必须让那个条件满足,才能调用kthread_stop()函数,否则的话,你永远也停止不掉这个线程,除非kill -9结果了它。
因为可睡眠的线程通常是调用wait_event_interruptible(wq, condition)宏来等待条件,通常的线程被设置成可被信号中断地等。
#define __wait_event_interruptible(wq, condition, ret) /
do { /
DEFINE_WAIT(__wait); /
/
for (;;) { /
prepare_to_wait(&wq, &__wait, TASK_INTERRUPTIBLE); /
if (condition) /
break; /
if (!signal_pending(current)) { /
schedule(); /
continue; /
} /
ret = -ERESTARTSYS; /
break; /
} /
finish_wait(&wq, &__wait); /
} while (0)
看的出来,线程被唤醒后首先再次去检查条件是否满足,条件满足才会退出睡眠,否则检查一下如果没有信号,会继续睡下去。所以kthread_stop()中只有一次唤醒这个线程的机会,错过了就错过了,除非另外的地方满足了这个条件。所以在停止线程之前,满足一下它等待的条件是非常可靠的。
另外一个情况如下,如果你的线程函数是这样写的:
struct sched_param param = { .sched_priority = RTPM_PRIO_TPD };
sched_setscheduler(current, SCHED_RR, ¶m);
do{
set_current_state(TASK_INTERRUPTIBLE);
wait_event_interruptible(waiter,条件); // 条件等待 tpd_flag!=0
清除条件 // tpd_flag = 0;
set_current_state(TASK_RUNNING);
if(kthread_should_stop())
continue;
{
数据读取处理
}
}while(!kthread_should_stop())
红色部位可能有时候时多余的,但是针对有些硬件的特点,在没有让你去读取数据的时候你读数据的时候往往会出错,因为这一次的唤醒根本就不是硬件说它已经准备好了,而是人为故意满足了这个条件而已。
如果你的线程写法是数据处理在前,检查睡眠在后,那么就没有这个添加的必要了。
int kthread_should_stop(void)
{
return to_kthread(current)->should_stop;
}
kthread_stop()函数中会做这样的操作:kthread->should_stop = 1;
最后的一种情况是,你的线程函数中没有等待某个条件,而是主动睡眠,然后其他地方根据你的线程的名字来唤醒的话,那么也就没有在停止之前人为满足其条件的动作了。
参考代码:kernel/kernel/workqueue.c worker_thread()函数,这个是工作者线程的线程函数。