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

二、创建线程方式一:kernel_thread

2.1 kernel_thread()声明在include/linux/sched.h

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

3.3 实际应用kernel\drivers\net\wireless\rockchip_wlan\cywdhd\bcmdhd\wl_iw.c

三、创建线程方式二:kthread_create

  3.1 kernel\include\linux\kthread.h

3.2 函数代码

/**
 * kthread_create_on_node - create a kthread.
 * @threadfn: the function to run until signal_pending(current).
 * @data: data ptr for @threadfn.
 * @node: task and thread structures for the thread are allocated on this node
 * @namefmt: printf-style name for the thread.
 *
 * Description: This helper function creates and names a kernel
 * thread.  The thread will be stopped: use wake_up_process() to start
 * it.  See also kthread_run().  The new thread has SCHED_NORMAL policy and
 * is affine to all CPUs.
 *
 * If thread is going to be bound on a particular cpu, give its node
 * in @node, to get NUMA affinity for kthread stack, or else give NUMA_NO_NODE.
 * When woken, the thread will run @threadfn() with @data as its
 * argument. @threadfn() can either call do_exit() directly if it is a
 * standalone thread for which no one will call kthread_stop(), or
 * return when 'kthread_should_stop()' is true (which means
 * kthread_stop() has been called).  The return value should be zero
* or a negative error number; it will be passed to kthread_stop().
 *
 * Returns a task_struct or ERR_PTR(-ENOMEM) or ERR_PTR(-EINTR).
 */
struct task_struct *kthread_create_on_node(int (*threadfn)(void *data),
					   void *data, int node,
					   const char namefmt[],
					   ...)
{
	DECLARE_COMPLETION_ONSTACK(done);
	struct task_struct *task;
	struct kthread_create_info *create = kmalloc(sizeof(*create),
						     GFP_KERNEL);

	if (!create)
		return ERR_PTR(-ENOMEM);
	create->threadfn = threadfn;
	create->data = data;
	create->node = node;
	create->done = &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 in killable state, for I might be chosen by
	 * the OOM killer while kthreadd is trying to allocate memory for
	 * new kernel thread.
	 */
	if (unlikely(wait_for_completion_killable(&done))) {
		/*
		 * If I was SIGKILLed before kthreadd (or new kernel thread)
		 * calls complete(), leave the cleanup of this structure to
		 * that thread.
		 */
		if (xchg(&create->done, NULL))
			return ERR_PTR(-EINTR);
		/*
		 * kthreadd (or new kernel thread) will call complete()
		 * shortly.
		 */
		wait_for_completion(&done);
	}
	task = create->result;
	if (!IS_ERR(task)) {
		static const struct sched_param param = { .sched_priority = 0 };
		va_list args;

		va_start(args, namefmt);
		vsnprintf(task->comm, sizeof(task->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(task, SCHED_NORMAL, &param);
		set_cpus_allowed_ptr(task, cpu_all_mask);
	}
	kfree(create);
	return task;
}

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

3.4 kthread_create创建线程流程

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

     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死循环重新运行

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

     kthread_create并没有创建线程,他只是将进程结构体kthread_create_info添加到kthread_create_list,然后唤醒2号进程,线程的创建工作是在kthreadd中调用do_fork去创建的。

3.5 实例如下:

#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.6  kthread_create()创建后,线程没有立即运行,需要将返回的值,即线程指针(struct task_struct *),作为参数传入到wake_up_process()唤起线程运行.

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

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

四、创建线程方式三:kthread_run

4.1 kernel\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;								   \
})

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

4.3 kthread_run()创建的线程,理解已经有wake_up_process,立刻运行。

4.4 实例

#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("tanghanyue");
MODULE_LICENSE("GPL");

#define CLONE_KERNEL    (CLONE_FS | CLONE_FILES | CLONE_SIGHAND)

int thy_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(thy_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);

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

五、一般的实际应用1,在串口调试信息的使用。

static int console_thread(void *data)
{
	struct platform_device *pdev = data;
	char *buf = console_buf;
	unsigned int len;

	while (1) {
		unsigned int dropped;

		set_current_state(TASK_INTERRUPTIBLE);
		if (kfifo_is_empty(&fifo))
			schedule();
		if (kthread_should_stop())
			break;
		set_current_state(TASK_RUNNING);
		while (!console_thread_stop) {
			len = kfifo_out(&fifo, buf, LINE_MAX);
			if (!len)
				break;
			console_put(pdev, buf, len);
		}
		dropped = console_dropped_messages;
		if (dropped && !console_thread_stop) {
			console_dropped_messages = 0;
			smp_wmb();
			len = snprintf(buf, LINE_MAX,
				       "** %u console messages dropped **\n",
				       dropped);
			console_put(pdev, buf, len);
		}
		if (!console_thread_stop)
			console_flush(pdev);
	}

	return 0;
}


#ifdef CONFIG_RK_CONSOLE_THREAD
	t->console_task = kthread_run(console_thread, pdev, "kconsole");
	if (!IS_ERR(t->console_task))
		t->pdata.console_write = console_write;
#endif


六、实例二,模拟pwm

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

#include <linux/platform_device.h>
//API for libgpio
#include <linux/gpio.h>
//API for malloc
#include <linux/slab.h>
//API for device tree
#include <linux/of_platform.h>
#include <linux/of_gpio.h>
#include <linux/of_device.h>
//API for thread 
#include <linux/kthread.h>
#include <linux/delay.h>
#include <linux/mutex.h>
#include <linux/pwm.h>
#include <drm/drmP.h>

static struct task_struct *thread_body;
struct gpio_demo_priv{
	int count;
	int gpio[0]; 
	struct mutex mtx;
	int mode;
};

