内核任务是指在内核态执行的任务,具体包括内核线程、系统调用、中断处理程序、下半部任务等几类。
一、内核任务及其并发关系
在下面实例中,涉及如下三种内核任务,分别是系统调用、内核线程和定时器任务队列。
1、系统调用:是用户程序通过门机制来进入内核执行的内核例程,它运行在内核态,处于进程上下文中,可以认为是代表用户进程的内核任务,因此具有用户态任务的特性。
2、内核线程:内核线程可以理解成在内核中运行的特殊进程,它有自己的进程上下文,所以同样被进程调度程序调度,也可以睡眠。不同之处就在于内核线程运行于内核空间,可访问内核数据,运行期间不能被抢占。
3、定时器任务队列:它属于下半部,在每次产生时钟节拍时得到处理。
上述三种内核任务存在如下竞争关系:系统调用和内核线程可能和各种内核任务并发执行,除了中断可以抢占它、产生并发外,它们还有可能自发地主动睡眠,放弃处理器,从而其他任务被重新调度,所以系统调用和内核线程除与定时器任务队列发生竞争,也会与其他系统调用和内核线程发生竞争。
二、问题描述
假设存在一个内核共享资源——链表(mine),有多个内核任务并发访问链表:200个内核线程sharelist向链表中加入新结点;内核定时器qt_task定时删除结点;系统调用share_exit销毁链表。这三种内核任务并发执行时,有可能破坏链表数据的完整性,所以必须对链表进行同步访问保护,以保证数据的一致性。
三、实现机制
1、驱动文件代码
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/workqueue.h>
#include <linux/kthread.h>
#include <linux/sched.h>
#include <linux/err.h>
#include <linux/timer.h>
#include <linux/slab.h>
#include <linux/list.h>
#include <linux/semaphore.h>
#define NTHREADS 200 /* 线程数 */
typedef struct my_struct {
struct list_head list;
int id;
int pid;
}my_struct_t;
static struct work_struct queue; /* 定义工作队列 */
static struct task_struct *sharelist_task = NULL;
static struct timer_list timer; /* 定时器队列 */
static struct semaphore sem; /* 内核线程进行同步的信号量 */
static spinlock_t lock; /* 保护对链表的操作 */
static LIST_HEAD(mine); /* sharelist头 */
static unsigned int list_len = 0;
static atomic_t my_count = ATOMIC_INIT(0); /* 以原子方式进行追加 */
static long count = 0; /* 行计数器,每行打印4个信息 */
static void qt_task(unsigned long data);
static void kthread_launcher(struct work_struct *q); /* 创建内核线程 */
static void start_kthread(void); /* 调度内核线程 */
static int sharelist(void *data); /* 从共享链表增删结点的线程 */
/**
*
* 该函数通过kthread_create方法创建内核线程sharelist
*
*/
static void kthread_launcher(struct work_struct *q)
{
sharelist_task = kthread_create(sharelist, NULL, "sharelist_task");/* 创建内核线程sharelist */
if(IS_ERR(sharelist_task)){
printk(KERN_ERR "Unable to create sharelist thread.\n");
sharelist_task = NULL;
return;
}
up(&sem);
}
/**
*
* 该函数负责调度内核线程
* 创建内核线程kthread_launcher的任务挂载工作队列上,也就是
* 说该任务受内核中默认的工作者线程events调度。
* 备注:使用信号量同步机制保证了串行地创建内核线程,虽然串行
* 并非必需。
*/
static void start_kthread(void)
{
down(&sem);
schedule_work(&queue); /* 调度工作队列 */
}
/**
*
* 对共享链表操作的内核线程
* 注意:为防止定时器任务队列抢占执行时造成链表的数据不一致,需要
* 在操作链表期间进行同步保护。
*/
static int sharelist(void *data)
{
struct my_struct *p;
if(count++ % 4 == 0)
printk("\n");
spin_lock(&lock); /* 添加锁,保护共享资源 */
if(list_len < 100) {
p = kzalloc(sizeof(struct my_struct), GFP_KERNEL);
if(!p)
return -ENOMEM;
p->id = atomic_read(&my_count); /* 原子变量操作 */
atomic_inc(&my_count);
p->pid = current->pid;
list_add(&p->list, &mine); /* 向队列中添加新结点 */
list_len++;
printk(KERN_INFO "thread add: %-5d\t", p->id);
} else { /* 队列超过定长则删除结点 */
struct my_struct *my = NULL;
my = list_entry(mine.prev, struct my_struct, list);
list_del(mine.prev); /* 从队列尾部删除结点 */
list_len--;
printk(KERN_INFO "thread del: %-5d\t", my->id);
kfree(my);
}
spin_unlock(&lock);
return 0;
}
/**
*
* 删除结点的定时器任务
*
*/
static void qt_task(unsigned long data)
{
if(!list_empty(&mine)) {
struct my_struct *my = NULL;
if(count++ % 4 == 0)
printk("\n");
my = list_entry(mine.next, struct my_struct, list); /* 取下一个结点 */
list_del(mine.next); /* 删除结点 */
list_len--;
printk(KERN_INFO "timer del: %-5d\t", my->id);
kfree(my);
}
mod_timer(&timer, jiffies + 1); /* 修改定时器时间:从现在开始1个节拍 */
}
static int __init share_init(void)
{
int i;
printk(KERN_INFO "share_init.\n");
spin_lock_init(&lock);
INIT_WORK(&queue, kthread_launcher); /* 初始化工作队列 */
sema_init(&sem, 1); /* 初始化信号量 */
setup_timer(&timer, qt_task, 0); /* 设置定时器 */
add_timer(&timer); /* 添加定时器 */
for(i = 0; i < NTHREADS; i++)
start_kthread(); /* 再启动200个内核线程来添加结点 */
wake_up_process(sharelist_task);
return 0;
}
/**
*
* 负责销毁链表
* 注意:销毁时内核线程与定时器任务都在运行,应该进行同步保护,即锁住链表,通过
* 自旋锁机制达到的,因为自旋锁保证了任务执行的串行化,此刻其他任务就没有机会执
* 行了。当重新打开自旋锁时,其他任务就可以运行。
*/
static void __exit share_exit(void)
{
struct list_head *n, *p = NULL;
struct my_struct *my = NULL;
printk(KERN_INFO "share_exit.\n");
if(sharelist_task){
kthread_stop(sharelist_task);
sharelist_task = NULL;
}
del_timer(&timer);
spin_lock(&lock);
list_for_each_safe(p, n, &mine) { /* 删除所有结点,销毁链表 */
if(count++ % 4 == 0)
printk("\n");
my = list_entry(p, struct my_struct, list);
list_del(p);
printk(KERN_INFO "syscall del: %d\t", my->id);
kfree(my);
}
spin_unlock(&lock);
printk(KERN_INFO "Over\n");
}
module_init(share_init);
module_exit(share_exit);
MODULE_LICENSE("GPL");
MODULE_VERSION("v1.0");
MODULE_AUTHOR("xz@vichip.com.cn");
2、Makefile
ifeq ($(KERNELRELEASE),)
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
#$(warning "11111111111111111111111)
all:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
clean:
$(MAKE) -C $(KERNELDIR) M=$(PWD) clean
else
obj-m := test1.o