L4:shell和os

lab通关记录

MIT-6.828实验通关记录

视频课第 4 讲:Shell & OS 组织

讲座主题:

  1. 内核系统调用API

  2. 细节和设计

  3. 通过shell和作业2说明

概览图

  1. 系统程序根据权级分为:用户态/内核态

  2. 进程 = 隔离的地址空间 + 线程

  3. 进入内核态过程:打开应用 -> printf() -> 系统接口write() -> 调用SYSTEM CALL函数 -> sys_write() -> …

  4. 用户级库是应用的私有业务

  5. 内核内部函数不能被用户调用,只能通过调用接口来间接使用

  6. xv6 有几十个系统调用;Linux 几百个左右

  7. 今天的细节主要是关于 UNIX 系统调用 API:xv6、Linux、OSX、POSIX 标准等的基础。 jos 有非常不同的系统调用;您将通过 jos 自己构建 UNIX 调用

作业解决方案

  • 让我们复习视频课 2 (sh.c)
    • 执行
      为什么有两个 execv() 参数?:执行文件定位和文件所需参数列表
      参数会发生什么?被定位,然后第二个传入执行程序
      当 exec’d 进程完成时会发生什么?执行加载的文件
      execv() 可以返回吗?失败会返回,成功不会
      命令完成后shell如何继续?子shell进程转为所需进程,主shell不被影响

    • 重定向
      exec’d 进程如何了解重定向? 不同的fd 表中,使用自己的fd来引用同一个文件
      重定向(或错误退出)是否会影响主 shell?不会,通过子进程执行这一过程

    • 管道
      ls | wc -l
      如果 ls 产生输出的速度比 wc 消耗它的速度快怎么办?管道buffer的多余的东西避免丢失ls输出
      如果 ls 比 wc 慢怎么办?wc等待ls产生,就立即进行
      每个命令如何决定何时退出?执行后退出
      如果读者没有关闭写端怎么办?无所谓,每个进程的端并不互相影响,进程退出后消失
      如果作者没有关闭读取端怎么办?无所谓,每个进程的端并不互相影响,进程退出后消失
      内核如何知道何时释放管道缓冲区?当内核结束

    • shell 如何知道管道已完成?wc执行成功后子进程退出,返回信号给shell父进程
      例如 ls | sort | tail -1

    • 什么是进程树?

      1
      2
      3
      4
      5
      6
      给定命令的进程树: ls | (sort | tail -1)
      sh进程
      sh1进程
      (ls进程 sh2进程)
      (sort进程 tail进程)
      我们可以认为:自左向右执行命令(实际的进程切换不影响这个简单的”认为“,只是提高了效率)
    • shell 需要分叉这么多次吗?

      • 如果 sh 没有为 pcmd->left fork 怎么办?[尝试一下]
        即调用 runcmd() 而不分叉?sh2进程下的子进程会无限等待管道的输入区
      • 如果 sh 没有为 pcmd -> right fork怎么办?[尝试一下]
        用户可见的行为会改变吗?会仅仅执行ls
        sleep 10 | echo hi你会发现hi之后等待了十秒,说明进程是交错执行,造成了并行假象,而不是左边进程执行完毕后右边才执行
    • 为什么只有在进程都启动后才使用wait()?为了子进程交错执行成功,否则父进程和左进程执行,右进程不执行。
      如果 sh wait()ed for pcmd->left 在第二个 fork 之前会怎样?[尝试一下]可能第一个进程创造了很多buf但是第二个进程没有及时执行,造成效率低下
      ls | wc -l
      cat < big | wc -l//将big输入到cat,输出流重定向屏幕到管道,wc -l执行使用管道,最后得到统计结果而不是文件内容

    • 重点:系统调用可以有多种组合方式
      获得不同的行为。

    • 综上所述,管道|不仅完成了进程通信,即使不使用缓冲区,这个命令也规定了子进程运行顺序:并行

