zoukankan      html  css  js  c++  java
  • 2020-2021-1 20209326 《Linux内核原理与分析》第九周作业

    作业信息

    这个作业属于哪个课程 <2020-2021-1Linux内核原理与分析)>
    这个作业要求在哪里 <2020-2021-1Linux内核原理与分析第九周作业>
    这个作业的目标 <进程调度与进程切换过程>
    作业正文 <博客>

    实验:进程调度相关源代码跟踪和分析

    1.在实验楼环境中执行以下命令,更新内核版本。

    cd LinuxKernel
    rm -rf menu
    git clone https://github.com/mengning/menu.git
    cd menu
    

    2.开启调试模式,设置断点,查看进程调度过程。

    //开启调试模式
    qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img -S -s
    gdb
    file ./linux-3.18.6/vmlinux
    target remote:1234
    b schedule
    b context_switch
    b switch_to
    b pick_next_task
    



    执行断点:
    schedule()是进程调度的主体函数;pick_next_task()在schedule()中负责根据调度策略和调度算法选择下一个进程;context_switch()在schedule()中实现进程切换;switch_to()在context_switch()中进行进程关键上下文切换。


    3.关键代码分析
    context_switch关键代码部分:

    static inline void context_switch(struct rq *rq, struct task_struct *prev, struct task_struct *next)
    {
        arch_start_context_switch(prev);
    
        if (unlikely(!mm)) {    //如果被切换进来的进程的mm为空切换,内核线程mm为空
            next->active_mm = oldmm;  //将共享切换出去的进程的active_mm
            atomic_inc(&oldmm->mm_count);  //有一个进程共享,所有引用计数加一
            enter_lazy_tlb(oldmm, next);  //将per cpu变量cpu_tlbstate状态设为LAZY
        } else   //普通mm不为空,则调用switch_mm切换地址空间
            switch_mm(oldmm, mm, next);
        //这里切换寄存器状态和栈 
        switch_to(prev, next, prev);
    

    switch_to关键代码部分:

    #define switch_to(prev, next, last)
    do {
        unsigned long ebx, ecx, edx, esi, edi;
        asm volatile(
                 "pushfl
    	"  //保存当前进程flags
                 "pushl %%ebp
    	"  //当前进程堆栈基址压栈
                 "movl %%esp,%[prev_sp]
    	"  //保存ESP,将当前堆栈栈顶保存起来
                 "movl %[next_sp],%%esp
    	"  //更新ESP,将下一栈顶保存到ESP中
                         // 完成内核堆栈的切换
                 "movl $1f,%[prev_ip]
    	"    //保存当前进程的EIP
                 "pushl %[next_ip]
    	"       //将next进程起点压入堆栈,即next进程的栈顶为起点
                 __switch_canary              //next_ip一般为$1f,对于新创建的子进程是ret_from_fork      
                 "jmp __switch_to
    "    //prve进程中,设置next进程堆栈,jmp与call不同,是通过寄存器传递参数(call通过堆栈),所以ret时弹出的是之前压入栈顶的next进程起点
                 //完成EIP的切换
                 "1:	"            //next进程开始执行       
                 "popl %%ebp
    	"  //restore EBP
                 "popfl
    "         //restore flags
                 //输出量
                 : [prev_sp] "=m" (prev->thread.sp),   //保存当前进程的esp
                   [prev_ip] "=m" (prev->thread.ip),     //保存当前进仓的eip
                   "=a" (last),
                   //要破坏的寄存器
                   "=b" (ebx), "=c" (ecx), "=d" (edx),
                   "=S" (esi), "=D" (edi)
                   __switch_canary_oparam
                  //输入量
                 : [next_sp]  "m" (next->thread.sp),   //next进程的内核堆栈栈顶地址,即esp
                   [next_ip]  "m" (next->thread.ip),     //next进程的eip
                   // regparm parameters for __switch_to(): 
                   [prev]     "a" (prev),
                   [next]     "d" (next)
                   __switch_canary_iparam
                 : //重新加载段寄存器
                "memory");
    } while (0)
    

    总结

    1.进程调度
    (1)中断:起到切出进程指令流的作用。中断处理程序是与进程无关的内核指令流。
    中断类型:
    硬中断:可屏蔽中断和不可屏蔽中断。高电平说明有中断请求。
    软中断/异常:
    故障:出问题,但可以恢复到当前指令,如除零错误。
    退出:不可恢复的严重故障,只能退出。如连续发生故障。
    陷阱(Trap):程序主动产生的异常。
    (2)schedule函数
    Linux内核通过schedule函数实现进程调度,它在运行队列中找到一个进程,把CPU分配给它。所以调用该函数一次就是调度一次,调用该函数的时机就是进程调度的时机。调用schedule函数的两种方法如下:
    进程主动调用schedule(),如进程调用阻塞的系统调用等待外设或者主动睡眠。
    松散调用,内核代码可以随时根据需要调用schedule使当前内核路径让出CPU。
    (3)上下文
    一般来说,CPU在任何时刻都处于以下三种情况之一:
    运行于用户空间,执行用户进程上下文。
    运行于内核空间,处于进程上下文。
    运行于内核空间,处理中断上下文。
    (4)进程调度的时机
    用户进程通过特定的系统调用主动让出CPU。
    中断处理程序在内核返回用户态时进行调度。
    内核线程主动调用schedule函数让出CPU。
    中断处理程序主动调用schedule函数让出CPU。

    2.调度策略与算法
    (1)基本调度策略
    实时进程的调度策略(优先级0~99,静态设定)
    SCHED_FIFO 先进先出
    SCHED_RR 轮转策略(时间片)
    -普通进程的调度类 —— SCHED_NORMAL,只有nice值,映射到优先级为100~139。按优先级占比计算占用CPU的时间。
    (2)CFS调度算法
    调度周期:进程越多,周期越长;上限默认8ms;一个时间周期内队列的所有进程都会至少被调度一次。
    __sched_period = nr_running(进程数)*sysctl_sched_min_granularity(默认值)
    理论运行时间:每次可获取CPU后最长可占用时间为ideal_runtime.
    ideal_runtime = __sched_period * 进程权重/队列运行总权重
    虚拟运行时间:每个进程拥有一个vruntime,每次需要调度时就运行队列中拥有最小的vruntime的进程来运行,最长运行时间为ideal_runtime.
    vruntime = 实际运行时间 * NICE_0_LOAD / 进程权重
    = 实际运行时间 * 1024 / 进程权重
    NICE_0_LOAD = 1024, 表示nice值为0的进程权重
    时钟中断周期:1/CONFIG_HZ秒
    Linux传统优先级与权重的转化关系是经验值。
    Linux采用红黑树来存储就绪进程指针,插入时根据vruntime排序,调度时选择最左边的即可。

    3.进程的切换
    为了控制进程的执行,内核必须有能力挂起正在CPU上执行的进程,并恢复以前挂起的某个进程的执行,这叫做进程切换、任务切换、上下文切换;
    挂起正在CPU上执行的进程,与中断时保存现场是不同的,中断前后是在同一个进程上下文中,只是由用户态转向内核态执行;
    进程上下文包含了进程执行需要的所有信息
    用户地址空间: 包括程序代码,数据,用户堆栈等
    控制信息 :进程描述符,内核堆栈等
    硬件上下文(注意中断也要保存硬件上下文只是保存的方法不同)
    schedule()函数选择一个新的进程来运行,并调用context_switch进行上下文的切换,这个宏调用switch_to来进行关键上下文切换
    next = pick_next_task(rq, prev);//进程调度算法都封装这个函数内部
    context_switch(rq, prev, next);//进程上下文切换
    进程切换关键环节分析示意图:

  • 相关阅读:
    动画 + 设置contentoffset,然后就 蛋疼了,
    xmpp这一段蛋疼的 坑,
    项目,
    一段测试代码,哦哦哦,
    libresolv,
    mutating method sent to immutable object'
    解析json,是还是不是,
    济南学习 Day 4 T1 am
    济南学习 Day 3 T3 pm
    济南学习 Day 3 T2 pm
  • 原文地址:https://www.cnblogs.com/gann/p/14083527.html
Copyright © 2011-2022 走看看