现代操作系统 第十章 UNIX、Linux 和 Android 下

现代操作系统 第十章 UNIX、Linux 和 Android

文章目录

  • 现代操作系统 第十章 UNIX、Linux 和 Android
    • Linux中的I/O系统
      • I/O在Linux中的实现
    • Linux文件系统
      • 基本概念
      • Linux文件系统的实现
        • Linux虚拟文件系统
        • Linux ext2文件系统
        • Linux ext4文件系统
        • /proc文件系统
    • 习题

本文为读书摘要(个人认为重要的知识点)

Linux中的I/O系统

Linux把设备当作一种特殊文件整合到文件系统中。毎个I/O设备都被分配了一条路径,通常在/dev目录下。例如:一个磁盘的路径可能是 “dev/hdl” ,ー个打印机的路径可能是 “dev/lp”,网络的路径可能是 “dev/net”

特殊文件(设备)分为两类,块特殊文件和字符特殊文件

  • ー个块特殊文件由一组具有编号的块组成。 块特殊文件的主要特性是:每ー个块都能够被独立地寻址和访问。也就是说,ー个程序能够打开ー个块特殊文件,并且不用读第。块到第123块就能够读第124块。磁盘就是块特殊文件的典型应用。

  • 字符特殊文件通常用于表示输入和输出字符流的设备。 键盘、打印机、网络、鼠标、绘图机以及大部分接受用户数据或向用户输出数据的设备都使用字符特殊文件来表示。访问ー个鼠标的第124块是不可能的(甚至是无意义的)。

每个特殊文件都和一个处理其对应设备的设备驱动相关联。每个驱动程序都通过一个主设备号来标识。

网络的 I/O 则被封装为 套接字,也是通过文件句柄访问

I/O在Linux中的实现

​ 在Linux中I/O是通过ー系列的设备驱动来实现的,每个设备类型对应ー个设备驱动。设备驱动的功能是对系统的其他部分隔离硬件的特质。通过在驱动程序和操作系统其他部分之间提供ー层标准的接口, 使得大部分I/O系统可以被划归到内核的机器无关部分。

当用户访问ー个特殊文件时,由文件系统提供此特殊文件的主设备号和次设备号,并判断它是ー个块特殊文件还是ー个字符特殊文件。

图10-21展示了一部分可以跟不同的字符设备关联的操作。每一行指向ー个单独的 I/O 设备(即ー个单独的驱动程序)。列表示所有的字符驱动程序必须支持的功能。除此之外,还有几个其他的功能。当一个操作要在ー个字符特殊文件上执行时,系统通过检索字符设备的散列表来选择合适的数据结构,然后调用相应的功能来执行此操作。因此,每个文件操作都包含指向相应驱动程序的一个函数指针。

现代操作系统 第十章 UNIX、Linux 和 Android 下_第1张图片

I/O系统被划分为两大部分:处理块特殊文件的部分和处理字符特殊文件的部分。

Linux系统在磁盘驱动程序和文件系统之间设置了一个高速缓存(cache)(减少传输次数)

现代操作系统 第十章 UNIX、Linux 和 Android 下_第2张图片

除了正常的磁盘文件,还有其他的块特殊文件,也被称为原始块文件(raw block file)。这些文件允许程序通过绝对块号来访问磁盘,而不考虑文件系统。它们通常被用于**分页和系统维护**。 swap分区

可加载模块(动态添加驱动模块)是在系统运行时可以加载到内核的代码块。大部分情况下,这些模块是字符或者块设备驱动,但是它们也可以是完整的文件系统.网络协议、性能监控工具或者其他想要添加的模块。

块设备文件是块设备的物理寻址空间;普通文件(内存中)是块设备的虚拟寻址空间。普通文件比块设备文件多一层文件系统的地址转换机构。

Linux文件系统

Linux的设计非常有意思,因为它忠实地秉承了 “小的就是美好的” (Small is Beautiful)的设计原则。虽然只是使用了最简的机制和少量的系统调用,但是Linux却提供了强大而优美的文件系统。

基本概念

  • 绝对路径
  • 相对路径
  • 链接
    • 硬链接是指通过索引节点来进行链接
    • 软链接就是一个普通文件,只是数据块内容有点特殊,文件用户数据块中存放的内容是另一文件的路径名的指向

