当前课程知识点:Linux 内核分析与应用 > 第7章 内核同步 > 7.3 动手实践-内核多任务并发实例(上) > Video
大家好 今天给大家带来的是动手实践
内核多任务并发实例
在前两节中
陈老师给大家讲解了有关同步的概念
和一些内核同步机制
今天我们用一个实例来看看
在Linux中是如何使用这些机制
实现复杂的多任务同步的
首先 我给大家来介绍一下我们今天要完成的这个例子
有这样一个内核共享资源
链表同时 有3种不同类型的内核任务
会访问该链表并对其进行插入或者删除结点的操作
内核线程负责向链表加入新结点
内核定时器负责
定时删除结点
而系统调用则负责在某个时候
销毁整个链表
这三种内核任务
并发执行时 有可能会破坏链表数据的完整性
所以我们必须对链表进行同步访问保护
以确保数据的一致性
在这样一个多任务并发访问的模型中
我们需要选用合适的内核同步机制
来管理这些任务
使得它们能够有条不紊的执行
我们主要用到的工具有
信号量 自旋锁
工作队列和内核定时器
在前两节的课程中
陈老师为我们详细的讲解了信号量
和自旋锁的概念
和相应的使用方法
在这里我就不再赘述了
接下来我带大家简单的复习一下
工作队列和内核定时器
在内核代码中
经常希望延缓部分工作
到将来某个时间执行
内核中提供了许多机制来实现这种延迟执行
例如可延迟函数 工作队列等
工作队列(workqueue)可以把工作推后
并交由一个特殊的内核线程
工作者线程来执行
该线程运行在进程上下文环境中
可以被阻塞
工作队列的优势
在于它允许重新调度甚至是睡眠
我们把推后执行的任务叫做工作(work)
描述它的数据结构为work_struct
这些工作以队列结构组织成工作队列(workqueue)
其数据结构为workqueue_struct
工作队列的实现原理
当用户初始化一个工作队列时
内核就开始为用户分配一个workqueue
工作队列对象
并且将其链到一个全局的workqueue队列中
然后Linux会根据当前CPU的情况
为工作队列对象分配
与CPU个数相同的cpu_workqueue_struct也就是
cpu工作队列对象
每个cpu工作队列对象都会有一条任务队列
Linux会为每一个cpu工作队列对象
分配一个内核线程
即daemon守护进程
用来处理每个任务队列中的任务
至此 工作队列的初始化就完毕了
将会返回一个指向工作队列的指针
在工作队列初始化完毕之后呢
是将任务运行的上下文环境构建了起来
但是还没有具体可执行的任务
任务队列为空 那么内核守护进程就在
cpu_workqueue_struct的中
这个等待队列上睡眠
cpu_workqueue_struct的中
这个等待队列上睡眠
所以 我们还需要定义具体的work_struct工作对象
然后将work_struct加入到任务队列中
Linux就会唤醒守护进程去处理相应的任务
在Workqueue工作队列机制中
内核在系统初始化的时候
已为我们创建好了默认的工作队列keventd_wq
我们在使用的时候可以
不需要再创建工作队列
只需要创建工作 并将工作添加到
内核工作队列keventd上即可
当然 我们也可以创建并使用自己的工作队列
工作队列常用的API
使用工作队列的一般流程为
1.定义声明工作处理函数function
指向工作队列的指针
和工作结构体变量work_struct
我们这里使用DECLARE_WORK
或者INIT_WORK这两个宏
来指定工作处理函数
第二步调用create_singlethread_workqueue
或者create_workqueue
函数来创建自己的工作队列
这两个函数的区别在于
create_workqueue这个函数呢它创建一个工作队列
并且为系统中每一个CPU
都会创建一个内核线程守护进程
去监视
相应的任务队列
而create_singlethread_workqueue
它只会创建一个内核线程
一个内核的守护进程
这里呢我们如果使用内核默认的
工作队列kevent 也可以省去这一步
第三步我们需要将
工作添加到自己创建的工作队列中等待执行
这里我们使用的函数是queue_work
调度执行一个指定workqueue中的任务
需要传入的参数就是指定workqueue的指针
还有一个具体任务对象的指针
同样的我们这里如果使用内核工作队列kevent的话
我们需要用到另一个函数schedule_work
它是调度执行一个具体的任务
并将执行的任务
挂入到Linux系统提供的工作队列kevent中
最后一步就是删除自己的工作队列
我们这里使用destroy_workqueue这样一个函数
释放我们创建的工作队列
如果我们使用内核工作队列
这一步也可以被省略
关于工作队列的部分就是这些
下面给大家介绍的是内核定时器
内核定时器 也称为动态定时器
是管理内核时间的基础
它是一种用来推迟执行程序的工具
在2.4之前老版本中有静态定时器的概念
而2.4之后静态定时器就被完全去掉了
我们在使用关于定时器的相关函数时
先来看看内核是如何描述定时器的
内核用timer_list
这样的数据结构来描述内核定时器
其中包含了这样几个重要的成员
entry是定时器hlist 链表的入口
expires 是定时器到期时的tick数
系统当前的tick数保存在一个名为jiffies的全局变量中
有关tick和jiffies
我会在接下来的PPT中给大家详细讲述
接着呢是一个函数指针
它是用来
指定定时器到期时的处理函数的
最后这个Flag 呢是传给定时器处理函数所需要的参数的
在Linux系统中
时钟分为硬件时钟和软件时钟
软件时钟又叫做系统时钟
在对内核编程中 我们经常用到的是系统时钟
系统时钟的初始值在系统启动时
通过读取硬件时钟来获得
然后由Linux内核来维护
系统时钟以某种频率触发时钟中断
这个频率就称为节拍率(tick rate)
节拍率是通过静态预处理定义的
被定义为HZ
内核会根据HZ值 设置时钟事件
启动tick节拍中断
HZ表示1秒内产生多少个时钟硬件中断
tick就表示连续两个中断的间隔时间
使用如下的命令 可以查看HZ数
HZ=250
所以在我的电脑上一个tick为4ms
而名为 jiffies 全局变量
是用来记录系统启动以来所经过的节拍数的
动态定时器 动态二字是指
内核的定时器队列是可以动态变化的
其关键就在于“定时器向量”的概念
所谓“定时器向量”就是指一条定时器队列
队列中的每一个元素
都是一个timer_list结构
而队列中所有的定时器
都会在同一个时刻到期
也就是说队列中每一个timer_list结构
都具有相同的expires值
不同的定时器队列根据其expires值不同
再连接成为一个双向循环的队列
定时器expires值与jiffies变量的差值
决定了一个定时器在多长时间后到期
在32位系统中
这个时间差值的最大值
应该为2的32次方
如果是基于“定时器向量”的定义
那么内核将要至少
维护2的32次方个timer_list结构类型的指针
这显然是不现实的 另一方面
从内核本身来看
它所关心的定时器应当是那些当前已经到期
或者马上就要到期的定时器
所以 对于即将到期的定时器
Linux会严格按照
定时器向量的基本定义来组织它们
将最近到期的定时器
按照各自不同的expires值
组织成256个定时器向量
对于其他的定时器
由于他们离到期还有一段时间
因此内核并不关心他们
而是将它们组织在一个松散的队列中
即各定时器的expires值可以互不相同的一个定时器队列
这种定时器组织形式的基本思想
想就来源于时间轮 (Timing-Wheel) 算法
另外 由于对定时器到期时间的检查
总是由可延迟函数进行
而可延迟函数被激活以后
很长时间才能被执行
因此 内核不能保证
定时器函数
正好在定时器到期时被执行
只能保证它们在适当的时间执行
或者说它们会在定时器到期后
延迟几百毫秒再被执行
因此 内核定时器的精度不高
对于必须严格遵守
定时时间的那些实时应用来说
应当选用hrtimer高精度定时器
内核定时器常用的API
使用内核定时器的一般流程为
1定义timer_list结构 编写回调函数
2为timer初始化
为其expires flag function等成员赋值
我们这里使用define_timer
或者timer_setup
这两个宏来初始化内核定时器
第三步我们需要调用add_timer
将定时器添加到系统中
或者我们可以直接调用这个mod_timer这样一个函数
该函数原本是用来修改一个定时器的
超时时间的
这里也可以用它来激活一个定时器
第四步呢当定时器到期时呢 回调函数将会被执行
第五步也就是最后一步
我们需要在程序中适当地调用del_timer
或者mod_timer
来删除定时器或者改动定时器的到期时间
好了 了解了这些 我们就可以来一同看看
代码是如何实现的
-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