linux 内核线程创建

147 篇文章 1 订阅
65 篇文章 0 订阅
转载 http://lupaworld.blogbus.com/logs/37201575.html
1.        头文件
#include <linux/sched.h>   //wake_up_process()

#include <linux/kthread.h> //kthread_create()kthread_run()

#include <err.h>              //IS_ERR()PTR_ERR()

2.       实现

2.1创建线程

在模块初始化时,可以进行线程的创建。使用下面的函数和宏定义:

struct task_struct *kthread_create(int (*threadfn)(void *data),

                            void *data,

                            const char namefmt[], ...);

#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;                                                     \

})

例如:

static struct task_struct *test_task;

static int test_init_module(void)

{

    int err;

    test_task = kthread_create(test_thread, NULL, "test_task");

    if(IS_ERR(test_task)){

      printk("Unable to start kernel thread.\n");

      err = PTR_ERR(test_task);

      test_task = NULL;

      return err;

    }

wake_up_process(test_task);
       return 0;
   }

   module_init(test_init_module);

2.2线程函数

在线程函数里,完成所需的业务逻辑工作。主要框架如下所示:

int threadfunc(void *data){

        

        while(1){

               set_current_state(TASK_UNINTERRUPTIBLE);

               if(kthread_should_stop()) break;

               if(){//条件为真

                      //进行业务处理

               }

               else{//条件为假

                      //让出CPU运行其他线程,并在指定的时间内重新被调度

                      schedule_timeout(HZ);

               }

        }

        

        return 0;

}

2.3结束线程

在模块卸载时,可以结束线程的运行。使用下面的函数:

int kthread_stop(struct task_struct *k);

例如:

              static void test_cleanup_module(void)

{

            if(test_task){

                kthread_stop(test_task);

                test_task = NULL;

            }

}

module_exit(test_cleanup_module);

3.       注意事项

(1)       在调用kthread_stop函数时,线程函数不能已经运行结束。否则,kthread_stop函数会一直进行等待。

(2)       线程函数必须能让出CPU,以便能运行其他线程。同时线程函数也必须能重新被调度运行。在例子程序中,这是通过schedule_timeout()函数完成的。

4性能测试

可以使用top命令来查看线程(包括内核线程)的CPU利用率。命令如下:

       top –p 线程号

可以使用下面命令来查找线程号:

       ps aux|grep 线程名

可以用下面的命令显示所有内核线程:
      ps afx

        注:线程名由 kthread_create 函数的第三个参数指定

linux 两种方式 创建内核线程

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内核线程创建销毁机制

这个话题乍一听貌似比较大,其实线程创建本身就是一件很平常的事情。

下面将要介绍的是,新版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, &param);

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, &param);   set_cpus_allowed_ptr(create.result, cpu_all_mask);  }  return create.result; }

    注意箭头所指代码,这个函数只是把要创建的线程加入了一个链表,之后唤醒一个kthreadd_task,而kthreadd_task也是一个已经存在的线程,它专门负责创建新的线程。
    还有,我们可以发现,这个函数创建的线程是有优先级策略的——SCHED_NORMAL sched_priority = 0 ,这个策略是普通时间片轮询,所以在内核中我们用这个函数创建的线程是这样的优先级,那么这个优先级可以改吗?
    可以,内核提供了两个函数,sched_setscheduler_nochecksched_setscheduler,不过这两个函数在驱动中使用的不多,或者说几乎不用,我倒是找到一处:在acpi_pad.c 文件中
sched_setscheduler(current, SCHED_RR, &param);
    二、工作队列有优先级策略吗?
    这个可真不好说,为什么呢?实际上一个工作队列一定对应于一个工作者线程,这个工作者线程当然是有策略的,毕竟他是线程,但是工作队列中的每一个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, &param);  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).

Linux内核本身和进程的区别 内核线程、用户进程、用户线程

这个概念是很多人都混淆的了,我也是,刚开始无法理解OS时,把Linux内核也当做一个进程。
其实内核本身不是以进程形式存在的,最多在初始化的过程中表现得就像一个进程,但是内核绝对没有进程的数据结构task_struct,可以严格跟进程区分开 。自从创建init 进程之后,内核就不再主动占有cpu了。只有当进程主动要求和中断到来时,内核才动一动,很快又把cpu还给合适的进程,不是想象中的,以后台服务进程的形式存在。

Linux上进程分3种,内核线程(或者叫核心进程)、用户进程、用户线程

内核线程拥有 进程描述符、PID、进程正文段、核心堆栈
当和用户进程拥有相同的static_prio 时,内核线程有机会得到更多的cpu资源
内核线程的bug直接影响内核,很容易搞死整个系统
内核线程不需要访问用户空间内存,这是再好不过了。所以内核线程的task_struct 的mm域为空
但是刚才说过,内核线程还有核心堆栈,没有mm怎么访问它的核心堆栈呢?这个核心堆栈跟task_struct的
thread_info共享8k的空间,所以不用mm描述。
但是内核线程总要访问内核空间的其他内核啊,没有mm域毕竟是不行的。
所以内核线程被调用时,内核会将其task_strcut 的active_mm指向前一个被调度出的进程的mm域
,在需要的时候,内核线程可以使用前一个进程的内存描述符。
因为内核线程不访问用户空间,只操作内核空间内存,而所有进程的内核空间都是一样的。这样就省下了一个mm域的内存。

用户进程拥有 进程描述符、PID、进程正文段、核心堆栈 、用户空间的数据段和堆栈

用户线程拥有 进程描述符、PID、进程正文段、核心堆栈,同父进程共享用户空间的数据段和堆栈
用户线程也可以通过exec函数族拥有自己的用户空间的数据段和堆栈,成为用户进程。



  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值