现代操作系统 第十章 UNIX、Linux 和 Android 下_第3张图片

  • 支持的文件类型

    • 普通的文件

    • 字符特殊文件

      建模串行I/O设备,比如键盘和打印机。如果打开并从/dec/tty中读取内容,等于从键盘读取内容,而如果打开并向 /dev/lp 中写内容,等于向打印机输出内容。

    • 块特殊文件

      通常有类似于/dev/hdl的文件名,它用来直接向硬盘分区中读取和写入内容,而不需要考虑文件系统。ー个偏移为 k 字节的read操作,将会从相应分区开始的第 k 个字节开始读取,而完全忽略i节点和文件的结构。原始块设备常被ー些建立(如mkfs)或修补(如fsck)文件系统的程序用来进行分页和交换。

  • 当一台机器上安装了多个磁盘的时候,就产生了如何处理它们的问题。

    • ー个解决方法是在每一个磁盘上安装自包含的文件系统,使它们之间互相独立,如图10-25(a)所示。
    • Linux的解决方法是允许ー个磁盘挂载到另ー个磁盘的目录树上,比如,我们可以把DVD挂载在目录 /b 上,构成如图10-25b所示的文件系统。

现代操作系统 第十章 UNIX、Linux 和 Android 下_第4张图片

    • POSIX提供了一种灵活的、细粒度的机制,允许ー个进程使用ー个不可分割的操作对小到ー个字节、大到整个文件加锁。加锁机制要求加锁者标识要加锁的文件、开始位置以及要加锁的字节数。如果操作成功,系统会在表格中添加记录说明要求加锁的字节(如数据库的一条记录)已被锁住。
    • 系统提供了两种锁:共享锁和互斥锁。
  • 相关系统调用
    • pipe系统调用用来创建一个shell 管线。它创建了一种伪文件(pseudo­ file), 用于缓冲管线通信的数据,并给缓冲区的读写都返回文件描述符。
      • 以下面的管线为例:
        sort
      • 在执行sort的进程中,文件描述符1 (标准输出)被设置为写入管道,执行head的进程中,文件描述符 0(标准输入)被设置为从管道读取。
      • 通过这种方式,sort只是从文件描述符。(被设置为文件in)读取, 写入到文件描述符1 (管道),甚至不会觉察到它们已经被重定向了。如果它们没有被重定向,sort将会自动从键盘读取数据,而后输出到显示器(默认设备)。同样地,当head从文件描述符〇中读取数据时, 它读取到的是sort写入到管道缓冲区中的数据,head甚至不知道自己使用了管道。

现代操作系统 第十章 UNIX、Linux 和 Android 下_第5张图片

现代操作系统 第十章 UNIX、Linux 和 Android 下_第6张图片

Linux文件系统的实现

Linux虚拟文件系统

为了使应用程序能够与在本地或远程设备上的不同文件系统进行交互,Linux采用了一个和其他UNIX系统相同的方法:虚拟文件系统。 VFS定义了一个基本的文件系统抽象以及这些抽象上允许的操作集合。调用上节中提到的系统调用访问VFS的数据结构,确定要访问的文件所属的文件系统,然后通过存储在VFS数据结构中的函数指针调用该文件系统的相应操作。

图10∙30总结了VFS支持的四个主要的文件系统结构。

  • 其中,超级块包含了文件系统布局的重要信息,破坏了超级块将会导致文件系统不可读。

  • 每个i节点表示某个确切的文件(inode)。 值得注意的是,在Linux中目录和设备也被当作文件,所以它们也有自己对应的i节点。超级块和i节点都有相应的结构,由文件系统所在的物理磁盘维护。

  • 为了便于目录操作及路径(比如 /usr/ast/bin)的遍历,VFS支持dentry数据结构,它表示一个目录项。 这个数据结构由文件系统在运行过程中创建。目录项被缓存在dentry-cache中,比如,dentry_cache会包含/, /usr, /usr/ast的目录项。 如果多个进程通过同一个硬连接(即相同路径)访问同一个文件,它们的文件对象都会指向这个cache中的同一个目录项。

  • file数据结构是ー个打开文件在内存中的表示,并且在调用open系统调用时被创建。 它支持read、 write, sendfile、lock等上一节中提到的系统调用。

    struct file结构体定义在include/Linux/fs.h中定义。文件结构体代表一个打开的文件,系统中的每个打开的文件在内核空间都有一个关联的 struct file。它由内核在打开文件时创建,并传递给在文件上进行操作的任何函数。在文件的所有实例都关闭后,内核释放这个数据结构。在内核创建和驱动源码中,struct file的指针通常被命名为file或filp。如下所示:

    fd只是一个小整数,在open时产生。起到一个索引的作用,进程通过PCB中的文件描述符表找到该fd所指向的文件指针filp。

