linux内核线程「建议收藏」

linux内核线程「建议收藏」内核经常需要在后台执行一些操作,这种任务就可以通过内核线程(kernlethread)完成,内核线程是独立运行在内核空间的标准进程。内核线程和普通的进程间的区别在于内核线程没有独立的地址空间,mm指针被设置为NULL;它只在内核空间运行,从来不切换到用户空间去;并且和普通进程一样,可以被调度,也可以被抢占。实际上,内核线程只能由其他内核线程创建,linux驱动模块中可以用kernel_threa…

大家好,又见面了,我是你们的朋友全栈君。

版权声明:本文为博主原创文章,未经博主允许不得转载。
https://blog.csdn.net/huangweiqing80/article/details/83088465

内核经常需要在后台执行一些操作,这种任务就可以通过内核线程(kernle thread)完成,内核线程是独立运行在内核空间的标准进程。内核线程和普通的进程间的区别在于内核线程没有独立的地址空间,mm指针被设置为NULL;它只在内核空间运行,从来不切换到用户空间去;并且和普通进程一样,可以被调度,也可以被抢占。实际上,内核线程只能由其他内核线程创建,linux驱动模块中可以用kernel_thread(),kthread_create()/kthread_run()两种方式创建内核线程,另外还可以用第三方库(如pthread,除此之外还有其他的第三方库),在驱动模块中创建线程(pthread也可以用在用户空间):

一、linux进程的创建

kernel/init/main.c

asmlinkage void __init start_kernel(void)
{ 
   
     ...
	/* Do the rest non-__init'ed, we're now alive */
	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 | CLONE_SIGHAND);     //1号进程
	numa_default_policy();
	pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);     //2号进程
	rcu_read_lock();
	kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns);
	rcu_read_unlock();
	complete(&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_idle();
}

在这里插入图片描述
在这里插入图片描述

kthreadd这个2号进程所做的事就是:
1 : 遍历链表 kthread_create_list
如果为空 : 让出cpu,休眠
2 : 如果不为空: 从链表中取出来,创建内核进程

kernel/kernel/kthread.c

int kthreadd(void *unused)
{ 
   
    struct task_struct * tak = current;
    /** 设定task的名字 */
    set_task_comm(tsk,"kthreadd");
    { 
   
        ...
        strlcpy(tsk->comm,buf,sizeof(tsk->comm));
        ...
    }
    /** 忽略所有的信号 */
    ignore_signals(tsk);
    { 
   
        int i ;
        for (i = 0; i < _NSIG; ++i)
        { 
   
            /** 默认处理函数设置为SIG_IGN //act.sa_handler = do_sig; //act.sa_handler = SIG_IGN; act.sa_handler = SIG_DFL; manpage中有这样的描述: sa_handler specifies the action to be associated with signum and may be SIG_DFL for the default action, SIG_IGN to ignore this signal, */
            t->sighand->action[i].sa.sa_handler = SIG_IGN;
        }
        flush_signals(t);
    }

    ....
    /** 死循环 */
    for(;;)
    { 
   
        /** 设置该内核线程的状态为TASK_INTERRUPTIBLE 是为了下面调用schedule()。 */
        set_current_state(TASK_INTERRUPTIBLE);

        /** 如果kthread_create_list链表为空,那么 就休眠。 那么它在什么时候 被谁来唤醒呢? 由kthread_create()函数唤醒 wake_up_process(kthreadd_task); 下面会有分析 */
        if (list_empty(&kthread_create_list))
        { 
   
            schedule();
        }
        /** 如果该线程被唤醒、或者上面的条件不成立, 那么就设置该线程的状态为TASK_RUNNING */
        __set_current_state(TASK_RUNNING);

        spin_lock(&kthread_create_lock);

        /** 判断kthread_create_list是否为空 如果不为空,会进入这个while循环,会创建内核线程 创建一个线程,删掉一个节点,这样直到kthread_create_list为空,跳出while循环,再次进入for循环 */
        while (!list_empty(&kthread_create_list)) 
        { 
   
            struct kthread_create_info *create;

            /* 取出全局链表kthread_create_list第一个节点 那么谁往这个全局链表上放入节点呢?是kthread_create()函数来完成的 list_add_tail(&create.list, &kthread_create_list); 下面有分析 */
            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);
            { 
   
                pid = kernel_thread(kthread, create, CLONE_FS | CLONE_FILES | SIGCHLD);
                { 
   
                    .....
                    /** 最终还是调用了do_fork()函数来创建的。<<linux内核设计与实现 3版>>书中说,内核线程是内核态的标准进程。 */
                    return do_fork(flags|CLONE_VM|CLONE_UNTRACED, 0, &regs, 0, NULL, NULL);
                }
            }

            spin_lock(&kthread_create_lock);
        }

        spin_unlock(&kthread_create_lock);

    }
    return 0;
}

