关注微信公众号:Linux内核拾遗
本文将重点介绍内核线程,涵盖Linux中进程和线程的概念以及它们的区别和各自的优势,同时还将深入探讨内核线程编程技术。
1 什么是内核线程
在介绍内核线程之前,首先需要认识Linux内核中的进程和线程的基本概念。
- 进程:
- 程序的执行实例,有时候也把执行中的程序被称为“任务”。
- 进程是重量级的,进程之间的上下文切换开销大且非常耗时。
- 线程:
- 是共享同一进程地址空间的独立执行流。
- 一方面,线程资源开销较小,上下文切换速度较快,因此线程也被称为轻量级进程。
- 另一方面,由于同一进程内的所有线程共享相同的地址空间,因此线程之间的通信比进程之间的通信更容易且更省时,但需要同步机制来处理并发问题。
由于线程容易导致并发问题,因此需要同步机制来处理,这就是线程管理。
线程是一系列指令,CPU一次只能处理一条指令。为了在并行线程之间切换指令,需要保存执行状态。执行状态的最简单形式是程序计数器和CPU寄存器,前者告诉CPU下一条要执行的指令,后这保存执行参数,例如操作数。
因此,线程管理实际上就是线程之间切换时,完成状态保存、状态恢复以及决定下一个执行的线程等工作。
Linux中有两类线程:
- 用户级线程:内核不感知这类型的线程,所有线程操作由用户线程库维护。该线程库包含创建和销毁线程、在线程之间传递消息和数据、调度线程执行以及保存和恢复线程上下文等的相关代码,并且所有这些操作都在用户空间中进行。
- 内核级线程:内核级线程是内核中运行的轻量级线程,具有独立的控制流,它由操作系统内核管理和执行,因此线程操作在内核代码中实现。
2 内核线程管理函数
内核线程管理函数声明在linux/kthread.h
头文件中,包含创建、启动、停止内核线程等,接下来将逐一进行介绍。
2.1 创建内核线程
函数kthread_create
用于创建一个内核线程:
struct task_struct *kthread_create (int (*threadfn)(void *data),
void *data, const char namefmt[], ...);
内核线程创建之后,需要手动唤醒该线程。线程唤醒后,它将运行threadfn()
函数,参数为data
。
如果一个内核线程是独立线程,即没有人会调用kthread_stop
,那么threadfn()
中可以直接调用do_exit()
退出函数的执行;否则应该在kthread_should_stop()
为真的时候(意味着kthread_stop()
被调用了)退出函数执行。
2.2 启动内核线程
函数wake_up_process
用于唤醒特定的进程,并将其移动到可运行进程集中。
int wake_up_process (struct task_struct *p);
返回1表示进程已被唤醒,返回0表示进程已在运行。
2.3 运行内核线程
Linux内核中还提供了另一个函数,同时完成线程创建和启动操作,即kthread_run()
。
kthread_run (threadfn, data, namefmt, ...);
实际上它就是kthread_create()
和wake_up_process()
的简单包装函数。
2.4 停止内核线程
函数kthread_stop
用于停止由kthread_create
创建的线程。
int kthread_stop (struct task_struct *k);
该函数实际上是让线程k的kthread_should_stop()
调用返回true,然后唤醒该线程并等待其退出。
如果我们期望创建的内核线程是通过kthread_stop()
这种方式来退出的,那么内核线程函数内部不得自行调用do_exit()
退出执行。
如果该函数是在kthread_create()
之后、wake_up_process()
之前调用,这是合法的,此时内核线程将在不调用线程函数的情况下退出。
2.5 其他内核线程函数
2.5.1 kthread_should_stop
前面已经提到过了,它的语义是“此内核线程现在是否应返回”。通常与kthread_stop()
配合使用。
int kthread_should_stop (void);
2.5.2 kthread_bind
用于将刚创建的内核线程绑定到指定的CPU。
void kthread_bind (struct task_struct *k, unsigned int cpu);
3 内核线程代码演示
使用内核线程应该遵循一些常用模式:
- 如果该线程是长时间运行的线程,则需要每次检查
kthread_should_stop()
,因为任何函数都可能调用kthread_stop
。如果任何函数调用了kthread_stop
,那么kthread_should_stop
将返回true,此时必须退出我们的线程函数。 - 否则如果线程函数不是长时间运行的,则让该线程完成其任务并使用
do_exit
自行终止。
下面是完整的内核线程示例代码:
kernel_driver.c
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kdev_t.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/kthread.h>
#include <linux/sched.h>
#include <linux/delay.h>
#include <linux/sysfs.h>
#include <linux/kobject.h>
#include <linux/interrupt.h>
#include <asm/io.h>
#include <linux/err.h>
#include <linux/smp.h>
volatile int my_value = 0;
struct my_node
{
struct list_head list;
int data;
};
static LIST_HEAD(my_list);
static struct workqueue_struct *own_workqueue;
static void static_wq_fn(struct work_struct *work)
{
struct my_node *node = NULL;
pr_info("Static workqueue function called on CPU[%d]\n", smp_processor_id());
node = kmalloc(sizeof(struct my_node), GFP_KERNEL);
node->data = my_value;
INIT_LIST_HEAD(&node->list);
list_add_tail(&node->list, &my_list);
}
static DECLARE_WORK(static_work, static_wq_fn);
static void dynanic_wq_fn(struct work_struct *work)
{
pr_info("Dynamic workqueue function called on CPU[%d]\n", smp_processor_id());
}
static struct work_struct dynamic_work;
#define IRQ_NO 63
static irqreturn_t irq_handler(int irq, void *dev_id)
{
pr_info("Shared IRQ[%d]: Interrupt Occurred\n", irq);
queue_work(own_workqueue, &static_work);
queue_work_on(3, own_workqueue, &dynamic_work);
return IRQ_HANDLED;
}
static char thread_data1[] = "my_thread1";
static char thread_data2[] = "my_thread2";
static struct task_struct *my_thread1 = NULL;
static struct task_struct *my_thread2 = NULL;
static int thread_fn(void *data)
{
char *name = (char *)data;
int i = 0;
while (!kthread_should_stop())
{
pr_info("In My Thread Function [%s] %d\n", name, i++);
msleep(1000);
}
return 0;
}
dev_t dev = 0;
static struct class *dev_class;
static struct cdev my_cdev;
struct kobject *kobj_ref;
static ssize_t sysfs_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
return sprintf(buf, "%d", my_value);
}
static ssize_t sysfs_store(struct kobject *kobj,
struct kobj_attribute *attr, const char *buf, size_t count)
{
sscanf(buf, "%d", &my_value);
return count;
}
struct kobj_attribute my_attr = __ATTR(my_value, 0660, sysfs_show, sysfs_store);
static ssize_t my_read(struct file *filp,
char __user *buf, size_t len, loff_t *off)
{
struct my_node *node;
int i = 0;
list_for_each_entry(node, &my_list, list)
{
pr_info("Node[%d] { data = %d }\n", i++, node->data);
}
pr_info("Total Nodes = %d\n", i);
return 0;
}
static ssize_t my_write(struct file *filp,
const char __user *buf, size_t len, loff_t *off)
{
char __buf[10] = {0};
if (copy_from_user(__buf, buf, len) == 0)
{
pr_info("Write: %s", __buf);
sscanf(__buf, "%d", &my_value);
}
generic_handle_irq(IRQ_NO);
return len;
}
static struct file_operations fops = {
.owner = THIS_MODULE,
.read = my_read,
.write = my_write,
};
static int __init my_driver_init(void)
{
if ((alloc_chrdev_region(&dev, 0, 1, "my_dev")) < 0)
return -1;
cdev_init(&my_cdev, &fops);
my_cdev.owner = THIS_MODULE;
my_cdev.ops = &fops;
if ((cdev_add(&my_cdev, dev, 1)) < 0)
goto r_class;
if (IS_ERR(dev_class = class_create(THIS_MODULE, "my_class")))
goto r_class;
if (IS_ERR(device_create(dev_class, NULL, dev, NULL, "my_device")))
goto r_device;
kobj_ref = kobject_create_and_add("my_sysfs", kernel_kobj);
if (sysfs_create_file(kobj_ref, &my_attr.attr))
goto r_sysfs;
if (request_irq(IRQ_NO, irq_handler, IRQF_SHARED,
"my_device", (void *)(irq_handler)))
goto irq;
INIT_WORK(&dynamic_work, dynanic_wq_fn);
own_workqueue = create_workqueue("own_wq");
if ((my_thread1 = kthread_create(thread_fn, &thread_data1, "My Thread 1")))
wake_up_process(my_thread1);
else
goto r_device;
if (!(my_thread2 = kthread_run(thread_fn, &thread_data2, "My Thread 2")))
goto r_device;
return 0;
irq:
free_irq(IRQ_NO, (void *)(irq_handler));
r_sysfs:
kobject_put(kobj_ref);
sysfs_remove_file(kernel_kobj, &my_attr.attr);
r_device:
device_destroy(dev_class, dev);
class_destroy(dev_class);
r_class:
unregister_chrdev_region(dev, 1);
cdev_del(&my_cdev);
return -1;
}
static void __exit my_driver_exit(void)
{
struct my_node *cur, *next;
if (my_thread1)
kthread_stop(my_thread1);
if (my_thread2)
kthread_stop(my_thread2);
list_for_each_entry_safe(cur, next, &my_list, list)
{
list_del(&cur->list);
kfree(cur);
}
destroy_workqueue(own_workqueue);
free_irq(IRQ_NO, (void *)(irq_handler));
kobject_put(kobj_ref);
sysfs_remove_file(kernel_kobj, &my_attr.attr);
device_destroy(dev_class, dev);
class_destroy(dev_class);
cdev_del(&my_cdev);
unregister_chrdev_region(dev, 1);
}
module_init(my_driver_init);
module_exit(my_driver_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("feifei <feifei@gmail.com>");
MODULE_DESCRIPTION("A simple device driver");
MODULE_VERSION("1.16");
代码编译运行,结果如下图所示:
关注微信公众号:Linux内核拾遗