shell挑战题

  • 如何用“;”实现自定义子进程执行顺序?
    gcc sh.c; ./a.out
    echo a; echo
    为什么在 scmd-> 之前等待()?[尝试一下]:如果并行,那么文件执行失败,等同于&&命令
  • 如何实现“&”?
    $ sleep 5 & //&在命令后代表后台执行:和父进程并行
  • $wait//父进程等待sleep子进程:改变后台执行状态
    & 和 wait 的实现是主要的——为什么?见上面回答
    如果后台进程退出而 sh 等待前台进程怎么办?子进程退出会给父进程发送一些信息,因此wait后会在sleep进程输出结果多+done ,还有sleep 5这样的字眼
  • 如何实现嵌套?
    (echo a; echo b) | wc -l
    *我的 ( … ) 实现仅在 sh 的解析器中,而不是 runcmd()
    sh 管道代码不必知道它适用于序列,这很巧妙,只需要知道和buffer的动作交互即可
  • 这些有什么不同?
    echo a > x ; echo b > x//字符a输入文件(没有就新建)x,然后b再输入x,覆盖a的内容
    ( echo a ; echo b ) > x//将a和b的输出流一起使用>重定向到文件x
    避免覆盖的机制是什么?括号。
  • ps:如果你想要在第一行命令基础上完成目标,对b替换>为>>,变为append模式即可

UNIX 系统调用观察

  • fork/exec 拆分看起来很浪费——fork() 复制 mem,exec() 丢弃。
    为什么不例如 pid = forkexec(path, argv, fd0, fd1) ?
    因为fork/exec 拆分很有用:
    fork(); I/O 重定向;runcmd()
    或者fork(); 复杂的嵌套命令;exit。
    如 ( cmd1 ; cmd2 ) | cmd3
    fork() 单独:并行处理
    exec() 单独: /bin/login … exec(“/bin/sh”)
    fork 对于小程序来说效率高——在我的机器上:
    fork+exec 需要 400 微秒(2500/秒)
    单独 fork 需要 80 微秒(12000 / 秒)
    涉及到一些技巧——您将在 jos 中实现它们!

  • 文件描述符设计:

    • FD 是一个句柄的间接级别:就是作为偏移的多级指针啦
      • 进程的真实 I/O 环境隐藏在内核中
      • 保留在 fork 和 exec 上
      • 将 I/O 设置与使用分开
      • 想象写文件(文件名,偏移量,缓冲区大小)
    • FD 有助于使程序更通用:不需要特殊情况
      文件 vs 控制台 vs 管道
  • 理念:一小组概念上简单的调用,它们结合得很好
    例如 fork()、open()、dup()、exec()
    命令行设计有类似的方法
    ls | wc -l

  • 为什么内核必须支持管道——为什么不用 sh 模拟它们,例如
    ls > 临时文件;wc -l < 临时文件:进程不同,临时文件没法传递

  • 系统调用接口简单,只有整数和字符缓冲区。为什么没有 open()
    返回指向内核文件对象的指针引用?open调用系统接口,如果返回指针就破坏了内核态和用户态的隔离,容易操作不当破坏内核区

  • 核心 UNIX 系统调用是古老的;他们坚持得好吗?
    是的; 非常成功,并且经过多年的发展
    历史:设计迎合命令行和软件开发
    系统调用接口方便程序员使用命令行用户,如命名文件、管道等,对开发、调试、服务器维护很重要。
    但是 UNIX 的想法并不完美:
    程序员对于系统调用 API 的便利性来说通常不是很好
    程序员使用诸如 Python 之类的库来隐藏系统调用的详细信息
    应用程序可能与文件和c几乎没有关系,例如在智能手机上
    一些 UNIX 抽象不是很有效
    用于多 GB 进程的 fork() 非常慢
    FD 隐藏了可能很重要的细节:
    例如磁盘文件的块大小
    例如网络消息的时间和大小
    所以有很多关于替代计划的工作
    有时为现有的类 UNIX 内核提供新的系统调用和抽象
    有时内核应该做什么的全新方法
    *在学习的时候你应该问“为什么这样?设计 X 不是更好吗?”

