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

30 篇文章 78 订阅

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

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值