一、内存管理
1、内核
使用alloc_pages申请物理页帧
使用kmalloc申请专用/通用内存块、vmalloc申请物理不连续,逻辑相连内存
使用kmap建立高端地址映射
外碎片:空闲内存零散分布,无法满足大内存需求(伙伴系统解决)
内碎片:申请一块内存真正使用的只有小部分(slab,通用内存块解决)
2、用户进程
1)申请内存
使用malloc动态分配,分配的内存并不是立即调入,而是拥有该内存的访问权,等到真正访问时,引发缺页异常调入
malloc为glibc库函数,实际使用brk/mmap系统调用
申请内存<128k时,调用brk,简单移动堆指针
>128时调用mmap进行内存映射 边界值可通过mallopt进行设定
free一段内存并不是马上还给os,通过malloc依次分配A、B、C三块内存,free A,堆指针无法收缩,保留A供下次调用malloc时使用;free B C(B+C 超过 128k)此时才收缩堆指针,将C这段内存还给os
2)访问内存
cpu引用一个虚拟地址,TLB命中,直接得到物理地址
未命中,通过cr3寄存器存放全局目录地址,一层层索引+偏移得到物理地址
将物理地址发送给高速缓存,缓存命中直接得到对应数据,未命中则继续访问下级缓存或直接访问内存
3、内核与用户空间的数据传递
通过寄存器
内核不会直接引用用户内存,通过get_user/put_user、copy_from_user/copy_to_user等宏进行拷贝
二、进程调度
1、内核栈与进程描述符
根据sp快速获取到进程描述符
2、调度规则
总的来说实时进程就是大爷,优先级高的一直运行,同优先级的再根据FIFO(先到先得)、RoundRobin(轮转)的规则
没有就绪实时进程才轮到普通进程,普通进程目前采用CFS调度算法
3、调度框架
1)主调度器 主动让出cpu
2)周期调度器 时间中断更新统计信息,设置是否需要调度的标志,中断返回时根据标志调用主调度器
4、虚拟运行时间
将进程优先级转换成权重,相邻优先级间的比重相差10%
延迟调度周期,默认20ms,在这个时间段内所有就绪进程至少运行一次
A进程运行1ms(-1优先级),B进程运行1ms(1优先级),A的vruntime为0.9,B的vruntime为1.1
cfs_rq还有一个min_vruntime,是这个队列运行的一个基准。表示这个队列之前运行的进程中,运行一次花得最长的时间
红黑树的key值是每个调度实体的vruntime减去min_vruntime
进程调度伪代码:
def schedule():
'主调度器框架'
while True:
preempt_disable() # '调度开始前先关内核抢占,避免丢失进程信息'
__schedule()
preempt_enable()
if(not need_resched()):
break
def __schedule():
next = pick_next()
if(next != curr):
switch_mm() # 切换虚拟内存、刷新TLB、缓存失效
switch_to() # 保存当前内核栈、寄存器信息,恢复即将运行进程的状态
def pick_next():
if curr.sched_class == cfs and cfs_rq.nrunning == rq.nrunning:
# 调用cfs的pick_next
return cfs_pick_next()
else:
# 遍历所有调度器
for sched_class in (rt_clss, cfs_class, idle_class):
p = sched_class.pick_next()
if p:
return p
def cfs_pick_next():
p = min(curr, first, second) # first红黑树最左,second左边第二
p = min(p, last, next) # 最近一次唤醒成功、失败的进程
同一个进程中的线程间切换,少了switch_mm
不会造成页目录切换,缓存失效,切换代价相对较小
switch_to之后已经切换到其他进程了,A 切换到 B 再由 C切换回A时,用两个参数A不知道上一个进程是谁,但需要可能A需要替C收尸,所以 switch_to有三个参数prev、next、last