现代操作系统 第十章 UNIX、Linux 和 Android 下_第7张图片

Linux ext2文件系统

ext2的磁盘分区包含了一个如图10-31所示的文件系统。块0不被Linux使用,而通常用来存放启动计算机的代码。在块0后面,磁盘分区被划分为若干个块组,划分时不考虑磁盘的物理结构。每个块组的结构如下:

  • 第一个块是超级块,它包含了该文件系统的信息,包括i节点的个数、磁盘块数以及空闲块链表的起始位置(通常有几百个项)。

  • 下ー个是组描述符,存放了位图(bitmap)的位置、空闲块数、组中的i节点数,以及组中目录数等信息,这个信息很重要,因为ext2试图把目录均匀地分散存储到磁盘上。两个位图分别记录空闲块和空闲i节点。

现代操作系统 第十章 UNIX、Linux 和 Android 下_第8张图片

  • 在超级块(含组描述符)之后是i节点存储区域,它们被编号为1到某个最大值。每个i节点的大小是128字节,并且
    毎一个i节点恰好描述ー个文件。i节点包含了统计信息(包含了stat系统调用能获得的所有信息,实际上
    stat就是从i节点读取信息的),也包含了所有存放该文件数据的磁盘块的位置。
  • 在i节点区后面是数据块区,所有文件和目录都存放在这个区域。对于ー个包含了一个以上磁盘块
    的文件和目录,这些磁盘块是不需要连续的。实际上,ー个大文件的块有可能遍布在整个磁盘上。

现代操作系统 第十章 UNIX、Linux 和 Android 下_第9张图片

  • 图10-32中的每个目录项由四个固定长度的域和一个可变长度的域组成。第一个域是i节点号,文件 colossal 的i节点号是19,文件voluminous的i节点号是42,目录bigdir的i节点号是88。接下来是rec_len域, 标明该目录项的大小(以字节为单位),可能包括名字后面的ー些填充。

  • 在图10-32b中,我们看到的是文件voluminous的目录项被移除后同一个目录的内容。这是通过增加 colossal 的域的长度,将voluminous以前所在的域变为第一个目录项的填充。当然,这个填充可以用来作为后续的目录项。

  • **i节点(inode)**被存放在i节点表中,其中i节点表是ー个内核数据结构,用于保存所有当前打开的文件和目录的i节点。i节点表项的格式至少要包含stat系统调用返回的所有域,以保证stat正常运行(见图10-28)。图10-33中列出了i节点结构中由Linux文件系统层支持的ー些域。

现代操作系统 第十章 UNIX、Linux 和 Android 下_第10张图片

考虑ー个shell脚本s,它由顺序执行的两个命令pl和p2组成。

s > x

如果该shell脚本在命令行下被调用,我们预期p1将它的输出写到x中,然后p2也将输出写到ス中,并且从pl结束的地方开始。
当shell生成pl时,ズ初始是空的,从而pl从文件位置。开始写入。然而,当pl结束时就必须通过某种机制使得p2看到的初始文件位置不是〇 (如果将文件位置存放在文件描述符表中,p2将看到0),而是p1结束时的位置。

实现这ー点的方法如图10-34所示。实现的技巧是在文件描述符表和i节点表之间引入ー个新的表, 叫作打开文件描述表,并将文件读写位置(以及读/写位)放到里面。 在这个图中,父进程是shell而子进程首先是p1然后是p2。当shell生成pl时,pl的用户结构(包括文件描述符表)是shell的用户结构的ー个副本,因此两者都指向相同的打开文件描述表的表项。当p1结束时,shell的文件描述符仍然指向包含p1的文件位置的打开文件描述。当shell生成p2时,新的子进程自动继承文件读写位置,甚至p2和shell都不需要知道文件读写位置到底是在哪里。