二、linux线程

2.1 创建线程

(1) kernel_thread()

kernel_thread()声明在include/linux/sched.h里面:

extern pid_t kernel_thread(int (*fn)(void *), void *arg, unsigned long flags);

参数说明:
fn:线程函数地址
arg:线程函数的形参,没有,可以是NULL
flags:标志,一般用CLONE_KERNEL(定义在linux/sched.h中,注意有的版本中,没用定义),其他标志及含义见uapi/linux/sched.h中。
返回值:返回线程ID值。

注意:kernel_thread()由于没有用EXPORT_SYMBOL导出来,所以用kernel_thread()这能用在和内核一起编译的驱动代码中,如用在ubuntu系统上,不随内核一起编译,单独的驱动程序时,可以编译过,但是用insmod加载驱动模块时,会报未知符号错误”Unknown symbol in module”,而不能加载。

(2) kthread_create():

/** 该函数主要做了两件事情 : 1 : 构造了一个kthread_create_info 结构体,将其挂接到 kthread_create_list链表上 2 : 唤醒 内核线程kthreadd ,让其创建内核线程 */
struct task_struct *kthread_create(int (*threadfn)(void *data),void *data,const char namefmt[],...)
{ 
   
    struct kthread_create_info create;

/** 根据传入的 参数,初始化kthread_create_info结构 struct kthread_create_info { int (*threadfn)(void *data); //用于新创建的内核线程的运行函数 void *data; //参数 struct task_struct *result; //创建成功后 返回的进程描述符 struct completion done; //完成量 struct list_head list; //用于连接到全局链表kthread_create_list } */

    create.threadfn = threadfn;
    create.data = data;

    /** 初始化完成量 */
    init_completion(&create.done);

    spin_lock(&kthread_create_lock);
    /** 将kthread_create_info添加到 kthread_create_list链表上。 kthread_create()创建的内核进程请求信息都会挂接在这个链表上。 */
    list_add_tail(&create.list, &kthread_create_list);
    spin_unlock(&kthread_create_lock);

    /** 唤醒内核线程kthreadd pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES); kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns); */
    wake_up_process(kthreadd_task);
    /** 等待内核线程kthreadd创建内核线程完成 */
    wait_for_completion(&create.done);

    if (!IS_ERR(create.result)) 
    { 
   
        ...
    }
    return create.result;
}

kthread_create参数说明:
threadfn:线程函数地址
data:线程函数形参,如果没有可以定义为NULL
namefmt,arg…:线程函数名字,可以格式化输出名字。
返回值:返回线程指针(strcut task_struct *)

kthread_create创建线程流程
在结合前面的rest_init、kthreadd、kthread_create。我们可以总结:

  1. rest_init:
    调用
    pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES); //2号进程
    创建了进程kthreadd

  2. kthreadd:
    在for死循环中,判断kthread_create_list链表是否为空;如果为空,调用schedule()切换进程,让出cpu。
    当kthread_create里面调用wake_up_process(kthreadd_task)唤醒kthreadd时,程序则接着schedule,接着往下运行;然后在一个while循环中循环的遍历kthread_create_list,并取出节点,创建线程,创建完成然后删除节点,直到kthread_create_list链表为空跳出while循环,在for死循环重新运行

  3. kthread_create:
    3.1 : 构造了一个kthread_create_info 结构体,将其挂接到 kthread_create_list链表上
    3.2 : 调用wake_up_process(kthreadd_task),唤醒内核线程kthreadd

所以kthread_create并没有创建线程,他只是将进程结构体kthread_create_info添加到kthread_create_list,然后唤醒2号进程,线程的创建工作是在kthreadd中调用do_fork去创建的。所以我们可以用ps -ef命令看到,2号进程是线程的父进程:
在这里插入图片描述

注意:
kthread_create()创建后,线程没有立即运行,需要将返回的值,即线程指针(struct task_struct *),作为参数传入到wake_up_process()唤起线程运行

唤醒内核线程(可以唤醒所有进程(线程)):
wake_up_process(struct task_struct *k);
wake_up_process函数解析

函数schedule()实现调度程序。它的任务是从运行队列的链表rq中找到一个进程,并随后将CPU分配给这个进程。
Linux进程调度——schedule()函数分析

