内存管理与进程调度

一、内存管理

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



你可能感兴趣的:(计算机基础)