13 Linux 内核线程
Linux内核线程(kernel thread)是内核中用于执行任务的独立执行单元。它们与用户空间的线程不同,完全在内核空间中运行,具有更高的权限和更低的延迟。内核线程的主要作用包括:
1). 处理异步任务:内核线程可以处理需要异步执行的任务,例如定时器、工作队列等。
2). 资源管理:内核线程可以用于管理系统资源,如内存管理、I/O操作等。
3). 提高并发性:通过内核线程,可以更好地利用多核处理器,提高系统的并发性能。
13.1 内核Thread API
内核线程的创建和管理主要通过以下几个函数实现:
• kthread_create:创建一个内核线程,但不立即启动。
• wake_up_process:启动一个已经创建的内核线程。
• kthread_stop:停止一个内核线程。
13.2 驱动里创建thread
创建一个demo_thread.c的文件, 内容如下:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/kthread.h>
#include <linux/delay.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/sysfs.h>
static struct task_struct *thread_st;
static struct kobject *td_kobj;
// thread handle
static int thread_fn(void *unused)
{
while (!kthread_should_stop())
{
printk(KERN_INFO "Kernel Thread Running\n");
ssleep(5); // sleep 5 seconds, just for demo thread using.
}
printk(KERN_INFO "Kernel Thread Stopping\n");
return 0;
}
// start thread
static ssize_t start_thread_by_sysfs(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count)
{
if (!thread_st)
{
printk(KERN_INFO "Creating Kernel Thread\n");
thread_st = kthread_create(thread_fn, NULL, "mythread");
if (thread_st)
{
wake_up_process(thread_st);
}
else
{
printk(KERN_ERR "Failed to create thread\n");
}
}
return count;
}
// stop thread
static ssize_t stop_thread_by_sysfs(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count)
{
if (thread_st)
{
printk(KERN_INFO "Stopping Kernel Thread\n");
kthread_stop(thread_st);
thread_st = NULL;
}
return count;
}
// define sysfs attribute.
static struct kobj_attribute start_attr = __ATTR(start, 0220, NULL, start_thread_by_sysfs);
static struct kobj_attribute stop_attr = __ATTR(stop, 0220, NULL, stop_thread_by_sysfs);
// init driver
static int __init init_thread(void)
{
int retval;
td_kobj = kobject_create_and_add("demo_thread", kernel_kobj);
if (!td_kobj)
return -ENOMEM;
retval = sysfs_create_file(td_kobj, &start_attr.attr);
if (retval)
kobject_put(td_kobj);
retval = sysfs_create_file(td_kobj, &stop_attr.attr);
if (retval)
kobject_put(td_kobj);
return retval;
}
// exit driver
static void __exit cleanup_thread(void)
{
printk(KERN_INFO "Cleaning Up\n");
if (thread_st)
{
kthread_stop(thread_st);
}
kobject_put(td_kobj);
}
module_init(init_thread);
module_exit(cleanup_thread);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Linx Zhang");
MODULE_DESCRIPTION("A Simple Kernel Thread Example with Sysfs Interface");
同样, 这里也通过sysfs接口来启动和停止线程。
13.3 Makefile
obj-m += demo_thread.o
all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
13.4 编译及加载
编译驱动, 如下:
neardi@LPA3588:~/drivers/thread$ make
make -C /lib/modules/5.10.110/build M=/home/neardi/drivers/thread modules
make[1]: Entering directory '/usr/src/linux-headers-5.10.110'
CC [M] /home/neardi/drivers/thread/demo_thread.o
MODPOST /home/neardi/drivers/thread/Module.symvers
LD [M] /home/neardi/drivers/thread/demo_thread.ko
make[1]: Leaving directory '/usr/src/linux-headers-5.10.110'
加载驱动, 如下:
neardi@LPA3588:~/drivers/thread$ sudo insmod demo_thread.ko
13.5 测试线程
可以通过如下命令来测试线程运行情况, 如下:
root@LPA3588:~# echo 1 > /sys/kernel/demo_thread/start
root@LPA3588:~# dmesg
[17375.050579] Cleaning Up
[17509.053595] Creating Kernel Thread
[17509.054424] Kernel Thread Running
[17514.229022] Kernel Thread Running
[17519.349217] Kernel Thread Running
root@LPA3588:~# echo 1 > /sys/kernel/demo_thread/stop
root@LPA3588:~# dmesg
[17375.050579] Cleaning Up
[17509.053595] Creating Kernel Thread
[17509.054424] Kernel Thread Running
[17514.229022] Kernel Thread Running
[17519.349217] Kernel Thread Running
[17524.469383] Kernel Thread Running
[17529.589373] Kernel Thread Running
[17534.709686] Kernel Thread Running
[17534.860426] Stopping Kernel Thread
[17539.829651] Kernel Thread Stopping
Log显示线程运行成功。
13.6 线程同步
当多个线程同时访问和修改共享资源时,可能会导致数据竞争(race condition),因此需要线程同步机制,以提高程序的可靠性和稳定性,确保程序在多线程环境下能够正确运行。
Linux内核提供了多种资源同步方法, 以下是一些常用的同步方法:
1). 互斥锁(Mutex)
互斥锁用于保护临界区,确保同一时间只有一个线程可以访问共享资源。常用的API包括mutex_lock和mutex_unlock。
2). 自旋锁(Spinlock)
自旋锁在等待锁释放时会不断循环检查,适用于短时间的锁定操作。常用的API包括spin_lock和spin_unlock。
3). 读写锁(Read-Write Lock)
读写锁允许多个读者同时访问,但写者独占访问。常用的API包括rwlock_read_lock和rwlock_write_lock。
4). 信号量(Semaphore)
信号量用于控制对资源的访问数量,适用于需要限制并发访问数量的场景。常用的API包括down和up。
5). 原子操作(Atomic Operations)
原子操作是不可分割的操作,确保在多处理器环境中操作的原子性。常用的API包括atomic_inc和atomic_dec。
6). 屏障(Barrier)
屏障用于确保某些操作在其他操作之前完成,常用于内存屏障(memory barrier)和编译器屏障(compiler barrier)。
7). 完成变量(Completion Variables)
完成变量用于等待某个事件完成,常用的API包括init_completion和wait_for_completion。
13.7 Kernel线程同步实例
更改demo_thread.c文件, 如下:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/kthread.h>
#include <linux/mutex.h>
#include <linux/spinlock.h>
#include <linux/delay.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/sysfs.h>
static struct task_struct *thread1;
static struct task_struct *thread2;
static struct kobject *td_kobj;
static DEFINE_MUTEX(demo_mutex);
static DEFINE_SPINLOCK(demo_spinlock);
static int shared_resource_a = 0;
static int shared_resource_b = 100;
static bool stop_threads = false;
// thread1
static int thread_fn1(void *data)
{
int i;
/**
* access share resource a
*/
for (i = 0; i < 5; i++)
{
if (stop_threads)
{
break;
}
mutex_lock(&demo_mutex);
printk(KERN_INFO "Thread 1: Acquired mutex lock\n");
shared_resource_a++;
printk(KERN_INFO "Thread 1: Shared resource value: %d\n", shared_resource_a);
mutex_unlock(&demo_mutex);
ssleep(1);
}
/**
* access share resource b
*/
for (i = 0; i < 5; i++)
{
if (stop_threads)
{
break;
}
spin_lock(&demo_spinlock);
printk(KERN_INFO "Thread 1: Acquired spinlock\n");
shared_resource_b--;
printk(KERN_INFO "Thread 1: Shared resource value: %d\n", shared_resource_b);
spin_unlock(&demo_spinlock);
ssleep(1);
}
thread1 = NULL;
return 0;
}
// thread2
static int thread_fn2(void *data)
{
int i;
/**
* access share resource a
*/
for (i = 0; i < 5; i++)
{
if (stop_threads)
{
break;
}
mutex_lock(&demo_mutex);
printk(KERN_INFO "Thread 2: Acquired mutex lock\n");
shared_resource_a++;
printk(KERN_INFO "Thread 2: Shared resource value: %d\n", shared_resource_a);
mutex_unlock(&demo_mutex);
ssleep(1);
}
/**
* access share resource b
*/
for (i = 0; i < 5; i++)
{
if (stop_threads)
{
break;
}
spin_lock(&demo_spinlock);
printk(KERN_INFO "Thread 2: Acquired spinlock\n");
shared_resource_b--;
printk(KERN_INFO "Thread 2: Shared resource value: %d\n", shared_resource_b);
spin_unlock(&demo_spinlock);
ssleep(1);
}
thread2 = NULL;
return 0;
}
// start thread
static ssize_t start_thread_by_sysfs(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count)
{
stop_threads = false;
/**
* create thread1
*/
if (!thread1)
{
printk(KERN_INFO "Creating Kernel Thread1\n");
thread1 = kthread_create(thread_fn1, NULL, "thread_1");
if (thread1)
{
wake_up_process(thread1);
}
else
{
printk(KERN_ERR "Failed to create thread1\n");
}
}
/**
* create thread1
*/
if (!thread2)
{
printk(KERN_INFO "Creating Kernel Thread2\n");
thread2 = kthread_create(thread_fn2, NULL, "thread2");
if (thread2)
{
wake_up_process(thread2);
}
else
{
printk(KERN_ERR "Failed to create thread2\n");
}
}
return count;
}
// stop thread
static ssize_t stop_thread_by_sysfs(struct kobject *kobj, struct kobj_attribute *attr, const char *buf, size_t count)
{
stop_threads = true;
/**
* stop thread1
*/
if (thread1)
{
printk(KERN_INFO "Stopping Kernel Thread1\n");
kthread_stop(thread1);
thread1 = NULL;
}
/**
* stop thread2
*/
if (thread2)
{
printk(KERN_INFO "Stopping Kernel Thread2\n");
kthread_stop(thread2);
thread2 = NULL;
}
return count;
}
// define sysfs attribute.
static struct kobj_attribute start_attr = __ATTR(start, 0220, NULL, start_thread_by_sysfs);
static struct kobj_attribute stop_attr = __ATTR(stop, 0220, NULL, stop_thread_by_sysfs);
// init driver
static int __init init_thread(void)
{
int retval;
td_kobj = kobject_create_and_add("demo_thread", kernel_kobj);
if (!td_kobj)
return -ENOMEM;
retval = sysfs_create_file(td_kobj, &start_attr.attr);
if (retval)
{
kobject_put(td_kobj);
return retval;
}
retval = sysfs_create_file(td_kobj, &stop_attr.attr);
if (retval)
kobject_put(td_kobj);
return retval;
}
// exit driver
static void __exit cleanup_thread(void)
{
printk(KERN_INFO "Cleaning Up\n");
if (thread1)
{
kthread_stop(thread1);
}
if (thread2)
{
kthread_stop(thread2);
}
kobject_put(td_kobj);
}
module_init(init_thread);
module_exit(cleanup_thread);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Linx Zhang");
MODULE_DESCRIPTION("A Simple Kernel Thread Example with Sysfs Interface");
这里有2个全局资源, 如下:
static int shared_resource_a = 0;
static int shared_resource_b = 100;
在多线程应用场合, 分别使用mutex和spinlock方法来同步资源, 首先是初始化, 如下:
static DEFINE_MUTEX(demo_mutex);
static DEFINE_SPINLOCK(demo_spinlock);
之后在线程中使用, 如下:
/**
* access share resource a
*/
for (i = 0; i < 5; i++)
{
if (stop_threads)
{
break;
}
mutex_lock(&demo_mutex);
printk(KERN_INFO "Thread 1: Acquired mutex lock\n");
shared_resource_a++;
printk(KERN_INFO "Thread 1: Shared resource value: %d\n", shared_resource_a);
mutex_unlock(&demo_mutex);
ssleep(1);
}
/**
* access share resource b
*/
for (i = 0; i < 5; i++)
{
if (stop_threads)
{
break;
}
spin_lock(&demo_spinlock);
printk(KERN_INFO "Thread 1: Acquired spinlock\n");
shared_resource_b--;
printk(KERN_INFO "Thread 1: Shared resource value: %d\n", shared_resource_b);
spin_unlock(&demo_spinlock);
ssleep(1);
}
13.8 测试多线程
编译Makefile文件不用更改, 直接编译即可。
使用sudo insmod demo_thread.ko加载此thread驱动, 之后如下命令测试:
neardi@LPA3588:~/drivers/thread$ sudo insmod demo_thread.ko
neardi@LPA3588:~/drivers/thread$ sudo -i
root@LPA3588:~# echo 1 > /sys/kernel/demo_thread/start
root@LPA3588:~# dmesg
[ 1316.937486] Creating Kernel Thread1
[ 1316.938205] Creating Kernel Thread2
[ 1316.938338] Thread 1: Acquired mutex lock
[ 1316.938351] Thread 1: Shared resource value: 1
[ 1316.939890] Thread 2: Acquired mutex lock
[ 1316.939904] Thread 2: Shared resource value: 2
[ 1317.947900] Thread 1: Acquired mutex lock
[ 1317.947913] Thread 1: Shared resource value: 3
[ 1317.948036] Thread 2: Acquired mutex lock
[ 1317.948049] Thread 2: Shared resource value: 4
[ 1318.961212] Thread 1: Acquired mutex lock
[ 1318.961226] Thread 1: Shared resource value: 5
[ 1318.961306] Thread 2: Acquired mutex lock
[ 1318.961322] Thread 2: Shared resource value: 6
root@LPA3588:~# echo 1 > /sys/kernel/demo_thread/stop
root@LPA3588:~# dmesg
[ 1316.937486] Creating Kernel Thread1
[ 1316.938205] Creating Kernel Thread2
[ 1316.938338] Thread 1: Acquired mutex lock
[ 1316.938351] Thread 1: Shared resource value: 1
[ 1316.939890] Thread 2: Acquired mutex lock
[ 1316.939904] Thread 2: Shared resource value: 2
[ 1317.947900] Thread 1: Acquired mutex lock
[ 1317.947913] Thread 1: Shared resource value: 3
[ 1317.948036] Thread 2: Acquired mutex lock
[ 1317.948049] Thread 2: Shared resource value: 4
[ 1318.961212] Thread 1: Acquired mutex lock
[ 1318.961226] Thread 1: Shared resource value: 5
[ 1318.961306] Thread 2: Acquired mutex lock
[ 1318.961322] Thread 2: Shared resource value: 6
[ 1319.974489] Thread 1: Acquired mutex lock
[ 1319.974505] Thread 1: Shared resource value: 7
[ 1319.974590] Thread 2: Acquired mutex lock
[ 1319.974603] Thread 2: Shared resource value: 8
[ 1320.987913] Thread 1: Acquired mutex lock
[ 1320.987929] Thread 1: Shared resource value: 9
[ 1320.987980] Thread 2: Acquired mutex lock
[ 1320.987996] Thread 2: Shared resource value: 10
[ 1322.001184] Thread 1: Acquired spinlock
[ 1322.001199] Thread 1: Shared resource value: 99
[ 1322.001281] Thread 2: Acquired spinlock
[ 1322.001294] Thread 2: Shared resource value: 98
[ 1323.014481] Thread 1: Acquired spinlock
[ 1323.014495] Thread 1: Shared resource value: 97
[ 1323.014589] Thread 2: Acquired spinlock
[ 1323.014602] Thread 2: Shared resource value: 96
[ 1324.027848] Thread 1: Acquired spinlock
[ 1324.027862] Thread 1: Shared resource value: 95
[ 1324.027913] Thread 2: Acquired spinlock
[ 1324.027929] Thread 2: Shared resource value: 94
[ 1324.918362] Stopping Kernel Thread1
[ 1325.034593] Stopping Kernel Thread2
上面的测试结果显示, 多线程资源同步成功。