操作系统组织

  • 如何实现系统调用接口?

  • 为什么不只是一个lib库?
    即没有内核,直接在硬件上运行app+library。
    灵活:如果不需要,应用程序可以绕过库
    应用程序可以直接与硬件交互
    库适用于单一用途的设备
    但是如果计算机用于多项活动呢?

  • 内核的关键要求:
    隔离:内核态和用户态隔离,用户进程之间也隔离
    多路复用:多个进程执行合为一个cpu来做
    相互作用:进程通信

  • 有用的方法:抽象资源而不是原始硬件交互:你想写软件的时候写硬件交互代码吗?
    文件系统,而不是原始磁盘:你想自己计算磁盘扇区位置吗?
    进程,而不是原始 CPU/内存:你想小心翼翼不要让机子爆炸吗?
    TCP,而不是以太网数据包:你想手动解析,思考数据包的内容吗
    抽象通常可以实现隔离、多路复用和交互
    也更方便便携:即计算机领域,任何一个问题都可以加一个抽象层来实现:)

  • 从隔离开始,因为这通常是最困难的要求。

  • 隔离目标:
    应用程序不能直接与硬件交互
    应用程序不会损害操作系统
    应用之间不能直接影响
    应用程序只能通过操作系统界面与世界交互

  • 处理器提供有助于隔离的机制

    • 硬件提供用户模式和内核模式
      • 某些指令只能在内核模式下执行
        设备访问、处理器配置、隔离机制
    • 硬件禁止应用程序执行特权指令
      • 而是陷入内核模式
      • 内核可以清理(例如,终止进程)
    • 硬件让内核模式在用户模式上配置各种约束
      最关键:页表将用户软件限制在其自己的地址空间
  • 内核建立在硬件隔离机制之上

    • 操作系统以内核模式运行

      • 内核是一个大程序
        服务:进程、文件系统、网络
        低级:设备、虚拟内存
        所有内核都以完整的硬件特权运行(方便)
    • 应用程序在用户模式下运行

      • 内核设置每个进程隔离的地址空间
      • 系统调用在用户模式和内核模式之间切换
        应用程序执行特殊指令进入内核
        硬件切换到内核模式
        但仅限于内核指定的入口点
  • 在内核中放什么?

    • xv6 遵循传统设计:所有操作系统都在内核模式下运行

      • 一个带有文件系统、驱动程序等的大程序
      • 这种设计称为单片内核
      • 内核接口 == 系统调用接口
      • 好:易于子系统协作
        文件系统和虚拟内存共享一个缓存
      • 不好:交互很复杂
        导致错误
        内核中没有隔离
    • 微内核设计

      • 许多操作系统服务作为普通用户程序运行
        文件服务器中的文件系统
      • 内核实现了在用户空间运行服务的最小机制
        有记忆的进程
        进程间通信(IPC)
      • 内核接口 != 系统调用接口
      • 好:更多隔离
      • 差:可能很难获得良好的性能
    • exokernel:没有抽象
      应用程序可以半直接使用硬件,但 O/S 隔离
      例如应用程序可以读/写自己的页表,但 O/S 审计
      例如应用程序可以读/写磁盘块,但 O/S 跟踪块所有者
      好:对于要求苛刻的应用程序具有更大的灵活性
      jos 将是微内核和外内核的混合体

  • 可以在没有硬件支持的内核/用户模式的情况下进行进程隔离吗?
    是的!
    见 Singularity O/S,在学习的后期
    但硬件用户/内核模式是最受欢迎的计划

下一讲:x86 硬件隔离机制以及 xv6 的使用

Donate
  • Copyright: Copyright is owned by the author. For commercial reprints, please contact the author for authorization. For non-commercial reprints, please indicate the source.
  • Copyrights © 2020-2024 环烷烃
  • Visitors: | Views:

我很可爱,请我喝一瓶怡宝吧~

支付宝
微信