当前课程知识点:Linux 内核分析与应用 >  第7章 内核同步 >  7.4 动手实践-内核多任务并发实例(下) >  Video

返回《Linux 内核分析与应用》慕课在线视频课程列表

Video在线视频

Video

下一节:Video

返回《Linux 内核分析与应用》慕课在线视频列表

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命令

可以看到 链表中剩余的结点

就被系统调用依次删除掉了

这就是本次分享的全部内容

谢谢大家

Linux 内核分析与应用课程列表:

第1章 概述

-1.1 Linux操作系统概述

--1.1 Linux 操作系统概述

-1.2 Linux内核结构以及内核模块编程

--Video

-1.3 Linux内核源码中的双链表结构

--Video

-1.4 源码分析-内核中的哈希表

--Video

-1.5 动手实践-Linux内核模块的插入和删除

--Video

-第1章 概述--章节测验

-第1章导学--引领你进入Linux内核的大门

第2章 内存寻址

-2.1 内存管理之内存寻址

--Video

-2.2 段机制

--Video

-2.3分页机制

--Video

-2.4 动手实践-把虚拟地址转换成物理地址

--Video

-第2章 内存寻址--章节测验

-第二章导学-从零打造自己的操作系统

第3章 进程管理

-3.1 进程概述

--Video

-3.2 Linux进程创建

--Video

-3.3 Linux进程调度

--Video

-3.4 动手实践-打印进程描述符task_struct中的字段

--Video

-3.5工程实践-基于内核模块的负载监控

--Video

-第3章 进程管理--章节测验

-第三章导学-进程背后琳琅满目的宝贝到哪里挖?

第4章 内存管理

-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章 中断

-5.1 中断机制概述

--Video

-5.2 中断处理机制

--Video

-5.3 中断下半部处理机制

--Video

-5.4 时钟中断机制

--Video

-5.5 动手实践-中断上半部的代码分析及应用

--Video

-5.6 动手实践-中断下半部的代码分析及应用

--Video

-第5章 中断--章节测验

第6章 系统调用

-6.1 Linux中的各种API

--Video

-6.2 系统调用机制

--Video

-6.3 动手实践-添加系统调用(系统调用日志收集系统)

--Video

-第6章 系统调用--章节测验

第7章 内核同步

-7.1 内核同步概述

--Video

-7.2 内核同步机制

--Video

-7.3 动手实践-内核多任务并发实例(上)

--Video

-7.4 动手实践-内核多任务并发实例(下)

--Video

-第7章 内核同步--章节测验

第8章 文件系统

-8.1 虚拟文件系统的引入

--Video

-8.2 虚拟文件系统的主要数据结构

--Video

-8.3 文件系统中的各种缓存

--Video

-8.4 页高速缓存机制以及读写

--Video

-8.5 动手实践-编写一个文件系统(上)

--Video

-8.6 动手实践-编写一个文件系统(中)

--Video

-8.7 动手实践-编写一个文件系统(下)

--Video

-第8章 文件系统--章节测验

第9章 设备驱动

-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

直播视频:从Linux内核学习到自主操作系统研发

-从Linux内核学习到自主操作系统研发

附录:实验代码、课件以及相关素材

-各章实验代码

-《Linux内核分析与应用》课件

-《Linux操作系统原理与应用》教材课堂视频

Video笔记与讨论

也许你还感兴趣的课程:

© 柠檬大学-慕课导航 课程版权归原始院校所有,
本网站仅通过互联网进行慕课课程索引,不提供在线课程学习和视频,请同学们点击报名到课程提供网站进行学习。