struct gpio_demo_priv *priv;
static long rk3288_giada_gpio_pwm=255;

static ssize_t mipibrightness_show(struct device *dev,
		struct device_attribute *attr, char *buf)
{
	int ret;
	struct pwm_device * pwm1 = NULL;
	pwm1 = pwm_request(1, "backlight-pwm");
	if (IS_ERR(pwm1)) {
		ret = PTR_ERR(pwm1);
		if (ret != -EPROBE_DEFER)
			printk( "unable to request PWM1\n");
		
		printk( "Unable to request PWM1\n");
		    return sprintf(buf, "unable to request PWM1\n");
	}
	pwm_config(pwm1, 500000, 1000000);
	pwm_enable(pwm1);
	

	return sprintf(buf, "%ld\n", rk3288_giada_gpio_pwm);
}

static ssize_t mipibrightness_store(struct device *dev,
		struct device_attribute *attr, const char *buf, size_t count)
{
	int rc;
	
	struct pwm_device * pwm2 = NULL;
	pwm2 = pwm_request(2, "mipibacklight-pwm");
	pwm_config(pwm2, 500000, 1000000);
	pwm_enable(pwm2);
	
	//unsigned long brightness;
	rc = kstrtoul(buf, 0, &rk3288_giada_gpio_pwm);
	return rc ? rc : count;
}
static DEVICE_ATTR(mipibrightness, S_IWUSR | S_IRUSR, mipibrightness_show, mipibrightness_store);


static struct attribute *bl_device_attrs[] = {
	&dev_attr_mipibrightness.attr,
	NULL,
};

static const struct attribute_group bl_attr_group = {
	.attrs = bl_device_attrs,
};


static int thread_func(void *data) {	

	//int  count;
	while (1){
		//count++;
		/*
		mutex_lock(&priv->mtx);
		for ( i = 0; i < priv->count; i++){
			//gpio_set_value(priv->gpio[i], count%2);
		}
		mutex_unlock(&priv->mtx);
		*/
		
		//printk(KERN_INFO "thread count %d priv->count=%d  priv->gpio[i]=%d\n", count,priv->count,priv->gpio[0]);
		//if(count%rk3288_giada_gpio_pwm==0)
		gpio_set_value(priv->gpio[0], 1);
	    ndelay(20000*rk3288_giada_gpio_pwm/255);
	    if(rk3288_giada_gpio_pwm==255)
		   continue;
		gpio_set_value(priv->gpio[0], 0);
	    ndelay(20000*(255-rk3288_giada_gpio_pwm)/255);
	}
	return 0;
}

static int gpio_demo_probe(struct platform_device *pdev){

	int ret,i;
	struct device *dev = &pdev->dev;
	struct device_node *node = dev->of_node;

	if (!node)
		return -EINVAL;
		
	ret = of_gpio_count(node);
	if (ret == 0){
		return -EINVAL;
	}
	
	priv = devm_kzalloc(dev, sizeof(*priv) + sizeof(int) * ret, GFP_KERNEL);
	
	if (!priv){
		return -ENOMEM;
	}
	
	priv->count = ret;
	mutex_init(&priv->mtx);
	for (i = 0; i < priv->count; i++) {
		unsigned int gpio;
		gpio = of_get_gpio(node, i);
		if (gpio < 0) {
			dev_warn(dev, "Unable to get gpio #%d\n", i);
			continue;		
		}
		ret = devm_gpio_request_one(dev, gpio, GPIOF_DIR_OUT, pdev->name);
		priv->gpio[i] = gpio;
		if (ret < 0) {

			dev_warn(dev, "Unable to re quest GPIO %d: %d\n",
                      gpio, ret);
			continue;
		}
		printk(KERN_INFO "success request gpio=%d priv->gpio[i]=%d\n",gpio,priv->gpio[i]);
		
		gpio_direction_output(gpio, 1); //设置输出的电平
		
	}

	ret = sysfs_create_group(&dev->kobj, &bl_attr_group);
	if (ret < 0)
		dev_warn(dev, "attr group create failed\n");
    else
		dev_info(dev, "attr group create success!\n");


	
	platform_set_drvdata(pdev,priv);
    printk("gpio_demo_probe kthread_create\r\n ");
	thread_body = kthread_create(thread_func, NULL, "thread_pwm");
    if((thread_body))
    {
        wake_up_process(thread_body);
    }

	return 0;
}

static struct of_device_id gpio_demo_of_match[] = {
	{ .compatible = "giada-gpio-pwm"},
	{},
}

MODULE_DEVICE_TABLE(of,gpio_demo_of_match);

static struct platform_driver gpio_demo_driver = {
	.probe = gpio_demo_probe,
	.driver = {
		.name = "gpio-demo-device",
		.owner = THIS_MODULE,
		.of_match_table = of_match_ptr(gpio_demo_of_match),
	}
};

static int __init gpio_demo_init(void){
	
	return  platform_driver_register(&gpio_demo_driver);
}

static void __exit gpio_demo_exit(void){
	platform_driver_unregister(&gpio_demo_driver);
}

late_initcall(gpio_demo_init);
module_exit(gpio_demo_exit);

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Gpio demo Driver");
MODULE_ALIAS("platform:gpio-demo");


七、用ps 或者top命令查看是否已经创建线程

八、有价值的参考文章

https://www.codenong.com/17795739/

Linux内核线程_帅的没朋友~的博客-CSDN博客

运行不息的内核线程kthread_kthread_stop卡死_angle_birds的博客-CSDN博客

linux内核线程(kthread_run) - 代码先锋网

Linux 内核线程kthread_河边一枝花的博客-CSDN博客

Logo

更多推荐