Loading...
  • [ ] 一些值得问的问题解决了吗: 查询到2.11

1.Overview

1.1 Goal:

  • 了解操作系统(O/S)的设计和实现
  • 有扩展小型O/S的实践经验
  • 有编写系统软件的实际经验

1.2 O/S的目标:

  • 在许多应用程序中复用硬件
  • 隔离应用程序以包含错误
  • 允许在协作应用程序之间共享
  • 安全控制共享
  • 不要妨碍高表现
  • 支持广泛的应用

1.3 组成:分层图

  • user applications: vi, gcc, DB, &c
  • kernel services
  • h/w: CPU, RAM, disk, net, &c

我们非常关心接口和内核内部结构


1.4 O/S内核通常提供的服务:

  • 进程(正在运行的程序)
  • 内存分配
  • 文件内容
  • 文件名、目录
  • 访问控制(安全)
  • 其他:用户、IPC、网络、时间、终端

1.5 应用程序-内核的接口:system call

1
2
3
fd = open("out", 1);
write(fd, "hello\n", 6);
pid = fork();

1.6 O/S设计实现既困难又有趣:

  • 无情的环境:古怪的h/w,难以调试
  • 高效vs抽象/可移植/通用
  • 功能强大vs简单的界面
  • 灵活与安全
  • 交互功能: fd = open(); fork()
  • 用途多种多样:笔记本电脑、智能手机、云计算、虚拟机、嵌入式
  • 不断发展的硬件:NVRAM、多核、快速网络

2.UNIX系统调用简介

2.1 应用程序通过系统调用看到O/S;这个界面将是一个大焦点


2.2 展示一些示例,并在xv6上运行它们

xv6具有与UNIX系统(如Linux)类似的结构。
但是要简单得多——能够消化所有的xv6

  • 开源,文档齐全,设计简洁,广泛使用
  • 如果您需要了解Linux内部,学习xv6将会有所帮助
  • 核心功能示例:虚拟内存、多核、中断等

2.3 例子:copy.c, 将输入复制到输出

1
$ copy

从输入中读取字节,并将其写入输出

  • read()和write()是系统调用
  • read()/write()参数1:是一个“文件描述符”(fd)
  • read()参数2:是指向要读取的内存的指针
  • read()参数3:是要读取的最大字节数
  • read()可以少读,但不能多读
  • 返回值:实际读取的字节数,或者-1表示错误

fd是什么:

  • 传递给内核,告诉它读/写哪个“打开的文件”
  • 一定是之前打开过的
  • FD连接到文件/设备/套接字等
  • 一个进程可以打开很多文件,有很多fd
  • UNIX约定:fd 0为“标准输入”,1为“标准输出”
  • FD是一个小整数
  • FD索引到由内核维护的每个进程表中
  • 不同的进程有不同的FD命名空间

2.4 例子:open.c,创建一个文件

1
2
$ open
$ cat output.txt

Open()创建一个文件,返回一个文件描述符(或-1表示错误)


2.5 当程序调用像open()这样的系统调用时会发生什么?

  • 看起来像一个函数调用,但实际上是一个特殊指令
  • 硬件保存了一些用户寄存器
  • 硬件提高特权级别
  • 硬件跳转到内核中已知的“入口点”

2.6 输入UNIX的命令行界面shell。

  • shell打印“$”提示符
  • shell允许您运行UNIX命令行实用程序
  • UNIX也支持其他类型的交互:window systems, GUIs, servers, routers, &c
  • 我们可以通过shell执行许多系统调用

2.7 例子:fork.c 创建一个新进程

shell为键入的每个命令创建一个新进程


1
$ echo hello

fork()系统调用创建一个新进程


1
$ fork

内核生成一个调用进程的副本

  • 指令、数据、寄存器、文件描述符、当前目录
  • 产生“父”进程和“子”进程
  • 父进程与子进程唯一的区别是:fork()在parent中返回一个pid,在child中返回0
  • fork.c的"fork() returned"在两个进程中执行,所以“if(pid == 0)”允许代码进行区分

pid(进程ID):一个整数,内核给每个进程一个不同的pid


2.8 例子:exec.c 用可执行文件替换调用进程

shell是如何运行程序的:

1
$ echo a b c
  • 程序存储在文件中:指令和初始存储器,由编译器和链接器创建
  • 有一个名为echo的文件,包含指令

1
$ exec

exec()用可执行文件替换当前进程

  • 丢弃指令和数据存储器
  • 从文件加载指令和内存
  • 保留文件描述符

exec(filename, argument-array)

  • argument-array保存命令行参数;exec传递给main()

2.9 例子:forkexec.c ,Fork()一个新进程,exec()一个程序

1
$ forkexec

forkexec.c包含一个常见的UNIX习语:

  • fork()子进程
  • exec()子进程中的命令
  • 父进程wait()s子进程完成

shell会为您输入的每个命令fork/exec/wait:

  • 在wait()之后,shell打印下一个提示符
  • 要在后台运行 – & – shell跳过wait()

Exit (status) -> wait(&status):

  • 状态约定:0 =成功,1 =命令遇到错误

2.10 例子:redirect.c ,重定向命令的输出

1
$ echo hello > out

fork, change FD 1 in child, exec echo


1
2
$ redirect
$ cat output.txt

open()总是选择最低的未使用FD;1由于关闭(1)


fork、fd和exec很好地相互作用以实现I/O重定向

  • 在执行之前,给child一个改变fd的机会
  • fd提供了间接指导:命令只使用fd 0和fd 1,不需要知道它们的位置
  • exec保留sh设置的fd
  • 只有sh需要知道I/O重定向,而不是每个程序

2.11 一些值得问的问题

  • Why these I/O and process abstractions? Why not something else?
  • Why provide a file system? Why not let programs use the disk their own way?
  • Why FDs? Why not pass a filename to write()?
  • Why are files streams of bytes, not disk blocks or formatted records?
  • Why not combine fork() and exec()?

2.12 例子:pipe1.c 通过pipe通信

shell是如何实现的

1
$ ls | grep x

1
$ pipe1

FD可以引用“pipe”,也可以引用文件


pipe()系统调用创建两个fd

  • 从第一个FD读取
  • 写入第二个FD

内核为每个管道维护一个缓冲区

  • write()追加到缓冲区
  • read()等待,直到有数据

2.13 例子:pipe2.c 进程间通信

pipe很好地与fork()结合来实现ls / grep x

  • shell创建pipe
  • forks(twice)
  • 将ls的FD 1连接到pipe的write FD,并将fd0设置为管道

2.14 list.c 列出目录中的文件


– ⌚️03:06 —