├── book.pdf ├── fig ├── as.pdf ├── os.pdf ├── inode.pdf ├── race.pdf ├── smp.pdf ├── fslayer.pdf ├── mkernel.pdf ├── switch.pdf ├── deadlock.pdf ├── fslayout.pdf ├── xv6_layout.pdf ├── processlayout.pdf ├── riscv_address.pdf ├── riscv_pagetable.pdf ├── mkernel.svg ├── os.svg ├── race.svg ├── fslayer.svg ├── deadlock.svg ├── smp.svg ├── as.svg ├── switch.svg └── fslayout.svg ├── aspell.words ├── font ├── MinionPro-Bold.otf ├── MinionPro-It.otf ├── MinionPro-BoldIt.otf ├── MinionPro-Regular.otf ├── MinionPro-Semibold.otf ├── MinionPro-SemiboldIt.otf ├── README └── LucidaSans-Typewriter83.afm ├── sum.tex ├── README-en.md ├── bin ├── double.pl └── capital.py ├── README.md ├── Makefile ├── LICENSE ├── acks.tex ├── book.tex ├── lineref ├── book.bib ├── lock2.tex ├── interrupt.tex ├── tr2tex ├── book.mac ├── first.tex └── trap.tex /book.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HelloYJohn/xv6-riscv-book-zh-cn/HEAD/book.pdf -------------------------------------------------------------------------------- /fig/as.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HelloYJohn/xv6-riscv-book-zh-cn/HEAD/fig/as.pdf -------------------------------------------------------------------------------- /fig/os.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HelloYJohn/xv6-riscv-book-zh-cn/HEAD/fig/os.pdf -------------------------------------------------------------------------------- /aspell.words: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HelloYJohn/xv6-riscv-book-zh-cn/HEAD/aspell.words -------------------------------------------------------------------------------- /fig/inode.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HelloYJohn/xv6-riscv-book-zh-cn/HEAD/fig/inode.pdf -------------------------------------------------------------------------------- /fig/race.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HelloYJohn/xv6-riscv-book-zh-cn/HEAD/fig/race.pdf -------------------------------------------------------------------------------- /fig/smp.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HelloYJohn/xv6-riscv-book-zh-cn/HEAD/fig/smp.pdf -------------------------------------------------------------------------------- /fig/fslayer.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HelloYJohn/xv6-riscv-book-zh-cn/HEAD/fig/fslayer.pdf -------------------------------------------------------------------------------- /fig/mkernel.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HelloYJohn/xv6-riscv-book-zh-cn/HEAD/fig/mkernel.pdf -------------------------------------------------------------------------------- /fig/switch.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HelloYJohn/xv6-riscv-book-zh-cn/HEAD/fig/switch.pdf -------------------------------------------------------------------------------- /fig/deadlock.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HelloYJohn/xv6-riscv-book-zh-cn/HEAD/fig/deadlock.pdf -------------------------------------------------------------------------------- /fig/fslayout.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HelloYJohn/xv6-riscv-book-zh-cn/HEAD/fig/fslayout.pdf -------------------------------------------------------------------------------- /fig/xv6_layout.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HelloYJohn/xv6-riscv-book-zh-cn/HEAD/fig/xv6_layout.pdf -------------------------------------------------------------------------------- /fig/processlayout.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HelloYJohn/xv6-riscv-book-zh-cn/HEAD/fig/processlayout.pdf -------------------------------------------------------------------------------- /fig/riscv_address.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HelloYJohn/xv6-riscv-book-zh-cn/HEAD/fig/riscv_address.pdf -------------------------------------------------------------------------------- /fig/riscv_pagetable.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HelloYJohn/xv6-riscv-book-zh-cn/HEAD/fig/riscv_pagetable.pdf -------------------------------------------------------------------------------- /font/MinionPro-Bold.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HelloYJohn/xv6-riscv-book-zh-cn/HEAD/font/MinionPro-Bold.otf -------------------------------------------------------------------------------- /font/MinionPro-It.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HelloYJohn/xv6-riscv-book-zh-cn/HEAD/font/MinionPro-It.otf -------------------------------------------------------------------------------- /font/MinionPro-BoldIt.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HelloYJohn/xv6-riscv-book-zh-cn/HEAD/font/MinionPro-BoldIt.otf -------------------------------------------------------------------------------- /font/MinionPro-Regular.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HelloYJohn/xv6-riscv-book-zh-cn/HEAD/font/MinionPro-Regular.otf -------------------------------------------------------------------------------- /font/MinionPro-Semibold.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HelloYJohn/xv6-riscv-book-zh-cn/HEAD/font/MinionPro-Semibold.otf -------------------------------------------------------------------------------- /font/MinionPro-SemiboldIt.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HelloYJohn/xv6-riscv-book-zh-cn/HEAD/font/MinionPro-SemiboldIt.otf -------------------------------------------------------------------------------- /font/README: -------------------------------------------------------------------------------- 1 | These fonts are taken from 2 | 3 | /home/am8/rsc/font/Adobe/TypeClassics/MinionPro 4 | /home/am8/rsc/font/BellLabs 5 | 6 | They are copyrighted material used under license 7 | and cannot be redistributed. 8 | 9 | -------------------------------------------------------------------------------- /sum.tex: -------------------------------------------------------------------------------- 1 | 2 | 3 | \chapter{Summary} 4 | \label{CH:SUM} 5 | 6 | 本文通过对操作系统xv6的逐行研究,介绍了操作系统的主要思想。有些代码行体现了主要思想的本质(例如,上下文切换、用户/内核边界、锁等),并且每一行都很重要;其他代码行提供了如何实现特定操作系统想法的说明,并且可以通过不同的方式轻松完成(例如,更好的调度算法、更好的磁盘数据结构来表示文件、更好的日志记录以允许并发事务等)。 )。所有的想法都是在一个特定的、非常成功的系统调用接口(Unix 接口)的背景下阐述的,但这些想法也延续到了其他操作系统的设计中。 7 | 8 | 9 | -------------------------------------------------------------------------------- /README-en.md: -------------------------------------------------------------------------------- 1 | This edition of the book has been converted to LaTeX. 2 | In order to build it, ensure you have a TeX distribution that contains 3 | the `pdflatex` command. With that, you should be able to build the book 4 | by running `make`, which will clone the OS itself and build the book 5 | to `book.pdf` in the main directory. 6 | 7 | Figures are drawn using `inkscape`. 8 | -------------------------------------------------------------------------------- /bin/double.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl 2 | 3 | # Detects duplicated words even when they are 4 | # are repeated between lines. 5 | # Taken from the ORA regex book 6 | 7 | $/ = ".\n"; 8 | while (<>) { 9 | next if !s/\b([a-z]+)((\s|<[^>]+>)+)(\1\b)/\e[7m$1\e[m$2\e[7m$4\e[m/ig; 10 | 11 | s/^([^\e]*\n)+//mg; 12 | s/^/$ARGV: /mg; 13 | print; 14 | } 15 | 16 | # also test for things like 17 | # [^\w+]a\w+[aeiou] and [^\w+]an\w+[!aeiou] 18 | -------------------------------------------------------------------------------- /bin/capital.py: -------------------------------------------------------------------------------- 1 | #!/bin/python 2 | 3 | import re 4 | import sys 5 | 6 | # look for uncapitalized xv6 7 | regexsmall = re.compile(r'\.\s+(xv6)') 8 | 9 | with open(sys.argv[1], 'r') as f: 10 | d = f.read() 11 | for w in re.findall(regexsmall, d): 12 | print("Error smallcaps %s: %s" % (sys.argv[1], w)) 13 | 14 | # look for capitalized code names (e.g., \lstinline{Exec}), but 15 | # names that are all caps 16 | regexbig = re.compile(r'\\(lstinline|indexcode){([A-Z][a-z]+[a-zA-Z_]+)') 17 | 18 | with open(sys.argv[1], 'r') as f: 19 | line = f.readline() 20 | cnt = 1 21 | while line: 22 | for w in re.findall(regexbig, line): 23 | print("%s:%d: error: %s" % (sys.argv[1], cnt, w[1])) 24 | line = f.readline() 25 | cnt += 1 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 本书的这个版本已转换为 LaTeX。 2 | 为了构建它,请确保您有一个 TeX 发行版,其中包含 3 | `xelatex` 命令。 有了这个,你应该能够构建这本书 4 | 通过运行 `make`,它将克隆操作系统本身并构建这本书 5 | 到主目录中的 `book.pdf`。 6 | 7 | 图形是使用 `inkscape` 绘制的。 8 | 9 | # 项目来源 10 | [xv6-riscv-book](https://github.com/mit-pdos/xv6-riscv-book.git) 11 | 12 | # 翻译工具 13 | [MathTranslate](https://github.com/SUSYUSTC/MathTranslate.git) 14 | 15 | # 项目编译 16 | ## 环境准备 17 | ### LaTeX 本地环境 18 | #### mac 19 | 20 | ``` bash 21 | brew install --cask mactex 22 | ``` 23 | #### other 24 | 25 | [TeX Live](https://www.tug.org/texlive/quickinstall.html) 26 | 27 | ### Overleaf LaTeX 在线环境 28 | 29 | [Overleaf](https://www.overleaf.com) 30 | 31 | 32 | ## 编译 33 | 34 | ``` bash 35 | make 36 | ``` 37 | 38 | 如果 LaTeX 本地环境已经准备好,会直接生成 book.pdf 39 | 40 | 使用在线环境 执行完 make 后打包上传到 overleaf,然后再编译即可 41 | 42 | # contribution 43 | 由于是机器翻译,人工校对,难免有疏漏,大家可以在 github 项目下提 issue or pr ,只能说会尽快解决。 44 | 45 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | SRC=xv6-riscv-src/ 2 | 3 | T=latex.out 4 | 5 | TEX=$(foreach file, $(SPELLTEX), $(T)/$(file)) 6 | SPELLTEX=$(wildcard *.tex) 7 | 8 | all: book.pdf 9 | .PHONY: all src clean 10 | 11 | $(T)/%.tex: %.tex | src 12 | mkdir -p latex.out 13 | ./lineref $(notdir $@) $(SRC) > $@ 14 | 15 | src: 16 | if [ ! -d $(SRC) ]; then \ 17 | git clone git@github.com:mit-pdos/xv6-riscv.git $(SRC) ; \ 18 | else \ 19 | git -C $(SRC) pull ; \ 20 | fi; \ 21 | true 22 | 23 | book.pdf: src book.tex $(TEX) 24 | xelatex book.tex 25 | bibtex book 26 | xelatex book.tex 27 | xelatex book.tex 28 | 29 | clean: 30 | rm -f book.aux book.idx book.ilg book.ind book.log\ 31 | book.toc book.bbl book.blg book.out 32 | rm -rf latex.out 33 | rm -rf $(SRC) 34 | 35 | spell: 36 | @ for i in $(SPELLTEX); do aspell --mode=tex -p ./aspell.words -c $$i; done 37 | @ for i in $(SPELLTEX); do perl bin/double.pl $$i; done 38 | @ for i in $(SPELLTEX); do perl bin/capital.py $$i; done 39 | @ ( head -1 aspell.words ; tail -n +2 aspell.words | sort ) > aspell.words~ 40 | @ mv aspell.words~ aspell.words 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The xv6 book sources are: 2 | 3 | Copyright (c) 2006-2022 Russ Cox, Frans Kaashoek, and Robert Morris, 4 | Massachusetts Institute of Technology 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy of 7 | this source and associated documentation files (the "Book"), to deal in the Book 8 | without restriction, including without limitation the rights to use, copy, 9 | modify, merge, publish, distribute, sublicense, and/or sell copies of the Book, 10 | and to permit persons to whom the Book is furnished to do so, subject to the 11 | following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Book. 15 | 16 | THE BOOK IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 17 | INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 18 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 19 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 20 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 21 | CONNECTION WITH THE BOOK OR THE USE OR OTHER DEALINGS IN THE BOOK. 22 | 23 | -------------------------------------------------------------------------------- /acks.tex: -------------------------------------------------------------------------------- 1 | 2 | 3 | \chapter*{Foreword and acknowledgments} 4 | 5 | 这是为操作系统课程准备的文本草稿。它通过研究一个名为 xv6 的示例内核来解释操作系统的主要概念。 Xv6 仿照 Dennis Ritchie 和 Ken Thompson 的 Unix Version 6 (v6)~ \cite{unix} 。 Xv6 大致遵循 v6 的结构和风格,但在多核 RISC-V~ \cite{riscv} 的 ANSI C~ \cite{kernighan} 中实现。 6 | 7 | 本文应与 xv6 的源代码一起阅读,该方法的灵感来自 John Lions 的 UNIX 第六版评论 ~ \cite{lions} 。看 8 | \url{https://pdos.csail.mit.edu/6.1810} 用于指向 v6 和 xv6 的在线资源,包括使用 xv6 的多个实验室作业。 9 | 10 | 我们在 6.828 和 6.1810(麻省理工学院的操作系统类)中使用了此文本。我们感谢那些直接或间接为 xv6 做出贡献的教师、助教和学生。我们特别要感谢 Adam Belay、Austin Clements 和 Nickolai Zeldovich。最后,我们要感谢通过电子邮件向我们发送文本中的错误或改进建议的人:Abutalib Aghayev, Sebastian Boehm, brandb97, Anton 11 | Burtsev, Raphael Carvalho, Tej Chajed, Rasit Eskicioglu, Color Fuzzy, 12 | Wojciech Gac, Giuseppe, Tao Guo, Haibo Hao, Naoki Hayama, Chris 13 | Henderson, Robert Hilderman, Eden Hochbaum, Wolfgang Keller, Henry 14 | Laih, Jin Li, Austin Liew, Pavan Maddamsetti, Jacek Masiulaniec, 15 | Michael McConville, m3hm00d, miguelgvieira, Mark Morrissey, Muhammed 16 | Mourad, Harry Pan, Harry Porter, Siyuan Qian, Askar Safin, Salman 17 | Shah, Huang Sha, Vikram Shenoy, Adeodato Simó, Ruslan Savchenko, Pawel Szczurko, 18 | Warren Toomey, tyfkda, tzerbib, Vanush Vaswani, Xi Wang, and Zou Chang 19 | Wei, Sam Whitlock, LucyShawYang, and Meng Zhou 20 | 21 | 如果您发现错误或有改进建议,请发送电子邮件至 Frans Kaashoek 和 Robert Morris (kaashoek,rtm@csail.mit.edu)。 22 | 23 | 24 | -------------------------------------------------------------------------------- /book.tex: -------------------------------------------------------------------------------- 1 | \documentclass[12pt]{book} 2 | \usepackage{xeCJK} 3 | \usepackage{amsmath} 4 | 5 | \usepackage[T1]{fontenc} 6 | \usepackage{times} 7 | \usepackage{listings} 8 | \usepackage{graphicx} 9 | \usepackage{xcolor} 10 | \usepackage{url} 11 | \usepackage{imakeidx} 12 | \usepackage{booktabs} 13 | \usepackage{url} 14 | \usepackage{etoolbox} 15 | \usepackage{fullpage} 16 | \usepackage{soul} 17 | \usepackage[utf8]{inputenc} 18 | \usepackage{hyperref} 19 | \frenchspacing 20 | \hypersetup{pdfauthor={Russ Cox, Frans Kaashoek, Robert Morris}, 21 | pdftitle={xv6: a simple, Unix-like teaching operating system},} 22 | 23 | \lstset{basicstyle=\small\ttfamily} 24 | \lstset{morecomment=[is]{[[[}{]]]}} 25 | \lstset{escapeinside={(*@}{@*)}} 26 | \lstset{xleftmargin=5.0ex} 27 | \newcommand{\github}{https://github.com/mit-pdos/xv6-riscv/blob/riscv/} 28 | \newcommand{\fileref}[1]{\href{\github/#1}{\small{(#1)}}} 29 | \newcommand{\lineref}[2]{\href{\github/#1\#L#2}{\small{(#1:#2)}}} 30 | \newcommand{\linerefs}[3]{\href{\github/#1\#L#2-L#3}{\small(#1:#2-#3)}} 31 | 32 | \newcommand{\indextext}[1]{\textit{#1}\index{#1}} 33 | \newcommand{\indextextx}[1]{{#1}\index{#1}} 34 | \newcommand{\indexcode}[1]{\lstinline{#1}\index{#1@\lstinline{#1}}} 35 | \newcommand{\insertnote}[3]{\noindent\textcolor{#1}{\textbf{#2:} #3}} 36 | \newcommand{\note}[1]{\insertnote{blue}{NOTE}{#1}} 37 | \newcommand{\rtm}[1]{\insertnote{red}{RTM}{#1}} 38 | \newcommand{\mfk}[1]{\insertnote{red}{MFK}{#1}} 39 | %% for publishing book without notes 40 | %\renewcommand{\insertnote}[3]{} 41 | 42 | \title{xv6:一个简单的类Unix教学操作系统 } 43 | \author{Russ Cox \and Frans Kaashoek \and Robert Morris} 44 | \makeindex 45 | \begin{document} 46 | 47 | 48 | 49 | \maketitle 50 | 51 | \tableofcontents 52 | 53 | \input{latex.out/acks} 54 | \input{latex.out/unix} 55 | \input{latex.out/first} 56 | \input{latex.out/mem} 57 | \input{latex.out/trap} 58 | \input{latex.out/interrupt} 59 | \input{latex.out/lock} 60 | \input{latex.out/sched} 61 | \input{latex.out/fs} 62 | \input{latex.out/lock2} 63 | \input{latex.out/sum} 64 | 65 | { \interlinepenalty =10000 66 | \bibliographystyle{plain} 67 | \bibliography{book} } 68 | 69 | \printindex 70 | 71 | 72 | 73 | \end{document} 74 | 75 | -------------------------------------------------------------------------------- /lineref: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import os 4 | import re 5 | import sys 6 | 7 | if len(sys.argv) < 2: 8 | print("error: too few arguments", file=sys.stderr) 9 | 10 | path = sys.argv[2] 11 | 12 | def lookup_regex(fname, pat1, pat2=None): 13 | fname = fname.replace("\_", "_") 14 | p1 = re.compile(pat1) 15 | if pat2 != None: 16 | p2 = re.compile(pat2) 17 | else: 18 | p2 = None 19 | cnt = 1 20 | i = None 21 | j = None 22 | p = p1 23 | try: 24 | with open(path+'/'+fname) as f: 25 | line = f.readline() 26 | while line: 27 | m = p.search(line) 28 | if m != None: 29 | if p2 == None: 30 | return (cnt, None) 31 | else: 32 | if i == None: 33 | i = cnt 34 | p = p2 35 | else: 36 | j = cnt 37 | return (i, j) 38 | cnt += 1 39 | line = f.readline() 40 | if i == None: 41 | print("%s: cannot find pat %s" % (fname, p1), file=sys.stderr) 42 | else: 43 | print("%s: cannot find pat %s" % (fname, p2), file=sys.stderr) 44 | return (None, None) 45 | except IOError: 46 | print("error: cannot open %s" % fname, file=sys.stderr) 47 | return (None, None) 48 | 49 | def lineref(l): 50 | # file:/pattern/delta 51 | p = re.compile(r'\\lineref{(.*):\/(.*)\/([+-]?\d+)?}') 52 | m = p.search(l) 53 | if m != None: 54 | f = m.groups()[0] 55 | (i, j) = lookup_regex(f, m.groups()[1]) 56 | delta = 0 57 | if m.groups()[2] != None: 58 | delta = int(m.groups()[2]) 59 | if i != None: 60 | l = p.sub(r'\\lineref{%s}{%s}' % (f,str(i+delta)), l) 61 | print(l, end="") 62 | return 63 | # file:/line/ 64 | p = re.compile(r'\\lineref\{(.*):(\d+)\}') 65 | m = p.search(l) 66 | if m != None: 67 | f = m.groups()[0] 68 | n = m.groups()[1] 69 | l = p.sub(r'\\lineref{%s}{%s}' % (f, n), l) 70 | print(l, end="") 71 | return 72 | # file:/pattern/delta,pattern/delta 73 | p = re.compile(r'\\linerefs{(.*):\/(.*)\/([+-]?\d+)?,\/(.*)\/([+-]?\d+)?}') 74 | m = p.search(l) 75 | if m != None: 76 | f = m.groups()[0] 77 | (i, j) = lookup_regex(f, m.groups()[1], m.groups()[3]) 78 | delta1 = 0 79 | if m.groups()[2] != None: 80 | delta1 = int(m.groups()[2]) 81 | delta2 = 0 82 | if m.groups()[4] != None: 83 | delta2 = int(m.groups()[4]) 84 | if i != None and j != None: 85 | l = p.sub(r'\\linerefs{%s}{%s}{%s}' % (f, str(i+delta1), str(j+delta2)), l) 86 | print(l, end="") 87 | return 88 | print(l, end="") 89 | 90 | with open(sys.argv[1]) as f: 91 | line = f.readline() 92 | while line: 93 | lineref(line) 94 | line = f.readline() 95 | -------------------------------------------------------------------------------- /book.bib: -------------------------------------------------------------------------------- 1 | @book{riscv, 2 | author = {Patterson, David and Waterman, Andrew}, 3 | title = {The {RISC-V} Reader: an open architecture Atlas}, 4 | year = {2017}, 5 | isbn = {099924910X, 9780999249109}, 6 | publisher = {Strawberry Canyon}, 7 | } 8 | 9 | @book{lions, 10 | author = {John Lions}, 11 | title = {Commentary on UNIX 6th Edition}, 12 | year = 2000, 13 | publisher = {Peer to Peer Communications}, 14 | isbn = {1-57398-013-7}, 15 | } 16 | 17 | @article{unix, 18 | author = {Ritchie, Dennis M. and Thompson, Ken}, 19 | title = {The {UNIX} Time-sharing System}, 20 | journal = {Commun. ACM}, 21 | issue_date = {July 1974}, 22 | volume = {17}, 23 | number = {7}, 24 | month = jul, 25 | year = {1974}, 26 | pages = {365--375}, 27 | numpages = {11}, 28 | url = {http://doi.acm.org/10.1145/361011.361061}, 29 | doi = {10.1145/361011.361061}, 30 | publisher = {ACM}, 31 | } 32 | 33 | @book{knuth, 34 | author = {Knuth, Donald}, 35 | title = {Fundamental Algorithms. The Art of Computer Programming. (Second ed.)}, 36 | year = 1997, 37 | volume = 1, 38 | publisher = Addison-Wesley, 39 | isbn = {0-201-89683-4}, 40 | } 41 | 42 | @document{riscv:priv, 43 | title = {The {RISC-V} instruction set manual {Volume II}: privileged architecture}, 44 | editor = {Andrew Waterman and Krste Asanovic and John Hauser}, 45 | year = 2021, 46 | howpublished = {\url{https://github.com/riscv/riscv-isa-manual/releases/download/Priv-v1.12/riscv-privileged-20211203.pdf}} 47 | } 48 | 49 | @document{riscv:user, 50 | title = {The {RISC-V} instruction set manual {Volume I}: unprivileged {ISA}}, 51 | editor = {Andrew Waterman and Krste Asanovic}, 52 | year = 2019, 53 | howpublished={\url{https://github.com/riscv/riscv-isa-manual/releases/download/Ratified-IMAFDQC/riscv-spec-20191213.pdf}} 54 | } 55 | 56 | @book{kernighan, 57 | author = {Kernighan, Brian W.}, 58 | editor = {Ritchie, Dennis M.}, 59 | title = {The C Programming Language}, 60 | year = {1988}, 61 | isbn = {0131103709}, 62 | edition = {2nd}, 63 | publisher = {Prentice Hall Professional Technical Reference}, 64 | } 65 | 66 | @document{u54, 67 | author = {SiFive}, 68 | title = {SiFive {FU540-C000} manual}, 69 | howpublished={\url{https://sifive.cdn.prismic.io/sifive%2F590bbcb6-598e-4ed8-b5d3-88c2c7458ebf_u54-core-complex-manual-v19.05.pdf}}, 70 | year = "2018", 71 | } 72 | 73 | @document{virtio, 74 | author = {{OASIS} Open}, 75 | title = {Virtual {I/O} Device ({VIRTIO}) Version 1.0}, 76 | year = "2016", 77 | month = "March", 78 | howpublished={\url{http://docs.oasis-open.org/virtio/virtio/v1.0/virtio-v1.0.html}}, 79 | } 80 | 81 | 82 | @document{dijkstra65, 83 | author = {Edsger Dijkstra}, 84 | title = {Cooperating Sequential Processes}, 85 | year = "1965", 86 | howpublished={\url{https://www.cs.utexas.edu/users/EWD/transcriptions/EWD01xx/EWD123.html}}, 87 | } 88 | 89 | @document{ns16550a, 90 | author = {Martin Michael and Daniel Durich}, 91 | year = "1987", 92 | title = {The {NS16550A}: {UART} Design and Application Considerations}, 93 | howpublished = {\url{http://bitsavers.trailing-edge.com/components/national/_appNotes/AN-0491.pdf}}, 94 | } 95 | 96 | @article{boehm04, 97 | author = {Boehm, Hans-J}, 98 | title = {Threads cannot be implemented as a library}, 99 | journal = {ACM PLDI Conference}, 100 | year = {2005}, 101 | } 102 | 103 | 104 | @article{lamport:bakery, 105 | author = {Lamport, L}, 106 | title = {A New Solution of Dijkstra's Concurrent Programming Problem}, 107 | journal = {Communications of the ACM}, 108 | year = {1974}, 109 | } 110 | 111 | 112 | @MISC{mckenney:rcuusage, 113 | author = {Paul E. Mckenney and Silas Boyd-wickizer and Jonathan Walpole}, 114 | title = {{RCU} Usage In the Linux Kernel: One Decade Later}, 115 | year = {2013} 116 | } 117 | 118 | 119 | @book{herlihy:art, 120 | author = {Herlihy, Maurice and Shavit, Nir}, 121 | title = {The Art of Multiprocessor Programming, Revised Reprint}, 122 | year = {2012}, 123 | } 124 | 125 | @INPROCEEDINGS{Presotto91plan9, 126 | author = {Dave Presotto and Rob Pike and Ken Thompson and Howard Trickey}, 127 | title = {Plan 9, A Distributed System}, 128 | booktitle = {In Proceedings of the Spring 1991 EurOpen Conference}, 129 | year = {1991}, 130 | pages = {43--50} 131 | } 132 | 133 | @inproceedings{sel4, 134 | author = {Klein, Gerwin and Elphinstone, Kevin and Heiser, Gernot and Andronick, June and Cock, David and Derrin, Philip and Elkaduwe, Dhammika and Engelhardt, Kai and Kolanski, Rafal and Norrish, Michael and Sewell, Thomas and Tuch, Harvey and Winwood, Simon}, 135 | title = {SeL4: Formal Verification of an {OS} Kernel}, 136 | year = {2009}, 137 | booktitle = {Proceedings of the ACM SIGOPS 22nd Symposium on Operating Systems Principles}, 138 | pages = {207–220}, 139 | } 140 | 141 | @MISC{aleph:smashing, 142 | author="Aleph One", 143 | title={Smashing The Stack For Fun And Profit}, 144 | howpublished={\url{http://phrack.org/issues/49/14.html#article}}, 145 | } 146 | 147 | @MISC{mitre:cves, 148 | title = {Linux Common Vulnerabilities and Exposures ({CVEs})}, 149 | howpublished={\url{https://cve.mitre.org/cgi-bin/cvekey.cgi?keyword=linux}}, 150 | } 151 | -------------------------------------------------------------------------------- /lock2.tex: -------------------------------------------------------------------------------- 1 | 2 | \chapter{Concurrency revisited} 3 | \label{CH:LOCK2} 4 | 5 | 同时获得良好的并行性能、并发性下的正确性以及可理解的代码是内核设计中的一大挑战。直接使用锁是实现正确性的最佳途径,但并不总是可行。本章重点介绍 xv6 被迫以相关方式使用锁的示例,以及 xv6 使用类似锁的技术但不使用锁的示例。 6 | 7 | \section{锁模式 } 8 | 9 | 缓存的项目通常很难锁定。例如,文件系统的块缓存 \lineref{kernel/bio.c:/^struct/} 存储最多 { \tt NBUF } 磁盘块的副本。给定的磁盘块在缓存中至多有一个副本至关重要;否则,不同的进程可能会对本应是同一块的不同副本进行冲突的更改。每个缓存块都存储在 { \tt 结构体 buf } 中 10 | \lineref{kernel/buf.h:1} 。 { \tt 结构体 buf } 有一个锁定字段,有助于确保一次只有一个进程使用给定的磁盘块。但是,该锁还不够:如果缓存中根本不存在某个块,并且两个进程想要同时使用它怎么办?没有 { \tt 结构体 buf } (因为该块尚未缓存),因此没有什么可以锁定。 Xv6 通过将附加锁 ( { \tt bcache.lock } ) 与缓存块的身份集相关联来处理这种情况。需要检查块是否已缓存(例如 { \tt bget } \lineref{kernel/bio.c:/^bget/} )或更改缓存块集的代码必须保存 { \tt bcache.lock } ;在该代码找到所需的块和 { \tt 结构体 buf } 后,它可以释放 { \tt bcache.lock } 并仅锁定特定块。这是一种常见模式:一组项目一把锁,每个项目一把锁。 11 | 12 | 通常,获取锁的同一函数也会释放它。但更精确的看待事物的方法是,在必须表现为原子的序列开始时获取锁,并在该序列结束时释放锁。如果序列在不同的函数、不同的线程或不同的 CPU 上开始和结束,则锁获取和释放必须执行相同的操作。锁的作用是强制其他用途等待,而不是将一条数据固定到特定的代理。一个例子是 { \tt yield } 中的 { \tt acquire } 13 | \lineref{kernel/proc.c:/^yield/} ,在调度线程中释放,而不是在获取过程中释放。另一个例子是 14 | { \tt acquiresleep } { \tt ilock } \lineref{kernel/fs.c:/^ilock/} ;这段代码经常在读磁盘时休眠;它可能会在不同的CPU上唤醒,这意味着锁可能会在不同的CPU上获取和释放。 15 | 16 | 释放受嵌入在对象中的锁保护的对象是一件微妙的事情,因为拥有锁并不足以保证释放是正确的。当其他线程在 { \tt acquire } 中等待使用该对象时,就会出现问题;释放对象会隐式释放嵌入的锁,这将导致等待线程发生故障。一种解决方案是跟踪存在多少个对该对象的引用,以便仅在最后一个引用消失时才释放该对象。参见 { \tt pipeclose } 17 | 以 \lineref{kernel/pipe.c:/^pipeclose/} 为例; 18 | { \tt pi->readopen } 和 { \tt pi->writeopen } 跟踪管道是否有文件描述符引用它。 19 | 20 | 通常人们会看到对一组相关项的读写序列周围的锁;这些锁确保其他线程只能看到已完成的更新序列(只要它们也锁定)。如果更新是对单个共享变量的简单写入,那么情况又如何呢?例如, 21 | \texttt{setkilled} 和 \texttt{killed} 22 | \lineref{kernel/proc.c:/^setkilled/} 锁定其简单用途 23 | \lstinline{p->killed} 。如果没有锁,一个线程可以写 24 | \lstinline{p->killed} 同时另一个线程读取它。这是一个 \indextextx{race} ,C 语言规范表示竞赛会产生 \indextext{undefined behavior} ,这意味着程序可能会崩溃或产生不正确的结果 \footnote{ \url{https://en.cppreference.com/w/c/language/memory_model} 中的“线程和数据竞争” } 。锁可以防止竞争并避免未定义的行为。 25 | 26 | 竞争可能破坏程序的原因之一是,如果没有锁或等效结构,编译器可能会生成以与原始 C 代码完全不同的方式读取和写入内存的机器代码。例如,线程调用的机器码 27 | \texttt{killed} 可以将 \lstinline{p->killed} 复制到寄存器并仅读取该缓存值;这意味着线程可能永远不会看到任何写入 28 | \lstinline{p->killed} 。锁可以防止此类缓存。 29 | 30 | \section{类似锁的模式 } 31 | 32 | 在许多地方,xv6 以类似锁的方式使用引用计数或标志来指示对象已分配且不应释放或重复使用。进程的 { \tt p->state } 以这种方式起作用, { \tt file } 、 { \tt inode } 和 { \tt buf } 结构中的引用计数也是如此。虽然在每种情况下锁都会保护标志或引用计数,但后者可以防止对象过早释放。 33 | 34 | 文件系统使用 { \tt struct inode } 引用计数作为一种可由多个进程持有的共享锁,以避免代码使用普通锁时出现的死锁。例如, { \tt namex } \lineref{kernel/fs.c}{652} 中的循环依次锁定每个路径名组件指定的目录。但是, { \tt namex } 必须在循环结束时释放每个锁,因为如果它持有多个锁,如果路径名包含点(例如 { \tt a/./b } ),它可能会与自身发生死锁。它还可能因涉及目录和 { \tt .. } 的并发查找而死锁。正如 Chapter~\ref{CH:FS} 所解释的,解决方案是循环将目录 inode 传递到下一次迭代,其引用计数递增,但不锁定。 35 | 36 | 某些数据项在不同时间受到不同机制的保护,并且有时可能通过 xv6 代码的结构而不是通过显式锁来隐式地防止并发访问。例如,当物理页空闲时,它受到 \texttt{kmem.lock} 的保护 37 | \lineref{kernel/kalloc.c:/^. kmem;/} 。如果该页随后分配为管道 \lineref{kernel/pipe.c:/^pipealloc/} ,则它会受到不同锁(嵌入式 \lstinline{pi->lock} )的保护。如果该页被重新分配给新进程的用户内存,则它根本不受锁保护。相反,分配器不会将该页面分配给任何其他进程(直到它被释放),这一事实可以保护它免受并发访问。新进程内存的所有权很复杂:首先父进程在 { \tt 分叉 } 中分配和操作它,然后子进程使用它,并且(子进程退出后)父进程再次拥有内存并将其传递给 { \tt kfree } 。这里有两个教训:数据对象可以在其生命周期的不同点以不同的方式防止并发,并且保护可以采取隐式结构而不是显式锁的形式。 38 | 39 | 最后一个类似锁的示例是需要禁用对 { \tt mycpu() } \lineref{kernel/proc.c:/^myproc/} 调用的中断。禁用中断会导致调用代码相对于计时器中断而言是原子的,这可能会强制进行上下文切换,从而将进程移动到不同的 CPU。 40 | 41 | \section{根本没有锁 } 42 | 43 | 有一些地方 xv6 完全不加锁地共享可变数据。其中之一是自旋锁的实现,尽管人们可以将 RISC-V 原子指令视为依赖于硬件中实现的锁。另一个是 { \tt main.c } 中的 { \tt started } 变量 44 | \lineref{kernel/main.c:/^volatile/} ,用于防止其他 CPU 运行,直到 CPU 0 完成初始化 xv6; { \tt volatile } 确保编译器实际生成加载和存储指令。 45 | 46 | Xv6 包含一个 CPU 或线程写入一些数据,而另一个 CPU 或线程读取数据的情况,但没有专门用于保护该数据的特定锁。例如,在 { \tt fork } 中,父进程写入子进程的用户内存页面,而子进程(不同的线程,可能位于不同的 CPU 上)读取这些页面; nolock 显式保护这些页面。严格来说,这不是一个锁定问题,因为子进程直到父进程完成写入后才开始执行。这是一个潜在的内存排序问题(请参阅第 \ref{CH:LOCK} 章),因为如果没有内存屏障,就没有理由期望一个 CPU 看到另一个 CPU 的写入。但是,由于父进程释放锁,而子进程在启动时获取锁,因此 { \tt acquire } 和 { \tt release } 中的内存屏障确保子进程的 CPU 可以看到父进程的写入。 47 | 48 | \section{并行性 } 49 | 50 | 锁定主要是为了正确性而抑制并行性。由于性能也很重要,因此内核设计者通常必须考虑如何以既实现正确性又允许并行性的方式使用锁。虽然 xv6 不是针对高性能而系统设计的,但仍然值得考虑哪些 xv6 操作可以并行执行,以及哪些操作可能会发生锁冲突。 51 | 52 | xv6 中的管道是相当好的并行性的一个例子。每个管道都有自己的锁,这样不同的进程可以在不同的CPU上并行读写不同的管道。然而,对于给定的管道,写入者和读取者必须等待对方释放锁;他们不能同时读/写同一个管道。还有一种情况是,从空管道读取(或向满管道写入)必须阻塞,但这不是由于锁定方案造成的。 53 | 54 | 上下文切换是一个更复杂的例子。两个内核线程(各自在自己的 CPU 上执行)可以同时调用 { \tt yield } 、 { \tt sched } 和 { \tt swtch } ,并且这些调用将并行执行。线程各自持有一个锁,但它们是不同的锁,因此它们不必互相等待。然而,一旦进入 { \tt scheduler } ,两个 CPU 在搜索进程表(即 { \tt RUNNABLE } )时可能会发生锁冲突。也就是说,xv6 很可能在上下文切换期间从多个 CPU 中获得性能优势,但可能没有那么多。 55 | 56 | 另一个示例是从不同 CPU 上的不同进程并发调用 { \tt fork } 。对于 { \tt pid\_lock } 和 { \tt kmem.lock } ,以及在进程表中搜索 { \tt UNUSED } 进程所需的每进程锁,这些调用可能必须相互等待。另一方面,两个分叉进程可以完全并行地复制用户内存页面和格式化页表页面。 57 | 58 | 上述每个示例中的锁定方案在某些情况下都会牺牲并行性能。在每种情况下,都可以使用更精细的设计来获得更多的并行性。是否值得取决于细节:相关操作被调用的频率、代码在持有竞争锁的情况下花费了多长时间、有多少个 CPU 可能同时运行冲突的操作、代码的其他部分是否是更具限制性的瓶颈。很难猜测给定的锁定方案是否会导致性能问题,或者新的设计是否明显更好,因此通常需要对实际工作负载进行测量。 59 | 60 | \section{练习 } 61 | 62 | \begin{enumerate} 63 | 64 | 65 | \item 修改 xv6 的管道实现,以允许对同一管道的读取和写入在不同内核上并行进行。 \item 修改xv6的 \texttt{scheduler()} 以减少不同核心同时寻找可运行进程时的锁争用。 \item 消除 xv6 的 \texttt{fork()} 中的一些序列化。 \end{enumerate} 66 | 67 | 68 | -------------------------------------------------------------------------------- /interrupt.tex: -------------------------------------------------------------------------------- 1 | 2 | \chapter{Interrupts and device drivers} 3 | \label{CH:INTERRUPT} 4 | 5 | 6 | \indextext{Driver} 是操作系统中管理特定设备的代码:它配置设备硬件,告诉设备执行操作,处理产生的中断,并与可能正在等待设备 I/O 的进程交互。驱动程序代码可能很棘手,因为驱动程序与其管理的设备同时执行。此外,驱动程序必须了解设备的硬件接口,该接口可能很复杂且记录很少。 7 | 8 | 需要操作系统关注的设备通常可以配置为生成中断,这是一种陷阱。内核陷阱处理代码识别设备何时引发中断并调用驱动程序的中断处理程序;在 xv6 中,此调度发生在 { \tt devintr } \lineref{kernel/trap.c:/^devintr/} 中。 9 | 10 | 许多设备驱动程序在两个上下文中执行代码:在进程的内核线程中运行的 \indextext{tophalf} 和在中断时执行的 \indextext{bottomhalf} 。上半部分通过系统调用(例如 { \tt read } 和 { \tt write } )来调用,这些系统调用希望设备执行 I/O。该代码可能会要求硬件启动一个操作(例如,要求磁盘读取一个块);然后代码等待操作完成。最终设备完成操作并引发中断。驱动程序的中断处理程序充当下半部分,确定哪些操作已完成,如果合适,则唤醒等待进程,并告诉硬件开始处理任何等待的下一个操作。 11 | 12 | \section{代码:控制台输入 } 13 | 14 | 控制台驱动程序 \fileref{kernel/console.c} 是驱动程序结构的简单说明。控制台驱动程序通过连接到 RISC-V 的 \indextext{UART} 串行端口硬件接受人类输入的字符。控制台驱动程序一次累积一行输入,处理特殊输入字符,例如退格键和 control-u。用户进程(例如 shell)使用 { \tt read } 系统调用从控制台获取输入行。当您在 QEMU 中向 xv6 键入输入时,您的击键将通过 QEMU 的模拟 UART 硬件传送到 xv6。 15 | 16 | 驱动程序与之通信的 UART 硬件是由 QEMU 模拟的 16550chip~ \cite{ns16550a} 。在真实的计算机上,16550 将管理连接到终端或其他计算机的 RS232 串行链路。运行 QEMU 时,它会连接到您的键盘和显示器。 17 | 18 | UART 硬件对于软件来说表现为一组 \indextext{memory-mapped} 控制寄存器。也就是说,RISC-V 硬件有一些物理地址连接到 UART 设备,以便加载和存储与设备硬件而不是 RAM 进行交互。 UART 的内存映射地址从 0x10000000 或 { \tt UART0 } 开始 19 | \lineref{kernel/memlayout.h:/UART0.0x/} 。有几个 UART 控制寄存器,每个寄存器的宽度为一个字节。它们与 { \tt UART0 } 的偏移量定义在 20 | \lineref{kernel/uart.c:/define.RHR/} 。例如, 21 | { \tt LSR } 寄存器包含指示输入字符是否正在等待软件读取的位。这些字符(如果有)可以从 22 | { \tt RHR } 寄存器。每次读取一个字符时,UART 硬件都会将其从等待字符的内部 FIFO 中删除,并在 FIFO 为空时清除 { \tt LSR } 中的“就绪”位。 UART 发送硬件很大程度上独立于接收硬件;如果软件向 { \tt THR } 写入一个字节,则 UART 会传输该字节。 23 | 24 | Xv6 的 { \tt main } 调用 { \tt consoleinit } 25 | \lineref{kernel/console.c:/^consoleinit/} 初始化 UART 硬件。此代码将 UART 配置为在 UART 接收到每个输入字节时生成一个接收中断,并在每次 UART 完成发送一个输出字节 \lineref{kernel/uart.c:/^uartinit/} 时生成一个 \indextext{transmit complete} 中断。 26 | 27 | xv6 shell 通过 { \tt init.c } \lineref{user/init.c:/open..console/} 打开的文件描述符从控制台读取。对 { \tt read } 系统调用的调用通过内核到达 { \tt consoleread } \lineref{kernel/console.c:/^consoleread/} 。 { \tt consoleread } 等待输入到达(通过中断)并在 { \tt cons.buf } 中进行缓冲,将输入复制到用户空间,然后(在整行到达后)返回到用户进程。如果用户尚未输入整行,则任何读取进程都将在 28 | { \tt sleep } 调用 29 | \lineref{kernel/console.c:/sleep..cons/} (第~\ref{CH:SCHED} 解释了 { \tt sleep } 的详细信息)。 30 | 31 | 当用户键入字符时,UART 硬件会要求 RISC-V 引发中断,从而激活 xv6 的陷阱处理程序。陷阱处理程序调用 { \tt devintr } 32 | \lineref{kernel/trap.c:/^devintr/} ,它查看 RISC-V { \tt scause } 寄存器以发现中断来自外部设备。然后它询问一个称为 PLIC 的硬件单元 33 | \cite{riscv:priv} 告诉它哪个设备中断了 34 | \lineref{kernel/trap.c:/plic.claim/} 。如果是 UART,则 { \tt devintr } 调用 { \tt UARTINTR } 。 35 | 36 | { \tt UARTINTR } 37 | \lineref{kernel/uart.c:/^uartintr/} 从 UART 硬件读取任何等待的输入字符并将它们交给 { \tt consoleintr } 38 | \lineref{kernel/console.c:/^consoleintr/} ;它不等待字符,因为将来的输入将引发新的中断。 { \tt consoleintr } 的工作是将输入字符累积在 39 | { \tt cons.buf } 直到整行到达。 40 | { \tt consoleintr } 特别对待退格键和其他一些字符。当换行符到达时, { \tt consoleintr } 醒来等待 { \tt consoleread } (如果有)。 41 | 42 | 一旦唤醒, { \tt consoleread } 将观察 { \tt cons.buf } 中的整行,将其复制到用户空间,然后返回(通过系统调用机制)到用户空间。 43 | 44 | \section{代码:控制台输出 } 45 | 46 | 对连接到控制台的文件描述符的 { \tt write } 系统调用最终到达 47 | { \tt UARTPUC } 48 | \lineref{kernel/uart.c}{87} 。设备驱动程序维护一个输出缓冲区( { \tt uart\_tx\_buf } ),以便写入过程不必等待 UART 完成发送;相反, { \tt UARTPUC } 将每个字符附加到缓冲区,调用 { \tt uartstart } 启动设备传输(如果尚未准备好),然后返回。 { \tt UARTPUC } 等待的唯一情况是缓冲区已满。 49 | 50 | 每次 UART 完成发送一个字节时,都会生成一个中断。 51 | { \tt UARTINTR } 调用 { \tt uartstart } ,它检查设备是否确实已完成发送,并将下一个缓冲输出字符交给设备。因此,如果进程将多个字节写入控制台,通常第一个字节将由 { \tt UARTPUC } 调用 { \tt uartstart } 发送,其余缓冲字节将在传输完成中断到达时由 { \tt uartstart } 调用从 { \tt UARTINTR } 发送。 52 | 53 | 需要注意的一般模式是通过缓冲和中断将设备活动与进程活动解耦。即使没有进程正在等待读取输入,控制台驱动程序也可以处理输入;随后的读取将看到输入。同样,进程可以发送输出而无需等待设备。这种解耦可以通过允许进程与设备 I/O 并发执行来提高性能,并且当设备速度较慢(如 UART)或需要立即关注(如回显键入的字符)时尤其重要。这个想法有时被称为 \indextext{I/O 54 | concurrency} 。 55 | 56 | \section{驱动程序中的并发性 } 57 | 58 | 您可能已经注意到 { \tt consoleread } 和 { \tt consoleintr } 中对 { \tt acquire } 的调用。这些调用获取锁,以保护控制台驱动程序的数据结构免遭并发访问。这里存在三个并发危险:不同 CPU 上的两个进程可能会同时调用 { \tt consoleread } ;硬件可能会要求 CPU 提供控制台(实际上是 UART)中断,而该 CPU 已经在内部执行 59 | { \tt consoleread } ;并且当 { \tt consoleread } 执行时,硬件可能会在不同的 CPU 上传递控制台中断。这些危险可能会导致竞争或僵局。第~\ref{CH:LOCK} 章探讨了这些问题以及锁如何解决这些问题。 60 | 61 | 驱动程序中需要注意并发性的另一种方式是,一个进程可能正在等待来自设备的输入,但是当另一个进程(或根本没有进程)正在运行时,输入的中断信号到达可能会到达。因此,中断处理程序不允许考虑它们中断的进程或代码。例如,中断处理程序无法使用当前进程的页表安全地调用 { \tt copyout } 。中断处理程序通常执行相对较少的工作(例如,仅将输入数据复制到缓冲区),并唤醒上半部分代码来完成其余的工作。 62 | 63 | \section{定时器中断 } 64 | 65 | Xv6 使用定时器中断来维护其时钟并使其能够在计算密集型进程之间进行切换; { \tt usertrap } 和 { \tt kerneltrap } 中的 { \tt yield } 调用会导致此切换。定时器中断来自连接到每个 RISC-V CPU 的时钟硬件。 Xv6 对该时钟硬件进行编程以定期中断每个 CPU。 66 | 67 | RISC-V 要求定时器中断在机器模式下进行,而不是在管理程序模式下进行。 RISC-V 机器模式执行时无需分页,并具有一组单独的控制寄存器,因此在机器模式下运行普通 xv6 内核代码是不切实际的。因此,xv6 handlertimer 中断完全独立于上面的陷阱机制布局。 68 | 69 | 在 { \tt start.c } 中、在 { \tt main } 之前以机器模式执行的代码设置为接收定时器中断 70 | \lineref{kernel/start.c:/^timerinit/} 。部分工作是对 CLIINT 硬件(核心本地中断器)进行编程,使其在一定延迟后生成中断。另一部分是设置一个临时区域,类似于陷阱帧,以帮助定时器中断处理程序保存寄存器和CLINT寄存器的地址。最后, { \tt start } 将 { \tt mtvec } 设置为 { \tt timervec } 并启用定时器中断。 71 | 72 | 定时器中断可以在用户或内核代码执行时的任何时刻发生;内核无法在关键操作期间禁用定时器中断。因此,定时器中断处理程序必须以保证不会干扰中断的内核代码的方式完成其工作。基本策略是处理程序要求 RISC-V 引发“软件中断”并立即返回。 RISC-V通过普通的陷阱机制将软件中断传递给内核,并允许内核禁用它们。处理定时器中断生成的软件中断的代码可以在 { \tt devintr } \lineref{kernel/trap.c}{205} 中看到。 73 | 74 | 机器模式定时器中断处理程序是 { \tt timervec } 75 | \lineref{kernel/kernelvec.S:/^timervec/} 。它在 { \tt start } 准备的临时区域中保存一些寄存器,告诉CLINT何时生成下一个定时器中断,要求RISC-V引发软件中断,恢复寄存器,然后返回。定时器中断处理程序中没有 C 代码。 76 | 77 | \section{真实世界 } 78 | 79 | Xv6 允许在内核中执行时以及执行用户程序时发生设备和定时器中断。定时器中断强制从定时器中断处理程序进行线程切换(调用 { \tt yield } ),即使在内核中执行时也是如此。如果内核线程有时花费大量时间进行计算而不返回用户空间,那么在内核线程之间公平地对 CPU 进行时间切片的能力非常有用。然而,内核代码需要注意它可能会被挂起(由于计时器中断)并稍后在不同的 CPU 上恢复,这是 xv6 中某些复杂性的根源(请参阅~部分~\ref{s:lockinter} )。如果设备和定时器中断仅在执行用户代码时发生,则内核可以变得更简单。 80 | 81 | 支持典型计算机上的所有设备是一项艰巨的工作,因为设备数量很多,设备具有很多功能,并且设备和驱动程序之间的协议可能很复杂且文档记录很少。在许多操作系统中,驱动程序所占的代码比核心内核还要多。 82 | 83 | UART 驱动程序通过读取 UART 控制寄存器一次检索一个字节的数据;该模式称为 \indextext{programmed I/O} ,因为软件正在驱动数据移动。编程 I/O 很简单,但在高数据速率下使用速度太慢。需要高速移动大量数据的设备通常使用 \indextext{direct memory access (DMA)} 。 DMA 设备硬件直接将传入数据写入 RAM,并从 RAM 读取传出数据。现代磁盘和网络设备使用 DMA。 DMA 设备的驱动程序将在 RAM 中准备数据,然后使用对控制寄存器的单次写入来告诉设备处理准备好的数据。 84 | 85 | 当设备在不可预测的时间需要关注且频率不高时,中断就有意义。但中断的CPU开销很高。因此,高速设备(例如网络和磁盘控制器)使用减少中断需求的技巧。一个技巧是为整批传入或传出请求引发单个中断。驱动程序的另一个技巧是完全禁用中断,并定期检查设备以查看是否需要关注。该技术称为 \indextext{polling} 。如果设备执行操作非常快,则轮询是有意义的,但如果设备大部分时间处于空闲状态,则轮询会浪费 CPU 时间。某些驱动程序根据当前设备负载在轮询和中断之间动态切换。 86 | 87 | UART 驱动程序首先将传入数据复制到内核中的缓冲区,然后复制到用户空间。这在低数据速率下是有意义的,但这样的双副本会显着降低快速生成或消耗数据的设备的性能。一些操作系统能够直接在用户空间缓冲区和设备硬件之间移动数据,通常使用 DMA。 88 | 89 | 正如章节~\ref{CH:UNIX} 中提到的,控制台对应用程序来说是一个常规文件,应用程序使用 \lstinline{read} 和 \lstinline{write} 系统调用读取输入和写入输出。应用程序可能想要控制无法通过标准文件系统调用表达的设备方面(例如,在控制台驱动程序中启用/禁用行缓冲)。 Unix 操作系统支持针对此类情况的 \lstinline{ioctl} 系统调用。 90 | 91 | 计算机的某些用途要求系统必须在充足的时间内做出响应。例如,在安全关键系统中,错过最后期限可能会导致灾难。 Xv6 不适合硬实时设置。硬实时操作系统倾向于与应用程序链接的库,以便进行分析以确定最坏情况的响应时间。 xv6 也不适合软实时应用程序,偶尔错过最后期限是可以接受的,因为 xv6 的调度程序过于简单,并且它的内核代码路径会长时间禁用中断。 92 | 93 | \section{练习 } 94 | 95 | \begin{enumerate} 96 | 97 | 98 | \item 修改 { \tt uart.c } 以完全不使用中断。您可能还需要修改 { \tt console.c } 。 \item 添加以太网卡驱动程序。 \end{enumerate} 99 | 100 | 101 | -------------------------------------------------------------------------------- /fig/mkernel.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 20 | 27 | 32 | 33 | 40 | 45 | 46 | 53 | 54 | 72 | 75 | 76 | 78 | 79 | 81 | image/svg+xml 82 | 84 | 85 | 86 | 87 | 88 | 92 | Microkernel 103 | 110 | 117 | shell 128 | 135 | File server 146 | 150 | 155 | 159 | userspace 175 | kernelspace 191 | 196 |   207 | Send message 218 | 219 | 220 | -------------------------------------------------------------------------------- /tr2tex: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import os 4 | import re 5 | import sys 6 | 7 | def end(l): 8 | # look for " .", etc. 9 | p = re.compile(r'\s+([\.,:;])') 10 | l = p.sub(r'\1', l) 11 | # look for ) ( 12 | p = re.compile(r'\s+\)([\.,]*) \(\n') 13 | m = p.search(l) 14 | if m != None: 15 | l = p.sub(r'', l) 16 | if len(m.groups()) > 0: 17 | l = "(" + l + ")%s\n" % m.groups()[0] 18 | else: 19 | l = "(" + l + ")\n" 20 | # look for '' `` 21 | p = re.compile(r"\s+''([\.,]*) ``\n") 22 | m = p.search(l) 23 | if m != None: 24 | l = p.sub(r'', l) 25 | if len(m.groups()) > 0: 26 | l = "``" + l + "''%s\n" % m.groups()[0] 27 | else: 28 | l = "``" + l + "''\n" 29 | # check for " )" 30 | p = re.compile(r'\s+([\)])') 31 | l = p.sub(r'\1', l) 32 | return l 33 | 34 | def ig2dotdot(): 35 | line = f.readline() 36 | while line: 37 | if line.startswith('..'): 38 | return 39 | print("%", line, end="") 40 | line = f.readline() 41 | 42 | def chapter(l): 43 | p = re.compile(r'.chapter ([:\w]+) "(.+)"') 44 | l = p.sub(r'\\chapter{\2}\n\\label{\1}', l) 45 | print(l, end="") 46 | 47 | def chapter1(l): 48 | p = re.compile(r'.chapterlike "(.+)"') 49 | l = p.sub(r'\\chapter*{\1}\n', l) 50 | print(l, end="") 51 | 52 | def comment(l): 53 | p = re.compile(r'\.\\"(.*)') 54 | l = p.sub(r'%% \1', l) 55 | print(l, end="") 56 | 57 | def index(l): 58 | p = re.compile(r'.index ([-\w]+)') 59 | l = p.sub(r'\\index{\1}', l) 60 | p = re.compile(r'.index "(.*)"') 61 | l = p.sub(r'\\index{\1}', l) 62 | l = end(l) 63 | print(l, end="") 64 | 65 | def defindex(l): 66 | p = re.compile(r'.italic-index ([-\w]+)') 67 | l = p.sub(r'\\textit{\1}\\index{\1}', l) 68 | p = re.compile(r'.italic-index "(.*)"') 69 | l = p.sub(r'\\textit{\1}\\index{\1}', l) 70 | l = end(l) 71 | print(l, end="") 72 | 73 | codep = r'([\/\.\->_\w\&<\(\)\#\[\]\|=\*%\!]+)' 74 | 75 | def code(l): 76 | p = re.compile(r'.code "(.*)"') 77 | l = p.sub(r'\\lstinline{\1}', l) 78 | p = re.compile(r'.code ' + codep) 79 | l = p.sub(r'\\lstinline{\1}', l) 80 | l = end(l) 81 | print(l, end="") 82 | 83 | def codeindex(l): 84 | p = re.compile(r'.code-index ' + codep) 85 | l = p.sub(r'\\lstinline{\1}\\index{\1@\\lstinline{\1}}', l) 86 | p = re.compile(r'.code-index "(.*)"') 87 | l = p.sub(r'\\lstinline{\1}\\index{\1@\\lstinline{\1}}', l) 88 | l = end(l) 89 | print(l, end="") 90 | 91 | def section(l): 92 | p = re.compile(r'.section "(.+)"') 93 | l = p.sub(r'\\section{\1}', l) 94 | print(l, end="") 95 | 96 | def figref(l): 97 | p = re.compile(r'.figref ([:\w]+)') 98 | m = p.search(l) 99 | if p != None: 100 | n = m.groups()[0].split(':') 101 | if len(n) > 1: 102 | n = n[1] 103 | else: 104 | n = n[0] 105 | l = p.sub(r'Figure~\\ref{fig:%s}' % n, l) 106 | l = end(l) 107 | print(l, end="") 108 | 109 | def caption(fname): 110 | with open(fname) as f: 111 | line = f.readline() 112 | while line: 113 | if not line.startswith("."): 114 | return line.rstrip('\n') 115 | line = f.readline() 116 | 117 | def istable(fname): 118 | with open(fname) as f: 119 | line = f.readline() 120 | while line: 121 | if line.startswith(".TS"): 122 | return True 123 | line = f.readline() 124 | return False 125 | 126 | def table(fname): 127 | try: 128 | with open(fname) as f: 129 | print(r'\begin{tabular}{ll}') 130 | line = f.readline() # F1 131 | line = f.readline() # .TS 132 | line = f.readline() # center 133 | line = f.readline() # lb lb 134 | line = f.readline() # l l. 135 | line = f.readline() # heading 136 | p = re.compile(r'([\w\ ]+)\t+([\w]+)\n') 137 | line = p.sub(r'{\\bf \1} & {\\bf \2} \\\\', line) 138 | print(line) 139 | print("\midrule") 140 | # table 141 | line = f.readline() 142 | while line: 143 | if line.startswith(".TE"): 144 | break 145 | p = re.compile(r'([\(\)\w]+)\t+(.+)\n') 146 | line = p.sub(r'\1 & \2 \\\\', line) 147 | print(line) 148 | line = f.readline() 149 | line = f.readline() # F2 150 | line = f.readline() # caption 151 | print("\end{tabular}") 152 | print("\caption{" + line.strip("\n") + "}") 153 | except IOError: 154 | print("error: couldn't open %s" % fname, file=sys.stderr) 155 | 156 | def insert(l): 157 | l = l.rstrip('\n') 158 | fname = l.split(' ')[1] 159 | try: 160 | with open(fname) as f: 161 | line = f.readline() 162 | while line: 163 | print(line, end="") 164 | line = f.readline() 165 | except IOError: 166 | print("error: couldn't open %s" % fname, file=sys.stderr) 167 | 168 | def listing(l): 169 | print(r'\begin{lstlisting}[]') 170 | line = f.readline() 171 | while line: 172 | if line.startswith(".so"): 173 | insert(line) 174 | elif line.startswith(".P2"): 175 | break 176 | else: 177 | print(line, end="") 178 | line = f.readline() 179 | print("\end{lstlisting}"); 180 | 181 | def figure(l): 182 | p = re.compile(r'.figure (.+)\n') 183 | l = p.sub(r'\1', l) 184 | name = "fig/%s.t" % l 185 | print("") 186 | print(r'\begin{figure}[t]') 187 | print("\center") 188 | if istable(name): 189 | table(name) 190 | else: 191 | print("\includegraphics[scale=0.5]{fig/%s.eps}" % l) 192 | print("\caption{%s}" % caption(name)) 193 | print("\label{fig:%s}" % l) 194 | print("\end{figure}") 195 | 196 | def italic(l): 197 | p = re.compile(r'.italic ([\w]+)') 198 | l = p.sub(r'\\textit{\1}', l) 199 | p = re.compile(r'.italic "(.*)"') 200 | l = p.sub(r'\\textit{\1}', l) 201 | 202 | l = end(l) 203 | print(l, end="") 204 | 205 | def address(l): 206 | p = re.compile(r'.address (\w+)') 207 | l = p.sub(r'\\texttt{\1}', l) 208 | l = end(l) 209 | print(l, end="") 210 | 211 | def register(l): 212 | p = re.compile(r'.register (\w+)') 213 | l = p.sub(r'\\texttt{\%\1}', l) 214 | l = end(l) 215 | print(l, end="") 216 | 217 | def lineref(l): 218 | l = l.replace('!', '\\') 219 | p = re.compile(r'.line (.*):\/(.*)\/([+-]?\d+)?') 220 | l = p.sub(r'\\lineref{\1:/\2/\3}', l) 221 | p = re.compile(r'.line (.*):(\d+)') 222 | l = p.sub(r'\\lineref{\1:\2}', l) 223 | l = end(l) 224 | print(l, end="") 225 | 226 | def linerefs(l): 227 | l = l.replace('!', '\\') 228 | p = re.compile(r'.lines (.*):\/(.*)\/([+-]?\d+)?,\/(.*)\/([+-]?\d+)?') 229 | l = p.sub(r'\\linerefs{\1:/\2/\3,/\4/\5}', l) 230 | l = end(l) 231 | print(l, end="") 232 | 233 | def indent(l): 234 | p = re.compile(r'.IP (.*)\n') 235 | l = p.sub(r'\\paragraph{\\textbullet}', l) 236 | l = end(l) 237 | print(l, end="") 238 | 239 | def file(l): 240 | p = re.compile(r'.file ([-\w\/\.]+)') 241 | l = p.sub(r'\\fileref{\1}', l) 242 | p = re.compile(r'.file "(.*)"') 243 | l = p.sub(r'\\fileref{\1}', l) 244 | l = end(l) 245 | print(l, end="") 246 | 247 | def doref(l): 248 | # replace references in a line 249 | p = re.compile(r' \\\*\[([\w:]*)\]') 250 | l = p.sub(r'~\\ref{\1}', l) 251 | return l 252 | 253 | def doquote(l): 254 | # replace references in a line 255 | p = re.compile(r'"([\w]+)"') 256 | l = p.sub(r"``\1''", l) 257 | return l 258 | 259 | def docarrot(l): 260 | p = re.compile(r'(\d+)\^(\d+)(-\d+)*') 261 | l = p.sub(r'$\1^{\2}\3$', l) 262 | return l 263 | 264 | def dourl(l): 265 | p = re.compile(r'https://([\w\.\/]+)') 266 | l = p.sub(r'\\url{https://\1}', l) 267 | return l 268 | 269 | def tr2tex(l): 270 | if l.startswith(". "): 271 | l = "." + l[2:] 272 | 273 | if l.startswith(".ig"): 274 | ig2dotdot() 275 | elif l.startswith(".chapter "): 276 | chapter(l) 277 | elif l.startswith(".chapterlike"): 278 | chapter1(l) 279 | elif l.startswith(".PP"): 280 | print("") 281 | elif l.startswith(".code "): 282 | code(l) 283 | elif l.startswith(".italic-index"): 284 | defindex(l) 285 | elif l.startswith(".index"): 286 | index(l) 287 | elif l.startswith(".code-index"): 288 | codeindex(l) 289 | elif l.startswith(".section"): 290 | section(l) 291 | elif l.startswith(r'.\"'): 292 | comment(l) 293 | elif l.startswith(r'.figref'): 294 | figref(l) 295 | elif l.startswith(r'.figure '): 296 | figure(l) 297 | elif l.startswith(r'.italic'): 298 | italic(l) 299 | elif l.startswith(r'.address '): 300 | address(l) 301 | elif l.startswith(r'.register '): 302 | register(l) 303 | elif l.startswith(r'.line '): 304 | lineref(l) 305 | elif l.startswith(r'.lines '): 306 | linerefs(l) 307 | elif l.startswith(r'.P1'): 308 | listing(l) 309 | elif l.startswith(r'.IP'): 310 | indent(l) 311 | elif l.startswith(r'.file'): 312 | file(l) 313 | elif l.startswith(r'.sheet'): 314 | return 315 | elif l.startswith(r"."): 316 | print("TODO", l) 317 | else: 318 | l = docarrot(l) 319 | l = doref(l) 320 | l = doquote(l) 321 | l = dourl(l) 322 | print(l, end="") 323 | 324 | with open(sys.argv[1]) as f: 325 | line = f.readline() 326 | while line: 327 | tr2tex(line) 328 | line = f.readline() 329 | -------------------------------------------------------------------------------- /fig/os.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 20 | 28 | 34 | 35 | 43 | 49 | 50 | 58 | 64 | 65 | 73 | 79 | 80 | 87 | 93 | 94 | 101 | 102 | 124 | 129 | 130 | 132 | 133 | 135 | image/svg+xml 136 | 138 | 139 | 140 | 141 | 142 | 147 | 154 | Kernel 165 | 172 | 180 | shell 191 | 199 | cat 210 | 215 | 220 | 225 | userspace 241 | kernelspace 257 | systemcall 273 | 278 | 283 | 284 | 285 | -------------------------------------------------------------------------------- /book.mac: -------------------------------------------------------------------------------- 1 | .\" 2 | .\" preliminary formatting, subject to change. 3 | .\" mostly lifted from rsc's thesis and cleaned up. 4 | .\" 5 | .do xflag 3 \" heirloom troff: enable long names like groff 6 | .\" all warnings 7 | .warn +w 8 | .mediasize letter 9 | .hylang en_US 10 | .lc_ctype en_US.UTF-8 11 | .nr realnumbers 1 12 | .if \n[realnumbers] .pn \np 13 | .\" 14 | .\" fonts 15 | .\" 16 | .fp 1 R MinionPro-Regular otf 17 | .feature R +tnum 18 | .fp 2 I MinionPro-It otf 19 | .fp 3 B MinionPro-Bold otf 20 | .fp 4 BI MinionPro-BoldIt otf 21 | .fp 5 C LucidaSans-Typewriter83 pfa 22 | .\" Use vertically centered tilde, circumflex 23 | .ftr R ~\[asciitilde] 24 | .ftr I ~\[asciitilde] 25 | .ftr B ~\[asciitilde] 26 | .ftr BI ~\[asciitilde] 27 | .ftr C ~\[asciitilde] 28 | .ftr R ^\[asciicircum] 29 | .ftr I ^\[asciicircum] 30 | .ftr B ^\[asciicircum] 31 | .ftr BI ^\[asciicircum] 32 | .ftr C ^\[asciicircum] 33 | .\" 34 | .\" date 35 | .\" 36 | .\"9 .fp 5 C LucidaSansCW83 37 | .if \n(mo==1 .ds mo January 38 | .if \n(mo==2 .ds mo February 39 | .if \n(mo==3 .ds mo March 40 | .if \n(mo==4 .ds mo April 41 | .if \n(mo==5 .ds mo May 42 | .if \n(mo==6 .ds mo June 43 | .if \n(mo==7 .ds mo July 44 | .if \n(mo==8 .ds mo August 45 | .if \n(mo==9 .ds mo September 46 | .if \n(mo==10 .ds mo October 47 | .if \n(mo==11 .ds mo November 48 | .if \n(mo==12 .ds mo December 49 | .nr _ (\n(yr%100)+2000 50 | .ds yr \n_ 51 | .ds date "\*(mo \n(dy, \*(yr 52 | .\" 53 | .\" initialization 54 | .\" 55 | .ds center-footer "\\n% 56 | .ds center-header " 57 | .ds left-footer "\s-2DRAFT as of \\*[date]\s+2 58 | .ds left-header " 59 | .ds right-footer "\s-2https://pdos.csail.mit.edu/6.828/xv6\s+2 60 | .ds right-header " 61 | .nr IL 0 62 | .nr ID 4n 63 | .nr IN 0 64 | .nr h1 0 1 65 | .nr line-length 6i 66 | .nr page-line-length 0 67 | .nr left-margin 1.25i 68 | .nr P0 0.4v 69 | .nr page-length 11i 70 | .nr footer-margin 1i 71 | .nr header-margin 1i 72 | .nr point-size 12 73 | .nr RN 0 1 74 | .nr T0 4n 75 | .nr exercise 0 1 76 | .ft 1 77 | .\" 78 | .\" wa - print a warning 79 | .\" 80 | .de wa 81 | .tm \\n(.F:\\n(.c: \\$1 \\$2 \\$3 \\$4 \\$5 \\$6 \\$7 \\$8 \\$9 82 | .. 83 | .\" 84 | .\" init - run-time initialization 85 | .\" 86 | .de notrap 87 | .wh 0 88 | .wh -\\n[footer-margin]u 89 | .wh -\\n[footer-margin]u/2u 90 | .. 91 | .de trap 92 | .wh 0 new-page 93 | .wh -\\n[footer-margin]u page-footer 94 | .wh -\\n[footer-margin]u/2u page-bottom 95 | .. 96 | .de init 97 | .ll \\n[line-length]u 98 | .po \\n[left-margin]u 99 | .pl \\n[page-length]u 100 | .new-page 101 | .trap 102 | .nr page-number 1 103 | .. 104 | .\" 105 | .\" new page 106 | .\" 107 | .de new-page 108 | .sp \\n[header-margin]u/2u 109 | .ev 1 110 | .in 0i 111 | .ll \\n[line-length]u 112 | .ad c 113 | 'tl '\\*[left-header]'\\*[center-header]'\\*[right-header]' 114 | .nr top \\n[header-margin] 115 | \\X'top \\n[top]' 116 | .if \\n[have-postponed-figs] \{ .\" 117 | .br 118 | .ad l 119 | .sp |\\n[top]u 120 | .postponed-figs 121 | .mk top 122 | .ds postponed-figs " 123 | .nr have-postponed-figs 0 124 | .\} 125 | .ev 126 | 'sp |\\n[top]u 127 | .. 128 | .\" 129 | .\" page footer 130 | .\" 131 | .de page-footer 132 | .nr column +1 133 | .ie \\n[column]<\\n[columns] .next-column 134 | .el \{\ 135 | .ll \\n[line-length]u 136 | .po -((\\n[columns]u-1u)*(\\n[line-length]u+\\n[column-margin]u)) 137 | .nr saved-po \\n(.o 138 | .ch page-footer 20i 139 | .ev 1 140 | 'sp |(\\n[page-length]u-\\n[footer-margin]u) 141 | .bp 142 | .ev 143 | .po \\n[saved-po]u 144 | .ch page-footer -\\n[footer-margin]u 145 | .nr column 0 146 | .\} 147 | .. 148 | .\" 149 | .\" .multi-column n margin 150 | .\" 151 | .de multi-column 152 | .nr columns \\$1 153 | .nr column-margin \\$2 154 | .nr page-line-length \\n[line-length]u 155 | .nr line-length (\\n[line-length]u-((\\n[columns]-1)*\\n[column-margin]u))/\\n[columns] 156 | .. 157 | .nr columns 1 158 | .nr column-margin 0 159 | .de next-column 160 | 'sp |\\n[top]u 161 | .po +\\n[line-length]u 162 | .po +\\n[column-margin]u 163 | .. 164 | .\" 165 | .\" page bottom 166 | .\" 167 | .de page-bottom 168 | .nr save-ps \\n(.s 169 | .nr save-ft \\n(.f 170 | .ft 1 171 | .ps \\n[point-size] 172 | .ie \\n[page-line-length]>0 'lt \\n[page-line-length]u 173 | .el 'lt \\n[line-length]u 174 | .po \\n[left-margin]u 175 | .if \\n%>0 .tl \(ts\\*[left-footer]\(ts\\*[center-footer]\(ts\\*[right-footer]\(ts 176 | .\" show index terms in upper right margin 177 | .ch page-bottom 20i 178 | .index-trap 179 | .ft \\n[save-ft] 180 | .ps \\n[save-ps] 181 | .sp |\\nxu 182 | .ch page-bottom -\\n[footer-margin]u/2u 183 | .. 184 | .de index-trap 185 | .ev 1 186 | .mk x 187 | .sp |\\n[top]u 188 | .ad l 189 | .ps 9 190 | .vs 10.8 191 | .nr marginalia \\n[left-margin]u+\\n[line-length]u+0.2i 192 | .po \\n[marginalia]u 193 | .ll (8.4i-\\n[marginalia]u) 194 | .index-terms 195 | .br 196 | .ds index-terms " 197 | .po \\n[left-margin]u 198 | .ev 199 | .. 200 | .de index-terms 201 | .. 202 | .\" 203 | .\" insert index term 204 | .\" (no display, just creates the entry) 205 | .\" 206 | .de index 207 | .if !'\\$2'' .wa "likely missing quote in .index argument: \\$1 \\$2 \\$3 \\$4 \\$5 208 | .am index-terms xx 209 | .if \\n[index-margins] \\h'-0.1i'\\$1\c 210 | \\X'index \\$1' 211 | .br 212 | .xx 213 | .. 214 | .\" 215 | .\" bold 216 | .\" 217 | .de bold 218 | .font-change B "\\$1" "\\$2\\&" "\\$3" 219 | .. 220 | .\" 221 | .\" code font 222 | .\" 223 | .de code 224 | .font-change C "\\$1" "\\$2\\&" "\\$3" 225 | .. 226 | .de code-index 227 | .font-change C "\\$1" "\\$2\&" "\\$3" 228 | .index "\\$1+code 229 | .. 230 | .\" 231 | .\" font change 232 | .\" 233 | .de font-change 234 | .ie \\n(.$==1 .ft \\$1 235 | .el \&\\$4\\f\\$1\\$2\&\\fP\\$3 236 | .. 237 | .\" 238 | .\" italic 239 | .\" 240 | .de italic 241 | .font-change I "\\$1" "\\$2\\&" "\\$3" 242 | .. 243 | .de italic-index 244 | .font-change I "\\$1" "\\$2\&" "\\$3" 245 | .index "\\$1 246 | .. 247 | .\" 248 | .\" left-adjust paragraph 249 | .\" 250 | .nr indent 0 251 | .nr para-spacing 0 252 | .de LP 253 | .in \\n[indent]u 254 | .br 255 | .hy 1 256 | .ad b 257 | .fi 258 | .ft R 259 | .sp \\n[para-spacing]u 260 | .PS 261 | .. 262 | .\" 263 | .\" ordinary paragraph 264 | .\" 265 | .nr LP 0 266 | .de PP 267 | .if \\n(LP>0 .sp 0.2v 268 | .LP 269 | .if \\n(LP>0 .nr LP 0 270 | .ti 2m 271 | .. 272 | .\" 273 | .\" indented paragraph 274 | .\" 275 | .de IP 276 | .br 277 | .ne 3 278 | .sp 0.2v 279 | .ie '\\$2'' .nr IP 4n 280 | .el .nr IP \\$2 281 | .in \\n(INu+\\n(IPu 282 | .if !'\\$1'' \{\ 283 | . ti -\\n(IPu 284 | \&\\$1 285 | . ie \\n(.k>=\\n(.i .br 286 | . el .sp -1\} 287 | .. 288 | .\" 289 | .\" set point size 290 | .\" 291 | .de PS 292 | .ps \\n[point-size] 293 | .nr _ \\n[point-size]*1200u 294 | .vs \\n_u 295 | .. 296 | .\" 297 | .\" chapter-like 298 | .\" 299 | .de big-heading 300 | .init 301 | .mk x 302 | .if !'\\$2'' \v'-1v-4p'\\X'PDFMark: Bookmark 0 \\$2' 303 | .if !'\\$3'' \A'toc-\\$3' 304 | .sp |\\nxu 305 | .ft B 306 | .ps 20 307 | .vs 24 308 | .if !'\\$1'' \\$1 309 | .. 310 | .\" 311 | .\" chapter-like heading 312 | .\" 313 | .de chapterlike 314 | .big-heading "" "\\$1" "\\$1 315 | \\$1 316 | .sp 317 | .LP 318 | .. 319 | .\" 320 | .\" chapter heading 321 | .\" 322 | .de chapter 323 | .big-heading "Chapter \\*[\\$1]" "\\*[\\$1]: \\$2" "\\*[\\$1] 324 | .sp 325 | \\$2 326 | .sp 327 | .LP 328 | .. 329 | .\" 330 | .\" Appendix heading 331 | .\" 332 | .de appendix 333 | .big-heading "Appendix \\*[\\$1]" "\\*[\\$1]: \\$2" "\\*[\\$1] 334 | .sp 335 | \\$2 336 | .sp 337 | .LP 338 | .. 339 | .\" 340 | .\" section heading 341 | .\" 342 | .de section 343 | .br 344 | .ft B 345 | .ps 16 346 | .vs 18 347 | .sp 1.5 348 | \\$1\v'-1v'\\X'PDFMark: Bookmark 1 \\$1' 349 | .PS 350 | .sp 0.5 351 | .ft R 352 | .LP 353 | .. 354 | .\" 355 | .\" exercises 356 | .\" 357 | .de exercise 358 | .LP 359 | \\n+[exercise]. 360 | .. 361 | .de xx 362 | .. 363 | .de answer xx 364 | .ig 365 | .xx 366 | .\" 367 | .\" font change aliases 368 | .\" 369 | .de register-font 370 | .code "\\$1" "\\$2" "\\$3" 371 | .. 372 | .de register 373 | .register-font "%\\$1" "\\$2" "\\$3" 374 | .. 375 | .de opcode 376 | .code "\\$1" "\\$2" "\\$3" 377 | .. 378 | .de file 379 | .code "\\$1" "\\$2" "\\$3" 380 | .. 381 | .de address 382 | .code "\\$1" "\\$2" "\\$3" 383 | .. 384 | .de smallcaps 385 | \&\\$3\s-3\\$1\s+3\\$2 386 | .. 387 | .de sheet 388 | .line "\\$1:1" "\\$2" "\\$3" 389 | .. 390 | .de line 391 | .ds LINE xxxx 392 | .sy touch .turd 393 | .sy ./line \\$1 >.turd 394 | .so .turd 395 | .sy rm -f .turd 396 | .if 'xxxx'\\*[LINE]' .tm LINE FAIL: "\\$1" 397 | \&\\$3\s-3(\\*[LINE])\s+3\\$2 398 | .. 399 | .de lines 400 | .line "\\$1" "\\$2" "\\$3" 401 | .. 402 | .\" 403 | .\" P1 - code start 404 | .\" 405 | .de P1 406 | .sp 0.3v 407 | .IP \\$1 408 | .nf 409 | .ft C 410 | .ta +4n +4n +4n +4n +4n +4n +4n +4n 411 | .ps 10 412 | .vs 12 413 | .. 414 | .\" 415 | .\" P2 - code end 416 | .\" 417 | .de P2 418 | .sp 0.4v 419 | .PS 420 | .LP 421 | .. 422 | .\" 423 | .\" P3 - code break 424 | .\" 425 | .de P3 426 | .P2 427 | .P1 428 | .. 429 | .\" 430 | .\" TS/TE - no-ops for tbl 431 | .\" 432 | .de TS 433 | .. 434 | .de TE 435 | .. 436 | .\" F1/F2/F3 - Figures 437 | .\" 438 | .\" .F1 439 | .\" Figure 440 | .\" .F2 441 | .\" Caption 442 | .\" .F3 443 | .\" 444 | .de F1 445 | .nr top 0 446 | .ll \\n[line-length]u 447 | .po \\n[left-margin]u 448 | .pl \\n[page-length]u 449 | .sp |0i 450 | .mk x 451 | .ds Fignum \\*[fig:\\*[the-figure]] 452 | \\X'start \\nx'\A'fig-\\*[Fignum]' 453 | .. 454 | .de F2 455 | .LP 456 | .ps -2 457 | .vs -2 458 | .br 459 | \fBFigure \\*[Fignum]\fP. 460 | .. 461 | .de F3 462 | .br 463 | .ps +2 464 | .vs +2 465 | \D'l \n[line-length]u 0' 466 | .sp 0.125i 467 | .mk x 468 | .index-trap 469 | \\X'end \\nx' 470 | .. 471 | .\" 472 | .\" figure - load figure from file 473 | .\" 474 | .\" .figure name file 475 | .\" 476 | .nr have-postponed-figs 0 477 | .de postponed-figs 478 | .. 479 | .de figure 480 | .br 481 | .if '\\$1'' .wa .figure needs name argument 482 | .if !'\\$2'' .wa .figure file is implied by name 483 | .\" flush postponed figures 484 | .\" to avoid reordering: want Figure 2 after Figure 1. 485 | .if \\n[have-postponed-figs] .bp 486 | .ds Fignum \\*[fig:\\$1] 487 | .nr Fight \\n[fig-\\$1-height] 488 | .ds Figfile \\*[fig-\\$1-file] 489 | .ie \\n[Fight]-\\n(.t \{ .\" 490 | .\" postpone figure to next page. 491 | .\".wa "Figure \\*[Fignum] postponed \\n[Fight] from \\*[Figfile] 492 | .nr have-postponed-figs 1 493 | .am postponed-figs 494 | \\X'figure \\*[Figfile] \\n[Fight]' 495 | .sp \\n[Fight]u 496 | \\.. 497 | .\} 498 | .el \{ .\" 499 | .\" put figure at top of this page. 500 | .\".wa "Figure \\*[Fignum] at top of this page from \\*[Figfile] 501 | \\X'top \\n[top]' 502 | \\X'figure \\*[Figfile] \\n[Fight]' 503 | .nr top +\\n[Fight] 504 | .sp \\n[Fight]u 505 | .\} 506 | .. 507 | .\" 508 | .\" figref - hyperlinked figure reference 509 | .\" 510 | .de figref 511 | \\$3\T'fig-\\*[fig:\\$1]'Figure \\*[fig:\\$1]\T\\$2 512 | .. 513 | .\" 514 | .\" EPS - Encapsulated PostScript 515 | .\" 516 | .\" .EPS file [scale] 517 | .\" 518 | .\" scale is a percentage (25 means 1/4) 519 | .\" 520 | .de EPS 521 | .psbb \\$1 522 | .nr eps_scale 100 523 | .if !'\\$2'' .nr eps_scale \\$2 524 | .nr y (\\n[ury]p-\\n[lly]p)*\\n[eps_scale]/100 525 | .nr x (\\n[urx]p-\\n[llx]p)*\\n[eps_scale]/100 526 | .nr z (\\n[line-length]u-\\nxu)/2 527 | .\" Troff sees the PI request as a character on a line. 528 | .\" Tell it the line height is the same height as the 529 | .\" EPS file, and then adjust the EPS file upward 530 | .\" to match. 531 | .PI \\$1 "\\nyu,\\nxu,0,\\nzu" 532 | .sp \\nyu 533 | .\" Reset line height. 534 | .PS 535 | .. 536 | .nr index-margins 1 \" 1 = show index terms in margins, 0 = don't 537 | .\" 538 | .\" 539 | .\" table of contents 540 | .\" 541 | .ds CH:UNIX 0 542 | .ds CH:FIRST 1 543 | .ds CH:MEM 2 544 | .ds CH:TRAP 3 545 | .ds CH:LOCK 4 546 | .ds CH:SCHED 5 547 | .ds CH:FS 6 548 | .ds CH:SUM 7 549 | .ds APP:HW A 550 | .ds APP:BOOT B 551 | -------------------------------------------------------------------------------- /fig/race.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 20 | 27 | 32 | 33 | 40 | 41 | 59 | 66 | 67 | 69 | 70 | 72 | image/svg+xml 73 | 75 | 76 | 77 | 78 | 79 | 84 | 88 | Memory 99 | CPU 1 111 | CPU2 122 | 127 | 131 | 135 | 15 147 | l->next 158 | 163 | 16 175 | list 186 | 191 | 195 | 15 207 | 16 219 | list 230 | l->next 241 | 246 | Time 258 | 259 | 260 | -------------------------------------------------------------------------------- /font/LucidaSans-Typewriter83.afm: -------------------------------------------------------------------------------- 1 | StartFontMetrics 2.0 2 | Comment AFM Generated by Ghostscript/pf2afm 3 | FontName LucidaSans-Typewriter83 4 | FullName Lucida Sans Typewriter 83 5 | FamilyName LucidaSansTypewriter83 6 | Weight Medium 7 | Notice Copyright (c) 1991 Bigelow & Holmes Inc. and Y&Y, Inc. (508) 371-3286. All Rights Reserved. 8 | ItalicAngle 0 9 | IsFixedPitch true 10 | UnderlinePosition -100 11 | UnderlineThickness 50 12 | Version 1.003 13 | EncodingScheme FontSpecific 14 | FontBBox -12 -205 618 928 15 | StartCharMetrics 246 16 | C 32 ; WX 502 ; N space ; B 0 0 0 0 ; 17 | C 33 ; WX 502 ; N exclam ; B 201 0 301 603 ; 18 | C 34 ; WX 502 ; N quotedbl ; B 100 422 402 643 ; 19 | C 35 ; WX 502 ; N numbersign ; B 22 0 486 603 ; 20 | C 36 ; WX 502 ; N dollar ; B 88 -50 431 653 ; 21 | C 37 ; WX 502 ; N percent ; B 0 -15 502 618 ; 22 | C 38 ; WX 502 ; N ampersand ; B 9 -15 502 618 ; 23 | C 39 ; WX 502 ; N quoteright ; B 191 392 312 643 ; 24 | C 40 ; WX 502 ; N parenleft ; B 141 -121 442 643 ; 25 | C 41 ; WX 502 ; N parenright ; B 60 -121 362 643 ; 26 | C 42 ; WX 502 ; N asterisk ; B 67 252 435 603 ; 27 | C 43 ; WX 502 ; N plus ; B 35 0 467 432 ; 28 | C 44 ; WX 502 ; N comma ; B 191 -131 312 121 ; 29 | C 45 ; WX 502 ; N hyphen ; B 85 186 417 246 ; 30 | C 46 ; WX 502 ; N period ; B 191 0 312 121 ; 31 | C 47 ; WX 502 ; N slash ; B 30 -121 472 643 ; 32 | C 48 ; WX 502 ; N zero ; B 45 -15 457 618 ; 33 | C 49 ; WX 502 ; N one ; B 75 0 477 618 ; 34 | C 50 ; WX 502 ; N two ; B 68 0 413 618 ; 35 | C 51 ; WX 502 ; N three ; B 95 -15 428 618 ; 36 | C 52 ; WX 502 ; N four ; B 45 0 452 603 ; 37 | C 53 ; WX 502 ; N five ; B 110 -15 419 603 ; 38 | C 54 ; WX 502 ; N six ; B 66 -15 457 618 ; 39 | C 55 ; WX 502 ; N seven ; B 82 0 445 603 ; 40 | C 56 ; WX 502 ; N eight ; B 65 -15 448 618 ; 41 | C 57 ; WX 502 ; N nine ; B 61 -15 451 618 ; 42 | C 58 ; WX 502 ; N colon ; B 191 0 312 442 ; 43 | C 59 ; WX 502 ; N semicolon ; B 191 -131 312 442 ; 44 | C 60 ; WX 502 ; N less ; B 35 0 467 432 ; 45 | C 61 ; WX 502 ; N equal ; B 35 116 467 316 ; 46 | C 62 ; WX 502 ; N greater ; B 35 0 467 432 ; 47 | C 63 ; WX 502 ; N question ; B 67 0 442 618 ; 48 | C 64 ; WX 502 ; N at ; B 30 -15 502 618 ; 49 | C 65 ; WX 502 ; N A ; B 5 0 497 603 ; 50 | C 66 ; WX 502 ; N B ; B 75 0 453 603 ; 51 | C 67 ; WX 502 ; N C ; B 42 -15 470 618 ; 52 | C 68 ; WX 502 ; N D ; B 53 0 471 603 ; 53 | C 69 ; WX 502 ; N E ; B 88 0 459 603 ; 54 | C 70 ; WX 502 ; N F ; B 100 0 472 603 ; 55 | C 71 ; WX 502 ; N G ; B 31 -15 459 618 ; 56 | C 72 ; WX 502 ; N H ; B 60 0 442 603 ; 57 | C 73 ; WX 502 ; N I ; B 70 0 432 603 ; 58 | C 74 ; WX 502 ; N J ; B 80 -15 379 603 ; 59 | C 75 ; WX 502 ; N K ; B 69 0 489 603 ; 60 | C 76 ; WX 502 ; N L ; B 90 0 452 603 ; 61 | C 77 ; WX 502 ; N M ; B 38 0 463 603 ; 62 | C 78 ; WX 502 ; N N ; B 60 0 442 603 ; 63 | C 79 ; WX 502 ; N O ; B 25 -15 477 618 ; 64 | C 80 ; WX 502 ; N P ; B 93 0 471 603 ; 65 | C 81 ; WX 502 ; N Q ; B 25 -131 513 618 ; 66 | C 82 ; WX 502 ; N R ; B 75 0 489 603 ; 67 | C 83 ; WX 502 ; N S ; B 67 -15 454 618 ; 68 | C 84 ; WX 502 ; N T ; B 10 0 492 603 ; 69 | C 85 ; WX 502 ; N U ; B 63 -15 440 603 ; 70 | C 86 ; WX 502 ; N V ; B 6 0 496 603 ; 71 | C 87 ; WX 502 ; N W ; B 7 0 495 603 ; 72 | C 88 ; WX 502 ; N X ; B 4 0 496 603 ; 73 | C 89 ; WX 502 ; N Y ; B 13 0 499 603 ; 74 | C 90 ; WX 502 ; N Z ; B 40 0 462 603 ; 75 | C 91 ; WX 502 ; N bracketleft ; B 181 -121 432 643 ; 76 | C 92 ; WX 502 ; N backslash ; B 30 -121 472 643 ; 77 | C 93 ; WX 502 ; N bracketright ; B 70 -121 322 643 ; 78 | C 94 ; WX 502 ; N asciicircum ; B 35 121 467 603 ; 79 | C 95 ; WX 502 ; N underscore ; B 0 -60 502 0 ; 80 | C 96 ; WX 502 ; N quoteleft ; B 191 392 312 643 ; 81 | C 97 ; WX 502 ; N a ; B 55 -7 481 452 ; 82 | C 98 ; WX 502 ; N b ; B 70 -10 457 643 ; 83 | C 99 ; WX 502 ; N c ; B 67 -10 449 452 ; 84 | C 100 ; WX 502 ; N d ; B 52 -10 438 643 ; 85 | C 101 ; WX 502 ; N e ; B 58 -10 447 452 ; 86 | C 102 ; WX 502 ; N f ; B 60 0 488 653 ; 87 | C 103 ; WX 502 ; N g ; B 50 -171 439 452 ; 88 | C 104 ; WX 502 ; N h ; B 74 0 431 643 ; 89 | C 105 ; WX 502 ; N i ; B 70 0 322 643 ; 90 | C 106 ; WX 502 ; N j ; B 60 -171 364 643 ; 91 | C 107 ; WX 502 ; N k ; B 87 0 483 643 ; 92 | C 108 ; WX 502 ; N l ; B 70 0 322 643 ; 93 | C 109 ; WX 502 ; N m ; B 38 0 464 452 ; 94 | C 110 ; WX 502 ; N n ; B 74 0 431 452 ; 95 | C 111 ; WX 502 ; N o ; B 43 -10 459 452 ; 96 | C 112 ; WX 502 ; N p ; B 73 -161 459 452 ; 97 | C 113 ; WX 502 ; N q ; B 45 -161 432 452 ; 98 | C 114 ; WX 502 ; N r ; B 126 0 452 452 ; 99 | C 115 ; WX 502 ; N s ; B 76 -10 429 452 ; 100 | C 116 ; WX 502 ; N t ; B 53 -10 448 528 ; 101 | C 117 ; WX 502 ; N u ; B 72 -10 428 442 ; 102 | C 118 ; WX 502 ; N v ; B 25 0 477 442 ; 103 | C 119 ; WX 502 ; N w ; B 5 0 497 442 ; 104 | C 120 ; WX 502 ; N x ; B 38 0 469 442 ; 105 | C 121 ; WX 502 ; N y ; B 34 -161 479 442 ; 106 | C 122 ; WX 502 ; N z ; B 55 0 447 442 ; 107 | C 123 ; WX 502 ; N braceleft ; B 95 -121 417 643 ; 108 | C 124 ; WX 502 ; N bar ; B 221 -121 281 643 ; 109 | C 125 ; WX 502 ; N braceright ; B 85 -121 407 643 ; 110 | C 126 ; WX 502 ; N asciitilde ; B 35 142 467 290 ; 111 | C 161 ; WX 502 ; N exclamdown ; B 201 -161 301 442 ; 112 | C 162 ; WX 502 ; N cent ; B 80 0 413 603 ; 113 | C 163 ; WX 502 ; N sterling ; B 103 0 434 618 ; 114 | C 164 ; WX 502 ; N fraction ; B 0 -15 502 618 ; 115 | C 165 ; WX 502 ; N yen ; B 20 0 492 603 ; 116 | C 166 ; WX 502 ; N florin ; B 93 -121 466 618 ; 117 | C 167 ; WX 502 ; N section ; B 88 -133 418 618 ; 118 | C 168 ; WX 502 ; N currency ; B 45 96 457 508 ; 119 | C 169 ; WX 502 ; N quotesingle ; B 191 392 312 643 ; 120 | C 170 ; WX 502 ; N quotedblleft ; B 100 422 402 643 ; 121 | C 171 ; WX 502 ; N guillemotleft ; B 48 35 445 404 ; 122 | C 172 ; WX 502 ; N guilsinglleft ; B 142 35 361 404 ; 123 | C 173 ; WX 502 ; N guilsinglright ; B 142 35 361 404 ; 124 | C 174 ; WX 502 ; N fi ; B 50 0 427 653 ; 125 | C 175 ; WX 502 ; N fl ; B 50 0 427 653 ; 126 | C 177 ; WX 502 ; N endash ; B 55 221 447 271 ; 127 | C 178 ; WX 502 ; N dagger ; B 80 -121 422 603 ; 128 | C 179 ; WX 502 ; N daggerdbl ; B 80 -121 422 603 ; 129 | C 180 ; WX 502 ; N periodcentered ; B 201 171 301 271 ; 130 | C 182 ; WX 502 ; N paragraph ; B 51 -121 408 603 ; 131 | C 183 ; WX 502 ; N bullet ; B 131 118 372 359 ; 132 | C 184 ; WX 502 ; N quotesinglbase ; B 191 -131 312 121 ; 133 | C 185 ; WX 502 ; N quotedblbase ; B 100 -121 402 100 ; 134 | C 186 ; WX 502 ; N quotedblright ; B 100 422 402 643 ; 135 | C 187 ; WX 502 ; N guillemotright ; B 57 35 454 404 ; 136 | C 188 ; WX 502 ; N ellipsis ; B 43 0 458 80 ; 137 | C 189 ; WX 502 ; N perthousand ; B 0 -15 502 618 ; 138 | C 191 ; WX 502 ; N questiondown ; B 60 -171 435 442 ; 139 | C 193 ; WX 502 ; N grave ; B 171 522 352 643 ; 140 | C 194 ; WX 502 ; N acute ; B 151 522 332 643 ; 141 | C 195 ; WX 502 ; N circumflex ; B 116 522 387 643 ; 142 | C 196 ; WX 502 ; N tilde ; B 116 522 387 618 ; 143 | C 197 ; WX 502 ; N macron ; B 136 522 367 583 ; 144 | C 198 ; WX 502 ; N breve ; B 116 522 387 643 ; 145 | C 199 ; WX 502 ; N dotaccent ; B 211 522 291 603 ; 146 | C 200 ; WX 502 ; N dieresis ; B 136 522 367 593 ; 147 | C 202 ; WX 502 ; N ring ; B 180 522 327 669 ; 148 | C 203 ; WX 502 ; N cedilla ; B 202 -161 336 0 ; 149 | C 205 ; WX 502 ; N hungarumlaut ; B 123 522 399 643 ; 150 | C 206 ; WX 502 ; N ogonek ; B 172 -161 306 0 ; 151 | C 207 ; WX 502 ; N caron ; B 116 522 387 643 ; 152 | C 208 ; WX 502 ; N emdash ; B 25 221 477 271 ; 153 | C 225 ; WX 502 ; N AE ; B 0 0 487 603 ; 154 | C 227 ; WX 502 ; N ordfeminine ; B 97 291 421 618 ; 155 | C 232 ; WX 502 ; N Lslash ; B -10 0 452 603 ; 156 | C 233 ; WX 502 ; N Oslash ; B 25 -15 478 618 ; 157 | C 234 ; WX 502 ; N OE ; B 30 -15 495 618 ; 158 | C 235 ; WX 502 ; N ordmasculine ; B 90 291 412 618 ; 159 | C 241 ; WX 502 ; N ae ; B 20 -10 490 452 ; 160 | C 245 ; WX 502 ; N dotlessi ; B 70 0 311 442 ; 161 | C 248 ; WX 502 ; N lslash ; B 70 0 422 643 ; 162 | C 249 ; WX 502 ; N oslash ; B 43 -10 460 452 ; 163 | C 250 ; WX 502 ; N oe ; B 20 -10 492 452 ; 164 | C 251 ; WX 502 ; N germandbls ; B 78 -10 478 653 ; 165 | C -1 ; WX 502 ; N threesuperior ; B 128 291 388 618 ; 166 | C -1 ; WX 502 ; N Aring ; B 5 0 497 737 ; 167 | C -1 ; WX 502 ; N Ntilde ; B 60 0 442 748 ; 168 | C -1 ; WX 502 ; N Yacute ; B 13 0 499 773 ; 169 | C -1 ; WX 502 ; N ecircumflex ; B 58 -10 447 643 ; 170 | C -1 ; WX 502 ; N igrave ; B 70 0 327 643 ; 171 | C -1 ; WX 502 ; N odieresis ; B 43 -10 459 593 ; 172 | C -1 ; WX 502 ; N otilde ; B 43 -10 459 618 ; 173 | C -1 ; WX 502 ; N notequal ; B 35 35 467 397 ; 174 | C -1 ; WX 502 ; N twosuperior ; B 123 301 394 618 ; 175 | C -1 ; WX 502 ; N Eth ; B 13 0 471 603 ; 176 | C -1 ; WX 502 ; N Adieresis ; B 5 0 497 723 ; 177 | C -1 ; WX 502 ; N Udieresis ; B 63 -15 440 723 ; 178 | C -1 ; WX 502 ; N eacute ; B 58 -10 447 643 ; 179 | C -1 ; WX 502 ; N ocircumflex ; B 43 -10 459 643 ; 180 | C -1 ; WX 502 ; N partialdiff ; B 70 -10 434 653 ; 181 | C -1 ; WX 502 ; N plusminus ; B 35 0 467 432 ; 182 | C -1 ; WX 502 ; N Atilde ; B 5 0 497 748 ; 183 | C -1 ; WX 502 ; N Idieresis ; B 70 0 432 723 ; 184 | C -1 ; WX 502 ; N Ucircumflex ; B 63 -15 440 773 ; 185 | C -1 ; WX 502 ; N egrave ; B 58 -10 447 643 ; 186 | C -1 ; WX 502 ; N zcaron ; B 55 0 447 643 ; 187 | C -1 ; WX 502 ; N oacute ; B 43 -10 459 643 ; 188 | C -1 ; WX 502 ; N ydieresis ; B 34 -161 479 593 ; 189 | C -1 ; WX 502 ; N degree ; B 160 437 340 618 ; 190 | C -1 ; WX 502 ; N Acircumflex ; B 5 0 497 773 ; 191 | C -1 ; WX 502 ; N Icircumflex ; B 70 0 432 773 ; 192 | C -1 ; WX 502 ; N Uacute ; B 63 -15 440 773 ; 193 | C -1 ; WX 502 ; N integral ; B 97 -121 406 618 ; 194 | C -1 ; WX 502 ; N ccedilla ; B 67 -161 449 452 ; 195 | C -1 ; WX 502 ; N ograve ; B 43 -10 459 643 ; 196 | C -1 ; WX 502 ; N infinity ; B 10 45 492 382 ; 197 | C -1 ; WX 502 ; N thorn ; B 73 -161 459 643 ; 198 | C -1 ; WX 502 ; N greaterequal ; B 35 -60 467 492 ; 199 | C -1 ; WX 502 ; N registered ; B 70 256 432 618 ; 200 | C -1 ; WX 502 ; N Delta ; B 13 0 489 603 ; 201 | C -1 ; WX 502 ; N Aacute ; B 5 0 497 773 ; 202 | C -1 ; WX 502 ; N summation ; B 20 0 496 603 ; 203 | C -1 ; WX 502 ; N Iacute ; B 70 0 432 773 ; 204 | C -1 ; WX 502 ; N Ugrave ; B 63 -15 440 773 ; 205 | C -1 ; WX 502 ; N Zcaron ; B 40 0 462 773 ; 206 | C -1 ; WX 502 ; N lessequal ; B 35 -60 467 492 ; 207 | C -1 ; WX 502 ; N aring ; B 55 -7 481 669 ; 208 | C -1 ; WX 502 ; N logicalnot ; B 35 121 467 322 ; 209 | C -1 ; WX 502 ; N Agrave ; B 5 0 497 773 ; 210 | C -1 ; WX 502 ; N ntilde ; B 74 0 431 618 ; 211 | C -1 ; WX 502 ; N Igrave ; B 70 0 432 773 ; 212 | C -1 ; WX 502 ; N multiply ; B 31 0 471 440 ; 213 | C -1 ; WX 502 ; N Scaron ; B 67 -15 454 773 ; 214 | C -1 ; WX 502 ; N adieresis ; B 55 -7 481 593 ; 215 | C -1 ; WX 502 ; N eth ; B 45 -10 458 643 ; 216 | C -1 ; WX 502 ; N scaron ; B 76 -10 429 643 ; 217 | C -1 ; WX 502 ; N udieresis ; B 72 -10 428 593 ; 218 | C -1 ; WX 502 ; N yacute ; B 34 -161 479 643 ; 219 | C -1 ; WX 502 ; N brokenbar ; B 221 -121 281 643 ; 220 | C -1 ; WX 502 ; N threequarters ; B 17 -15 515 618 ; 221 | C -1 ; WX 502 ; N Edieresis ; B 88 0 459 723 ; 222 | C -1 ; WX 502 ; N Odieresis ; B 25 -15 477 723 ; 223 | C -1 ; WX 502 ; N atilde ; B 55 -7 481 618 ; 224 | C -1 ; WX 502 ; N idieresis ; B 70 0 382 593 ; 225 | C -1 ; WX 502 ; N ucircumflex ; B 72 -10 428 643 ; 226 | C -1 ; WX 502 ; N underline ; B 0 -90 502 -30 ; 227 | C -1 ; WX 502 ; N minus ; B 35 186 467 246 ; 228 | C -1 ; WX 502 ; N onehalf ; B -1 -15 492 618 ; 229 | C -1 ; WX 502 ; N Omega ; B 20 0 482 618 ; 230 | C -1 ; WX 502 ; N radical ; B 0 -121 502 658 ; 231 | C -1 ; WX 502 ; N Ecircumflex ; B 88 0 459 773 ; 232 | C -1 ; WX 502 ; N Otilde ; B 25 -15 477 748 ; 233 | C -1 ; WX 502 ; N Ydieresis ; B 13 0 499 723 ; 234 | C -1 ; WX 502 ; N acircumflex ; B 55 -7 481 643 ; 235 | C -1 ; WX 502 ; N icircumflex ; B 70 0 402 643 ; 236 | C -1 ; WX 502 ; N uacute ; B 72 -10 428 643 ; 237 | C -1 ; WX 502 ; N zero1 ; B 45 -15 457 618 ; 238 | C -1 ; WX 502 ; N onequarter ; B 0 -15 492 618 ; 239 | C -1 ; WX 502 ; N Eacute ; B 88 0 459 773 ; 240 | C -1 ; WX 502 ; N trademark ; B 0 301 492 603 ; 241 | C -1 ; WX 502 ; N Ocircumflex ; B 25 -15 477 773 ; 242 | C -1 ; WX 502 ; N pi ; B 12 0 493 442 ; 243 | C -1 ; WX 502 ; N aacute ; B 55 -7 481 643 ; 244 | C -1 ; WX 502 ; N iacute ; B 70 0 367 643 ; 245 | C -1 ; WX 502 ; N ugrave ; B 72 -10 428 643 ; 246 | C -1 ; WX 502 ; N underscore1 ; B 33 -60 468 0 ; 247 | C -1 ; WX 502 ; N onesuperior ; B 139 301 285 618 ; 248 | C -1 ; WX 502 ; N product ; B 28 0 474 603 ; 249 | C -1 ; WX 502 ; N lozenge ; B 10 0 492 482 ; 250 | C -1 ; WX 502 ; N Egrave ; B 88 0 459 773 ; 251 | C -1 ; WX 502 ; N Oacute ; B 25 -15 477 773 ; 252 | C -1 ; WX 502 ; N divide ; B 35 0 467 432 ; 253 | C -1 ; WX 502 ; N agrave ; B 55 -7 481 643 ; 254 | C -1 ; WX 502 ; N approxequal ; B 35 60 467 372 ; 255 | C -1 ; WX 502 ; N mu ; B 73 -121 429 442 ; 256 | C -1 ; WX 502 ; N copyright ; B 25 -15 477 618 ; 257 | C -1 ; WX 502 ; N nbspace ; B 0 0 0 0 ; 258 | C -1 ; WX 502 ; N Ccedilla ; B 42 -161 470 618 ; 259 | C -1 ; WX 502 ; N Thorn ; B 93 0 471 603 ; 260 | C -1 ; WX 502 ; N Ograve ; B 25 -15 477 773 ; 261 | C -1 ; WX 502 ; N edieresis ; B 58 -10 447 593 ; 262 | EndCharMetrics 263 | EndFontMetrics 264 | -------------------------------------------------------------------------------- /fig/fslayer.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 19 | 21 | 28 | 34 | 35 | 42 | 48 | 49 | 56 | 57 | 79 | 84 | 85 | 87 | 88 | 90 | image/svg+xml 91 | 93 | 94 | 95 | 96 | 97 | 102 |     Directory 135 | Inode 146 | Logging 157 | Buffer cache 168 | 175 | 180 |   191 | Pathname 202 | File descriptor 213 | 218 | 223 | 228 | 233 | 240 |   251 |   262 | Disk 273 | 274 | 275 | -------------------------------------------------------------------------------- /fig/deadlock.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 20 | 27 | 32 | 33 | 40 | 45 | 46 | 53 | 54 | 72 | 79 | 80 | 82 | 83 | 85 | image/svg+xml 86 | 88 | 89 | 90 | 91 | 92 | 97 | 101 | recv 113 | send 124 | 128 | 206 139 | 207 150 | 216 161 | Time 172 | sleep 183 | wakeup 194 | wait for wakeup forever 205 | 215 216 | test 227 | store p 238 | 204 249 | test 260 | 205 271 | spin forever 282 | 283 | 284 | -------------------------------------------------------------------------------- /first.tex: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | \chapter{Operating system organization} 7 | \label{CH:FIRST} 8 | 9 | 操作系统的一个关键要求是同时支持多个活动。例如,使用 Chapter~\ref{CH:UNIX} 中描述的系统调用接口,进程可以启动新进程: 10 | \lstinline{fork} 。操作系统必须 11 | \indextext{time-share} 这些进程中的计算机资源。例如,即使进程数量多于硬件 CPU 数量,操作系统也必须确保所有进程都有机会执行。操作系统还必须安排 12 | 进程之间的 \indextext{isolation} 。也就是说,如果一个进程有错误并且发生故障,它不应该影响不依赖于有错误的进程的进程。然而,完全隔离太强了,因为进程应该可以有意地交互;管道就是一个例子。因此操作系统必须满足三个要求:复用、隔离和交互。 13 | 14 | 本章概述了如何组织操作系统来实现这三个要求。事实证明,有很多方法可以做到这一点,但本文重点关注以 \indextext{monolithic kernel} 为中心的主流设计,许多 Unix 操作系统都使用它。本章还概述了 xv6 进程(xv6 中的隔离单元),以及 xv6 启动时第一个进程的创建。 15 | 16 | Xv6 在 \indextext{multi-core} \footnote{本文中的“多核”是指共享内存但并行执行的多个 CPU,每个 CPU 都有自己的一组寄存器。本文有时使用术语 17 | \indextext{multiprocessor} 是多核的同义词,但多处理器也可以更具体地指具有多个不同处理器芯片的计算机。 } RISC-V 微处理器上运行,其许多低级功能(例如,其流程实现)特定于 RISC-V。 RISC-V是64位CPU,xv6是用“LP64”C编写的,这意味着C编程语言中的long(L)和指针(P)是64位,但int是32位。本书假设读者已经在某些架构上完成了一些机器级编程,并将在出现时介绍 RISC-V 特定的想法。 RISC-V 的有用参考是“The RISC-V Reader: An Open Architecture Atlas” ~\cite{riscv} 。用户级 ISA 18 | ~\cite{riscv:user} 和特权架构 ~\cite{riscv:priv} 是官方规范。 19 | 20 | 完整计算机中的 CPU 周围环绕着支持硬件,其中大部分以 I/O 接口的形式存在。 Xv6 是为 qemu 的“-machine virt”选项模拟的支持硬件而编写的。这包括 RAM、包含启动代码的 ROM、与用户键盘/屏幕的串行连接以及用于存储的磁盘。 21 | \section{抽象物理资源 } 22 | 23 | 当遇到操作系统时,人们可能会问的第一个问题是为什么要有它?也就是说,可以将图~\ref{fig:api} 中的系统调用实现为一个库,应用程序可以与该库链接。在这个计划中,每个应用程序甚至可以拥有自己的适合其需求的库。应用程序可以直接与硬件资源交互,并以最适合应用程序的方式使用这些资源(例如,实现高或可预测的性能)。一些嵌入式设备或实时系统的操作系统就是以这种方式组织的。 24 | 25 | 这种库方法的缺点是,如果有多个应用程序正在运行,则这些应用程序必须表现良好。例如,每个应用程序必须定期放弃CPU,以便其他应用程序可以运行。如果所有应用程序相互信任并且没有错误,那么这种协作分时方案可能是可行的。对于应用程序来说,更常见的是彼此不信任并且存在错误,因此人们通常需要比协作方案提供的更强的隔离。 26 | 27 | 为了实现强隔离,禁止应用程序直接访问敏感硬件资源,并将资源抽象为服务,会很有帮助。例如,Unix 应用程序仅通过文件系统的 28 | \lstinline{open} , 29 | \lstinline{read} , 30 | \lstinline{write} 和 31 | \lstinline{close} 系统调用,而不是直接读写磁盘。这为应用程序提供了路径名的便利,并且允许操作系统(作为接口的实现者)管理磁盘。即使隔离不是一个问题,有意交互(或只是希望不妨碍彼此)的程序也可能会发现文件系统是比直接使用磁盘更方便的抽象。 32 | 33 | 类似地,Unix在进程之间透明地切换硬件CPU,根据需要保存和恢复寄存器状态,这样应用程序就不必担心时间共享。即使某些应用程序处于无限循环中,这种透明度也允许操作系统共享 CPU。 34 | 35 | 另一个例子,Unix 进程使用 36 | \lstinline{exec} 来构建它们的内存映像,而不是直接与物理内存交互。这允许操作系统决定将进程放置在内存中的位置;如果内存紧张,操作系统甚至可能将某些进程的数据存储在磁盘上。 37 | \lstinline{exec} 还为用户提供了方便的文件系统来存储可执行程序映像。 38 | 39 | Unix 进程之间的许多交互形式都是通过文件描述符发生的。文件描述符不仅抽象了许多细节(例如,管道或文件中的数据存储位置),而且还以简化交互的方式进行定义。例如,如果管道中的一个应用程序失败,内核将为管道中的下一个进程生成文件结束信号。 40 | 41 | 图~\ref{fig:api} 中的系统调用接口经过精心设计,既为程序员提供了便利,又提供了强隔离的可能性。 Unix 接口并不是抽象资源的唯一方法,但它已被证明是一个非常好的方法。 42 | \section{用户模式、管理员模式和系统调用 } 43 | 44 | 强隔离需要应用程序和操作系统之间有硬边界。如果应用程序出错,我们不希望操作系统失败或其他应用程序失败。相反,操作系统应该能够清理失败的应用程序并继续运行其他应用程序。为了实现强隔离,操作系统必须安排应用程序不能修改(甚至读取)操作系统的数据结构和指令,并且应用程序不能访问其他进程的内存。 45 | 46 | CPU为强隔离提供硬件支持。例如,RISC-V具有三种CPU执行指令的模式: 47 | \indextext{machine mode} , 48 | \indextext{supervisor mode} 和 49 | \indextext{user mode} 。在机器模式下执行的指令具有完全特权; CPU 以机器模式启动。机器模式主要用于配置计算机。 Xv6 在机器模式下执行几行,然后更改为管理模式。 50 | 51 | 在管理模式下,CPU 可以执行 52 | \indextext{privileged instructions} :例如,启用和禁用中断、读取和写入保存页表地址的寄存器等。如果用户模式下的应用程序尝试执行特权指令,则CPU不会执行该指令,而是切换到管理程序模式,以便主管模式代码可以终止应用程序,因为它做了一些不应该做的事情。Chapter~\ref{CH:UNIX} 中的图 ~\ref{fig:os} 说明了这种组织。应用程序只能执行用户模式指令(例如,添加数字等),并且据说运行在 53 | \indextext{user space} ,而管理模式下的软件也可以执行特权指令,据说运行在 54 | \indextext{kernel space} 。运行在内核空间(或管理模式)的软件称为 55 | \indextext{kernel} 。 56 | 57 | 想要调用内核函数的应用程序(例如 58 | xv6 中的 \lstinline{read} 系统调用必须转换到内核;应用程序不能直接调用内核函数。 CPU 提供了一种特殊的指令,可以将 CPU 从用户模式切换到管理模式,并在内核指定的入口点进入内核。 (RISC-V 提供 59 | \indexcode{ecall} 指令用于此目的。)一旦 CPU 切换到管理模式,内核就可以验证系统调用的参数(例如,检查传递给系统调用的地址是否是应用程序内存的一部分),决定应用程序是否允许执行请求的操作(例如,检查应用程序是否允许写入指定的文件),然后拒绝或执行它。内核控制转换到管理模式的入口点非常重要;例如,如果应用程序可以决定内核入口点,则恶意应用程序可以在跳过参数验证的点进入内核。 60 | \section{内核组织 } 61 | 62 | 一个关键的设计问题是操作系统的哪一部分应该在管理模式下运行。一种可能性是整个操作系统驻留在内核中,以便所有系统调用的实现都在管理程序模式下运行。这个组织被称为 63 | \indextext{monolithic kernel} 。 64 | 65 | 在这个组织中,整个操作系统以完全的硬件权限运行。这种组织很方便,因为操作系统设计者不必决定操作系统的哪一部分不需要完整的硬件权限。此外,操作系统的不同部分更容易协作。例如,操作系统可能具有可由文件系统和虚拟内存系统共享的缓冲区高速缓存。 66 | 67 | 整体组织的缺点是操作系统不同部分之间的接口通常很复杂(正如我们将在本文的其余部分中看到的),因此操作系统开发人员很容易犯错误。在单片内核中,错误是致命的,因为管理模式下的错误通常会导致内核失败。如果内核出现故障,计算机就会停止工作,因此所有应用程序也会失败。计算机必须重新启动才能重新启动。 68 | 69 | 为了降低内核出错的风险,操作系统设计者可以最大限度地减少在管理模式下运行的操作系统代码量,并在用户模式下执行操作系统的大部分代码。这个内核组织称为 70 | \indextext{microkernel} 。 71 | 72 | \begin{figure}[t] 73 | \center 74 | \includegraphics[scale=0.5]{fig/mkernel.pdf} 75 | \caption{带有文件系统服务器的微内核 } 76 | \label{fig:mkernel} 77 | \end{figure} 78 | 79 | 图~\ref{fig:mkernel} 说明了这种微内核设计。在图中,文件系统作为用户级进程运行。作为进程运行的操作系统服务称为服务器。为了允许应用程序与文件服务器交互,内核提供了进程间通信机制,将消息从一个用户模式进程发送到另一个用户模式进程。例如,如果像 shell 这样的应用程序想要读取或写入文件,它会向文件服务器发送一条消息并等待响应。 80 | 81 | 在微内核中,内核接口由一些低级函数组成,用于启动应用程序、发送消息、访问设备硬件等。这种组织使内核相对简单,因为大多数操作系统驻留在用户级服务器中。 82 | 83 | 在现实世界中,整体内核和微内核都很流行。许多 Unix 内核都是单一的。例如,Linux 具有整体内核,尽管某些操作系统功能作为用户级服务器运行(例如窗口系统)。 Linux 为操作系统密集型应用程序提供了高性能,部分原因是内核的子系统可以紧密集成。 84 | 85 | Minix、L4 和 QNX 等操作系统被组织为带有服务器的微内核,并且在嵌入式设置中得到了广泛部署。 L4 的变体 seL4 足够小,已经过内存安全和其他安全属性的验证~ \cite{sel4} 。 86 | 87 | 关于哪个组织更好,操作系统开发人员之间存在很多争论,并且没有任何确凿的证据。此外,它很大程度上取决于“更好”的含义:更快的性能、更小的代码大小、内核的可靠性、整个操作系统的可靠性(包括用户级服务)等。 88 | 89 | 还有一些实际考虑因素可能比哪个组织的问题更重要。一些操作系统具有微内核,但出于性能原因在内核空间中运行一些用户级服务。一些操作系统具有整体内核,因为它们就是这样开始的,并且几乎没有动力转向纯粹的微内核组织,因为新功能可能比重写现有操作系统以适应微内核设计更重要。 90 | 91 | 从本书的角度来看,微内核和单片操作系统有许多共同的关键思想。它们实现系统调用、使用页表、处理中断、支持进程、使用锁进行并发控制、实现文件系统等。本书重点讨论这些核心思想。 92 | 93 | 与大多数 Unix 操作系统一样,Xv6 是作为整体内核实现的。由此可见,xv6内核接口对应于操作系统接口,内核实现了完整的操作系统。由于 xv6 不提供很多服务,因此它的内核比某些微内核要小,但从概念上讲 xv6 是整体的。 94 | 95 | \section{代码:xv6 组织 } 96 | 97 | \begin{figure}[t] 98 | \center 99 | \begin{tabular}{l|l} 100 | {\bf File} & {\bf Description} \\ 101 | \midrule bio.c & Disk block cache for the file system. \\ console.c & Connect to the user keyboard and screen. \\ entry.S & Very first boot instructions. \\ exec.c & exec() system call. \\ file.c & File descriptor support. \\ fs.c & File system. \\ kalloc.c & Physical page allocator. \\ kernelvec.S & Handle traps from kernel, and timer interrupts. \\ log.c & File system logging and crash recovery. \\ main.c & Control initialization of other modules during boot. \\ pipe.c & Pipes. \\ plic.c & RISC-V interrupt controller. \\ printf.c & Formatted output to the console. \\ proc.c & Processes and scheduling. \\ sleeplock.c & Locks that yield the CPU. \\ spinlock.c & Locks that don't yield the CPU. \\ start.c & Early machine-mode boot code. \\ string.c & C string and byte-array library. \\ swtch.S & Thread switching. \\ syscall.c & Dispatch system calls to handling function. \\ sysfile.c & File-related system calls. \\ sysproc.c & Process-related system calls. \\ trampoline.S & Assembly code to switch between user and kernel. \\ trap.c & C code to handle and return from traps and interrupts. \\ uart.c & Serial-port console device driver. \\ virtio\_disk.c & Disk device driver. \\ vm.c & Manage page tables and address spaces. \\ 102 | \end{tabular} 103 | \caption{Xv6 内核源文件。 } 104 | \label{fig:source} 105 | \end{figure} 106 | 107 | xv6 内核源代码位于 {\tt kernel/} sub-directory。源代码被分成文件,遵循模块化的粗略概念; 108 | 图~\ref{fig:source} 列出了这些文件。模块间接口在 109 | \lstinline{defs.h} 110 | \fileref{kernel/defs.h} 中定义。 111 | \section{流程概览 } 112 | 113 | xv6 中的隔离单位(与其他 Unix 操作系统一样)是 114 | \indextext{process} 。进程抽象可以防止一个进程破坏或监视另一个进程的内存、CPU、文件描述符等。它还可以防止进程破坏内核本身,因此进程无法破坏内核的隔离机制。内核必须小心地实现进程抽象,因为有错误或恶意的应用程序可能会欺骗内核或硬件做一些坏事(例如,规避隔离)。内核用来实现进程的机制包括用户/管理程序模式标志、地址空间和线程的时间分片。 115 | 116 | 为了帮助实施隔离,进程抽象为程序提供了它拥有自己的私有机器的错觉。进程为程序提供了看似私有的内存系统,或者 117 | \indextext{address space} ,其他进程无法读取或写入。进程还为程序提供看似其自己的 CPU 来执行程序的指令。 118 | 119 | Xv6 使用页表(由硬件实现)为每个进程提供自己的地址空间。 RISC-V 页表转换(或“映射”)a 120 | \indextext{virtual address} (RISC-V 指令操作的地址)到 121 | \indextext{physical address} (CPU芯片发送到主存的地址)。 122 | 123 | \begin{figure}[t] 124 | \centering 125 | \includegraphics[scale=0.5]{fig/as.pdf} 126 | \caption{进程虚拟地址空间的布局 } 127 | \label{fig:as} 128 | \end{figure} 129 | 130 | Xv6 为每个进程维护一个单独的页表,用于定义该进程的地址空间。如图~\ref{fig:as} 所示,地址空间包括进程的 131 | \indextext{user memory} 从虚拟地址零开始。首先是指令,然后是全局变量,然后是堆栈,最后是进程可以根据需要扩展的“堆”区域(用于 malloc)。有很多因素限制了进程地址空间的最大大小:RISC-V 上的指针是 64 位宽;硬件在页表中查找虚拟地址时仅使用低 39 位;而 xv6 仅使用 39 位其中的38 位。因此,最大地址为 $2^{38}-1$ = 0x3fffffffff,即 \lstinline{MAXVA} ~\lineref{kernel/riscv.h:/define.MAXVA/} 。在地址空间的顶部,xv6 保留一个用于 \indextext{trampoline} 的页面和一个映射进程的 \indextext{trapframe} 的页面。 Xv6 使用这两个页面来转换到内核并返回;trampoline 页包含转换进和出内核的代码,并且映射 trapframe 对于保存/恢复用户进程的状态是必要的,正如我们将在第 \ref{CH:TRAP} 章中解释的那样。 132 | 133 | xv6 内核为每个进程维护许多状态,并将其收集到一个 134 | \indexcode{struct proc} 135 | \lineref{kernel/proc.h:/^struct.proc/} 。进程最重要的内核状态部分是其页表、内核堆栈和运行状态。我们将使用符号 136 | \indexcode{p->xxx} 引用的元素 137 | \lstinline{proc} 结构;例如, 138 | \indexcode{p->pagetable} 是指向进程页表的指针。 139 | 140 | 每个进程都有一个执行线程(或 141 | \indextext{thread} (简称 \indextext{thread} )执行进程的指令。线程可以挂起并稍后恢复。为了在进程之间透明地切换,内核挂起当前正在运行的线程并恢复另一个进程的线程。线程的大部分状态(局部变量、函数调用返回地址)都存储在线程的堆栈中。每个进程都有两个堆栈:用户堆栈和内核堆栈 ( \indexcode{p->kstack} )。当进程执行用户指令时,只有用户堆栈在使用,而内核堆栈为空。当进程进入内核时(进行系统调用或中断),内核代码在进程的内核堆栈上执行;当进程位于内核中时,其用户堆栈仍然包含保存的数据,但没有被主动使用。进程的线程在主动使用其用户堆栈和其内核堆栈之间交替。内核堆栈是独立的(并受到用户代码的保护),因此即使进程破坏了其用户堆栈,内核也可以执行。 142 | 143 | 进程可以通过执行 RISC-V \indexcode{ecall} 指令来进行系统调用。该指令提高了硬件特权级别并将程序计数器更改为内核定义的入口点。入口点的代码切换到内核堆栈并执行实现系统调用的内核指令。当系统调用完成后,内核通过调用 \indexcode{sret} 指令切换回用户堆栈并返回用户空间,这会降低硬件权限级别并在系统调用指令之后恢复执行用户指令。进程的线程可以在内核中“阻塞”以等待 I/O,并在 I/O 完成时从中断处恢复。 144 | 145 | \indexcode{p->state} 指示进程是否已分配、准备运行、正在运行、正在等待 I/O 或正在退出。 146 | 147 | \indexcode{p->pagetable} 以 RISC-V 硬件期望的格式保存进程的页表。 Xv6 导致分页硬件使用进程的 148 | 在用户空间中执行该进程时 \lstinline{p->pagetable} 。进程的页表还充当分配用于存储进程内存的物理页地址的记录。 149 | 150 | 总之,进程捆绑了两种设计思想:地址空间给进程提供了自己内存的假象,线程给进程提供了自己 CPU 的假象。在xv6中,一个进程由一个地址空间和一个线程组成。在实际操作系统中,一个进程可能有多个线程来利用多个 CPU。 151 | \section{代码:启动xv6,第一个进程和系统调用 } 为了使 xv6 更加具体,我们将概述内核如何启动和运行第一个进程。后续章节将更详细地描述本概述中显示的机制。 152 | 153 | 当 RISC-V 计算机开机时,它会进行自我初始化并运行存储在只读存储器中的引导加载程序。引导加载程序将 xv6 内核加载到内存中。然后,在机器模式下,CPU 从以下位置开始执行 xv6 154 | \indexcode{_entry} 155 | \lineref{kernel/entry.S:/^.entry:/} 。 RISC-V 在禁用分页硬件的情况下启动:虚拟地址直接映射到物理地址。 156 | 157 | 加载器将 xv6 内核加载到物理地址的内存中 158 | \texttt{0x80000000} 。它将内核置于 159 | \texttt{0x80000000} 而不是 160 | \texttt{0x0} 是因为地址范围 161 | \texttt{0x0:0x80000000} 包含 I/O 设备。 162 | 163 | 说明位于 164 | \lstinline{_entry} 设置一个堆栈,以便 xv6 可以运行 C 代码。 Xv6 为初始堆栈声明空间, 165 | \lstinline{stack0} ,在文件中 166 | \lstinline{start.c} 167 | \lineref{kernel/start.c:/stack0/} 。代码位于 168 | \lstinline{_entry} 加载堆栈指针寄存器 169 | \texttt{sp} 和地址 170 | \lstinline{stack0+4096} ,栈顶,因为RISC-V上的栈向下增长。现在内核有了堆栈, 171 | \lstinline{_entry} 调用 C 代码 172 | \lstinline{start} 173 | \lineref{kernel/start.c:/^start/} 。 174 | 175 | 功能 176 | \lstinline{start} 执行一些仅在机器模式下允许的配置,然后切换到管理程序模式。为了进入管理模式,RISC-V提供了指令 177 | \lstinline{mret} 。该指令最常用于从先前的调用从管理模式返回到机器模式。 178 | \lstinline{start} 不会从这样的调用中返回,而是将事情设置得好像曾经有过一样:它将寄存器中的前一个特权模式设置为supervisor 179 | \lstinline{mstatus} ,它将返回地址设置为 180 | \lstinline{main} 通过编写 181 | \lstinline{main} 的地址写入寄存器 182 | \lstinline{mepc} ,通过写入禁用管理模式下的虚拟地址转换 183 | \lstinline{0} 进入页表寄存器 184 | \lstinline{satp} ,并将所有中断和异常委托给管理模式。 185 | 186 | 在进入管理模式之前, 187 | \lstinline{start} 还执行一项任务:它对时钟芯片进行编程以生成定时器中断。完成此内务处理后,\lstinline{start} 通过调用 mret 开始 “returns”到管理模式 188 | \lstinline{mret} 。这会导致程序计数器更改为 189 | \lstinline{main} 190 | \lineref{kernel/main.c:/^main/} 。 191 | 192 | 后 193 | \lstinline{main} 194 | \lineref{kernel/main.c:/^main/} 初始化几个设备和子系统,它通过调用创建第一个进程 195 | \lstinline{userinit} 196 | \lineref{kernel/proc.c:/^userinit/} 。第一个进程执行一个用RISC-V汇编编写的小程序,这使得xv6中的第一个系统调用。 197 | \indexcode{initcode.S} 198 | \lineref{user/initcode.S:3} 加载 \lstinline{exec} 系统调用的编号, \lstinline{SYS_EXEC} 199 | \lineref{kernel/syscall.h:/exec/} ,进入寄存器 { \tt a7 } ,然后调用 \lstinline{ecall} 重新进入内核。 200 | 201 | 内核在 \lstinline{syscall} 中使用寄存器 { \tt a7 } 中的数字 202 | \lineref{kernel/syscall.c:/^syscall/} 调用所需的系统调用。系统调用表 \lineref{kernel/syscall.c:/syscalls/} 映射 203 | \lstinline{SYS_EXEC} 到 \lstinline{sys_exec} ,内核调用。正如我们在 Chapter~ \ref{CH:UNIX} 中看到的, \indexcode{exec} 用新程序(在本例中为 \indexcode{/init} )替换当前进程的内存和寄存器。 204 | 205 | 一旦内核完成 206 | \lstinline{exec} ,返回到 \lstinline{/init} 进程中的用户空间。 207 | \lstinline{init} 208 | 如果需要, \lineref{user/init.c:/^main/} 创建一个新的控制台设备文件,然后将其作为文件描述符 0、1 和 2 打开。然后它在控制台上启动一个 shell。系统已启动。 209 | 210 | \section{安全模型 } 211 | 212 | 您可能想知道操作系统如何处理错误或恶意代码。由于处理恶意行为比处理意外错误要困难得多,因此将本主题视为与安全相关是合理的。以下是操作系统设计中典型安全假设和目标的高级视图。 213 | 214 | 操作系统必须假设进程的用户级代码将尽最大努力破坏内核或其他进程。用户代码可能会尝试取消引用其允许的地址空间之外的指针;它可能会尝试执行任何 RISC-V 指令,甚至是那些不用于用户代码的指令;它可能会尝试读取和写入任何 RISC-V 控制寄存器;它可能会尝试直接访问设备硬件;它可能会将聪明的值传递给系统调用,试图欺骗内核崩溃或做一些愚蠢的事情。内核的目标是限制每个用户进程,使其只能读/写/执行自己的用户内存,使用 32 个通用 RISC-V 寄存器,并以系统调用允许的方式影响内核和其他进程。内核必须阻止任何其他操作。这通常是内核设计中的绝对要求。 215 | 216 | 对内核自身代码的期望有很大不同。内核代码被认为是由善意且细心的程序员编写的。内核代码应该没有错误,并且肯定不包含任何恶意内容。这个假设影响我们分析内核代码的方式。例如,有许多内部内核函数(例如自旋锁),如果内核代码正确使用它们,就会导致严重问题。当检查任何特定的内核代码片段时,我们希望说服自己它的行为正确。然而,我们假设内核代码总体上是正确编写的,并且遵循有关使用内核自身函数和数据结构的所有规则。在硬件层面,RISC-V CPU、RAM、磁盘等假定按照文档中宣传的方式运行,没有硬件错误。 217 | 218 | 当然,在现实生活中事情并不是那么简单。很难阻止聪明的用户代码通过消耗受内核保护的资源(磁盘空间、CPU 时间、进程表槽等)而导致系统无法使用(或导致系统崩溃)。通常不可能编写无错误的代码或设计无缺陷的硬件;如果恶意用户代码的编写者意识到内核或硬件错误,他们就会利用它们。即使在成熟、广泛使用的内核中,例如Linux,人们也会不断发现新的漏洞~ \cite{mitre:cves} 。值得在内核中设计保护措施以防止其存在错误:断言、类型检查、堆栈保护页等。最后,用户代码和内核代码之间的区别有时是模糊的:一些特权用户级进程可能提供基本服务并有效地成为内核代码的一部分。操作系统的权限,并且在某些操作系统中特权用户代码可以将新代码插入内核(与 Linux 的可加载内核模块一样)。 219 | \section{真实世界 } 220 | 221 | 大多数操作系统都采用了进程概念,并且大多数进程看起来与 xv6 类似。然而,现代操作系统支持一个进程内的多个线程,以允许单个进程利用多个 CPU。支持进程中的多个线程涉及 xv6 所没有的大量机制,包括潜在的接口更改(例如 Linux 的 222 | \lstinline{clone} ,一个变体 223 | \lstinline{fork} ),控制进程线程共享的哪些方面。 224 | \section{练习 } 225 | 226 | \begin{enumerate} 227 | 228 | 229 | \item 向 xv6 添加一个系统调用,返回可用的可用内存量。 \end{enumerate} 230 | 231 | 232 | -------------------------------------------------------------------------------- /fig/smp.svg: -------------------------------------------------------------------------------- 1 | 2 | 17 | 19 | 27 | 32 | 33 | 41 | 46 | 47 | 56 | 61 | 62 | 70 | 75 | 76 | 84 | 89 | 90 | 98 | 103 | 104 | 112 | 117 | 118 | 119 | 142 | 147 | 148 | 150 | 151 | 153 | image/svg+xml 154 | 156 | 157 | 158 | 159 | 160 | 166 | 173 | 180 | 187 | CPU 198 | 205 | CPU 216 | l->next = list 227 | l->next = list 238 | list 249 | 256 | 263 | 267 | 271 | Memory 282 | 286 | 290 | 294 | BUS 305 | 306 | 307 | -------------------------------------------------------------------------------- /fig/as.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 20 | 27 | 33 | 34 | 41 | 47 | 48 | 55 | 62 | 69 | 76 | 83 | 90 | 97 | 104 | 105 | 128 | 137 | 138 | 140 | 141 | 143 | image/svg+xml 144 | 146 | 147 | 148 | 149 | 150 | 155 | 162 | 167 | 172 | 177 | 182 | 187 |   198 | 0 209 |   220 |   231 | user textand data 247 | user stack 258 | heap 269 | 274 | MAXVA 285 | trampoline 296 | 301 | trapframe 312 | 313 | 314 | -------------------------------------------------------------------------------- /fig/switch.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 20 | 27 | 32 | 33 | 40 | 45 | 46 | 53 | 54 | 72 | 75 | 76 | 78 | 79 | 81 | image/svg+xml 82 | 84 | 85 | 86 | 87 | 88 | 92 | Kernel 103 | 110 | 120 | shell 131 | 141 | cat 152 | 156 | userspace 172 | kernelspace 188 | 195 | kstack shell 211 | 218 | kstack cat 234 |   252 | kstackscheduler 268 | 272 | 276 | save 287 | restore 298 | 302 | 306 | swtch 317 | swtch 328 | 329 | 330 | -------------------------------------------------------------------------------- /fig/fslayout.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 20 | 27 | 33 | 34 | 41 | 47 | 48 | 55 | 56 | 79 | 88 | 89 | 91 | 92 | 94 | image/svg+xml 95 | 97 | 98 | 99 | 100 | 101 | 106 |     135 | 140 | 0 152 | boot 163 | super 174 | inodes 185 | bit map 196 | data 207 | log 218 | 223 | 1 234 | 239 | 244 | 249 | 254 | 259 | 264 | 269 | 274 | data 287 | .... 298 | 2 309 | 314 | 315 | 316 | -------------------------------------------------------------------------------- /trap.tex: -------------------------------------------------------------------------------- 1 | 2 | 3 | \chapter{Traps and system calls} 4 | \label{CH:TRAP} 5 | 6 | 共有三种事件导致 CPU 搁置普通指令执行并强制将控制权转移到处理该事件的特殊代码。一种情况是系统调用,当用户程序执行 { \tt ecall } 指令以要求内核为其执行某些操作时。另一种情况是 \indextext{exception} :指令(用户或内核)执行非法操作,例如除以零或使用无效的虚拟地址。第三种情况是设备 \indextext{interrupt} ,当设备发出需要注意的信号时,例如当磁盘硬件完成读取或写入请求时。 7 | 8 | 本书使用 \indextext{trap} 作为这些情况的通用术语。通常,\indextext{trap}发生时正在执行的任何代码稍后都需要恢复,并且不需要知道发生了任何特殊情况。也就是说,我们经常希望\indextext{trap}是透明的;这对于设备中断尤其重要,而被中断的代码通常不会预料到这种情况。通常的顺序是\indextext{trap}强制将控制权转移到内核中;内核保存寄存器和其他状态,以便可以恢复执行;内核执行适当的处理程序代码(例如,系统调用实现或设备驱动程序);内核恢复保存的状态并从\indextext{trap}中返回;原始代码从中断处继续。 9 | 10 | xv6 处理内核中的所有\indextext{trap};\indextext{trap}不会传递给用户代码。对于系统调用来说,处理内核中的\indextext{trap}是很自然的。这对于中断来说是有意义的,因为隔离要求仅允许内核使用设备,并且因为内核是在多个进程之间共享设备的便捷机制。它对于异常也有意义,因为 xv6 通过杀死有问题的程序来响应用户空间的所有异常。 11 | 12 | Xv6 \indextext{trap}处理分四个阶段进行:RISC-V CPU 采取的硬件操作、为内核 C 代码做好准备的一些汇编指令、决定如何处理\indextext{trap}的 C 函数以及系统调用或设备驱动程序服务例程。虽然三种\indextext{trap}类型之间的共性表明内核可以使用单个代码路径处理所有\indextext{trap},但事实证明,对于三种不同的情况使用单独的代码会很方便:来自用户空间的\indextext{trap}、来自内核空间的\indextext{trap}和计时器中断。处理\indextext{trap}的内核代码(汇编程序或 C)通常称为 \indextext{handler} ;第一个处理程序指令通常用汇编程序(而不是 C)编写,有时称为 \indextext{vector} 。 13 | 14 | \section{RISC-V trap 机制 } 15 | 16 | 每个 RISC-V CPU 都有一组控制寄存器,内核写入这些控制寄存器来告诉 CPU 如何处理\indextext{trap},并且内核可以读取这些寄存器来找出已发生的\indextext{trap}。 RISC-V 文档包含完整的介绍~ \cite{riscv:priv} 。 { \tt riscv.h } 17 | \lineref{kernel/riscv.h}{1} 包含 xv6 使用的定义。以下是最重要寄存器的概述: 18 | 19 | \begin{itemize} 20 | 21 | 22 | \item \indexcode{stvec} :内核将其\indextext{trap}处理程序的地址写入此处; RISC-V 跳转到 { \tt stvec } 中的地址来处理\indextext{trap}。 \item \indexcode{sepc} :当\indextext{trap}发生时,RISC-V 将程序计数器保存在这里(因为 { \tt pc } 随后会被 { \tt stvec } 中的值覆盖)。这 23 | { \tt sret } (从\indextext{trap}返回)指令将 { \tt sepc } 复制到 24 | { \tt pc } 。内核可以编写 { \tt sepc } 来控制 { \tt sret } 的去向。 \item \indexcode{scause} :RISC-V 在此处放置一个数字来描述\indextext{trap}的原因。 \item \indexcode{sscratch} :\indextext{trap}处理程序代码使用 { \tt scratch } 来帮助避免在保存用户寄存器之前覆盖它们。 \item \indexcode{sstatus} : { \tt sstatus } 中的 SIE 位控制是否启用设备中断。如果内核清除 SIE,RISC-V 将推迟设备中断,直到内核设置 SIE。 SPP 位指示\indextext{trap}是来自用户模式还是管理模式,并控制 { \tt sret } 返回的模式。 \end{itemize} 25 | 26 | 上述寄存器与管理模式下处理的\indextext{trap}相关,并且不能在用户模式下读取或写入。对于机器模式下处理的\indextext{trap},有一组类似的控制寄存器; xv6 仅将它们用于定时器中断的特殊情况。 27 | 28 | 多核芯片上的每个 CPU 都有自己的一组寄存器,并且在任何给定时间都可能有多个 CPU 正在处理\indextext{trap}。 29 | 30 | 当需要强制\indextext{trap}时,RISC-V 硬件会对所有\indextext{trap}类型(定时器中断除外)执行以下操作: 31 | 32 | \begin{enumerate} 33 | 34 | 35 | \item 如果\indextext{trap}是设备中断,并且 { \tt sstatus } SIE 位清零,则不要执行以下任何操作。 \item 通过清除 { \tt sstatus } 中的 SIE 位来禁用中断。 \item 将 { \tt pc } 复制到 { \tt sepc } 。 \item 将当前模式(用户或管理员)保存在 { \tt sstatus } 的 SPP 位中。 \item 设置 { \tt scause } 以反映\indextext{trap}的原因。 \item 将模式设置为主管。 \item 将 { \tt stvec } 复制到 { \tt pc } 。 \item 在新的 { \tt pc } 处开始执行。 \end{enumerate} 36 | 37 | 请注意,CPU 不会切换到内核页表,不会切换到内核中的堆栈,并且不会保存除 { \tt pc } 之外的任何寄存器。内核软件必须执行这些任务。 CPU 在\indextext{trap}期间执行最少工作的原因之一是为软件提供灵活性。例如,某些操作系统在某些情况下省略页表切换以提高\indextext{trap}性能。 38 | 39 | 值得思考是否可以省略上面列出的任何步骤,也许是为了寻找更快的\indextext{trap}。尽管在某些情况下可以使用更简单的顺序,但一般来说,省略许多步骤是危险的。例如,假设 CPU 没有切换程序计数器。然后,来自用户空间的\indextext{trap}可以切换到管理员模式,同时仍然运行用户指令。这些用户指令可能会破坏用户/内核隔离,例如通过修改 { \tt stap } 寄存器以指向允许访问所有物理内存的页表。因此,CPU 切换到内核指定的指令地址(即 { \tt stvec } )非常重要。 40 | 41 | \section{来自用户空间的 trap } 42 | 43 | Xv6 对\indextext{trap}的处理方式有所不同,具体取决于\indextext{trap}是在内核中执行还是在用户代码中执行时发生。这是来自用户代码的\indextext{trap}的描述;章节~ \ref{s:ktraps} 描述了内核代码中的\indextext{trap}。 44 | 45 | 如果用户程序进行系统调用( { \tt ecall } 指令)、执行非法操作或者设备中断,则在用户空间执行时可能会发生\indextext{trap}。来自用户空间的\indextext{trap}的高级路径是 46 | { \tt uservec } 47 | \lineref{kernel/trampoline.S:/^uservec/} ,然后 { \tt usertrap } 48 | \lineref{kernel/trap.c:/^usertrap/} ;返回时, 49 | { \tt usertrap } 50 | \lineref{kernel/trap.c:/^usertrapret/} 然后 51 | { \tt userret } 52 | \lineref{kernel/trampoline.S:/^userret/} 。 53 | 54 | xv6 \indextext{trap}处理设计的一个主要限制是 RISC-V 硬件在强制\indextext{trap}时不会切换页表。这意味着 { \tt stvec } 中的\indextext{trap}处理程序地址必须在用户页表中具有有效的映射,因为这是\indextext{trap}处理代码开始执行时有效的页表。此外,xv6的\indextext{trap}处理代码需要切换到内核页表;为了能够在该切换后继续执行,内核页表还必须具有 { \tt stvec } 指向的处理程序的映射。 55 | 56 | Xv6 使用 \indextext{trampoline} 页满足这些要求。 \indextext{trampoline} 页面包含 { \tt uservec } ,即 { \tt stvec } 指向的 xv6 \indextext{trap}处理代码。\indextext{trampoline}页被映射到每个进程的页表中的地址 57 | \indexcode{TRAMPOLINE} 位于虚拟地址空间的顶部,因此它将位于程序自身使用的内存之上。\indextext{trampoline} 页也映射到内核页表中的地址 { \tt TRAMPOLINE } 。参见图~ \ref{fig:as} 和图~\ref{fig:xv6_layout} 。由于\indextext{trampoline}页面映射到用户页表中,因此没有 { \tt PTE\_U } 标志,\indextext{trap}可以在管理模式下开始执行。由于\indextext{trampoline}页映射到内核地址空间中的同一地址,因此\indextext{trap}处理程序在切换到内核页表后可以继续执行。 58 | 59 | { \tt uservec } \indextext{trap}处理程序的代码位于 { \tt trampoline.S } 中 60 | \lineref{kernel/trampoline.S:/^uservec/} 。当 { \tt uservec } 启动时,所有 32 个寄存器都包含被中断的用户代码拥有的值。这 32 个值需要保存在内存中的某个位置,以便当\indextext{trap}返回到用户空间时可以恢复它们。存储到内存需要使用寄存器来保存地址,但目前还没有可用的通用寄存器!幸运的是,RISC-V 以 { \tt scratch } 寄存器的形式提供了帮助。 { \tt uservec } 开头的 { \tt csrw } 指令将 { \tt a0 } 保存在 { \tt scratch } 中。现在 61 | { \tt uservec } 有一个寄存器( { \tt a0 } )可供使用。 62 | 63 | { \tt uservec } 的下一个任务是保存 32 个用户寄存器。内核为每个进程分配一个内存页 64 | { \tt trapframe } 结构(除其他外)有空间保存 32 个用户寄存器 65 | \lineref{kernel/proc.h}{43} 。因为 { \tt stap } 仍然引用用户页表,所以 { \tt uservec } 需要将trapframe映射到用户地址空间。 xv6将每个进程的trapframeat虚拟地址 { \tt trapframe } 映射到该进程的用户页表中; 66 | { \tt trapframe } 正好低于 { \tt TRAMPOLINE } 。进程的 { \tt p->trapframe } 也指向\indextext{trap}帧,尽管位于其物理地址,因此内核可以通过内核页表使用它。 67 | 68 | 因此, { \tt uservec } 将地址 { \tt trapframe } 加载到 { \tt a0 } 中,并在那里保存所有用户寄存器,包括从 { \tt scratch } 读回的用户的 { \tt a0 } 。 69 | 70 | { \tt trapframe } 包含当前进程的内核堆栈地址、当前CPU的hartid、 { \tt usertrap } 函数的地址以及内核页表的地址。 { \tt uservec } 检索这些值,将 { \tt stap } 切换到内核页表,并调用 { \tt usertrap } 。 71 | 72 | { \tt usertrap } 的工作是确定\indextext{trap}的原因、处理它并返回 73 | \lineref{kernel/trap.c:/^usertrap/} 。它首先更改 { \tt stvec } ,以便内核中的\indextext{trap}将由 74 | { \tt kernelvec } 而不是 { \tt uservec } 。它保存了 { \tt sepc } 寄存器(保存的用户程序计数器),因为 75 | { \tt usertrap } 可能会调用 \lstinline{yield} 来切换到另一个进程的内核线程,并且该进程可能会返回到用户空间,在此过程中它将修改 \lstinline{sepc} 。如果\indextext{trap}是系统调用,则 { \tt usertrap } 调用 { \tt syscall } 来处理它;如果是设备中断,则 { \tt devintr } ;否则它是异常,内核会终止出错的进程。系统调用路径向保存的用户程序计数器添加 4,因为 RISC-V 在系统调用的情况下使程序指针指向 { \tt ecall } 指令,但用户代码需要在后续指令处恢复执行。在出去时, { \tt usertrap } 检查进程是否已被终止或应让出 CPU(如果此\indextext{trap}是计时器中断)。 76 | 77 | 返回用户空间的第一步是调用 { \tt usertrapret } 78 | \lineref{kernel/trap.c:/^usertrapret/} 。该函数设置 RISC-V 控制寄存器,为用户空间的未来\indextext{trap}做好准备。这涉及更改 { \tt stvec } 以引用 { \tt uservec } ,准备\indextext{trap}帧字段 79 | { \tt uservec } 依赖于先前保存的用户程序计数器,并将 { \tt sepc } 设置为。最后, { \tt usertrap } 在映射到用户页表和内核页表中的\indextext{trampoline}页上调用 { \tt userret } ;原因是 { \tt userret } 中的汇编代码会切换页表。 80 | 81 | { \tt usertrap } 对 { \tt userret } 的调用将指针传递给 { \tt a0 } 中进程的用户页表 82 | \lineref{kernel/trampoline.S:/^userret/} 。 83 | { \tt userret } 将 { \tt stap } 切换到进程的用户页表。回想一下,用户页表映射了\indextext{trampoline}页和 { \tt trapframe } ,但没有映射来自内核的其他内容。用户页表和内核页表中相同虚拟地址的\indextext{trampoline}页映射允许 84 | { \tt userret } 在更改 { \tt stap } 后继续执行。从此时起, { \tt userret } 唯一可以使用的数据是寄存器内容和\indextext{trap}帧的内容。 85 | { \tt userret } 将 { \tt trapframe } 地址加载到 { \tt a0 } 中,通过 { \tt a0 } 从 trapframe 中恢复保存的用户寄存器,恢复保存的用户 { \tt a0 } ,并执行 { \tt sret } 返回用户空间。 86 | 87 | \section{代码:调用系统调用 } 88 | 89 | 第~ \ref{CH:FIRST} 结束于 90 | \indexcode{initcode.S} 调用 { \tt exec } 系统调用 91 | \lineref{user/initcode.S:/SYS_exec/} 。让我们看看用户调用如何进入内核中的 { \tt exec } 系统调用的实现。 92 | 93 | { \tt initcode.S } 放置参数 94 | \indexcode{exec} 位于寄存器 { \tt a0 } 和 { \tt a1 } 中,并将系统调用号放入 95 | \texttt{a7} 。系统调用号与 { \tt syscall } 数组(函数指针表)中的条目匹配 96 | \lineref{kernel/syscall.c:/syscalls/} 。 \lstinline{ecall} 指令陷入内核并导致 97 | { \tt uservec } , 98 | { \tt usertrap } ,然后 { \tt syscall } 执行,如我们上面所见。 99 | 100 | \indexcode{syscall} 101 | \lineref{kernel/syscall.c:/^syscall/} 从保存的系统调用号中检索 102 | trapframe 中的 \texttt{a7} 并使用它来索引 { \tt 系统调用 } 。对于第一个系统调用, 103 | \texttt{a7} 包含 104 | \indexcode{SYS_exec} 105 | \lineref{kernel/syscall.h:/SYS_exec/} ,导致调用系统调用实现函数 106 | \lstinline{sys_exec} 。 107 | 108 | 当 \lstinline{sys_exec} 返回时, 109 | \lstinline{syscall} 将其返回值记录在 110 | \lstinline{p->trapframe->a0} 。这将导致原始用户空间调用 111 | { \tt exec() } 返回该值,因为 RISC-V 上的 Ccalling 约定将返回值放在 { \tt a0 } 中。系统调用通常返回负数来指示错误,返回零或正数来指示成功。如果系统调用号无效, 112 | \lstinline{syscall} 打印错误并返回 $-1$ 。 113 | 114 | \section{代码:系统调用参数 } 115 | 116 | 内核中的系统调用实现需要找到用户代码传递的参数。由于用户代码调用系统调用包装函数,因此参数最初位于 RISC-V C 调用约定放置它们的位置:寄存器中。kerneltrap代码将用户寄存器保存到当前进程的\indextext{trap}框架中,内核代码可以在其中找到它们。核函数 117 | \lstinline{argint} , 118 | \lstinline{argaddr} 和 119 | \lstinline{argfd} 从\indextext{trap}帧中检索第 n 个系统调用参数作为整数、指针或文件描述符。它们都调用 { \tt argraw } 来检索适当的已保存用户寄存器 120 | \lineref{kernel/syscall.c:/^argraw/} 。 121 | 122 | 某些系统调用将指针作为参数传递,内核必须使用这些指针来读取或写入用户内存。例如, { \tt exec } 系统调用向内核传递一个指向用户空间中字符串参数的指针数组。这些指示提出了两个挑战。首先,用户程序可能有错误或恶意,并且可能向内核传递无效指针或旨在欺骗内核访问内核内存而不是用户内存的指针。其次,xv6 内核页表映射与用户页表映射不同,因此内核无法使用普通指令从用户提供的地址加载或存储。 123 | 124 | 内核实现了安全地与用户提供的地址之间传输数据的功能。 125 | { \tt fetchstr } 是一个示例 \lineref{kernel/syscall.c:/^fetchstr/} 。文件系统调用例如 126 | { \tt exec } 使用 { \tt fetchstr } 从用户空间检索字符串文件名参数。 127 | \lstinline{fetchstr} 调用 \lstinline{copyinstr} 来完成这项艰苦的工作。 128 | 129 | \indexcode{copyinstr} 130 | \lineref{kernel/vm.c:/^copyinstr/} 复制最多 \lstinline{max} 字节到 131 | \lstinline{dst} 来自用户页表 \lstinline{pagetable} 中的虚拟地址 \lstinline{srcva} 。由于 \lstinline{pagetable} 是 { \it 不 } 当前页表, 132 | \lstinline{copyinstr} 使用 { \tt walkaddr } (调用 { \tt walk } )来查找 133 | \lstinline{srcva} 中 134 | \lstinline{pagetable} ,产生物理地址 \lstinline{pa0} 。内核将每个物理RAM地址映射到相应的内核虚拟地址,因此 135 | { \tt copyinstr } 可以直接将字符串字节从 { \tt pa0 } 复制到 { \tt dst } 。 136 | { \tt walkaddr } 137 | \lineref{kernel/vm.c:/^walkaddr/} 检查用户提供的虚拟地址是否是进程用户地址空间的一部分,因此程序无法欺骗内核读取其他内存。类似的函数 { \tt copyin } 将数据从内核复制到用户提供的地址。 138 | 139 | \section{来自内核空间的trap} 140 | \label{s:ktraps} 141 | 142 | Xv6 对 CPU \indextext{trap}寄存器的配置略有不同,具体取决于执行的是用户代码还是内核代码。当内核在CPU上执行时,内核将 { \tt stvec } 指向 { \tt kernelvec } 处的汇编代码 143 | \lineref{kernel/kernelvec.S:/^kernelvec/} 。由于 xv6 已经在内核中,因此 { \tt kernelvec } 可以依赖设置为内核页表的 { \tt stap } 以及引用有效内核堆栈的堆栈指针。 144 | { \tt kernelvec } 将所有 32 个寄存器压入堆栈,稍后将从堆栈中恢复它们,以便中断的内核代码可以不受干扰地恢复。 145 | 146 | { \tt kernelvec } 将寄存器保存在被中断的内核线程的堆栈上,这是有意义的,因为寄存器值属于该线程。如果\indextext{trap}导致切换到不同的线程,这一点尤其重要——在这种情况下,\indextext{trap}实际上将从新线程的堆栈中返回,将被中断线程保存的寄存器安全地保留在其堆栈上。 147 | 148 | { \tt kernelvec } 跳转到 { \tt kerneltrap } 149 | 保存寄存器后的 \lineref{kernel/trap.c}{135} 。 150 | { \tt kerneltrap } 为两种类型的\indextext{trap}做好了准备:设备中断和异常。它调用 151 | { \tt devintr } 152 | \lineref{kernel/trap.c:/^devintr/} 检查并处理前者。如果\indextext{trap}不是设备中断,那么它一定是一个异常,并且如果它发生在 xv6 内核中,则始终是致命错误;内核调用 \lstinline{panic} 并停止执行。 153 | 154 | 如果由于计时器中断而调用 { \tt kerneltrap } ,并且进程的内核线程正在运行(而不是调度程序线程), 155 | { \tt kerneltrap } 调用 { \tt yield } 为其他线程提供运行的机会。在某些时候,其中一个线程将屈服,并让我们的线程及其 { \tt kerneltrap } 再次恢复。 Chapter~ \ref{CH:SCHED} 解释了 { \tt yield } 中发生的情况。 156 | 157 | 当 { \tt kerneltrap } 的工作完成时,它需要返回到被\indextext{trap}中断的任何代码。因为 { \tt yield } 可能会干扰 { \tt sepc } 和 { \tt sstatus } 中的先前模式, 158 | { \tt kerneltrap } 在启动时保存它们。现在它恢复这些控制寄存器并返回到 { \tt kernelvec } 159 | \lineref{kernel/kernelvec.S:/call.kerneltrap$/} 。 160 | { \tt kernelvec } 从堆栈中弹出保存的寄存器并执行 { \tt sret } ,它将 { \tt sepc } 复制到 { \tt pc } 并恢复中断的内核代码。 161 | 162 | 值得思考的是,如果 163 | 由于定时器中断, { \tt kerneltrap } 调用了 { \tt yield } 。 164 | 165 | 当CPU从用户空间进入内核时,Xv6将CPU的 { \tt stvec } 设置为 { \tt kernelvec } ;你可以在 { \tt usertrap } 中看到这一点 166 | \lineref{kernel/trap.c:/stvec.*kernelvec/} 。内核开始执行时有一个时间窗口,但 { \tt stvec } 仍设置为 { \tt uservec } ,并且在该窗口期间不发生设备中断至关重要。幸运的是,RISC-V 在开始捕获\indextext{trap}时始终会禁用中断,并且 xv6 在设置 { \tt stvec } 之前不会再次启用中断。 167 | 168 | \section{页面错误异常 } 169 | \label{sec:pagefaults} 170 | 171 | Xv6 对异常的响应非常无聊:如果用户空间发生异常,内核就会杀死出错的进程。如果内核中发生异常,内核就会发生 \indextext{panic}。真正的操作系统通常会以更有趣的方式做出响应。 172 | 173 | 举个例子,许多内核使用页面错误来实现 174 | \indextext{copy-on-write (COW) fork} 。为了解释写时复制\indextext{fork},请考虑 xv6 的 \lstinline{fork} ,如 Chapter~ \ref{CH:MEM} 中所述。 175 | \lstinline{fork} 导致子进程的初始内存内容与\indextext{fork}时父进程的初始内存内容相同。 Xv6 实现 fork 176 | \lstinline{uvmcopy} 177 | \lineref{kernel/vm.c:/^uvmcopy/} ,为子进程分配物理内存并将父进程的内存复制到其中。如果孩子和父母可以共享父母的物理内存,那么效率会更高。然而,直接实现这一点是行不通的,因为它会导致父进程和子进程对共享堆栈和堆的写入扰乱彼此的执行。 178 | 179 | 父进程和子进程可以通过适当使用页表权限和页错误来安全地共享物理内存。 CPU 提出一个 180 | \indextext{page-fault exception} 当使用在页表中没有映射的虚拟地址时,或者具有清除 \lstinline{PTE_V} 标志的映射,或者其权限位( \lstinline{PTE_R} , 181 | \lstinline{PTE_W} , 182 | \lstinline{PTE_X} , 183 | \lstinline{PTE_U} )禁止正在尝试的操作。 RISC-V 区分三种页面错误:加载页面错误(当加载指令无法翻译其虚拟地址时)、存储页面错误(当存储指令无法翻译其虚拟地址时)和指令页面错误(当程序计数器中的地址不正确时)。翻译)。这 184 | \lstinline{scause} 寄存器指示页错误的类型, \indexcode{stval} 寄存器包含无法转换的地址。 185 | 186 | COW fork 中的基本计划是让父进程和子进程最初共享所有物理页,但每个将它们映射为只读(使用 187 | \lstinline{PTE_W} 标志清除)。父母和孩子可以从共享物理内存中读取数据。如果其中任何一个写入给定页面,RISC-V CPU 就会引发页面错误异常。内核的\indextext{trap}处理程序通过分配新的物理内存页并将故障地址映射到的物理页复制到其中来做出响应。内核更改故障进程页表中的相关 PTE 以指向副本并允许写入和读取,然后在导致故障的指令处恢复故障进程。由于 PTE 允许写入,因此重新执行的指令现在将正确执行。写时复制需要簿记来帮助决定何时可以释放物理页面,因为根据\indextext{fork}、页面错误、执行和退出的历史记录,每个页面都可以由不同数量的页表引用。这种簿记允许进行重要的优化:如果进程发生存储页错误并且仅从该进程的页表引用物理页,则不需要复制。 188 | 189 | 写入时复制使 \lstinline{fork} 更快,因为 \lstinline{fork} 不需要复制内存。稍后写入时,某些内存必须被复制,但通常情况下,大多数内存永远不需要复制。一个常见的例子是 190 | \lstinline{fork} 后跟 \lstinline{exec} :在 \lstinline{fork} 之后可能会写入几页,但随后子进程的 \lstinline{exec} 会释放从父进程继承的大部分内存。写入时复制 \lstinline{fork} 无需复制该内存。此外,COW \indextext{fork}是透明的:无需对应用程序进行任何修改即可受益。 191 | 192 | 除了 COW 分支之外,页表和页错误的组合还开辟了广泛的有趣的可能性。另一个广泛使用的功能称为 \indextext{lazy allocation} ,它有两个部分。首先,当应用程序通过调用请求更多内存时 193 | \lstinline{sbrk} ,内核注意到大小的增加,但不分配物理内存,也不为新的虚拟地址范围创建 PTE。其次,当这些新地址之一发生页面错误时,内核会分配物理内存页面并将其映射到页表中。与 COW fork 一样,内核可以对应用程序透明地实现延迟分配。 194 | 195 | 由于应用程序经常要求比它们需要的更多的内存,因此延迟分配是一个胜利:内核不必为应用程序从不使用的页面执行任何工作。此外,如果应用程序要求大量增加地址空间,则没有延迟分配的 \lstinline{sbrk} 成本高昂:如果应用程序要求 1 GB 内存,则内核必须分配 262,144 4096 字节页面并将其归零。惰性分配允许这种成本随着时间的推移而分散。另一方面,惰性分配会带来页面错误的额外开销,这涉及内核/用户转换。操作系统可以通过为每个页面错误分配一批连续的页面而不是一个页面以及专门针对此类页面错误的内核进入/退出代码来降低此成本。 196 | 197 | 另一个广泛使用的利用页面错误的功能是 198 | \indextext{demand paging} 。在 \lstinline{exec} 中,xv6 将应用程序的所有文本和数据急切地加载到内存中。由于应用程序可能很大并且从磁盘读取数据的成本很高,因此这种启动成本可能会引起用户的注意:当用户从 shell 启动大型应用程序时,用户可能需要很长时间才能看到响应。为了缩短响应时间,现代内核为用户地址空间创建页表,但将页面的 PTE 标记为无效。发生页面错误时,内核从磁盘读取页面内容并将其映射到用户地址空间。与 COW fork 和延迟分配一样,内核可以对应用程序透明地实现此功能。 199 | 200 | 计算机上运行的程序可能需要比计算机 RAM 更多的内存。为了优雅地应对,操作系统可能会实现 \indextext{paging to disk} 。这个想法是只将一部分用户页面存储在 RAM 中,并将其余部分存储在磁盘上 201 | \indextext{paging area} 。内核将与存储在分页区域(因此不在 RAM 中)的内存相对应的 PTE 标记为无效。如果应用程序尝试使用已 { \it paged out } 到磁盘的页面之一,则应用程序将引发页面错误,并且该页面必须是 { \it paged in } :kerneltrap处理程序将分配物理 RAM 页面,将该页面从磁盘读取到 RAM 中,并修改相关PTE以指向RAM。 202 | 203 | 如果需要调入一个页面,但没有可用的物理 RAM,会发生什么情况?在这种情况下,内核必须首先释放物理页,方法是将其调出或 { \it evicting } 到磁盘上的分页区域,并将引用该物理页的 PTE 标记为无效。驱逐的成本很高,因此如果不频繁进行分页,则性能最佳:如果应用程序仅使用其内存页面的子集,并且子集的并集适合 RAM。该属性通常被称为具有良好的引用局部性。与许多虚拟内存技术一样,内核通常以对应用程序透明的方式实现对磁盘的分页。 204 | 205 | 无论硬件提供多少 RAM,计算机通常都使用很少或没有 { \it free } 物理内存来运行。例如,云提供商在一台机器上多路复用许多客户,以便经济高效地使用他们的硬件。另一个例子,用户在智能手机上的少量物理内存中运行许多应用程序。在这样的设置中,分配页面可能需要首先驱逐现有页面。因此,当可用物理内存稀缺时,分配成本很高。 206 | 207 | 当可用内存稀缺时,延迟分配和请求分页特别有利。急切地分配内存 208 | \lstinline{sbrk} 或 \lstinline{exec} 会产生额外的驱逐成本以使内存可用。此外,还存在浪费急切工作的风险,因为在应用程序使用该页面之前,操作系统可能已将其驱逐。 209 | 210 | 结合分页和页错误异常的其他功能包括自动扩展堆栈和内存映射文件。 211 | 212 | \section{真实世界 } 213 | 214 | \indextext{trampoline}和\indextext{trap}框架可能看起来过于复杂。一个驱动力是 RISC-V 在强制\indextext{trap}时有意尽可能少地执行操作,以允许非常快速的\indextext{trap}处理,事实证明这很重要。因此,kerneltrap处理程序的前几条指令实际上必须在用户环境中执行:用户页表和用户寄存器内容。\indextext{trap}处理程序最初不知道有用的事实,例如正在运行的进程的身份或内核页表的地址。解决方案是可能的,因为 RISC-V 提供了受保护的位置,内核可以在进入用户空间之前在其中隐藏信息: { \tt scratch } 寄存器和指向内核内存但由于缺少 \lstinline{PTE_U} 而受到保护的用户页表条目。 Xv6 的 \indextext{trampoline} 和 \indextext{trapframe} 利用了这些 RISC-V 功能。 215 | 216 | 如果将内核内存映射到每个进程的用户页表(具有适当的 PTE 权限标志),则可以消除对特殊\indextext{trampoline}页面的需求。当从用户空间捕获到内核时,这也将消除对页表切换的需要。这反过来又允许内核中的系统调用实现利用当前进程的用户内存映射,从而允许内核代码直接取消引用用户指针。许多操作系统都使用这些想法来提高效率。 Xv6 避免了它们,以减少由于无意使用用户指针而导致内核中出现安全错误的可能性,并减少确保用户和内核虚拟地址不重叠所需的复杂性。 217 | 218 | 生产操作系统实现写时复制\indextext{fork}、延迟分配、请求分页、分页到磁盘、内存映射文件等。此外,生产操作系统将尝试使用所有物理内存,无论是用于应用程序还是缓存(例如,缓冲区缓存)文件系统的部分,我们将在后面的部分~ \ref{s:bcache} 中介绍)。 Xv6 在这方面是 na\"{i}ve:您希望操作系统使用您付费的物理内存,但 xv6 不这样做。此外,如果 xv6 内存不足,它会向正在运行的应用程序返回错误或终止它,而不是逐出另一个应用程序的页面。 219 | 220 | \section{练习 } 221 | 222 | \begin{enumerate} 223 | 224 | 225 | \item 函数 { \tt copyin } 和 { \tt copyinstr } 在软件中遍历用户页表。设置内核页表,以便内核映射用户程序, { \tt copyin } 和 { \tt copyinstr } 可以使用 { \tt memcpy } 将系统调用参数复制到内核空间,依靠硬件进行页表遍历。 \item 实现惰性内存分配。 \item 实施 COW \indextext{fork}。 \item 有没有办法消除每个用户地址空间中的特殊 { \tt trapframe } 页面映射?例如,可以 226 | { \tt uservec } 是否可以修改为简单地将 32 个用户寄存器压入内核堆栈,或者将它们存储在 { \tt proc } 结构中? \item 是否可以修改 xv6 以消除特殊的 { \tt TRAMPOLINE } 页面映射? \end{enumerate} 227 | 228 | 229 | --------------------------------------------------------------------------------