当前课程知识点:Linux 内核分析与应用 > 第7章 内核同步 > 7.4 动手实践-内核多任务并发实例(下) > Video
大家好 在上一节中
我们简单的回顾了工作队列
和内核定时器的基本用法
现在 让我们看看在代码中是如何使用它们的
为了在内核中模拟多任务并发访问共享链表
我们需要完成下面几个任务
首先我们需要在内核中建立一个共享链表
并使用自旋锁结构对其进行访问保护
接着 利用工作队列机制建立若干个内核线程
每个内核线程都应该
够对共享链表进行插入删除操作
三 创建一个内核定时器
并编写其回调函数
使其在到期时能够删除共享链表中的结点
最后在模块卸载函数中 实现链表的销毁
这是我们模拟系统调用任务对共享链表的访问
下面 我们来依次看看各部分的代码
及其在并发实例中的作用
对于共享链表
我们利用内核提供的链表结构list_head
来创建其结点
这里将内核提供的list_head 类型的链表结构
包含到我们定义的共享链表结点
的结构体中 就完成了共享链表结点的定义
怎么样 是不是比我们自己定义一个
带前后指针的结点方便多了
而且 通过内核list_head
建立的链表 可以直接使用内核中的函数
对链表进行插入 删除 遍历等操作
接着 我们需要为链表创建一个头结点mine
这里使用LIST_HEAD宏来完成
这里需要注意的是
头结点是一个list_head类型的结构体
而并不是我们所定义的共享链表结点
my_struct结构体
操作时需要特别留意
对于自旋锁
我们使用DEFINE_SPINLOCK的宏来声明
并初始化一个自旋锁my_lock
这样我们就完成了第一个任务
在内核中建立了一个头结点为mine的共享链表
并为其创建了一个自旋锁my_lock
对其进行访问保护
下面我们来看看
如何使用工作队列创建内核线程
为了方便起见 这里使用内核工作队列kevent
我们第一步需要定义一个work_struct类型的工作queue
紧接着使用DEFINE_SEMAPHORE的宏声明一个信号量sem
并将其初始化为1
该信号量的作用在后面会给大家说明
我们再来看看模块加载函数
在模块加载函数中我们用INIT_WORK的宏
来初始化工作queue
并为其指定工作处理函数
kthread_launcher 也就是内核线程启动器
工作queue初始化完成后 我们只需要在适当的时候
将其插入到内核工作队列kevent中
等待其被执行就可以了
看到下面这个for循环
NTHREADS是预定义的宏
表示创建内核线程的个数这里是200
start_kthread函数被循环执行
用来将工作queue插入到内核工作队列中
我们来看看start_kthread函数
该函数的代码非常简单 只有两行
首先将信号量sem减一
如果没被阻塞的话 则执行schedule_work
将工作queue插入到内核工作队列中
紧接着的这个函数就是kthread_launcher工作处理函数
它的代码也只有两行 首先用kthread_run
这个函数创建并唤醒一个内核线程
第一项参数sharelist
是一个函数指针 指定该内核线程需要执行的函数
第二项则是指定的参数将被自动化传递给该函数
后面这两项则是格式化的为线程命名 类似于printf函数
count呢是一个全局变量
用来记录内核线程的序号 然后为其命名
线程创建完毕之后呢
将信号量sem加1
这里 我们有一个疑惑 信号量sem在这里的作用是什么呢
我们试着将信号量sem取消 执行后会发现
模块只能创建一个内核线程
但是我们明明调用了200次schedule_work函数
向工作队列中插入了200个queue工作
其他的199个工作去哪了呢
原因在于 我们执行schedule_work函数时
它会检查要插入的工作是否已经在工作队列中
如果是则结束执行 所以
在第一个work被执行之前 其他199次
对同一个work进行调度 都是无效操作
所以这里我们要使用信号量来保证
、每次
调度工作被执行之后才进行下一次的调度
实现线程启动函数之间的同步
另外 我们还要考虑一个问题
为什么我们要使用工作队列来创建内核线程
而不是直接调用200次kthread_run函数呢
这里 我的理解是
内核队列keventd_wq
它默认的工作者线程叫做events/n
这里的n是处理器的编号
每个处理器对应一个线程
比如单处理器的系统只有
events/0这样一个工作者线程
而在双处理器的系统中就会多一个events/1线程
所以 如果我们要创建大量的线程
将这一工作分配给多CPU并行执行
无疑会提升效率
当然 前提是你的系统存在多个CPU
如果我们不想使用工作队列来创建线程
那么也就可以不使用信号量sem了
现在来看看sharelist函数
它是内核线程需要执行的函数
在sharelist中 我们完成共享链表结点的插入或者删除
在这里我们注意到
对共享链表进行操作之前需要使用spin_lock上锁
list_len是一个全局变量用来记录链表的长度
当链表长度小于50时
内核线程执行结点的插入操作
一个原子变量my_count用来记录结点的序号
在这里我们创建了一个
新的共享链表结点
并按顺序为其赋值
使用list_add这样一个函数
将其插入到头结点mine的后面
当链表长度大于50时
线程执行的是删除结点的操作
在这里使用list_del的函数
删除头结点的前驱 也就是链表的尾结点
另外我们注意到有一个叫做list_entry的宏
它的作用是找到包含链表尾结点
的my_struct结构体的地址
因为我们不止要删除链表的指针
还需要删除共享结点结构体本身
我们找到这个结构体的地址之后呢
使用kfree将其销毁
就完成了共享链表结点的删除
在操作完成之后呢 使用spin_unlock解锁
保证每次只有一个线程或其他任务能对链表进行访问
到这里 我们完成了第二个任务
利用工作队列机制建立了200个内核线程
并且每个内核线程都能对共享链表进行插入或者删除的操作
接下来我们看看内核定时器的使用
按照上一节我们提到的使用流程
首先定义一个timer_list类型的定时器 mytimer
接着我们在模块加载函数中将其初始化
这里我们使用timer_setup宏
将mytimer初始化
并为其指定回调函数qt_task
紧接着使用add_timer将其激活
这样定时器就可以开始工作了
我们再来看看回调函数qt_task
在操作共享链表之前都需要进行枷锁
当链表不为空时 我们需要删除
头结点的后继结点
最后呢
使用mod_timer修改定时器的到期时间
将定时器的下一次到期时间设置为
1000毫秒之后
这是一个函数 用来将毫秒值转化为节拍数
好了 内核定时器的任务也就完成了
最后的任务是在模块卸载函数中销毁链表
首先是删除定时器my_timer
同样的 在访问链表之前要先上锁
紧接着是用list_for_each_safe的宏
来从头遍历链表
依次删除每一个结点
销毁整个链表
当我们在用户态调用rmmod命令删除该模块时
是通过系统调用
delete_module来实现的
delete_module系统调用会执行我们模块卸载函数中
写入的代码
销毁链表
从而模拟了系统调用任务对共享链表的访问
这就是
内核多任务并发实例的主要内容
剩下的是定义的一些宏和变量
包括线程数NTHREADS
链表的长度list_len
还有原子变量my_count
这是它的Makefile文件
好了我们来看看它的执行结果
make之后用insmod命令将其插入
执行dmesg命令 查看结果
可以看到内核线程
按顺序的执行插入或者删除操作
并且定时器在到期时
也能正确的删除头结点的后继结点
接着我们执行rmmod命令 将模块删除
再次执行dmesg命令
可以看到 链表中剩余的结点
就被系统调用依次删除掉了
这就是本次分享的全部内容
谢谢大家
-1.1 Linux操作系统概述
-1.2 Linux内核结构以及内核模块编程
--Video
-1.3 Linux内核源码中的双链表结构
--Video
-1.4 源码分析-内核中的哈希表
--Video
-1.5 动手实践-Linux内核模块的插入和删除
--Video
-第1章 概述--章节测验
-2.1 内存管理之内存寻址
--Video
-2.2 段机制
--Video
-2.3分页机制
--Video
-2.4 动手实践-把虚拟地址转换成物理地址
--Video
-第2章 内存寻址--章节测验
-3.1 进程概述
--Video
-3.2 Linux进程创建
--Video
-3.3 Linux进程调度
--Video
-3.4 动手实践-打印进程描述符task_struct中的字段
--Video
-3.5工程实践-基于内核模块的负载监控
--Video
-第3章 进程管理--章节测验
-4.1 Linux内存管理机制
--Video
-4.2 进程用户空间管理机制
--Video
-4.3 物理内存分配与回收机制(上)
--Video
-4.4 物理内存分配与回收机制(下)
--Video
-4.5 动手实践-Linux内存映射基础(上)
--Video
-4.6 动手实践-Linux内存映射实现(中)
--Video
-4.7 动手实践-Linux内存映射测试(下)
--Video
-4.8 初学者对内存管理的常见疑惑
-第4章 内存管理--章节测验
-5.1 中断机制概述
--Video
-5.2 中断处理机制
--Video
-5.3 中断下半部处理机制
--Video
-5.4 时钟中断机制
--Video
-5.5 动手实践-中断上半部的代码分析及应用
--Video
-5.6 动手实践-中断下半部的代码分析及应用
--Video
-第5章 中断--章节测验
-6.1 Linux中的各种API
--Video
-6.2 系统调用机制
--Video
-6.3 动手实践-添加系统调用(系统调用日志收集系统)
--Video
-第6章 系统调用--章节测验
-7.1 内核同步概述
--Video
-7.2 内核同步机制
--Video
-7.3 动手实践-内核多任务并发实例(上)
--Video
-7.4 动手实践-内核多任务并发实例(下)
--Video
-第7章 内核同步--章节测验
-8.1 虚拟文件系统的引入
--Video
-8.2 虚拟文件系统的主要数据结构
--Video
-8.3 文件系统中的各种缓存
--Video
-8.4 页高速缓存机制以及读写
--Video
-8.5 动手实践-编写一个文件系统(上)
--Video
-8.6 动手实践-编写一个文件系统(中)
--Video
-8.7 动手实践-编写一个文件系统(下)
--Video
-第8章 文件系统--章节测验
-9.1 设备驱动概述
--Video
-9.2 I/O空间管理
--Video
-9.3 设备驱动模型
--Video
-9.4 字符设备驱动程序简介
--Video
-9.5 块设备驱动程序简介
--Video
-9.6 动手实践-编写字符设备驱动程序
--Video
-9.7工程实践-编写块设备驱动的基础(上)
--Video
-9.8 工程实践-块设备驱动程序分析(中)
--Video
-9.9 工程实践-块设备驱动程序实现(下)
--Video
-第9章 设备驱动--章节测验
-致谢与说明
--Video