多任务
axtask
使用spawn来生成线程
单任务cpu,多任务并发的基本原理
对时间资源分片,分时复用
单cpu只有并发,没有并行
每个task分多少时间,看调度策略
任务切换,需要保存现场,重新调度可以恢复现场
任务无感知
任务调度基本机制
current是当前任务
ready queue里保存即将运行的task
然后随时switch
任务状态
当前任务有runing,如果被抢占,进入Ready
如果等待资源可以用,使用wait进入blocked状态,需要主动唤醒
子任务结束,不会完全释放,linux需要父任务清理,
这里设计一个专门的系统任务gc来负责清理
数据结构TaskInner
idle 是标记系统任务,别人都没工作,它会工作
init 就是主线程
entry 任务逻辑函数入口
可以认为这里所有并发的都是线程,没有进程的概念,因为就一个地址空间
state 就是4个状态
kstack 每个线程有自己的stack
调度是需要切换的上下文ctx
包含一系列寄存器状态,通过状态的保存恢复来完成调度
task_ext 任务扩展属性,用于向宏内核和Hypervisor扩展,这里是空
调度整体框架
支持协作式和抢占式调度通用框架
在这三里调来调去
当前任务,
run_queue,
并列的wait_queue,
还有定时器,比较关键
sleep,和wait带超时的,都需要定时器
上面是api
下面是调度算法
以及任务如何切换,
核心函数switch_to
api
spawn创建新任务
yield_now主动让出cpu执行权,
称为协作式
调度框架初始化
run_queue的初始化,
初始化idle,main,gc
初始化定时器
三个系统任务
main 没有多任务就这一个
其他子线程gc回收
把子任务的动态内存释放
对里面状态进行记录
idle
所有任务都阻塞,没有ready
对特定arch(体系结构),会等待中断irq,或其他非忙等指令
小结
任务如何切换
对任务上下文context进行切换 任务状态的最小寄存器集合
从两个角度看 左边
包含ra 函数调用返回地址 对应于执行流
sp 多任务之间关键就是换栈
s0-s11 体系结构要求必须保存
context_switch
进入的时候是current_task,
返回的时候从另一个任务返回next-task
内部实现
保存当前任务状态
所有寄存器
把下一个任务状态恢复出来
协作式调度
如果完全协作式调度
某个不让出或延迟让出
其他任务会饥饿
协作式算法FIFO
要不退出,要不主动让出yield_now
具体实现
实现trait Base Scheduler
重要的是pick_next_task
和put_prev_task
同步互斥
单cpu,只需要实现上面两层,
只要关中断,就没有其他抢占
如果系统允许抢占,还需要关抢占
多cpu需要一个争夺临界区特殊变量,
一个原子变量
互斥锁
一块是自旋锁 简化版可以用一个原子变量代替
一块等待队列
最多只能有一个任务持有资源
其他的进入睡眠状态
内存分配算法
tlsf
两级bitmap加链表
bitmap第一级,每个bit对应一个内存块
图右上角,1代表空闲,0占用,这里有两个1,2^15和2^6有空闲块
第二级,继续去找哪块空闲
有几个bit,对块几等分,这里是4等分
再指向一个链表头
Buddy
兄弟链表
linux用来管理page的一个传统核心算法
这里用来作为balloc字节分配器使用
order用来表示块大小
每一级不同大小,上一级是下一级两倍
查找空闲块,去找满足要求的那个最小块
释放,会反向,做合并
比较好解决内存碎片化问题
Slab
在linux里跟buddy配合的
用来做page的上一级,就是缓存的这一级的管理
也用在字节分配器balloc
也有一个order list
指向一个slab管理结构体
核心结构也是一个空闲链表,包含所有空闲块
刚开始初始化,从page里申请出来,根据单元大小切分
不足的时候继续按照buddy申请过程来
如果链表里块比较多,可以合并再放回去
抢占式调度概念
必须基于系统定时器来打断当前任务执行
抢占针对目标就是current task
不是无条件抢占
内部条件 比如时间片耗尽
外部条件 通过锁,保证临界区不抢占
抢占发生条件
内部条件 每次中断,对时间片计数递减,耗尽,设置pending标志,表示可以抢占
外部条件 设置临界区的控制标志
一个是自旋锁
自旋锁有两部分能力
保存中断状态,关中断
可以关抢占no preempt
单独关闭抢占
nopreempt
一个guard变量
这俩可叠加 有点像信号量
内外条件都具备的时候,可以抢占
抢占与协作式的主要区别
需要引入平台时钟中断
需要外部抢占控制,自旋锁+guard变量
时钟中断与抢占式调度细节
注册时钟中断响应函数
中断内部会先检测事件列表 check_events
RUN_QUEUE是跟当前task调度相关的
里面检查task_tick看内部条件是否满足
满足就可以pending了
抢占式调度算法
rr算法
简单,定时器递减,到了就pending
同时如果外部条件允许抢占,处于禁用到启用的边沿上,可以进行抢占
仍然可以使用yield自己主动让出
实现
保持一个时间片成员time_slice
循环列表,使用双端可操作队列VecQueue
如果时间片没耗尽,放回queue脑袋,下次继续提出这个
耗尽了,放回队尾,等待调度
task_tick,就是处理递减
<=1的时候可以被抢占了
CFS最常用的公平调度算法
vruntime最小的任务是优先级最高的任务
delta是可变的,其他固定
delta跟时钟中断相关
每次触发,只针对当前这个任务操作
对delta进行递加操作
vruntime向上走,超过其他的之后,它不是最小,会触发其他的抢占
实现
基于btreemap运行队列 可以当成一个有序队列 队首最小
插入进去的时候需要重新计算此时的runtime 使用get_vruntime 因为当前任务执行后 runtime一定会发生变化
取下个任务直接从有序队列取第一个
存在当前任务放进去后仍然队首,拿出来还是他
task_tick,第一次运行的时候允许进行一次调度,min_vruntime.is_none
再就是计算当前任务的vruntime是不是比min_vruntime大,说明有其他的可以调度,触发重新调度
具体数据结构
init_vruntime决定一开始是否容易被调度 后面影响变小
nice跟优先级相关,决定delta的变化速度,nice高,变化慢,优先级高
总结

Generated 2025-05-05 04:17:35 +0000
25bcfca-dirty 2025-05-04 14:47:56 +0000