• 哈工大李治军老师操作系统笔记【11】:操作系统的那棵“树”(Learning OS Concepts By Coding Them !)


    0 回顾

    多线程操作是进程切换的核心

    1 CPU

    1.1 运转CPU

    • 怎么使用CPU?不断的取址执行就是使用CPU
    • 单道程序,CPU的利用率很低,所以提出多道程序的概念,程序之间的切换可以用栈来做

    在这里插入图片描述


    • 单道程序设计效率不行
      在这里插入图片描述

    • 转入多道程序设计
    • 开始切换
      在这里插入图片描述

    1.2 利用栈进行程序切换

    • 利用栈来进行程序之间的切换
      在这里插入图片描述

    在这里插入图片描述


    • Yield就是一种跳转
    • Yield()找到下一个TcB→找到新的栈→切到新的栈

    • 因为不能一直执行用户态,这样内核进程就切不回去了
    • 所以引出了内核态

    在这里插入图片描述


    1.3 引入内核态

    • 用户级线程存在缺点,操作系统内核调用阻塞时无法感知用户级线程
      • 引出内核级线程及其切换

    在这里插入图片描述


    • 既然指令的切换就是栈的切换实现,那么到了内核是不是也有栈?
    • 从用户栈到内核栈,内核栈到TCB,TCB直接进行切换,TCB切换完了以后内核栈切换,切换完了跟着用户栈切换,这就是两套栈的切换,这是思维递进的结果

    1.4 idea的实现

    • 提出一个简单、清晰、明确的目标:在屏幕上交替输出A和B
    • 从用户代码开始,注意AB指的是主进程

    在这里插入图片描述


    在这里插入图片描述


    • int 0x80 -> sys_fork -> copy_process

    在这里插入图片描述


    • jne就是根据返回值进行操作判断
    • 上方是进行cmp比较,看res和0是否相等,等于就执行子进程;不等于就跳转到208接着运行父进程

    • int 0x80 -> sys_fork -> copy_process
    • 以下是int 0x80之后进入内核的过程
    • 进入内核后就有用户栈和内核栈
    • 进入到内核当中,int 0x80就是要执行system_call
    • 执行完system_call就要执行sys_fork
    • 执行完sys_fork就要跳转到copy_process
      在这里插入图片描述

    • 其中copy_process就是在内核中做出新的一套PCB来,然后做出一个新的栈

    在这里插入图片描述


    • 然后把PCB里面的tss都写好,eip都置好
    • eip就是父进程int 0x80的代码
    • 再把eax置成0
    • 内核中的子进程做成这个样子
    • tss->eip就是地址为100的那条指令

    在这里插入图片描述


    • 现在要想在屏幕上打印A,eip&esp这些栈的指针以及执行的地址都写到子进程里面,当子进程再开始调度的时候,屏幕上就会出现A了
    • 所以如果在程序内核当中做出这种结构体,栈也有空间存放,eip指向的就是print(A)的代码,这样就不愁屏幕上打印不出A了
    • 所以写代码的目的就是如此

    1.5 返回


    在这里插入图片描述


    • 执行完copy_process,父进程就要往回返了,父进程退回,进行调度,因为调度才能让屏幕打印出A来(就是让那个打印A的语句去执行)
    • system_call中,从call_sys_call_table返回后,会判断当前进程/线程是否阻塞,如果阻塞,就要调用reschedule
    • 之前还提到过,需要判断是否时间片已经用完,下面的代码中没有写
    • 现在是打印指令可以执行,但是不能执行,真正执行还得是在适当地方加上schedule
    • 此处返回是哪里做的?是在中断返回的时候加上schedule
    • 现在fork()并不调用schedule,因为时间片没有用完

    1.6 再次调用fork()

    • 因为上文只创建好了打印A的进程,所以还得创建打印B的进程
    • 再一次fork(),创建输出B的进程,下图中,主进程AB、子进程A、子进程B形成了一个就绪队列

    在这里插入图片描述


    • 所以要想在屏幕上打印出AB两个字母,就必须在内核当中做出两套结构
    • 又一次会产生PCB和栈
    • 两个结果非常相似

    在这里插入图片描述


    • 创建好两个打印进程之后,就该等待了wait(),所以父进程调用wait()
    • 这个系统调用就是把自己的状态变为阻塞,然后调用schedule()

    在这里插入图片描述


    • 现在schedule可以做最简单的实现:选择队首的就绪线程/进程
    • 由一个进程产生出打印A和打印B的两个子进程,子进程PCB分别贴好,贴的对应的就是打印A,B的函数,然后父进程阻塞调用schedule(),然后选择其中一个进程,选择第一个就行(打印A),就完成了切换
    • 打印A后要打印B就要进行切换

    1.7 switch_to切换


    在这里插入图片描述


    • 选择了一个进程后(选择了A),调用switch_to进行切换,使用TSS方案

      • 将当前CPU的现场信息保存到AB的TSS中
      • 将A的TSS中的信息恢复到CPU现场
    • A开始执行,不断的打印A


    在这里插入图片描述


    • 如图,eip = 100就继续往下执行,res返回的是0,所以不断的printf("A")
    • A不会进入阻塞,也没有主动释放CPU控制权,那怎么样才可以调度切换为B呢?
    • 什么时候调用schedule()呢?
    • 出时钟中断的慨念

    1.8 调用schedule()


    • 需要调度,才能打出B
    • 调度点在哪里?
      在这里插入图片描述

    • current是当前进程的PCB,current->counter是当前进程的剩余时间片
    • 每次时钟中断,都上当前进程的current->counter,如果减到了0,那么就调用schedule()

    疑问:一直学下来,只有在这个时钟中断的处理函数中,才会对counter–,并目立即判断了是否等于0,那么上一节视频中提到了系统调用的最后也要判断时间片是否多余?


    • 调度必须调用schedule()
    • 调用schedule()在哪里?在内核,所以必须进入到内核
    • 怎么进入内核?中断
    • 什么中断?
    • 时钟中断
      在这里插入图片描述

    • 时钟中断,使得当前进程的剩余时间片为0,那么就从A进程切换到B进程,B进程开始不断的输出B
    • 打一会儿A再打一会儿B,就能交替打印AB

    在这里插入图片描述


    在这里插入图片描述

    • 这里是把B的PCB的tss,esp赋值成300,就对应了打印B的指令
    • 打完了B后,就完成了切换

    在这里插入图片描述


    • 接下来不断的时钟中断,当进程B的剩余时间片为O时,就再切换为进程A(根据调度算法的不同,当然有可能还是B本身),以此类推,不断循环,那么屏幕上就交替的输出A和B

    在这里插入图片描述


    在这里插入图片描述


    在这里插入图片描述


    2 总结

    自己头脑写出一个代码,就完成了自己操作系统0.01

  • 相关阅读:
    JQuery
    如何在 macOS 中删除 Time Machine 本地快照
    谷粒学苑_第四天
    Windows系统如何部署Wing FTP Server与公网远程访问【内网穿透】
    【C++】Ubuntu18.04安装C++的IDE——KDevelop
    STM32F103C8T6 驱动MTS4温度传感器
    OG-488 SE|198139-51-4|Oregon Green 488 Succinimidyl Ester
    早餐与风景
    CSP赛前复习总结
    【接口测试】POST请求提交数据的三种方式及Postman实现
  • 原文地址:https://blog.csdn.net/weixin_44673253/article/details/126835394