kthread_create用法例子:

#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/init.h>
#include <linux/kmod.h>
#include <linux/sched.h>
#include <linux/delay.h>
#include <linux/kthread.h>

MODULE_AUTHOR("zimu");
MODULE_LICENSE("GPL");

#define CLONE_KERNEL (CLONE_FS | CLONE_FILES | CLONE_SIGHAND)

strcut task_struct *practice_task_p = NULL;

int my_kernel_thread(void *arg)
{ 
   
        int n = 0;

        while(1)
        { 
   
                printk("%s: %d\n",__func__,n++);
                ssleep(3);
        if(kthread_should_stop())
                { 
   
                        break;
                }

        }

        return 0;
}
static int __init practiceCall(void)
{ 
   
        printk("%s:\n",__func__);
        practice_task_p = kthread_create(my_kernel_thread,NULL,"practice task");    
        if(!IS_ERR(practice_task_p))
                wake_up_process(practice_task_p);
        return 0;
}

static void __exit practiceCallExit(void)
{ 
   
        printk("%s:\n",__func__);
        kthread_stop(practice_task_p);
}

module_init(practiceCall);
module_exit(practiceCallExit);

这个例子运行,每隔3秒n加1,并输出,当退出模块时,调用了kthread_stop()函数,当my_kernel_kernel()运行到if(kthread_should_stop())时,条件为真,退出while(1){…}线程退出停止。

注意:在编写线程循环体时,一般都要加入kthread_should_stop(),如果不加,调用kthread_stop()是没有效果的,也会导致模块退出后,线程仍然还在运行。

2.2 创建并运行线程

kthread_run():

kthread_run()声明在include/linux/kthread.h里面,如下:

/** * 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)) \ wake_up_process(__k); \ __k; \ })

参数说明:
threadfn:线程函数地址
data:线程函数形参,没有可以制定为NULL
namefmt, …:线程名字,可以格式化输出
返回值__k:线程结构体指针(struct task_struct * )

注意:kthread_run()创建的线程,是立刻运行的。

这个函数的英文注释里很明确的说明: 创建并启动一个内核线程。由kthread_create()和wake_up_process()两部分组成,这里的函数kthread_create()只是创建了内核线程,而后面的这个函数wake_up_process()则是启动了这个线程,让它在一开始就一直运行下去。直到遇见kthread_should_stop函数或者kthread_stop()函数。

kthread_run()负责内核线程的创建,参数包括入口函数threadfn,参数data,线程名称namefmt。可以看到线程的名字可以是类似sprintf方式组成的字符串。如果线程创建成功,再调用wake_up_process()唤醒新创建的线程。kthread_create()根据参数向kthread_create_list中发送一个请求,并唤醒kthreadd,之后会调用wait_for_completion(&create.done)等待线程创建完成。新创建的线程开始运行后,入口在kthread(),kthread()调用complete(&create->done)唤醒阻塞的模块进程,并使用schedule()调度出去。kthread_create()被唤醒后,设置新线程的名称,并返回到kthread_run中。kthread_run调用wake_up_process()重新唤醒新创建线程,此时新线程才开始运行kthread_run参数中的入口函数。

kthread_run用法例子:

#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/init.h>
#include <linux/kmod.h>
#include <linux/sched.h>
#include <linux/delay.h>
#include <linux/kthread.h>

MODULE_AUTHOR("zimu");
MODULE_LICENSE("GPL");

#define CLONE_KERNEL (CLONE_FS | CLONE_FILES | CLONE_SIGHAND)

int my_kernel_thread(void *arg)
{ 
   
        int n = 0;

        while(1)
        { 
   
                printk("%s: %d\n",__func__,n++);
                ssleep(3);
        if(kthread_should_stop())
                { 
   
                        break;
                }

        }

        return 0;
}
static int __init practiceCall(void)
{ 
   
        printk("%s:\n",__func__);
        practice_task_p = kthread_run(my_kernel_thread,NULL,"practice task");    

        return 0;
}

static void __exit practiceCallExit(void)
{ 
   
        printk("%s:\n",__func__);
        kthread_stop(practice_task_p);
}

module_init(practiceCall);
module_exit(practiceCallExit);

例子运行后,也是每隔3秒n加1,并且打印出来。卸载模块时,kthread_stop()通知线程退出。

注意:kernel_thread()的参数fn,以及kthread_create()/kthread_run()的参数threadfn即使是一样的函数,只要创建的线程不一样(线程名字不一样),那么fn,threadfn函数在不同的线程里面,运行的空间地址是不一样的。

2.3 退出线程

kthread_stop:通过发送信号给线程,使之退出。
int kthread_stop(struct task_struct *thread);线程一旦启动起来后,会一直运行,除非该线程主动调用do_exit函数,或者其他的进程调用kthread_stop函数,结束线程的运行。 但如果线程函数正在处理一个非常重要的任务,它不会被中断的。当然如果线程函数永远不返回并且不检查信号,它将永远都不会停止,因此,线程函数必须能让出CPU,以便能运行其他线程。同时线程函数也必须能重新被调度运行。在例子程序中,这是通过schedule_timeout()函数完成的(下面的例子会看到)。

/** 返回should_stop 标志, 看一下kthread_stop()源码 int kthread_stop(struct task_struct *k) { struct kthread *kthread; int ret; .... ... if (k->vfork_done != NULL) { kthread->should_stop = 1; //should_stop 被置1 wake_up_process(k); wait_for_completion(&kthread->exited);//等待线程结束 } ... */
int kthread_should_stop(void)
{ 
   
    return to_kthread(current)->should_stop;
}

