• why
    2019-05-03
    - 调度, 切换运行进程, 有两种方式
        - 进程调用 sleep 或等待 I/O, 主动让出 CPU
        - 进程运行一段时间, 被动让出 CPU
    - 主动让出 CPU 的方式, 调用 schedule(), schedule() 调用 __schedule()
        - __schedule() 取出 rq; 取出当前运行进程的 task_struct
        - 调用 pick_next_task 取下一个进程
            - 依次调用调度类(优化: 大部分都是普通进程), 因此大多数情况调用 fair_sched_class.pick_next_task[_fair]
            - pick_next_task_fair 先取出 cfs_rq 队列, 取出当前运行进程调度实体, 更新 vruntime
            - pick_next_entity 取最左节点, 并得到 task_struct, 若与当前进程不一样, 则更新红黑树 cfs_rq
        - 进程上下文切换: 切换进程内存空间, 切换寄存器和 CPU 上下文(运行 context_switch)
            - context_switch() -> switch_to() -> __switch_to_asm(切换[内核]栈顶指针) -> __switch_to()
            - __switch_to() 取出 Per CPU 的 tss(任务状态段) 结构体
            - > x86 提供以硬件方式切换进程的模式, 为每个进程在内存中维护一个 tss, tss 有所有寄存器, 同时 TR(任务寄存器)指向某个 tss, 更改 TR 会触发换出 tss(旧进程)和换入 tss(新进程), 但切换进程没必要换所有寄存器
            - 因此 Linux 中每个 CPU 关联一个 tss, 同时 TR 不变, Linux 中参与进程切换主要是栈顶寄存器
            - task_struct 的 thread 结构体保留切换时需要修改的寄存器, 切换时将新进程 thread 写入 CPU tss 中
            - 具体各类指针保存位置和时刻
                - 用户栈, 切换进程内存空间时切换
                - 用户栈顶指针, 内核栈 pt_regs 中弹出
                - 用户指令指针, 从内核栈 pt_regs 中弹出
                - 内核栈, 由切换的 task_struct 中的 stack 指针指向
                - 内核栈顶指针, __switch_to_asm 函数切换(保存在 thread 中)
                - 内核指令指针寄存器: 进程调度最终都会调用 __schedule, 因此在让出(从当前进程)和取得(从其他进程) CPU 时, 该指针都指向同一个代码位置.
    展开
    
     30
  • 安排
    2019-05-03
    proc文件系统里面可以看运行时间和切换次数,还可以看自愿切换和非自愿切换次数。

    老师请教一个问题,A切到B, B切到C,C切到A,当最后切换回A的时候,A要知道自己是从C切换过来的,也就是last,这样做的目的是什么呢?A要对C做什么善后操作吗?

    作者回复: 是的 finish_task_switch完成清理工作

    
     10
  • 刘強
    2019-05-05
    看了三遍,因为有一些基础,大概明白了。我觉得有个地方很巧妙。当函数返回的时候,由于切换了上下文,包括栈指针,所以一个进程函数执行return返回到了另一个进程,也就是完成了进程的切换。由此也可以看出,cpu也是比较"笨的",它只提供了基本的机制,至于如何利用这种机制,玩出花样,那就是各个操作系统自由发挥了。

    作者回复: 是的,这一点比较绕

    
     7
  • coyang
    2019-05-03
    vmstat 、 pidstat 和 /proc/interrupts可以查看进程的上下文切换。
    
     4
  • 尚墨
    2019-05-08
    刘老师,每个用户的进程都会被分配一个内核栈吗?

    作者回复: 是的

    
     2
  • 憨人
    2019-05-17
    进程切换需要搞明白:我从哪里来,我要到哪里去

    作者回复: 这句话赞

    
     1
  • 一笔一画
    2019-05-03
    老师,我还是对三个参数不解,A->B->C,如果再来一个D怎么办?

    作者回复: 不影响,这里只站在a的角度看问题,从a到b,让后中间经历一万个进程,然后到c再到a,也是这个样子的

    
     1
  • jssfy
    2019-12-26
    每个cpu核各自维护队列,请问task是通过什么方式分配到哪个cpu的队列中的,如果不是强制指定的话?
    
    
  • 雨后的夜
    2019-12-25
    太精彩了!!!
    
    
  • neohope
    2019-12-09
    老师您好,对于进程切换这样理解对吗:

    假设单核环境下,进程切换顺序为:A->B->C->A

    //A->B时三个参数的值为prev=A,next=B
    //而且这句话是在A里面运行的
    switch_to(prev, next, prev);

    //第二句话保证执行顺序
    barrier();

    //第一句话与第三句话之间,CPU时间给到B、C进程,A进程等待
    //进程C调用了switch_to以后,prev=C
    //CPU时间给到A,A继续执行
    return finish_task_switch(prev);

    另外,其实是内核需要知道切换顺序,并非是用户态的进程需要知道切换顺序,我这样理解对吗?
    展开
    
    
  • 陈志恒
    2019-11-25
    摘抄:这一节我们讲主动调度的过程,也即一个运行中的进程主动调用 __schedule 让出 CPU。在 __schedule 里面会做两件事情,第一是选取下一个进程,第二是进行上下文切换。而上下文切换又分用户态进程空间的切换和内核态的切换。
    
    
  • 秋天
    2019-10-30
    表示 已经更不上思路了 代码也看不太懂 请刘总指点
    
    
  • JT
    2019-09-29
    个人对于最后一部分指令指针的理解:每个进程都会调用schedule函数进行调度,那么在调用schedule函数的时候,相应的调用过程都会被压入内核栈中,schedule切换进程仅仅只是切换内核栈,而当schedule返回的时候,就会返回到内核栈中压入的函数(此内核栈为切换后进程的内核栈,所以会返回到切换后进程调用schedule时候的函数位置)
    
    
  • 石将从
    2019-09-20
    指针指令这块很懵,先跳过吧
    
    
  • 天王
    2019-09-04
    调度中 1 进程调度 做着做着A项目,然后去做B项目去了,主要有2种方式,一种是A项目,sleep休息一会,或者在等待IO操作,让出CPU去做B项目,这种叫主动调度,另一种是另一种是A项目做的时间长,B项目也需要做一下,就把CPU让给A项目。2 主动调度 2.1 常见的2种场景,btrfs btree的文件系统 ,在文件写入的时候,写入需要一段时间,不需要CPU,还不如让给其他线程,另一种场景 从tap网络设备准备一个读取,当没有数据到来,需要等待,也可以把CPU让给其他进程。计算机主要处理计算,网络,存储3个方面,计算主要是CPU和内存,存储和网络,多是和外部设备的合作,在操作外部设备的时候,一般会让出CPU,选择调用schedule函数。3 schedule函数的调用过程 3.1 schedule有一个主函数,_schedule函数,函数定义如下:struct task_struct *prev,*next,long *switch_count,struct rq_flags rf;struct rq *rq;int cpu;cpu=smp_processor_id();rq=cpu_rq(cpu);prev=rq->curr;3.2 首先在当前CPU上取出任务队列rq,将prev只向当前的进程curr,因为一旦切换下来,就成前任了,3.3取出下一个任务 next=pick_next_task,大部分是普通进程,调用的是fair_schedule_class类,调用的是pick_next_task_fair,cfs_rq ,schedule_entity se,task_struct *p,int new_tasks,对于cfs调度类,取出相应的队列cfs_rq,取出当前正在运行的任务curr,如果进程处于运行状态,则调用update_curr更新vruntime,然后pick_next_entity取出红黑树最左边的节点,se=pick_next_entity(cfs_rq,curr);p=task_of(se);得到下一个调度实体对应的task_struct,如果发现和前任不一样,更新前任的vruntime,put_prev_entity放回红黑树,set_next_entity将设为当前任务,开始进行上下文切换,继任者进程开始正式运行。4 进程上下文切换 进程切换主要干2件事情,一是切换进程空间,二是切换寄存器和CPU上下文,4.1 context_switch函数,主要是内存空间的切换,switch_to 寄存器和栈的切换,他调用到了_switch_to_asm,栈的切换,切换的是栈顶指针。switch_to函数里面有一个Per CPU的结构体tss task state segment,这里面维护着所有的寄存器,还有一个tr 任务寄存器,指向某个进程的TSS,会给每个CPU都关联一个tss,有一个tr一直指向tss,在linux中参与进程切换的只有栈顶指针寄存器,有一个成员变量thread_struct thread,指向进程切换时需要切换的寄存器,进程切换,就是将tss的值更新到cpu里的TR指向的tss_struct。5指令指针的保存与恢复 ,5.1 从进程A切换到进程B,切换内存空间的时候,在内存空间里的用户栈已经切换,_switch_to里面将_curren_task指向当前的task_struct,里面的void * stack指针指向的就是内核栈,_switch_to_asm已经切换了栈顶指针,并且在_switch_to_加载到了tss里面,用户栈的栈顶指针,如果当前在内核栈,那他在内核栈顶部的pt_regs,当从内核返回用户态的时候,pt_regs里面所有的在用户态开始运行的上下文信息,就可以开始运行了。5.2 进程的调度都会调用_schedule函数,进程调度第一定律
    展开
    
    
  • 俩孩儿他爸
    2019-08-26
    "通过三个变量 switch_to(prev = A, next=B,last = C);A 进程就明白了,我当时被切换走的时候,是切换成 B,这次切换回来,是从 C 回来的";这段话中,进程A从进程C切换回来时,进程A内核栈中变量的定义:prev=last=C,netxt=B,由于当前就是在进程A的地址空间里,所以,可以进程A可以说,当年我被切换到进程B,现在,由进程C又切换回来了。



    作者回复: 是的

    
    
  • kdb_reboot
    2019-07-27
    看起来ps 里面的TIME就是进程的 cpu runtime吧; 查看上下文切换,可以用cat /proc/x/status

    作者回复: 赞

    
    
  • kdb_reboot
    2019-07-27
    补充一下,看了最后的那张图,感觉切换,就是切内核态的 stack/rsp/pc, 这样下一个任务就能找到在哪执行了,以及继续怎么执行, 而内核态共享一片内存空间,所以不需要mm_switch,切换完了,返回用户态,用户态的stack/rsp/pc都被切换了, 而用户态的内存空间需要单独切换
    老师,我理解的对吧?

    作者回复: 是的

    
    
  • kdb_reboot
    2019-07-27
    关于指令指针的讲解,厉害了...
    专栏有时候可以反者看, 先看最后总结,然后往上顺藤模块看你的分析
    同时在读的书:lkd/ulk, 推荐给大家

    作者回复: 先看总结也挺好的

    
    
  • garlic
    2019-07-20
    通过ps -o etime= -p "$$" 可以 查看,进程的运行时间, 通过/proc/{pid}/status 中的 voluntary_ctxt_switches: nonvoluntary_ctxt_switches: 可以看到主动调度和抢占调度的次数, 也可以单独安装sysstat 使用pidstat -w 查看相关进程的调度信息 https://garlicspace.com/2019/07/20/查看进程运行时间及上下文切换次数/

    作者回复: 赞

    
    
我们在线,来聊聊吧