Linux内核学习9——内核多任务并发实例(上)

这里我们来看看Linux中是如何使用同步机制来实现复杂的多任务同步的

首先来介绍一下这个例子

在这里插入图片描述

有一个内核共享资源(链表),同时有三种不同类型的内核任务会访问该链表,并对其进行插入或者删除节点的操作,内核线程负责向链表加入新节点,内核定时器负责定时/删除节点,而系统调用负责在某个时刻销毁整个链表。这三种内核任务并发执行时,有可能会破坏链表数据的完整性,所以我们必须对链表进同步访问保护,以确保数据的一致性。在这样一个多任务并发访问的模型中,我们需要选用合适的内核同步机制来管理这些任务,使得它们能够有条不紊的执行。主要用到的工具有:信号量/自旋锁/工作队列/内核定时器

在这里插入图片描述
在这里插入图片描述

当用户初始化一个工作队列时,内核就开始为用户分配一个workqueue工作队列对象,并且将其链到一个全局的workqueue队列中,然后linux会根据当前CPU的情况为工作队列对象分配与CPU个数相同的cpu_workqueue_struct,也就是CPU工作队列对象,每个CPU工作队列对象都会有一条任务队列,linux会为每一个cpu工作队列对象分配一个内核线程,即daemon守护进程,用于处理每个工作队列的任务,至此,工作队列的初始化就完毕了,将会返回一个指向工作队列的指针。在工作队列初始化完毕之后,是将任务运行的上下文环境构建了起来,但是还没有具体可执行的任务,任务队列为空,那么内核守护进程就在cpu_workqueue_struct的中等待队列上睡眠,所以我们还需要定义具体的work_struct工作对象,然后将work_struct加入到任务队列中,linux就会唤醒守护进程去处理相应的任务。

在workqueue工作队列机制中,内核在系统初始化的时候,已经为我们创建好了默认的工作队列keventd_wq,我们在使用的时候可以不需要再创建工作队列,只需要创建工作,并将工作添加到内核工作队列keventd上即可,当然我们也可以创建并使用自己的工作队列

在这里插入图片描述

使用工作队列的一般流程为:

  1. 定义声明工作处理函数。指向工作队列的指针和工作结构体变量work_struct。我们这里使用DECLARE_WORK或INT_WORK这两个宏来指定工作处理函数。

  2. 调用create_siglethread_workqueue,或者create_workqueue函数来创建自己的工作队列,这两个函数的区别在于create_workqueue这个函数创建一个工作队列,并且为系统中每个CPU都会创建一个内核线程守护进程,去监视相应的任务队列,而create_siglethread_workqueue,它只会创建一个内核线程,一个内核守护进程,这里如果我们使用内核默认的工作队列kevent也可以省去这一步。

  3. 将工作添加到自己创建的工作队列中等待执行,这里我们使用的函数是queue_work,调度执行一个指定workqueue中的任务,需要传入的参数就是指定workqueue的指针,还有一个具体任务对象的指针,同样,我们这里如果使用内核工作队列kevent的话,我们需要用到另一个函数schedule_work,它是调度执行一个具体的任务,并将执行的任务挂入到linux系统提供的工作队列kevent中,

  4. 删除自己的工作队列,这里使用destory_workqueue这样一个函数释放我们创建的工作队列,如果我们使用内核工作队列,这一步也可用省略,

在这里插入图片描述

内核定时器,也称为动态定时器,是管理内核时间的基础,它是一种用来推迟程序执行的工具。在2.4之前的老版本中有静态定时器的概念,而2.4之后静态定时器就被完全去掉了,我们在使用定时器的相关函数时,先看看内核是如何描述定时器的

内核用timer_list这样的数据结构来描述内核定时器,其中包含了这样几个重要的成员;entry是定时器hlist链表的入口;expires是定时器到期时的tick数;系统当前的tick数保存在一个名叫jiffies的全局变量中。接着是一个函数指针”(*function(struct time_list *))“,指定定时器到期时的处理函数;最后的flag,是传递给定时器处理函数所需要的参数

在这里插入图片描述

在这里插入图片描述

动态定时器,动态是指内核的定时器队列是可用动态变化的,其关键就在于”定时器向量“的概念;所谓”定时器向量“就是指一条定时器队列,队列中的每个元素都是一个timer_list结构,而队列中所有的定时器都会在同一个时刻到期,即队列中每一个timer_list结构,都具有相同的expires值,不同的定时器队列根据其expires值不同,再连接成一个双向循环的队列。定时器expires值与jiffie变量的差值,决定了一个定时器在多长时间后到期,在32位系统中,这个时间差值的最大值位2^32,如果是基于”定时器向量“的定义,内核将要至少维护2的32次方个timer_list结构类型的指针,这显然是不显示的。

另一方面,从内核本身来看,它所关心的定时器应当是那些当前已经到期,或者马上就要到期的定时器,所以,对于即将到期的定时器,linux会严格按照定时器向量的基本定义来组织它们,将最近到期的定时器按照各自不同的expires值组织成256个定时器向量,对于其它的定时器,由于它们距离到期还有一段时间,因此内核并不关心它们,而是将它们组织在一个松散的队列中,即各定时器的expires值可用互不相同的一个定时器队列。

这种定时器组织形式的基本思想就来源于时间轮timing-wheel算法,另外,由于对定时器到期时间的检查,总是由可延迟函数进行,而可延迟函数被激活以后,很长时间才能被执行,因此内核不能保证定时器函数定时器函数在定时器期时被执行,只能保证它们在适当的时间执行,或者说它们会在定时器到期后延迟几百毫秒在被执行,因此内核定时器的精度不高,对于哪些必须严格遵守定时时间的那些实时应用来说,应当选用hrtimer高精度定时器

在这里插入图片描述

内核定时器的一般流程:

  1. 定义timer_list结构,编写回调函数

  2. 为timer初始化,为其expires flag function等成员赋值,这里使用define_timer或timer_setup这两个宏来初始化内核定时器

  3. 调用add_timer将定时器添加到系统中,或者我们可用直接调用mod_timer,该函数原本是用来修改定时器的超时时间,这里也可以用它来激活一个定时器

  4. 当定时器到期时,回调函数将会被执行

  5. 在程序中适当的调用del_timer或mod_timer来删除定时器或者修改定时器的到期时间

好了,了解了这些,我们就可以来看看代码是如何实现的
https://blog.csdn.net/weixin_45730790/article/details/122537278

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值