三、linux内核线程示例程序:

static struct task_struct *test_task;

int threadfunc(void *data){ 
   while(1){ 
   
              set_current_state(TASK_UNINTERRUPTIBLE);//将当前的状态表示设置为休眠
              if(kthread_should_stop()) break;  //解释见“注意”
              if(){ 
   //条件为真
                     //进行业务处理
              }
              else{ 
   //条件为假
                     //让出CPU运行其他线程,并在指定的时间内重新被调度
                    schedule_timeout(HZ);   // 休眠,与set_current_state配合使用,需要计算,这里表示休眠一秒
              }
       }return 0;
}

static inttest_init_module(void)    //驱动加载函数
{ 
   
    int err;
    test_task = kthread_create(threadfunc, 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;
}
   
static void test_cleanup_module(void)
{ 
   
           if(test_task){ 
   
               kthread_stop(test_task);
               test_task = NULL;
           }
}

module_exit(test_cleanup_module);
module_init(test_init_module);
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/163614.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • python执行测试用例_java的random生成随机数

    python执行测试用例_java的random生成随机数前言通常我们认为每个测试用例都是相互独立的,因此需要保证测试结果不依赖于测试顺序,以不同的顺序运行测试用例,可以得到相同的结果。pytest默认运行用例的顺序是按模块和用例命名的ASCII编码

    2022年7月28日
    4
  • eclipse添加logcat显示_eclipse的logcat不见了

    今天打开eclipse调了一会程序,突然发现logcat不见了,只有Console等,找了半天没找到,最后还是苦命的发现了,如下.Window ……Show View……Other…会出现如下对话框:选择LogCat后,eclipse就能正常查看LogCat的输出了。

    2022年3月9日
    41
  • 六十四卦详细解释_六十四卦断事

    六十四卦详细解释_六十四卦断事文章目录第1卦 乾为天(乾卦) 刚健中正 上上卦第2卦 坤为地(坤卦) 柔顺伸展 上上卦第3卦 水雷屯(屯卦) 起始维艰 下下卦第4卦 山水蒙(蒙卦) 启蒙奋发 中下卦第5卦 水天需(需卦) 守正待机 中上卦第6卦 天水讼(讼卦) 慎争戒讼 中下卦第7卦 地水师(师卦) 行险而顺 中上卦第8卦 水地比(比卦) 诚信团结 上上卦第9卦 风天小畜(小畜卦) 蓄养待进 下下卦第10卦 天泽履(履卦) 脚…

    2022年8月18日
    10
  • 主数据治理平台培训规程[通俗易懂]

    主数据治理平台培训规程[通俗易懂]主数据管理平台打通各业务链条,消除数据冗余,有助于打通部门、系统间壁垒,实现信息集成与共享。本文主要针对MDM主数据管理平台进行产品培训说明。

    2022年6月21日
    20
  • 皮亚诺曲线_皮亚诺余项o什么意思

    皮亚诺曲线_皮亚诺余项o什么意思概述皮亚诺曲线是一种奇怪的曲线,只要恰当选择函数和由定义的一条连续的参数曲线,当参数t在0,1区间取值时,曲线将遍历单位正方形中所有的点,得到一条充满空间的曲线。就一条联系而又不可导的曲线。目录皮亚诺曲线一些不同观点皮亚诺曲线皮亚诺(Peano)曲线是一条能够填满正方形的曲线。在传统概念中,曲线的数维是1维,正方形是2维。1890年,意大利数学…

    2025年8月15日
    4

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号