然而,当不相关的进程打开该文件时,它将得到自己的打开文件描述表项,以及自己的文件读写位置,而这正是我们所需要的。因此,打开文件描述表的重点是允许父进程和子进程共享一个文件读写位置,而给不相关的进程提供各自私有的值。

现代操作系统 第十章 UNIX、Linux 和 Android 下_第11张图片

Linux ext4文件系统

为了增强文件系统的健壮性,Linux依靠日志文件系统。ext3是ー个日志文件系统,它在ext2文件系统之上做了改进。ext4是ext3的改进,也是ー个日志文件系统,但不同于ext3,它改变了ext3所采用的块寻址方案,从而同时支持更大的文件和更大的整体文件系统。

日志是一个以环形缓冲器形式组织的文件。日志可以存储在主文件系统所在的设备上也可以存储在其他设备上。由于日志操作本身不被日志记录,这些操作并不是被日志所在的ext4文件系统处理的(不被自己处理),而是使用ー个独立的日志块设备(Journaling Block Device, JBD)来执行日志的读/写操作。

JBD支持三个主要数据结构:日志记录、原子操作处理和事务。ー个日志记录描述一个低级文件系统操作,该操作通常导致块内变化。鉴于系统调用(如write)包含多个地方的改动——i节点、现有的文件块、新的文件块、空闲块列表等,所以将相关的日志记录按照原子操作分成组。Ext3将系统调用过程的起始和结束通知JBD,这样JBD能够保证ー个原子操作中的所有日志记录或者都被应用,或者没有一个被
应用。(原子性)

/proc文件系统

另ー个Linux文件系统是/proc (process)文件系统。

其基本概念是为系统中的每个进程在/proc中创建一个目录。目录的名字是进程PID的十制数值。例如,/proc/619是与PID为619
的进程相对应的目录。在该目录下是进程信息的文件,如进程的命令行、环境变量和信号掩码等。

许多Linux扩展与/proc中其他的文件和目录相关。它们包含各种各样的关于CPU、磁盘分区、设备、 中断向量、内核计数器、文件系统、已加载模块等信息。

习题

  • 解释如何用C编写UNIX以使其更容易被移植到新机器。

由于汇编语言特定于每台机器,将 UNIX 移植到新机器需要用新机器的汇编语言重写整个代码。 另一方面,一旦 UNIX 用 C 语言编写,只有一小部分操作系统(例如 I/O 设备的设备驱动程序)需要重写。

  • POSIX接口定义了一组库程序。解释为什么要使用POS1X规范库程序,而不是使用系统调用接口。

系统调用接口与操作系统内核紧密耦合。 标准化系统调用接口会带来严格的限制(不太灵活)

  • Linux在被移植到新架构时依赖于GCC编译器。 描述这种依赖性的一个优点和一个缺点。

这允许 Linux 使用 gcc 编译器的特殊功能(如语言扩展),范围从提供快捷方式和简化到为编译器提供优化提示。 主要缺点是其他流行的、功能丰富的 C 编译器(如 LLVM)不能用于编译 Linux。 如果在未来某个时间 LLVM 或其他编译器在各方面都比 gcc 更好,Linux 将无法使用它。 这可能会成为一个严重的问题。

  • 标准输出和标准错误对于终端都是默认的,Linux为什么还要区分两者?

它们是分开的,因此可以重定向标准输出而不影响标准错误。 在管道中,标准输出可能会转到另一个进程,但标准错误仍会写入终端。

  • 为什么nice指令的负参数要专门保留给超级用户?

负值允许进程优先于所有正常进程。用户不能被信任拥有这样的权力。 它仅适用于超级用户,然后仅在紧急情况下使用。

  • 非实时Linux进程的优先级是从100到139,默认的靜态优先级是什么?如何使用优先值(nice值)来改变这种优先级?

text 说 nice 值在 -20 到 +19 的范围内,所以默认的静态优先级必须是 120,确实如此。 通过良好并选择积极的良好值,可以请求将进程置于较低优先级。

  • 通常情况下,你认为守护进程比交互进程具有更高的优先级还是更低的优先级?为什么?

