草庐IT

JOS和抢占式内核的一点随想

飞机云_ 2023-03-28 原文

几年前,我在面试现在的公司的时候,被问过一个很经典的问题

抢占式的内核是怎么工作的?

那个时候我对OS的调度流程理解很肤浅,并且也没有过hands-on experiences,读Linux内核的一些书其实也没有真正理解整个软件+硬件的行为。

只能凭着过去 CS 537和本科时候一点OS课的经验,泛泛的回答了一点 time slice,调度器,优先级之类的名词,结合自己想象中的流程瞎扯了一通。

听完我的回答后,我还记得谷雨并不满意的说道,“不是这样的。” 我们那个时候在用RTOS,做开发时,对进程调度,抢占式内核的理解是很重要的。

不过谢天谢地,最后大佬们还是offer了我,把对OS渣理解的我捞了起来。但是这个事情让我一直耿耿于怀,以至于后面有机会学习6.828的JOS,真正自己动手做round-robin的调度器、抢占式内核的时候,这个知识点还是我着重想去理解的地方。

 

闲话少说,关于JOS的preemptive multitasking,这里记录一些重要的细节以及个人理解:

  1. JOS 的preemptive 只能在CPU运行在user mode的时候进行抢占,进入kernel 后会关中断,就无法触发抢占操作。(注册IDT时必须走interrupt gates,而不是trap gates)
    1. The IF (interrupt-enable flag) controls the acceptance of external interrupts signalled via the INTR pin. When IF=0, INTR interrupts are inhibited; when IF=1, INTR interrupts are enabled. As with the other flag bits, the processor clears IF in response to a RESET signal. The instructions CLI and STI alter the setting of IF.

      CLI (Clear Interrupt-Enable Flag) and STI (Set Interrupt-Enable Flag) explicitly alter IF (bit 9 in the flag register). These instructions may be executed only if CPL <= IOPL. A protection exception occurs if they are executed when CPL > IOPL.

      The IF is also affected implicitly by the following operations:

      Interrupts through interrupt gates automatically reset IF, disabling interrupts. (Interrupt gates are explained later in this chapter.) 

      (x86 i386手册)

    2. 我们用了 i386 interupt gates注册了INT 31和所有的外部中断,所以发生系统调用和外部中断的时候,会自动reset EFLAGS里的IF 。

    3. 设置系统调用和抢断时钟中断的gate (第二个参数为0, 0 for an interrupt gate)
      SETGATE(idt[T_SYSCALL], 0, GD_KT, t48_entry, 3); 
      SETGATE(idt[IRQ_OFFSET + IRQ_TIMER], 0, GD_KT, irq_timer, 0);
    4. x86 setgate x86 setgate
       1  // Set up a normal interrupt/trap gate descriptor. 
       2 // - istrap: 1 for a trap (= exception) gate, 0 for an interrupt gate. 
       3 //     see section 9.6.1.3 of the i386 reference: "The difference between
       4 //     an interrupt gate and a trap gate is in the effect on IF (the 
       5 //     interrupt-enable flag). An interrupt that vectors through an 
       6 //     interrupt gate resets IF, thereby preventing other interrupts from
       7 //     interfering with the current interrupt handler. A subsequent IRET 
       8 //     instruction restores IF to the value in the EFLAGS image on the 
       9 //     stack. An interrupt through a trap gate does not change IF." 
      10 // - sel: Code segment selector for interrupt/trap handler 
      11 // - off: Offset in code segment for interrupt/trap handler 
      12 // - dpl: Descriptor Privilege Level - 
      13 // the privilege level required for software to invoke 
      14 // this interrupt/trap gate explicitly using an int instruction. 
      15 #define SETGATE(gate, istrap, sel, off, dpl) \ 
      16 { \ 
      17 (gate).gd_off_15_0 = (uint32_t) (off) & 0xffff; \
      18  (gate).gd_sel = (sel); \
      19  (gate).gd_args = 0; \
      20  (gate).gd_rsv1 = 0; \
      21  (gate).gd_type = (istrap) ? STS_TG32 : STS_IG32; \
      22  (gate).gd_s = 0; \
      23  (gate).gd_dpl = (dpl); \ (gate).gd_p = 1; \
      24  (gate).gd_off_31_16 = (uint32_t) (off) >> 16; \
      25  } 
    5. 内核在通过 gate时(无论从 kernel mode 还是user mode),都会保存当前的eflags,这是i386的硬件行为(跳转到 ISR之前)
    6. 退出内核或者中断时,使用 IRET 退出,会自动pop出来eflags。如果之前user mode打开了中断,这时就会重新恢复中断
    7. 退出中断或者系统调用的代码
       env_pop_tf(struct Trapframe *tf) 
      {
       // Record the CPU we are running on for user-space debugging 
      curenv->env_cpunum = cpunum();
      asm volatile( "\tmovl %0,%%esp\n"
        "\tpopal\n" 
        "\tpopl %%es\n"
        "\tpopl %%ds\n" 
        "\taddl $0x8,%%esp\n" /* skip tf_trapno and tf_errcode */ 
        "\tiret\n" 
        : : "g" (tf) : "memory");
      
      panic(
      "iret failed"); /* mostly to placate the compiler */ }

       

  2. 关于 IRET
    1. IRET是个很复杂的操作。行为取决于 中断进入时栈上的EFLAGS以及当前的EFLAGS 中的VM项,以及NT 项。
    2. IRET可以进行的跳转模式
      1. Return from virtual-8086 mode.
      2. Return to virtual-8086 mode.
      3. Intra-privilege level return.
      4. Inter-privilege level return.
      5. Return from nested task (task switch).
    3. 在JOS里,由于kernel mode下关了中断,所以所有的iret最终都会到user mode。这个和Linux 2.6以前的行为是一致的。Linux 2.6以后加了内核态抢占,就复杂了很多
    4. IRET最核心的还是 恢复几个寄存器:
    5. 所谓中断上下文,从硬件的角度来看,就是kernel stack上会多几个旧的寄存器值,并且切换ss + cs + ip,再加上关闭中断。 至于push哪些,取决于是否穿越 privilege。我们现实生活中说的中断上下文(只能使用自旋锁,以及不能sleep等约束),是可以由OS软件定义的行为,你自己也可以写一个一进ISR就把中断打开的软件(这当然很蠢)。
  3. 关于Blocking的Syscall
    1. JOS的抢占式内核,只能做User Mode下的抢占。所以系统调用间的控制转移,是cooperative模式,而非preemptive模式(这个名词时LKD里面提到的)
    2. 所谓合作式,就是在某个blocking系统调用 yield() + not_runnable 之后,后半部分,需要另外的系统调用来处理。
    3. 这个地方的处理,除了准备数据,就是准备返回值(ax)。一旦从not_runnable改变为runnable,那么重新运行的点,JOS会直接返回user mode
    4. 因为env_run会直接pop_tf,tf就是进入sys_call时的user mode点。
    5. sys_ipc_recv,yield之后的代码是永远不会被执行的,除非改变stack frame。
    6. sys_ipc_send 需要 把rcv调用的ax返回值准备好。否则会返回syscall num。
  4. Linux内核的抢占方式
    1. 内核在2.6以后全面支持了 kernel mode的抢占。一旦打开中断 (或者放掉锁, 大多数锁是会关掉中断的),在kernel mode也会被抢占
    2. yield时的context switch,会把 kernel stack也保存起来。通过_switch_to调用,部分clobbers寄存器是通过 call 自动压栈 来保存的
    3. signal 会将wait_interruptable的 process唤醒,所以要用mesa语义来检查是否是spurious唤醒。请使用推荐的wait - awake流程
    4. 由于有内核抢占 + 信号中断的情况,可能还是要注意可重入的问题

 

 先写到这里吧

