目录
1.内核多任务并发示意图
2.工作队列
3.定时器
4.实验与遗留
1.内核多任务并发示意图

本节我们会用一个实例来看看在linux中是如何使用这些同步机制实现复杂的多任务.
简单介绍一下本例:
1.共享资源--链表
2.三种不同类型的内核任务会访问该链表并对其进行插入或删除节点的操作.
2.1内核线程负责向链表加入新节点;
2.2内核定时器负责定时删除节点;
2.3系统调用负责在某个时候销毁整个链表.
这三种内核任务并发执行时有可能破坏内核数据的完整性,所以我们必须对链表进行
同步访问保护以确保数据的一致性,在这样一个多任务并发访问的模型中,我们需要
选用合适的内核同步机制来管理这些任务,使得它们能够有条不紊地执行,我们主要
用到的工具有
信号量,
自旋锁,
工作队列,
定时器.
2.工作队列



3.定时器



4.实验与遗留

啊啊啊啊啊啊啊啊啊
要崩溃了!!!
连续两个实验没有做成功了!!
不知道这个是啥!!
已经改了代码也不行!!!
我一定要搞个4.15的内核来搞了~!!!!!
本次实验过程中需要了解的知识:
1.系统对应内核版本4.10.1对应的Makefile文件是/home/muten/kernel/linux-4.10.1/Makefile;
2.将系统中对应的Makefile文件中的-Werror注释掉之后,警告不会被当成错误来处理.
这个是我在4.10.0下编译通过,但执行insmod的时候却系统崩溃的代码.
我必须升级一个4.15的代码了.
#include <linux/init.h>
#include <linux/module.h>
#include <linux/list.h>
#include <linux/semaphore.h>
#include <linux/sched.h>
#include <linux/timer.h>
#include <linux/spinlock_types.h>
#include <linux/workqueue.h>
#include <linux/slab.h> /*kmalloc的头文件*/
#include <linux/kthread.h>
#include <linux/kallsyms.h>
#include <linux/kernel.h>
#define NTHREADS 200 /* 线程数 */
struct my_struct {
struct list_head list;
int id;
int pid;
};
static struct work_struct queue;
static struct timer_list mytimer; /* 用于定时器队列 */
static LIST_HEAD(mine); /* sharelist头 */
static unsigned int list_len = 0;
static DEFINE_SEMAPHORE(sem); /* 内核线程启动器之间进行同步的信号量,4.15内核适用*/
static DEFINE_SPINLOCK(my_lock); /* 保护对链表的操作,4.15内核适用 */
static atomic_t my_count = ATOMIC_INIT(0); /* 以原子方式进行追加 */
static int count = 0;
static int sharelist(void *data);
static void start_kthread(void);
static void kthread_launcher(struct work_struct *q);
/* 内核线程,把节点加到链表 */
static int sharelist(void *data)
{
struct my_struct *p;
if (count++ % 4 == 0)
printk("\n");
spin_lock(&my_lock); /* 添加锁,保护共享资源 */
if (list_len < 50) {
if ((p = kmalloc(sizeof(struct my_struct), GFP_KERNEL)) == NULL)
return -ENOMEM;
p->id = atomic_read(&my_count); /* 原子变量操作 */
atomic_inc(&my_count);
p->pid = current->pid;
list_add(&p->list, &mine); /* 向队列中添加新字节 */
list_len++;
printk("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("THREAD DEL:%-5d\t", my->id);
kfree(my);
}
spin_unlock(&my_lock);
return 0;
}
/* 调用keventd来运行内核线程 */
static void start_kthread(void)
{
down(&sem);
schedule_work(&queue);
}
static void kthread_launcher(struct work_struct *q)
{
kthread_run(sharelist, NULL, "%d", count);
up(&sem);
}
//void qt_task(struct timer_list *timer)
void qt_task(unsigned long data)
{
//struct tse_pcs *pcs = (struct tse_pcs *)data;
struct timer_list *timer = (struct timer_list*)data;
spin_lock(&my_lock);
if (!list_empty(&mine)) {
struct my_struct *i;
if (count++ % 4 == 0)
printk("\n");
i = list_entry(mine.next, struct my_struct, list); /* 取下一个节点 */
list_del(mine.next); /* 删除节点 */
list_len--;
printk("TIMER DEL:%-5d\t", i->id);
kfree(i);
}
spin_unlock(&my_lock);
mod_timer(timer, jiffies + msecs_to_jiffies(1000));
}
static int __init share_init(void)
{
int i;
printk(KERN_INFO"share list enter\n");
INIT_WORK(&queue, kthread_launcher);
//timer_setup(&mytimer, qt_task, 0);
setup_timer(&mytimer, qt_task, 0);
add_timer(&mytimer);
for (i = 0; i < NTHREADS; i++)
start_kthread();
return 0;
}
static void __exit share_exit(void)
{
struct list_head *n, *p = NULL;
struct my_struct *my = NULL;
printk("\nshare list exit\n");
del_timer(&mytimer);
spin_lock(&my_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("SYSCALL DEL: %d\t", my->id);
kfree(my);
}
spin_unlock(&my_lock); /* 开锁 */
printk(KERN_INFO"Over \n");
}
MODULE_LICENSE("GPL");
module_init(share_init);
module_exit(share_exit);
老师的学生在他的4.15.0版本的内核上能正常运行的代码:
/*
为了在内核中模拟多任务并发访问链表,我们需要完成下面几个任务:
1.在内核中建立一个共享链表,并使用自旋锁结构对其进行访问保护;
2.利用工作队列机制建立若干个内核线程,每个内核线程都应该能对共享链表进行插入删除操作;
3.创建一个内核定时器,并编写其回调函数使其到期时能够删除共享链表中的节点;
4.在模块卸载函数中实现链表的销毁.
以上便是我们模拟系统调用任务对共享链表的访问.
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/list.h>
#include <linux/semaphore.h>
#include <linux/sched.h>
#include <linux/timer.h>
#include <linux/spinlock_types.h>
#include <linux/workqueue.h>
#include <linux/slab.h> /*kmalloc的头文件*/
#include <linux/kthread.h>
#include <linux/kallsyms.h>
#define NTHREADS 200 /* 线程数 */
/* 共享链表节点定义 */
struct my_struct {
struct list_head list;
int id;
int pid;
};
static struct work_struct queue; /* 定义工作队列 */
static struct timer_list mytimer; /* 用于定时器队列 */
static LIST_HEAD(mine); /* sharelist头 */
static unsigned int list_len = 0;
static DEFINE_SEMAPHORE(sem); /* 内核线程启动器之间进行同步的信号量,4.15内核适用*/
static DEFINE_SPINLOCK(my_lock); /* 声明并初始化一个自旋锁,保护对链表的操作,4.15内核适用 */
static atomic_t my_count = ATOMIC_INIT(0); /* 以原子方式进行追加 */
static int count = 0;
static int sharelist(void *data);
static void start_kthread(void);
static void kthread_launcher(struct work_struct *q);
/* 内核线程,把节点添加到链表或者从链表中删除 */
static int sharelist(void *data)
{
struct my_struct *p;
if (count++ % 4 == 0)
printk("\n");
spin_lock(&my_lock); /* 添加锁,保护共享资源 */
if (list_len < 50)
{
if ((p = kmalloc(sizeof(struct my_struct), GFP_KERNEL)) == NULL)
return -ENOMEM;
p->id = atomic_read(&my_count); /* 原子变量操作,my_count用来记录节点的序号 */
atomic_inc(&my_count);
p->pid = current->pid;
list_add(&p->list, &mine); /* 向队列中添加新字节 */
list_len++;
printk("THREAD ADD:%-5d\t", p->id);
}
else /* 队列超过定长则删除节点 */
{
struct my_struct *my = NULL;
/*
list_entry的作用是找到包含链表尾结点的my_struct结构体的地址,
因为我们不仅要删除链表的指针,还需要删除共享节点结构体本身,
找到结构体的地址之后用kfree就完成了共享链表节点的删除
*/
my = list_entry(mine.prev, struct my_struct, list);
list_del(mine.prev); /* 删除头结点的前驱,即从队列尾部删除节点 */
list_len--;
printk("THREAD DEL:%-5d\t", my->id);
kfree(my);
}
spin_unlock(&my_lock);
return 0;
}
/* 调用keventd来运行内核线程 */
static void start_kthread(void)
{
down(&sem);// 将信号量sem-1
schedule_work(&queue);// 如果上一步没有被阻塞,将会执行schedule_work,将queue插入到内核工作队列中
}
// kthread_launcher是内核线程启动器
// 工作处理函数
static void kthread_launcher(struct work_struct *q)
{
/*
kthread_run(sharelist, NULL, "%d", count);
创建并唤醒一个内核线程,
第一个参数:此处sharelist是一个函数指针,指定该内核线程需要执行的函数;
第二个参数:指定参数指定参数将自动传递给sharelist,此处不传参;
第三四个参数:"%d", count--格式化为线程命名,count是一个全局变量,用来记录线程的序号然后为其命名.
up(&sem);
线程创建完毕以后将为sem+1,这里有一个问题:sem在此处的作用是什么呢?
试着将sem取消,执行后会发现,模块只能创建一个内核线程,但是我们明明调用了200次schedule_work,向工作
队列中插入200个queue工作队列,其他的199个queue工作队列去哪里了呢?
原因在于schedule_work时它会检查要插入的工作是否已经在工作队列中,如果是,则结束执行;
所以在第一个work被执行之前,其他199次对同一个work进行调度都是无效操作.
这里使用信号量保证每次调度工作被执行之后才执行下一次,实现线程线程启动函数之间的同步.
另外还要思考一个问题,
为什么我们要使用工作队列来创建内核线程而不是直接调用200次kthread_run呢?
这里我的理解是:
内核队列kevent默认的工作线程是events_n,这里的n是处理器的编号,每个处理对应一个线程,
比如单处理器的系统只有events_0这样一个工作者线程,而在双处理器的系统中就会多一个events_1线程.
所以我们要创建大量的线程,将这些工作分配给多CPU并行执行无疑会提高效率,
当然您的系统存在多个CPU.
如果我们不想使用工作队列来创建线程,那么就可以不适用信号量sem了.
*/
kthread_run(sharelist, NULL, "%d", count);
up(&sem);
}
void qt_task(struct timer_list *timer)
{
spin_lock(&my_lock);
if (!list_empty(&mine)) {
struct my_struct *i;
if (count++ % 4 == 0)
printk("\n");
i = list_entry(mine.next, struct my_struct, list); /* 取下一个节点 */
list_del(mine.next); /* 删除节点 */
list_len--;
printk("TIMER DEL:%-5d\t", i->id);
kfree(i);
}
spin_unlock(&my_lock);
mod_timer(timer, jiffies + msecs_to_jiffies(1000));
}
/*模块加载函数*/
static int share_init(void)
{
int i;
printk(KERN_INFO"share list enter\n");
INIT_WORK(&queue, kthread_launcher);// 初始化工作队列并指定工作处理函数kthread_launcher
timer_setup(&mytimer, qt_task, 0);
add_timer(&mytimer);
for (i = 0; i < NTHREADS; i++)
start_kthread();// 被循环执行,用来将工作queue插入到内核工作队列中
return 0;
}
static void share_exit(void)
{
struct list_head *n, *p = NULL;
struct my_struct *my = NULL;
printk("\nshare list exit\n");
del_timer(&mytimer);
spin_lock(&my_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("SYSCALL DEL: %d\t", my->id);
kfree(my);
}
spin_unlock(&my_lock); /* 开锁 */
printk(KERN_INFO"Over \n");
}
module_init(share_init);
module_exit(share_exit);
MODULE_LICENSE("GPL v2");