通常,守护程序在后台运行,执行诸如打印和发送电子邮件之类的操作。 由于人们通常不会坐在椅子边上等待他们完成,因此他们的优先级较低,会占用交互式进程不需要的多余 CPU 时间。

  • 当ー个新进程被创建时,它一定会被分配ー个唯一的整数作为它的PID。在内核里只有一个计数器是否足够?每当创建一个进程时,计数器就会递增,并且作为新进程的PID。讨论你的结论。

PID 必须是唯一的。 计数器迟早会回绕并回到 0。然后它将向上移动到例如 15。如果恰好发生进程 15 是几个月前启动的,但仍在运行,则 15 不能分配给新的 过程。 因此,在使用计数器选择了建议的 PID 后,必须搜索进程表以查看 PID 是否已在使用中。

  • 在每个任务结构中的进程项中,父进程的PID被储存。为什么?

当进程退出时,父进程将被赋予其子进程的退出状态。 需要 PID 能够识别父进程,以便可以将退出状态转移到正确的进程。

  • 在fork系统调用中,写时复制机制被用作一个优选法,这样副本只有在ー个进程(父进程或子进程)试图写入页面オ会被创建。假设一个进程P1成功创建进程P2和进程P3。解释这种情况下页面共享是如何处理的。

在这种情况下,一个页面现在可以由所有三个进程共享。 通常,写时复制机制可能会导致多个进程共享一个页面。 为了处理这种情况,操作系统必须为每个共享页面保留一个引用计数。 如果 p1 在以三种方式共享的页面上写入,它会获得一个私有副本,而其他两个进程继续共享它们的副本。

  • 在Linux中打开文件描述符表为什么是必要的呢? (图10-34)
现代操作系统 第十章 UNIX、Linux 和 Android 下_第12张图片

共享文件的进程,包括当前文件指针位置,可以只共享一个打开文件描述符,而无需更新彼此的私有文件描述符表中的任何内容。同时,另一个进程可以通过单独的打开文件描述符访问同一个文件,获得不同的文件指针,并按照自己的意愿在文件中移动。

  • 在Linux中,数据段和堆栈段被分页并交换到特殊分页磁盘或分区的临时副本上,但是代码段却使用了可执行二进制文件。为什么?

代码段不能改变,所以它永远不必被分页。如果需要它的框架,它们可以被丢弃。始终可以从文件系统中检索页面。数据段不能被分页到可执行文件,因为它很可能在被引入后已经改变。分页会破坏可执行文件。堆栈段甚至不存在于可执行文件中。

  • 描述ー种使用mmap和信号量来构造一个进程内部间通信机制的方法。

两个进程可以同时将同一个文件映射到它们的地址空间。这为他们提供了一种共享物理内存的方法。共享内存的一半可以用作从 A 到 B 的缓冲区,一半用作从 B 到 A 的缓冲区。为了进行通信,一个进程将一条消息写入它的共享内存部分,然后向另一个进程发出一个信号,表明有一条消息在等待它。回复可以使用另一个缓冲区。

  • 在内存管理的伙伴系统中,两个相邻的同样大小的空闲内存块有没有可能同时存在而不会被合并到ー个块中?如果有可能,解释是怎么样的情况;如果没有可能,说明为什么?

现代操作系统 第十章 UNIX、Linux 和 Android 下_第13张图片

如果两个块不是朋友,这是可能的。 考虑图 10-17(e) 的情况。 有两个新的请求,每个请求八页。 此时,底部的 32 页内存由四个不同的用户拥有,每个用户有 8 页。 现在用户 1 和 2 发布他们的页面,但用户 0 和 3 持有他们的页面。 这会产生使用八页、空闲八页、空闲八页和使用八页的情况。 我们有两个大小相等的相邻块不能合并,因为它们不是伙伴。

  • 据说在代码段中分页分区要比分页文件性能更好。为什么呢?

分页到分区允许使用原始设备,而无需使用文件系统数据结构的开销。 要访问块 n,操作系统只需将其添加到分区的起始块即可计算其磁盘位置。 没有必要遍历所有原本需要的间接块。

你可能感兴趣的:(计算机系统,读书笔记,操作系统)