有关JOS和抢占式内核的一点随想的更多相关文章

  1. ruby - 为什么 Object 在 Ruby 中既包含内核又继承它? - 2

    在Ruby(1.8.X)中为什么Object既继承了内核又包含了内核?仅仅继承还不够吗?irb(main):006:0>Object.ancestors=>[Object,Kernel]irb(main):005:0>Object.included_modules=>[Kernel]irb(main):011:0>Object.superclass=>nil请注意,在Ruby1.9中情况类似(但更简洁):irb(main):001:0>Object.ancestors=>[Object,Kernel,BasicObject]irb(main):002:0>Object.included

  2. ruby - 编写一个 ruby​​ 命令行应用程序;最好的方法是做到这一点? - 2

    我有一个正在开发的命令行Ruby应用程序,我想允许它的用户提供将在部分过程中作为过滤器运行的代码。基本上,应用程序是这样做的:读入一些数据如果指定了过滤器,则使用它来过滤数据处理数据我希望过滤过程(第2步)尽可能灵活。我的想法是,用户可以提供一个Ruby文件,该文件设置一个已知常量以指向实现我定义的接口(interface)的对象,例如:#user'sfilterclassMyFilterdefdo_filter(array_to_filter)filtered_array=Array.new#domyfilteringonarray_to_filterfiltered_arrayen

  3. ruby - 为什么会存在 Ruby 模块内核? - 2

    在Ruby中的面向对象设计一书中,SandiMetz说模块的主要用途是用它们实现鸭子类型,并将它们包含在每个需要的类中。为什么RubyKernel是包含在Object中的模块?据我所知,它没有在其他任何地方使用。使用模块有什么意义? 最佳答案 理想情况下,Methodsinspirit(适用于任何对象),即使用接收器的方法,应在Object上定义上课,而Procedures(全局提供),即忽略接收者的方法,应该收集在Kernel中模块。Kernel#puts,例如不对其接收者做任何事情;它不调用它的私有(private)方法,它不访

  4. 驱动开发:内核无痕隐藏自身分析 - 2

    在笔者前面有一篇文章《驱动开发:断链隐藏驱动程序自身》通过摘除驱动的链表实现了断链隐藏自身的目的,但此方法恢复时会触发PG会蓝屏,偶然间在网上找到了一个作者介绍的一种方法,觉得有必要详细分析一下他是如何实现的进程隐藏的,总体来说作者的思路是最终寻找到MiProcessLoaderEntry的入口地址,该函数的作用是将驱动信息加入链表和移除链表,运用这个函数即可动态处理驱动的添加和移除问题。MiProcessLoaderEntry(pDriverObject->DriverSection,1)添加MiProcessLoaderEntry(pDriverObject->DriverSection,

  5. ruby - 为什么我可以使用像 `puts` 这样的内核单例方法? - 2

    在Ruby中,方法puts是Kernel的单例方法模块。通常,当一个模块是included或extend由另一个模块编辑,该模块(但不是它的单例类)被添加到继承树中。这有效地使模块的实例方法可用于模块或其单例类(分别用于include和extend)......但混合模块的单例方法仍然无法访问,因为单例类从未将模块添加到继承树中。那么为什么我可以使用puts(和其他内核单例方法)?Kernel.singleton_methods(false)#=>[:caller_locations,:local_variables,:require,:require_relative,:autolo

  6. ruby - Sidekiq 可以利用多个 CPU 内核吗? - 2

    我是Sidekiq的新手,将它与AmazonEC2实例上的Ruby结合使用,以使用ImageMagick处理图像来完成一些工作。在运行它时,我意识到每个工作人员都在同一个核心上运行。我使用EC2c3.2xlarge机器,它们有8个内核。它显示CPU使用率为15%,但一个内核使用了100%,而其他内核使用了0%。Sidekiq可以为不同的worker使用不同的CPU内核吗?如果可以,这种低效率是由ImageMagic造成的吗?我怎样才能让它使用其他内核? 最佳答案 如果您想使用MRI使用多个内核,则需要启动多个Sidekiq进程;为您

  7. ruby - 在 Ruby 中更优雅的方式来做到这一点 - 2

    我从Ruby开始,每天都在寻找新的、更短、更优雅的方式来编写代码。在解决ProjectEuler问题时,我写了很多类似的代码ifbest_score有没有更优雅的写法? 最佳答案 best_score=[best_score,current_score].max参见:可枚举。max免责声明:虽然这更具可读性(恕我直言),但性能较差:require'benchmark'best_score,current_score,n=1000,2000,100_000Benchmark.bmdo|x|x.report{n.timesdobest_

  8. ruby - 此修改后的二十一点游戏的最佳获胜策略是什么? - 2

    问题有没有可以保持的最佳值(value),这样我才能赢得尽可能多的比赛?如果是这样,那是什么?编辑:是否可以为给定的限制计算出确切的获胜概率,而与对手的所作所为无关?(自大学以来,我还没有做过概率和统计)。我有兴趣将其作为与模拟结果进行对比的答案。编辑:修复了我算法中的错误,更新了结果表。背景我一直在玩改进的二十一点游戏,其中对标准规则进行了一些相当烦人的规则调整。我已将与标准二十一点规则不同的规则斜体化,并为不熟悉的人添加了二十一点规则。修改二十一点规则正是两个人类玩家(经销商无关)每个玩家面朝下发两张牌双方玩家_ever_都不知道对手纸牌的_any_的值在_both_完成手牌之前,

  9. 学习 Linux 内核书籍推荐 - 2

    原文链接,欢迎关注:你为什么学习Linux内核?-CodeAllen的回答-知乎https://www.zhihu.com/question/31369673/answer/2894981254主要是工作需要,其实对于我自己的工作来说,在Linux开发的具体业务和算法才是重要的,内核的知识并没有那么重要,对于很多应用开发来说也差不多,最多也是先看看用户态即可。但是出于对技术的追求还是在通过看书和阅读源码学习。书的话主要是看了下边本,其他乱七八糟的还有一些不列举了:深入Linux内核架构这本可能不是那么经典,看这本的原因是网上找到了高清的PDF书籍,于是就画时间看了,结论是非常不错,我很多内核的

  10. ruby - 包含/扩展内核不会在主 :Object 上添加这些方法 - 2

    我正在尝试向Kernel添加一个方法模块,而不是重新打开Kernel并直接定义一个实例方法,我正在编写一个模块,我想要Kernel至extend/include那个模块。moduleTalkdefhelloputs"hellothere"endendmoduleKernelextendTalkend当我在IRB中运行它时:$helloNameError:undefinedlocalvariableormethod`hello'formain:Objectfrom(irb):12from/Users/JackC/.rvm/rubies/ruby-1.9.2-p290/bin/irb:16

随机推荐