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);
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

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

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)
blank

相关推荐

  • 对于长沙互联网发展,一个外来两年Java程序员的所见所感所愿

    惟楚有材,于斯为盛本文有感于2019长沙互联网求职招聘大会,内容比较多,但都是我自己的一些所见、所感和所愿。2019年3月的最后一天,参加2019长沙互联网求职招聘大会,看到了很多的招聘企业,也看到了很多来求职的技术朋友们。金三银四,找工作的黄金时间,也是招聘的旺季!从去年开始,网上一直可以看到说互联网的寒冬来了,一些一线城市互联网公司的程序员们纷纷被“优化”,那么对于长沙来说,一个互联网…

  • ubuntu14.04 安装pycharm

    ubuntu14.04 安装pycharm参考链接:http://itsfoss.com/install-pycharm-ubuntu/怎样在ubuntu14.04上安装pycharmpycharm是一款为python开发而生的IDE。它已经被专家认为是最好的pythonIDE之一。pycharm有社区版和专业版两种。社区版是免费的。但是专业版有更多的功能。我在下面的教程中展示如何安装这两种pycharm。…

  • hikaripool信息_HikariPool源码(四)资源状态[通俗易懂]

    hikaripool信息_HikariPool源码(四)资源状态[通俗易懂]Java极客|作者/铿然一叶这是Java极客的第55篇原创文章1.本章目的了解池资源的状态,以及状态如何变迁,用于池化资源设计参考。2.HikariPool资源核心类回顾HikariPool资源相关的类如下:类说明:类职责HikariPool资源池,客户端资源操作的入口。ConcurrentBag通用的并发包工具。CopyOnWriteArrayList一个列表,用于存储资源,…

  • 用批处理文件阻止win10强制更新

    用批处理文件阻止win10强制更新win10这个商业巨头微软的最新之作,以流氓行径著称,尤其是它的自动更新最受诟病。用户无权选择是否下载和安装更新。你选择不更新,他就在后台下载,下次开机自动安装。何况,这些更新有的没必要,有的装上反而起副作用。我之前就被装上的更新弄坏网卡驱动,重装一次系统;今年3月大范围爆发的更新bug,导致众多用户USB驱动坏掉,又重装一次系统。再后来,我知道了更新的危害,就每天手动删除那些更新安装包,结果有一…

  • activity启动FLAG之FLAG_ACTIVITY_CLEAR_TASK「建议收藏」

    activity启动FLAG之FLAG_ACTIVITY_CLEAR_TASK「建议收藏」随时随地阅读更多技术实战干货,获取项目源码、学习资料,请关注源代码社区公众号(ydmsq666)官方文档解释:IfsetinanIntentpassedtoContext.startActivity(),thisflagwillcauseanyexistingtaskthat…

  • 关于使用LayoutParams清除设置以及DateFormat无法正确转换格式化日期的问题

    关于使用LayoutParams清除设置以及DateFormat无法正确转换格式化日期的问题1、关于LayoutParams清除设置问题RelativeLayout.LayoutParamslp=(LayoutParams)mBtn.getLayoutParams();lp.addRule(RelativeLayout.ALIGN_PARENT_RIGHT,0);//清除上次设置(只有清除上次设置,这次设置才会起效,倘若代码设置过后不需要再次更改布局,则无须清除上次设置)

发表回复

您的电子邮箱地址不会被公开。

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