kernel_thread()、daemonize()
------------------------------------------------------
int thread_func(void *arg)
{
/*
* 设置线程名 -- thread_name,ps可以看到的线程名
* 如果不使用daemonize设置内核线程名,并且kernel_thread()
* 是用户态程序通过系统调用使用的,则ps将看到一个和用户进程同名
* 的一个内核线程。
*/
daemonize("thread_name");
return 0;
}
{
pid_t pid;
/* 创建内核线程 */
pid = kernel_thread(thread_func, NULL, CLONE_KERNEL);
if (pid < 0)
{
printk("kernel_thread error\n");
}
}
kthread_run() -- 这种创建方式比较简洁
------------------------------------------------------
#include <linux/kthread.h>
int thread_func(void *arg)
{
return 0;
}
{
struct task_struct * my_thread = NULL;
int rc;
my_thread = kthread_run(thread_func, NULL, "thread_name");
if (IS_ERR(my_thread))
{
rc = PTR_ERR(my_thread);
printk("error %d create thread_name thread", rc);
}
}
注: kthread_run()最终调用kernel_thread()创建线程
kernel_thread()类似application编程里面的fork()
------------------------------------------------------
static int jffs2_garbage_collect_thread(void *_c)
{
...
daemonize("jffs2_gcd_mtd%d", c->mtd->index);
...
complete(&c->gc_thread_start);
...
}
int jffs2_start_garbage_collect_thread(struct jffs2_sb_info *c)
{
pid_t pid;
int ret = 0;
BUG_ON(c->gc_task);
init_completion(&c->gc_thread_start);
init_completion(&c->gc_thread_exit);
pid =kernel_thread(jffs2_garbage_collect_thread, c, CLONE_FS|CLONE_FILES);
if (pid < 0) { // 判断出错条件
printk(KERN_WARNING "fork failed for JFFS2 garbage collect thread: %d\n", -pid);
complete(&c->gc_thread_exit);
ret = pid;
} else { // pid > 0 ,说明这个肯定在父进程中
printk(KERN_DEBUG "JFFS2: Garbage collect thread is pid %d\n", pid);
wait_for_completion(&c->gc_thread_start); //父进程等待子进程结束
}
return ret;
}
这个话题乍一听貌似比较大,其实线程创建本身就是一件很平常的事情。
下面将要介绍的是,新版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()函数,这个是工作者线程的线程函数。
linux 内核线程 工作队列 优先级——总结
今天看了一些有关linux优先级的问题,用户态的优先级那倒是资料很多,但是内核态的线程、优先级就不是很明了了。
一、何为linux内核线程?
当我搜索这个关键字时,查到的资料分为两类——kthread 和pthread,这里这两个线程,kthread属于真正的linux内核线程,至于pthread是什么,可以看下百度的解答:pthread_create是UNIX环境创建线程函数,另外,在编译时注意加上-lpthread参数,以调用静态链接库。因为pthread并非Linux系统的默认库。而且从使用上看pthread是位于用户空间的。
kthread就不多说了,在内核中,常见一个函数kthread_create();看下这个函数在做什么:
struct task_struct *kthread_create(int (*threadfn)(void *data),
void *data,
const char namefmt[],
...)
{
struct kthread_create_info create;
create.threadfn = threadfn;
create.data = data;
init_completion(&create.done);
spin_lock(&kthread_create_lock);
————>list_add_tail(&create.list, &kthread_create_list);
spin_unlock(&kthread_create_lock);
————>wake_up_process(kthreadd_task);
wait_for_completion(&create.done);
if (!IS_ERR(create.result)) {
————>struct sched_param param = { .sched_priority = 0 };
va_list args;
va_start(args, namefmt);
vsnprintf(create.result->comm, sizeof(create.result->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(create.result, SCHED_NORMAL, ¶m);
set_cpus_allowed_ptr(create.result, cpu_all_mask);
}
return create.result;
}
注意箭头所指代码,这个函数只是把要创建的线程加入了一个链表,之后唤醒一个kthreadd_task,而kthreadd_task也是一个已经存在的线程,它专门负责创建新的线程。
还有,我们可以发现,这个函数创建的线程是有优先级策略的——SCHED_NORMAL sched_priority = 0 ,这个策略是普通时间片轮询,所以在内核中我们用这个函数创建的线程是这样的优先级,那么这个优先级可以改吗?
可以,内核提供了两个函数,sched_setscheduler_nocheck和sched_setscheduler,不过这两个函数在驱动中使用的不多,或者说几乎不用,我倒是找到一处:在acpi_pad.c 文件中
sched_setscheduler(current, SCHED_RR, ¶m);
二、工作队列有优先级策略吗?
这个可真不好说,为什么呢?实际上一个工作队列一定对应于一个工作者线程,这个工作者线程当然是有策略的,毕竟他是线程,但是工作队列中的每一个work就没有什么策略了,不知我获得信息全不全。目前,我看到,工作者线程有两种策略——其中一种是SCHED_FIFO,至于另一种是什么,很可能是SCHED_NORMAL 。
工作队列的使用有两个办法,一是使用系统默认的工作队列,它对应一个默认的工作者线程,这个线程是什么策略我没有查过,应该是SCHED_NORMAL ,第二种方法就是自己创建一个队列,函数为以下4个:
#define create_workqueue(name) __create_workqueue((name), 0, 0, 0)
#define create_rt_workqueue(name) __create_workqueue((name), 0, 0, 1)
#define create_freezeable_workqueue(name) __create_workqueue((name), 1, 1, 0)
#define create_singlethread_workqueue(name) __create_workqueue((name), 1, 0, 0)
这些函数最后调用了下边这个函数:
static int create_workqueue_thread(struct cpu_workqueue_struct *cwq, int cpu)
{
————>struct sched_param param = { .sched_priority = MAX_RT_PRIO-1 };
struct workqueue_struct *wq = cwq->wq;
const char *fmt = is_wq_single_threaded(wq) ? "%s" : "%s/%d";
struct task_struct *p;
p = kthread_create(worker_thread, cwq, fmt, wq->name, cpu);
/*
* Nobody can add the work_struct to this cwq,
* if (caller is __create_workqueue)
* nobody should see this wq
* else // caller is CPU_UP_PREPARE
* cpu is not on cpu_online_map
* so we can abort safely.
*/
if (IS_ERR(p))
return PTR_ERR(p);
if (cwq->wq->rt)
————>sched_setscheduler_nocheck(p, SCHED_FIFO, ¶m);
cwq->thread = p;
trace_workqueue_creation(cwq->thread, cpu);
return 0;
}
如果我们使用create_rt_workqueue(name),就会创建一个调度策略为SCHED_FIFO的工作队列。
注意:一个工作队列只对应一个线程,这个线程负责运行这个工作队列上的各种work。这可以查看 worker_thread 这个函数:
static int worker_thread(void *__cwq) { struct cpu_workqueue_struct *cwq = __cwq; DEFINE_WAIT(wait);
if (cwq->wq->freezeable) set_freezable();
for (;;) { prepare_to_wait(&cwq->more_work, &wait, TASK_INTERRUPTIBLE); if (!freezing(current) && !kthread_should_stop() && list_empty(&cwq->worklist)) schedule(); finish_wait(&cwq->more_work, &wait);
try_to_freeze();
if (kthread_should_stop()) break;
run_workqueue(cwq); }
return 0; }
从这里可以看到工作者线程在执行每个work时,是顺序执行的。所谓的优先级策略是这个工作者线程的,也就是说,如果有两个工作队列,也意味着有两个工作者线程,这两个线程是有优先级策略的。
三、tasklet优先级?
这个网上资源很多,它拥有两级优先级,说明如下:
已调度的tasklet(等同于被触发的软中断)存放在两个单处理器数据结构:tasklet_vec(普通tasklet)和task_hi_vec高优先级的tasklet)中,这两个数据结构都是由tasklet_struct结构体构成的链表,链表中的每个tasklet_struct代表一个不同的tasklet。Tasklets由tasklet_schedule()和tasklet_hi_schedule()进行调度,它们接收一个指向tasklet_struct结构的指针作为参数。两个函数非常相似(区别在于一个使用TASKLET_SOFTIRQ而另外一个使用HI_SOFTIRQ).