linux模块编程(二)——运行不息的内核线程kthread

         上节中,我们成功地编译运行了一个linux模块。可惜的是,它只有两个函数,hello_init在模块加载时调用,hello_exit 在模块卸载时调用。这样下去,模块纵使有天大的本事,也只能压缩在这两个函数中。为了避免这种悲剧发生,本节就来学习一种让模块在加载后能一直运行下去的方法——内核线程。

      要创建一个内核线程有许多种方法,我们这里要学的是最简单的一种。打开include/linux/kthread.h,你就看到了它全部的API,一共三个函数。

struct task_struct kthread_run(int (*threadfn)(void *data),
	void *data, const char namefmt[],...);
int kthread_stop(struct task_struct *k);
int kthread_should_stop(void);

      kthread_run()负责内核线程的创建,参数包括入口函数threadfn,参数data,线程名称namefmt。可以看到线程的名字可以是类似sprintf方式组成的字符串。如果实际看到kthread.h文件,就会发现kthread_run实际是一个宏定义,它由kthread_create()和wake_up_process()两部分组成,这样的好处是用kthread_run()创建的线程可以直接运行,使用方便。

      kthread_stop()负责结束创建的线程,参数是创建时返回的task_struct指针。kthread设置标志should_stop,并等待线程主动结束,返回线程的返回值。线程可能在kthread_stop()调用前就结束。(经过实际验证,如果线程在kthread_stop()调用之前就结束,之后kthread_stop()再调用会发生可怕地事情—调用kthread_stop()的进程crash!!之所以如此,是由于kthread实现上的弊端,之后会专门写文章讲到)

      kthread_should_stop()返回should_stop标志。它用于创建的线程检查结束标志,并决定是否退出。线程完全可以在完成自己的工作后主动结束,不需等待should_stop标志。

 

    下面就来尝试一下运行不息的内核线程吧。

1、把上节建立的hello子目录,复制为新的kthread子目录。

 

2、修改hello.c,使其内容如下。

 

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kthread.h>

MODULE_LICENSE("Dual BSD/GPL");

static struct task_struct *tsk;

static int thread_function(void *data)
{
	int time_count = 0;
	do {
		printk(KERN_INFO "thread_function: %d times", ++time_count);
		msleep(1000);
	}while(!kthread_should_stop() && time_count<=30);
	return time_count;
}

static int hello_init(void)
{
	printk(KERN_INFO "Hello, world!\n");

	tsk = kthread_run(thread_function, NULL, "mythread%d", 1);
	if (IS_ERR(tsk)) {
		printk(KERN_INFO "create kthread failed!\n");
	}
	else {
		printk(KERN_INFO "create ktrhead ok!\n");
	}
	return 0;
}

static void hello_exit(void)
{
	printk(KERN_INFO "Hello, exit!\n");
	if (!IS_ERR(tsk)){
		int ret = kthread_stop(tsk);
		printk(KERN_INFO "thread function has run %ds\n", ret);
	}
}

module_init(hello_init);
module_exit(hello_exit);


       为了不让创建的内核线程一直运行浪费CPU,代码中采用周期性延迟的方式,每次循环用msleep(1000)延迟1s。为了防止线程一直运行下去,代码中使用了两个结束条件:一个是模块要求线程结束,一个是打印满一定次数,后者是为了防止printk输出信息太多。最后在hello_exit中结束线程,并打印线程运行的时间。

      这里要注意的是kthread_run的返回值tsk。不能用tsk是否为NULL进行检查,而要用IS_ERR()宏定义检查,这是因为返回的是错误码,大致从0xfffff000~0xffffffff。

 

3、编译运行模块,步骤参照前例。在运行过程中使用ps -e命令,可以看到有名字位mythread1的内核线程在运行。

 

 

      经过本节,我们学习了内核线程的创建使用方法,现在要创建一大堆的线程在内核中已经易如反掌。你会逐渐相信,我们模块的拓展空间是无限的。

 

附注:

      我们的重点在模块编程,不断学习内核API的使用。但如果能知其然,而知其所以然就更好了。所以有了文章后的附注部分。在附注部分,我们会尽量解释内核API的实现原理,对相关linux内核代码做简单的分析,以帮助大家学习理解相关的代码。分析的代码包含在linux-2.6.32中,但这些代码在相近版本中都变化不大。作者水平有限,请大家见谅。

      kthread的实现在kernel/kthread.c中,头文件是include/linux/kthread.h。内核中一直运行一个线程kthreadd,它运行kthread.c中的kthreadd函数。在kthreadd()中,不断检查一个kthread_create_list链表。kthread_create_list中的每个节点都是一个创建内核线程的请求,kthreadd()发现链表不为空,就将其第一个节点退出链表,并调用create_kthread()创建相应的线程。create_kthread()则进一步调用更深层的kernel_thread()创建线程,入口函数设在kthread()中。

      外界调用kthread_run创建运行线程。kthread_run是个宏定义,首先调用kthread_create()创建线程,如果创建成功,再调用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_stop()删除线程。kthread_stop首先设置结束标志should_stop,然后调用wake_for_completion(&kthread->exited)上,这个其实是新线程task_struct上的vfork_done,会在线程结束调用do_exit()时设置。

 

 

  • 7
    点赞
  • 41
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值