如何使用 Beamer 创建演示文稿

Summary: Beamer 将 LaTeX 强大的排版功能和生态系统带进创建幻灯片中。

Beamer 将 LaTeX 强大的排版功能和生态系统带进创建幻灯片中。

# 如何使用 Beamer 创建演示文稿

>
> Beamer 将 LaTeX 强大的排版功能和生态系统带进创建幻灯片中。
>
>
>

<img src="content_image/album_201901_13_113905one2p82t58vtqgtq.jpeg" alt="" title="">

[Beamer](https://www.overleaf.com/learn/latex/Beamer) 是用于生成幻灯片的 LaTeX 包。它最棒的功能之一是它可以利用 LaTeX 强大的排版系统和其生态系统中的所有其他软件包。例如，我经常在包含代码的 Beamer 演示文稿中使用 LaTeX 的 [listings](https://www.overleaf.com/learn/latex/Code_listing) 包。

### 创建演示文稿

要创建一个 Beamer 文档，输入：

```
\documentclass{beamer}
```

与任何其他 LaTeX 文档一样，添加你要使用的任何包。例如，要使用 `listings` 包，请输入：

```
\usepackage{listings}
```

将所有内容放在 `document` 环境中：

```
\begin{document}
```

Beamer 文档通常时是一系列的 `frame` 环境。包含代码的 `frame` 应该被标记为 `fragile`：

```
\begin{frame}[fragile]
```

使用标题开始你的 `frame`：

```
\frametitle{Function to Do Stuff}
```

### 开始演示前测试你的代码

世上最糟糕的感受之一你在演讲中说到代码时，突然发现了一个 bug —— 也许是拼错了关键词或者漏掉了括号。

解决方法之一就是测试演示的代码。在多数演示环境中，这意味着创建一个单独的文件、编写测试接着拷贝和粘贴。

然而，在 Beamer 中有一种更好的方法。想象一下，你有一个名为 `do_stuff.py` 的文件，其中包含代码。你可以在第二个文件中编写 `do_stuff.py` 代码的测试，你可以将其命名为 `test_do_stuff.py`，并且可以使用 [pytest](https://docs.pytest.org/en/latest/) 测试。但是，`do_stuff.py` 中的大多数行都缺乏教学价值，比如定义辅助函数。

要简化你受众看到的东西，你可在演示文稿中只导入你要讨论的行到 frame 中：

```
\lstinputlisting[
    language=Python,
    firstline=8,
    lastline=15
]{do_stuff.py}
```

由于你会对这几行（从 8 到 15）进行讨论，因此幻灯片上不需要任何其他内容。结束 `frame`：

```
\end{frame}
```

在下一张幻灯片中，你想展示刚才的 `do_stuff()` 函数的用法示例：

```
\begin{frame}[fragile]
\frametitle{Calling Function to Do Stuff}
\lstinputlisting[
    language=Python,
    firstline=17,
    lastline=19
]{do_stuff.py}
\end{frame}
```

你使用相同的文件，但这次显示调用该函数的行。最后，结束 `document`：

```
\end{document}
```

假设你在 `do_stuff.py` 中有一个合适的 Python 文件，这将生成一个含有 2 页的幻灯片。

Beamer 还支持必要的功能如渐进式演示，每次给观众展示一部分以免受到前面的打扰。在行中放入 `\pause` 会将页面分成不同的部分：

```
\begin{frame}
Remember:
\begin{itemize}
\item This will show up on the first slide. \pause
\item This will show up on the
      second slide, as well as the preceding point. \pause
\item Finally, on the third slide,
       all three bullets will show up.
\end{frame}
```

### 创建讲义

Beamer 中我最喜欢的功能是可以用 `\documentclass[ignorenonframetext]{beamer}` 设置忽略 `frame` 外的所有内容。当我准备演示文稿时，我离开顶部（声明文档类的位置）并自动生成它的两个版本：我的演示稿使用 Beamer 忽略任何 `frame` 之外的所有文本，另一个含有类似这样的头：

```
\documentclass{article}
\usepackage{beamerarticle}
```

这会生成一份讲义：一份含有所有 `frame` 和它们之间文字的 PDF。

当会议组织者要求我发布我的幻灯片时，我会包含原始幻灯片作为参考，但我希望人们拿到的是讲义，它包含了所有我不想在幻灯片上写的解释部分。

在创建幻灯片时，人们经常会考虑是为演讲优化讲稿还是为之后想要阅读它的人们优化。幸运的是，Beamer 提供了两全其美的办法。

---

via: <https://opensource.com/article/19/1/create-presentations-beamer>

作者：[Moshe Zadka](https://opensource.com/users/moshez) 选题：[lujun9972](https://github.com/lujun9972) 译者：[geekpi](https://github.com/geekpi) 校对：[wxy](https://github.com/wxy)

本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


JSON 的兴起与崛起

Summary: 我们不禁要问为什么 XML 的噩运来的如此之快，以至于短短十来年，“异步 JavaScript 和 XML” 这个名称就成为一个很讽刺的误称。

# JSON 的兴起与崛起

JSON 已经占领了全世界。当今，任何两个应用程序彼此通过互联网通信时，可以打赌它们在使用 JSON。它已被所有大型企业所采用：十大最受欢迎的 web API 接口列表中（主要由 Google、Facebook 和 Twitter 提供），仅仅只有一个 API 接口是以 XML 的格式开放数据的。   1  这个列表中的 Twitter API 为此做了一个鲜活的注脚：其对 XML 格式的支持到 2013 年结束，其时发布的新版本的 API 取消 XML 格式，转而仅使用 JSON。JSON 也在程序编码级别和文件存储上被广泛采用：在 Stack Overflow（LCTT 译注：一个面向程序员的问答网站）上，现在更多的是关于 JSON 的问题，而不是其他的数据交换格式。   2

XML 仍然在很多地方存在。网络上它被用于 SVG 和 RSS / Atom 信息流。Android 开发者想要获得用户权限许可时，需要在其 APP 的 `manifest` 文件中声明 —— 此文件是 XML 格式的。XML 的替代品也不仅仅只有 JSON，现在有很多人在使用 YAML 或 Google 的 Protocol Buffers 等技术，但这些技术的受欢迎程度远不如 JSON。目前来看，JSON 是应用程序在网络之间通信的首选协议格式。

考虑到自 2005 年来 Web 编程世界就垂涎于 “异步 JavaScript 和 XML” 而非 “异步 JavaScript 和 JSON” 的技术潜力，你可以发现 JSON 的主导地位是如此的让人惊讶。当然了，这可能与这两种通信格式的受欢迎程度无关，而仅反映出缩写 “AJAX” 似乎比 “AJAJ” 更具吸引力。但是，即使在 2005 年有些人（实际上没有太多人）已经用 JSON 来取代 XML 了，我们不禁要问 XML 的噩运来的如此之快，以至于短短十来年，“异步 JavaScript 和 XML” 这个名称就成为一个很讽刺的误称。那这十来年发生了什么？JSON 怎么会在那么多应用程序中取代了 XML？现在被全世界工程师和系统所使用、依赖的这种数据格式是谁提出的？

### JSON 之诞生

2001 年 4 月，首个 JSON 格式的消息被发送出来。此消息是从旧金山湾区某车库的一台计算机发出的，这是计算机历史上重要的的时刻。Douglas Crockford 和 Chip Morningstar 是一家名为 State Software 的技术咨询公司的联合创始人，他们当时聚集在 Morningstar 的车库里测试某个想法，发出了此消息。

在 “AJAX” 这个术语被创造之前，Crockford 和 Morningstar 就已经在尝试构建好用的 AJAX 应用程序了，可是浏览器对其兼容性不好。他们想要在初始页面加载后就将数据传递给应用程序，但其目标要针对所有的浏览器，这就实现不了。

这在今天看来不太可信，但是要记得 2001 年的时候 Internet Explorer（IE）代表了网页浏览器的最前沿技术产品。早在 1999 年的时候，Internet Explorer 5 就支持了原始形式的 `XMLHttpRequest`，开发者可以使用名为 ActiveX 的框架来访问此对象。Crockford 和 Morningstar 能够使用此技术（在 IE 中）来获取数据，但是在 Netscape 4 中（这是他们想要支持的另一种浏览器）就无法使用这种解决方案。为此 Crockford 和 Morningstar 只得使用一套不同的系统以兼容不同的浏览器。

第一条 JSON 消息如下所示：

```
<html><head><script>
    document.domain = 'fudco';
    parent.session.receive(
        { to: "session", do: "test",
          text: "Hello world" }
    )
</script></head></html>
```

消息中只有一小部分类似于今天我们所知的 JSON，本身其实是一个包含有一些 JavaScript 的 HTML 文档。类似于 JSON 的部分只是传递给名为 `receive()` 的函数的 JavaScript 对象字面量。

Crockford 和 Morningstar 决定滥用 HTML 的帧（`<frame>`）以发送数据。他们可以让一个帧指向一个返回的上述 HTML 文档的 URL。当接收到 HTML 时，JavaScript 代码段就会运行，就可以把数据对象字面量如实地传递回应用程序。只要小心的回避浏览器保护策略（即子窗口不允许访问父窗口），这种技术就可以正常运行无误。可以看到 Crockford 和 Mornginstar 通过明确地设置文档域这种方法来达到其目的。（这种基于帧的技术，有时称为隐藏帧技术，通常在 90 年代后期，即在广泛使用 XMLHttpRequest 技术之前使用。   3  ）

第一个 JSON 消息的惊人之处在于，它显然不是一种新的数据格式的首次使用。它就是 JavaScript！实际上，以此方式使用 JavaScript 的想法如此简单，Crockford 自己也说过他不是第一个这样做的人。他说 Netscape 公司的某人早在 1996 年就使用 JavaScript 数组字面量来交换信息。   4  因为消息就是 JavaScript，其不需要任何特殊解析工作，JavaScript 解释器就可搞定一切。

最初的 JSON 信息实际上与 JavaScript 解释器发生了冲突。JavaScript 保留了大量的关键字（ECMAScript 6 版本就有 64 个保留字），Crockford 和 Morningstar 无意中在其 JSON 中使用了一个保留字。他们使用了 `do` 作为了键名，但 `do` 是解释器中的保留字。因为 JavaScript 使用的保留字太多了，Crockford 做了决定：既然不可避免的要使用到这些保留字，那就要求所有的 JSON 键名都加上引号。被引起来的键名会被 JavaScript 解释器识别成字符串，其意味着那些保留字也可以放心安全的使用。这就为什么今天 JSON 键名都要用引号引起来的原因。

Crockford 和 Morningstar 意识到这技术可以应用于各类应用系统。想给其命名为 “JSML”，即 <ruby> JavaScript 标记语言 <rt>  JavaScript Markup Language </rt></ruby>的意思，但发现这个缩写已经被一个名为 Java Speech 标记语言的东西所使用了。因此他们决定采用 “JavaScript Object Notation”，缩写为 JSON。他们开始向客户推销，但很快发现客户不愿意冒险使用缺乏官方规范的未知技术。所以 Crockford 决定写一个规范。

2002 年，Crockford 买下了 [JSON.org](http://JSON.org) 域名，放上了 JSON 语法及一个解释器的实例例子。该网站仍然在运行，现在已经包含有 2013 年正式批准的 JSON ECMA 标准的显著链接。在该网站建立后，Crockford 并没有过多的推广，但很快发现很多人都在提交各种不同编程语言的 JSON 解析器实现。JSON 的血统显然与 JavaScript 相关联，但很明显 JSON 非常适合于不同语言之间的数据交换。

### AJAX 导致的误会

2005 年，JSON 有了一次大爆发。那一年，一位名叫 Jesse James Garrett 的网页设计师和开发者在博客文章中创造了 “AJAX” 一词。他很谨慎地强调：AJAX 并不是新技术，而是 “好几种蓬勃发展的技术以某种强大的新方式汇集在一起。   5  ” AJAX 是 Garrett 给这种正受到青睐的 Web 应用程序的新开发方法的命名。他的博客文章接着描述了开发人员如何利用 JavaScript 和 XMLHttpRequest 构建新型应用程序，这些应用程序比传统的网页更具响应性和状态性。他还以 Gmail 和 Flickr 网站已经使用 AJAX 技术作为了例子。

当然了，“AJAX” 中的 “X” 代表 XML。但在随后的问答帖子中，Garrett 指出，JSON 可以完全替代 XML。他写道：“虽然 XML 是 AJAX 客户端进行数据输入、输出的最完善的技术，但要实现同样的效果，也可以使用像 JavaScript Object Notation（JSON）或任何类似的结构数据方法等技术。 ”   6

开发者确实发现在构建 AJAX 应用程序时可以很容易的使用 JSON，许多人更喜欢它而不是 XML。具有讽刺意味的是，对 AJAX 的兴趣逐渐的导致了 JSON 的普及。大约在这个时候，JSON 引起了博客圈的注意。

2006 年，Dave Winer，一位高产的博主，他也是许多基于 XML 的技术（如 RSS 和 XML-RPC）背后的开发工程师，他抱怨到 JSON 毫无疑问的正在重新发明 XML。尽管人们认为数据交换格式之间的竞争不会导致某一技术的消亡。其写到：

>
> 毫无疑问，我可以编写一个例程来解析 JSON，但来看看他们要重新发明一个东西有多大的意义，出于某种原因 XML 本身对他们来说还不够好（我很想听听原因）。谁想干这荒谬之事？查找一棵树然后把节点串起来。可以立马试试。   7
>
>
>

我很理解 Winer 的挫败感。事实上并没有太多人喜欢 XML。甚至 Winer 也说过他不喜欢 XML。   8  但 XML 已被设计成一个可供任何人使用，并且可以用于几乎能想象到的所有事情。归根到底，XML 实际上是一门元语言，允许你为特定应用程序自定义该领域特定的语言。如 Web 信息流技术 RSS 和 SOAP（简单对象访问协议）就是例子。Winer 认为由于通用交换格式所带来的好处，努力达成共识非常重要。XML 的灵活性应该能满足任何人的需求，然而 JSON 格式呢，其并没有比 XML 提供更多东西，除了它抛弃了使 XML 更灵活的那些繁琐的东西。

Crockford 阅读了 Winer 的这篇文章并留下了评论。为了回应 JSON 重新发明 XML 的指责，Crockford 写到：“重造轮子的好处是可以得到一个更好的轮子。”   9

### JSON 与 XML 对比

到 2014 年，JSON 已经由 ECMA 标准和 RFC 官方正式认可。它有了自己的 MIME 类型。JSON 已经进入了大联盟时代。

为什么 JSON 比 XML 更受欢迎？

在 [JSON.org](http://JSON.org) 网站上，Crockford 总结了一些 JSON 的优势。他写到，JSON 的语法极少，其结构可预测，因此 JSON 更容易被人类和机器理解。   10  其他博主一直关注 XML 的冗长啰嗦及“尖括号负担”。   11  XML 中每个开始标记都必须与结束标记匹配，这意味着 XML 文档包含大量的冗余信息。在未压缩时，XML 文档的体积比同等信息量 JSON 文档的体积大很多，但是，更重要的，这也使 XML 文档更难以阅读。

Crockford 还声称 JSON 的另一个巨大优势是其被设计为数据交换格式。   12  从一开始，它的目的就是在应用程序间传递结构化信息的。而 XML 呢，虽然也可以使用来传递数据，但其最初被设计为文档标记语言。它演变自 SGML（通用标准标记语言），而它又是从被称为 Scribe 的标记语言演变而来，其旨在发展成类似于 LaTeX 一样的文字处理系统。XML 中，一个标签可以包含有所谓的“混合内容”，即带有围绕单词、短语的内嵌标签的文本。这会让人浮现出一副用红蓝笔记录的手稿画面，这是标记语言核心思想的形象比喻。另一方面，JSON 不支持对混合内容模型的清晰构建，但这也意味着它的结构足够简单。一份文档最好的建模就是一棵树，但 JSON 抛弃了这种文档的思想，Crockford 将 JSON 抽象限制为字典和数组，这是所有程序员构建程序时都会使用的最基本的、也最熟悉的元素。

最后，我认为人们不喜欢 XML 是因为它让人困惑。它让人迷惑的地方就是有很多不同的风格。乍一看，XML 本身及其子语言（如 RSS、ATOM、SOAP 或 SVG）之间的界限并不明显。典型的 XML 文档的第一行标识了该 XML 的版本，然后该 XML 文档应该符合特定的子语言。这就有变化需要考虑了，特别是跟 JSON 做比较，JSON 是如此简单，以至于永远不需要编写新版本的 JSON 规范。XML 的设计者试图将 XML 做为唯一的数据交换格式以支配所有格式，就会掉入那个经典的程序员陷阱：过度工程化。XML 非常笼统及概念化，所以很难于简单的使用。

在 2000 年的时候，发起了一场使 HTML 符合 XML 标准的活动，发布了一份符合 XML 标准的 HTML 开发规范，这就此后很出名的 XHTML。虽然一些浏览器厂商立即开始支持这个新标准，但也很明显，大部分基于 HTML 技术的开发者不愿意改变他们的习惯。新标准要求对 XHTML 文档进行严格的验证，而不是基于 HTML 的基准。但大多的网站都是依赖于 HTML 的宽容规则的。到 2009 年的时候，试图编写第二版本的 XHTML 标准的努力已经流产，因为未来已清晰可见，HTML 将会发展为 HTML5（一种不强制要求接受 XML 规则的标准）。

如果 XHTML 的努力取得了成功，那么 XML 可能会成为其设计者所希望的通用数据格式。想象一下，一个 HTML 文档和 API 响应具有完全相同结构的世界。在这样的世界中，JSON 可能不会像现在一样普遍存在。但我把 HTML 的失败看做是 XML 阵营的一种道义上的失败。如果 XML 不是 HTML 的最佳工具，那么对于其他应用程序，也许会有更好的工具出现。在这个世界，我们的世界，很容易看到像 JSON 格式这样的足够简单、量体裁衣的才能获得更大的成功。

如果你喜欢这博文，每两周会更新一次！ 请在 Twitter 上关注 [@TwoBitHistory](https://twitter.com/TwoBitHistory) 或订阅 [RSS feed](https://twobithistory.org/feed.xml)，以确保得到更新的通知。

---

1. <http://www.cs.tufts.edu/comp/150IDS/final_papers/tstras01.1/FinalReport/FinalReport.html#software> [↩](#fnref1)
2. <https://insights.stackoverflow.com/trends?tags=json%2Cxml%2Cprotocol-buffers%2Cyaml%2Ccsv> [↩](#fnref2)
3. Zakas, Nicholas C., et al. “What Is Ajax?” Professional Ajax, 2nd ed., Wiley, 2007. [↩](#fnref3)
4. <https://youtu.be/-C-JoyNuQJs?t=32s> [↩](#fnref4)
5. <http://adaptivepath.org/ideas/ajax-new-approach-web-applications/> [↩](#fnref5)
6. 同上 [↩](#fnref6)
7. <http://scripting.com/2006/12/20.html> [↩](#fnref7)
8. <http://blogoscoped.com/archive/2009-03-05-n15.html> [↩](#fnref8)
9. <https://scripting.wordpress.com/2006/12/20/scripting-news-for-12202006/#comment-26383> [↩](#fnref9)
10. <http://www.json.org/xml.html> [↩](#fnref10)
11. <https://blog.codinghorror.com/xml-the-angle-bracket-tax> [↩](#fnref11)
12. <https://youtu.be/-C-JoyNuQJs?t=33m50sgg> [↩](#fnref12)

---

via: <https://twobithistory.org/2017/09/21/the-rise-and-rise-of-json.html>

作者：[Two-Bit History](https://twobithistory.org) 选题：[lujun9972](https://github.com/lujun9972) 译者：[runningwater](https://github.com/runningwater) 校对：[wxy](https://github.com/wxy)

本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


Termtosvg：将你在 Linux 终端中操作录制成 SVG 动画

# Termtosvg：将你在 Linux 终端中操作录制成 SVG 动画

一般人喜欢使用历史命令功能来查看/再次调用之前在终端中输入的命令。不幸的是，那样做只会显示先前输入的命令，而不是之前输出的内容。

在 Linux 中，有许多可以用来记录终端活动的实用工具。这种工具将会帮助我们记录用户在终端中的活动，并帮助我们识别输出中有用的信息。

在这之前，我们已经介绍了一些这类实用工具了。今天，让我们接着讨论这类工具。

如果你希望尝试其它一些记录你终端活动的工具，我推荐你试试 [script](https://www.2daygeek.com/script-command-record-save-your-terminal-session-activity-linux/) 命令和 [Terminalizer](https://www.2daygeek.com/terminalizer-a-tool-to-record-your-terminal-and-generate-animated-gif-images/) 工具。`script` 是在无头服务器中记录终端活动的最佳方式之一。`script` 是一个记录在终端中输入过的 Unix 命令的实用工具（在某些终端中，它会记录显示在你终端中的所有东西）。它会在当前工作目录下以文本文件方式储存所有终端输出。

不过，如果你在寻找 [GIF 录制器](https://www.2daygeek.com/category/gif-recorder/) ，你可以尝试 [Gifine](https://www.2daygeek.com/gifine-create-animated-gif-vedio-recorder-linux-mint-debian-ubuntu/)、[Kgif](https://www.2daygeek.com/kgif-create-animated-gif-file-active-window-screen-recorder-capture-arch-linux-mint-fedora-ubuntu-debian-opensuse-centos/) 和 [Peek](https://www.2daygeek.com/peek-create-animated-gif-screen-recorder-capture-arch-linux-mint-fedora-ubuntu/)。

### 什么是 Termtosvg

Termtosvg 是一个用 Python 编写的 Unix 终端录制器，它可以将你的命令行会话保存为 SVG 动画。

### Termtosvg 的特点

* 可以制作嵌入于项目主页的简洁美观的动画。
* 可以在 SVG 模板中自定义配色、终端 UI 和动画。
* 兼容 asciinema 录制格式。
* 要求 Python 版本为 3.5 或更高。

### 如何在 Linux 中安装 Termtosvg

它是用 Python 编写的，所以我推荐使用 `pip` 来安装它。

请确保你已经安装了 python-pip 包。如果你还没安装，请输入下面的命令。 对于 Debian 或 Ubuntu 用户，请使用 [apt](https://www.2daygeek.com/apt-command-examples-manage-packages-debian-ubuntu-systems/) 或 [apt-get](https://www.2daygeek.com/apt-get-apt-cache-command-examples-manage-packages-debian-ubuntu-systems/) 来安装 `pip`。

```
sudo apt install python-pip
```

对于 Archlinux 用户，请使用 [pacman](https://www.2daygeek.com/pacman-command-examples-manage-packages-arch-linux-system/) 来安装 `pip`。

```
sudo pacman -S python-pip
```

对于 Fedora 用户，请使用 [dnf](https://www.2daygeek.com/dnf-command-examples-manage-packages-fedora-system/) 来安装 `pip`。

```
sudo dnf install python-pip
```

对于 CentOS 或 RHEL 用户，请使用 [yum](https://www.2daygeek.com/yum-command-examples-manage-packages-rhel-centos-systems/) 来安装 `pip`。

```
sudo yum install python-pip
```

对于 openSUSE 用户，请使用 [zypper](https://www.2daygeek.com/zypper-command-examples-manage-packages-opensuse-system/) 来安装 `pip`。

```
sudo zypper install python-pip
```

最后，请执行 [pip](https://www.2daygeek.com/install-pip-manage-python-packages-linux/) 来安装 Termtosvg。

```
sudo pip3 install termtosvg pyte python-xlib svgwrite
```

### 如何使用 Termtosvg

成功安装 Termtosvg 后，请使用以下命令来开始录制。

```
$ termtosvg
Recording started, enter "exit" command or Control-D to end
```

如果只是想测试它是否正常工作，随意输入几行命令即可。

```
$ uname -a
Linux daygeek-Y700 4.19.8-2-MANJARO #1 SMP PREEMPT Sat Dec 8 14:45:36 UTC 2018 x86_64 GNU/Linux
$ hostname
daygeek-Y700
$ cat /etc/*-release
Manjaro Linux
DISTRIB_ID=ManjaroLinux
DISTRIB_RELEASE=18.0
DISTRIB_CODENAME=Illyria
DISTRIB_DESCRIPTION="Manjaro Linux"
Manjaro Linux
NAME="Manjaro Linux"
ID=manjaro
ID_LIKE=arch
PRETTY_NAME="Manjaro Linux"
ANSI_COLOR="1;32"
HOME_URL="https://www.manjaro.org/"
SUPPORT_URL="https://www.manjaro.org/"
BUG_REPORT_URL="https://bugs.manjaro.org/"
$ free -g
free: Multiple unit options doesn't make sense.
$ free -m
free: Multiple unit options doesn't make sense.
$ pip3 --version
pip 18.1 from /usr/lib/python3.7/site-packages/pip (python 3.7)
```

完成后，你可以按下 `CTRL+D` 或输入 `exit` 来停止录制。录制完后，输出文件会以一个独一无二的名字被保存在 `/tmp` 文件夹中。

```
$ exit
exit
Recording ended, SVG animation is /tmp/termtosvg_5waorper.svg
```

我们可以在任意浏览器中打开 SVG 文件。

```
firefox /tmp/termtosvg_5waorper.svg
```

---

via: <https://www.2daygeek.com/termtosvg-record-your-terminal-sessions-as-svg-animations-in-linux/>

作者：[Magesh Maruthamuthu](https://www.2daygeek.com/author/magesh/) 选题：[lujun9972](https://github.com/lujun9972) 译者：[zhs852](https://github.com/zhs852) 校对：[校对者ID](https://github.com/%E6%A0%A1%E5%AF%B9%E8%80%85ID)

本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


极客漫画：你准备好微服务了吗？

# 极客漫画：你准备好微服务了吗？

<img src="content_image/album_201901_14_123302dn2g2zmgzr29g9tz.png" alt="" title="">

微服务是个好东西，就像乐高积木一样，你可以拼成各种东西，当前，前提是你足够会玩。

从早些年的 SOA 和中间件，到现在的微服务和容器，但似乎历史总是螺旋式变化的。看起来笨拙而大而无当的独石应用，其实在很多场景，要比微服务更适合。

话说，微服务的锅该那只汤姆猫背吗？:->

---

via: <http://turnoff.us/geek/are-you-ready-for-microservices/>

作者：[Daniel Stori](http://turnoff.us/about/) 译者&点评&校对：[wxy](https://github.com/wxy) 合成：[wxy](https://github.com/wxy)

本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


Linux 上查看系统/服务器运行时间的 11 种方法

# Linux 上查看系统/服务器运行时间的 11 种方法

<img src="content_image/album_201901_15_100936fvejs5lsgwkws552.jpg" alt="" title="">

你是否想知道自己的 Linux 系统正常运行了多长时间而没有宕机？系统是什么时候启动的？

Linux 上有多个查看服务器/系统运行时间的命令，大多数用户喜欢使用标准并且很有名的 `uptime` 命令获取这些具体的信息。

服务器的运行时间对一些用户来说不那么重要，但是当服务器运行诸如在线商城<ruby> 门户 <rt>  portal </rt></ruby>、网上银行门户等<ruby> 关键任务应用 <rt>  mission-critical applications </rt></ruby>时，它对于<ruby> 服务器管理员 <rt>  server adminstrators </rt></ruby>来说就至关重要。

它必须做到零宕机，因为一旦停机就会影响到数百万用户。

正如我所说，许多命令都可以让用户看到 Linux 服务器的运行时间。在这篇教程里我会教你如何使用下面 11 种方式来查看。

<ruby> 正常运行时间 <rt>  uptime </rt></ruby>指的是服务器自从上次关闭或重启以来经过的时间。

`uptime` 命令获取 `/proc` 文件中的详细信息并输出正常运行时间，而 `/proc` 文件并不适合人直接看。

以下这些命令会输出系统运行和启动的时间。也会显示一些额外的信息。

### 方法 1：使用 uptime 命令

`uptime` 命令会告诉你系统运行了多长时间。它会用一行显示以下信息。

当前时间、系统运行时间、当前登录用户的数量、过去 1 分钟/5 分钟/15 分钟系统负载的均值。

```
# uptime

 08:34:29 up 21 days,  5:46,  1 user,  load average: 0.06, 0.04, 0.00
```

### 方法 2：使用 w 命令

`w` 命令为每个登录进系统的用户，每个用户当前所做的事情，所有活动的负载对计算机的影响提供了一个快速的概要。这个单一命令结合了多个 Unix 程序：`who`、`uptime`，和 `ps -a` 的结果。

```
# w

 08:35:14 up 21 days,  5:47,  1 user,  load average: 0.26, 0.09, 0.02
USER     TTY      FROM              LOGIN@   IDLE   JCPU   PCPU WHAT
root     pts/1    103.5.134.167    08:34    0.00s  0.01s  0.00s w
```

### 方法 3：使用 top 命令

`top` 命令是 Linux 上监视实时系统进程的基础命令之一。它显示系统信息和运行进程的信息，例如正常运行时间、平均负载、运行的任务、登录用户数量、CPU 数量 & CPU 利用率、内存 & 交换空间信息。

**推荐阅读：**[TOP 命令监视服务器性能的例子](https://www.2daygeek.com/top-command-examples-to-monitor-server-performance/)

```
# top -c

top - 08:36:01 up 21 days,  5:48,  1 user,  load average: 0.12, 0.08, 0.02
Tasks:  98 total,   1 running,  97 sleeping,   0 stopped,   0 zombie
Cpu(s):  0.0%us,  0.3%sy,  0.0%ni, 99.7%id,  0.0%wa,  0.0%hi,  0.0%si,  0.0%st
Mem:   1872888k total,  1454644k used,   418244k free,   175804k buffers
Swap:  2097148k total,        0k used,  2097148k free,  1098140k cached

  PID USER      PR  NI  VIRT  RES  SHR S %CPU %MEM    TIME+  COMMAND
    1 root      20   0 19340 1492 1172 S  0.0  0.1   0:01.04 /sbin/init
    2 root      20   0     0    0    0 S  0.0  0.0   0:00.00 [kthreadd]
    3 root      RT   0     0    0    0 S  0.0  0.0   0:00.00 [migration/0]
    4 root      20   0     0    0    0 S  0.0  0.0   0:34.32 [ksoftirqd/0]
    5 root      RT   0     0    0    0 S  0.0  0.0   0:00.00 [stopper/0]
```

### 方法 4：使用 who 命令

`who` 命令列出当前登录进计算机的用户。`who` 命令与 `w` 命令类似，但后者还包含额外的数据和统计信息。

```
# who -b
system boot 2018-04-12 02:48
```

### 方法 5：使用 last 命令

`last` 命令列出最近登录过的用户。`last` 回溯 `/var/log/wtmp` 文件并显示自从文件创建后登录进（出）的用户。

```
# last reboot -F | head -1 | awk '{print $5,$6,$7,$8,$9}'
Thu Apr 12 02:48:04 2018
```

### 方法 6：使用 /proc/uptime 文件

这个文件中包含系统上次启动后运行时间的详细信息。`/proc/uptime` 的输出相当精简。

第一个数字是系统自从启动的总秒数。第二个数字是总时间中系统空闲所花费的时间，以秒为单位。

```
# cat /proc/uptime
1835457.68 1809207.16
```

### 方法 7：使用 tuptime 命令

`tuptime` 是一个汇报系统运行时间的工具，输出历史信息并作以统计，保留重启之间的数据。和 `uptime` 命令很像，但输出更有意思一些。

```
$ tuptime
```

### 方法 8：使用 htop 命令

`htop` 是运行在 Linux 上的一个交互式进程查看器，是 Hisham 使用 ncurses 库开发的。`htop` 比起 `top` 有很多的特性和选项。

**推荐阅读：** [使用 Htop 命令监控系统资源](https://www.2daygeek.com/htop-command-examples-to-monitor-system-resources/)

```
# htop

  CPU[|                         0.5%]     Tasks: 48, 5 thr; 1 running
  Mem[|||||||||||||||     165/1828MB]     Load average: 0.10 0.05 0.01
  Swp[                      0/2047MB]     Uptime: 21 days, 05:52:35

  PID USER      PRI  NI  VIRT   RES   SHR S CPU% MEM%   TIME+  Command
29166 root       20   0  110M  2484  1240 R  0.0  0.1  0:00.03 htop
29580 root       20   0 11464  3500  1032 S  0.0  0.2 55:15.97 /bin/sh ./OSWatcher.sh 10 1
    1 root       20   0 19340  1492  1172 S  0.0  0.1  0:01.04 /sbin/init
  486 root       16  -4 10780   900   348 S  0.0  0.0  0:00.07 /sbin/udevd -d
  748 root       18  -2 10780   932   360 S  0.0  0.0  0:00.00 /sbin/udevd -d
```

### 方法 9：使用 glances 命令

`glances` 是一个跨平台的基于 curses 库的监控工具，它是使用 python 编写的。可以说它非常强大，仅用一点空间就能获得很多信息。它使用 psutil 库从系统中获取信息。

`glances` 可以监控 CPU、内存、负载、进程、网络接口、磁盘 I/O、<ruby> 磁盘阵列 <rt>  RAID </rt></ruby>、传感器、文件系统（与文件夹）、容器、监视器、Alert 日志、系统信息、运行时间、<ruby> 快速查看 <rt>  Quicklook </rt></ruby>（CPU，内存、负载）等。

**推荐阅读：** [Glances （集大成）– Linux 上高级的实时系统运行监控工具](https://www.2daygeek.com/install-glances-advanced-real-time-linux-system-performance-monitoring-tool-on-centos-fedora-ubuntu-debian-opensuse-arch-linux/)

```
glances

ubuntu (Ubuntu 17.10 64bit / Linux 4.13.0-37-generic) - IP 192.168.1.6/24                Uptime: 21 days, 05:55:15

CPU  [|||||||||||||||||||||    90.6%]   CPU -    90.6%  nice:     0.0%  ctx_sw:    4K      MEM \   78.4%  active:     942M      SWAP -    5.9%      LOAD    2-core
MEM  [||||||||||||||||         78.0%]   user:    55.1%  irq:      0.0%  inter:   1797      total:  1.95G  inactive:   562M      total:   12.4G      1 min:    4.35
SWAP [|                         5.9%]   system:  32.4%  iowait:   1.8%  sw_int:   897      used:   1.53G  buffers:   14.8M      used:     749M      5 min:    4.38
                                        idle:     7.6%  steal:    0.0%                     free:    431M  cached:     273M      free:    11.7G      15 min:   3.38

NETWORK     Rx/s   Tx/s   TASKS 211 (735 thr), 4 run, 207 slp, 0 oth sorted automatically by memory_percent, flat view
docker0       0b   232b
enp0s3      12Kb    4Kb   Systemd          7    Services loaded: 197 active: 196 failed: 1
lo          616b   616b
_h478e48e     0b   232b     CPU%  MEM%  VIRT   RES   PID USER        NI S     TIME+   R/s   W/s Command
                            63.8  18.9 2.33G  377M  2536 daygeek      0 R   5:57.78     0     0 /usr/lib/firefox/firefox -contentproc -childID 1 -isForBrowser -intPrefs 6:50|7:-1|19:0|34:1000|42:20|43:5|44:10|51
DefaultGateway     83ms     78.5  10.9 3.46G  217M  2039 daygeek      0 S  21:07.46     0     0 /usr/bin/gnome-shell
                             8.5  10.1 2.32G  201M  2464 daygeek      0 S   8:45.69     0     0 /usr/lib/firefox/firefox -new-window
DISK I/O     R/s    W/s      1.1   8.5 2.19G  170M  2653 daygeek      0 S   2:56.29     0     0 /usr/lib/firefox/firefox -contentproc -childID 4 -isForBrowser -intPrefs 6:50|7:-1|19:0|34:1000|42:20|43:5|44:10|51
dm-0           0      0      1.7   7.2 2.15G  143M  2880 daygeek      0 S   7:10.46     0     0 /usr/lib/firefox/firefox -contentproc -childID 6 -isForBrowser -intPrefs 6:50|7:-1|19:0|34:1000|42:20|43:5|44:10|51
sda1       9.46M    12K      0.0   4.9 1.78G 97.2M  6125 daygeek      0 S   1:36.57     0     0 /usr/lib/firefox/firefox -contentproc -childID 7 -isForBrowser -intPrefs 6:50|7:-1|19:0|34:1000|42:20|43:5|44:10|51
```

### 方法 10：使用 stat 命令

`stat` 命令显示指定文件或文件系统的详细状态。

```
# stat /var/log/dmesg | grep Modify
Modify: 2018-04-12 02:48:04.027999943 -0400
```

### 方法 11：使用 procinfo 命令

`procinfo` 从 `/proc` 文件夹下收集一些系统数据并将其很好的格式化输出在标准输出设备上。

```
# procinfo | grep Bootup
Bootup: Fri Apr 20 19:40:14 2018 Load average: 0.16 0.05 0.06 1/138 16615
```

---

via: <https://www.2daygeek.com/11-methods-to-find-check-system-server-uptime-in-linux/>

作者：[Magesh Maruthamuthu](https://www.2daygeek.com/author/magesh/) 选题：[lujun9972](https://github.com/lujun9972) 译者：[LuuMing](https://github.com/LuuMing) 校对：[wxy](https://github.com/wxy)

本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


将旧的 Linux 台式机变成家庭媒体中心

Summary: 重新利用过时的计算机来浏览互联网并在大屏电视上观看视频。

重新利用过时的计算机来浏览互联网并在大屏电视上观看视频。

# 将旧的 Linux 台式机变成家庭媒体中心

>
> 重新利用过时的计算机来浏览互联网并在大屏电视上观看视频。
>
>
>

我第一次尝试搭建一台“娱乐电脑”是在 20 世纪 90 年代后期，使用了一台带 Trident ProVidia 9685 PCI 显卡的普通旧台式电脑。我使用了所谓的“电视输出”卡，它有一个额外的输出可以连接到标准电视端子上。屏幕显示看起来不太好，而且没有音频输出。并且外观很丑：有一条 S-Video 线穿过了客厅地板连接到我的 19 英寸 Sony Trinitron CRT 电视机上。

我在 Linux 和 Windows 98 上得到了同样令人遗憾的结果。在和那些看起来不对劲的系统挣扎之后，我放弃了几年。值得庆幸的是，如今的 HDMI 拥有更好的性能和标准化的分辨率，这使得廉价的家庭媒体中心成为现实。

我的新媒体中心娱乐电脑实际上是我的旧 Ubuntu Linux 桌面，最近我用更快的电脑替换了它。这台电脑在工作中太慢，但是它的 3.4GHz 的 AMD Phenom II X4 965 处理器和 8GB 的 RAM 足以满足一般浏览和视频流的要求。

以下是我让旧系统在新角色中发挥最佳性能所采取的步骤。

### 硬件

首先，我移除了不必要的设备，包括读卡器、硬盘驱动器、DVD 驱动器和后置 USB 卡，我添加了一块 PCI-Express 无线网卡。我将 Ubuntu 安装到单个固态硬盘 （SSD） 上，这可以切实提高任何旧系统的性能。

### BIOS

在 BIOS 中，我禁用了所有未使用的设备，例如软盘和 IDE 驱动器控制器。我禁用了板载显卡，因为我安装了带 HDMI 输出的 NVidia GeForce GTX 650 PCI Express 显卡。我还禁用了板载声卡，因为 NVidia 显卡芯片组提供音频。

### 音频

Nvidia GeForce GTX 音频设备在 GNOME 控制中心的声音设置中被显示为 GK107 HDMI Audio Controller，因此单条 HDMI 线缆可同时处理音频和视频。无需将音频线连接到板载声卡的输出插孔。

*GNOME 音频设置中的 HDMI 音频控制器。*

### 键盘和鼠标

我有罗技的无线键盘和鼠标。当我安装它们时，我插入了两个外置 USB 接收器，它们可以使用，但我经常遇到信号反应问题。接着我发现其中一个被标记为联合接收器，这意味着它可以自己处理多个罗技输入设备。罗技不提供在 Linux 中配置联合接收器的软件。但幸运的是，有个开源程序 [Solaar](https://pwr.github.io/Solaar/) 能够做到。使用单个接收器解决了我的输入性能问题。

*Solaar 联合接收器界面。*

### 视频

最初很难在我的 47 英寸平板电视上阅读文字，所以我在 Universal Access 下启用了“大文字”。我下载了一些与电视 1920x1080 分辨率相匹配的壁纸，这看起来很棒！

### 最后处理

我需要在电脑的冷却需求和我对不受阻碍的娱乐的渴望之间取得平衡。由于这是一台标准的 ATX 微型塔式计算机，我确保我有足够的风扇转速，以及在 BIOS 中精心配置过的温度以减少噪音。我还把电脑放在我的娱乐控制台后面，以进一步减少风扇噪音，但同时我可以按到电源按钮。

最后得到一台简单的、没有巨大噪音的机器，而且只使用了两根线缆：交流电源线和 HDMI。它应该能够运行任何主流或专门的媒体中心 Linux 发行版。我不期望去玩高端的游戏，因为这可能需要更多的处理能力。

*Ubuntu Linux 的关于页面。*

*在大屏幕上测试 YouTube 视频。*

我还没安装像 [Kodi](https://kodi.tv/) 这样专门的媒体中心发行版。截至目前，它运行的是 Ubuntu Linux 18.04.1 LTS，而且很稳定。

这是一个有趣的挑战，可以充分利用我已经拥有的东西，而不是购买新的硬件。这只是开源软件的一个好处。最终，我可能会用一个更小，更安静的带有媒体中心的系统或其他小机顶盒替换它，但是现在，它很好地满足了我的需求。

---

via: <https://opensource.com/article/18/11/old-linux-desktop-new-home-media-center>

作者：[Alan Formy-Duval](https://opensource.com/users/alanfdoss) 选题：[lujun9972](https://github.com/lujun9972) 译者：[geekpi](https://github.com/geekpi) 校对：[wxy](https://github.com/wxy)

本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


2 的威力，Linux 的威力：终端中的 2048

Summary: 正在寻找基于终端的游戏来打发时间么？来看看 2048-cli 吧。

正在寻找基于终端的游戏来打发时间么？来看看 2048-cli 吧。

# 2 的威力，Linux 的威力：终端中的 2048

>
> 正在寻找基于终端的游戏来打发时间么？来看看 2048-cli 吧。
>
>
>

你好，欢迎来到今天的 Linux 命令行玩具日历。每天，我们会为你的终端带来一个不同的玩具：它可能是一个游戏或任何简单的消遣，可以帮助你获得乐趣。

很可能你们中的一些人之前已经看过我们日历中的各种玩具，但我们希望每个人至少见到一件新事物。

今天的玩具是我最喜欢的休闲游戏之一 [2048](https://github.com/gabrielecirulli/2048) （它本身就是另外一个克隆品的克隆）的[命令行版本](https://github.com/tiehuis/2048-cli)。

要进行游戏，你只需将滑块向上、向下、向左、向右移动，组合成对的数字，并增加数值，直到你得到数字为 2048 的块。最吸引人的地方（以及挑战）是你不能只移动一个滑块，而是需要移动屏幕上的每一块。（LCTT 译注：不知道有没有人在我们 Linux 中国的网站上遇到过 [404](https://linux.cn/404) 页面？那就是一个 2048 游戏，经常我错误地打开一个不存在的页面时，本应该去修复这个问题，却不小心沉迷于其中……）

它简单、有趣，很容易在里面沉迷几个小时。这个 2048 的克隆 [2048-cli](https://github.com/tiehuis/2048-cli) 是 Marc Tiehuis 用 C 编写的，并在 MIT 许可下开源。你可以在 [GitHub](https://github.com/tiehuis/2048-cli) 上找到源代码，你也可在这找到适用于你的平台的安装说明。由于它已为 Fedora 打包，因此我来说，安装就像下面那样简单：

```
$ sudo dnf install 2048-cli
```

这是这样，玩得开心！

你有特别喜欢的命令行小玩具需要我介绍的吗？这个系列要介绍的小玩具大部分已经有了落实，但还预留了几个空位置。如果你有特别想了解的可以评论留言，我会查看的。如果还有空位置，我会考虑介绍它的。如果没有，但如果我得到了一些很好的意见，我会在最后做一些有价值的提及。

查看昨天的玩具，[在 Linux 终端中玩俄罗斯方块](https://opensource.com/article/18/12/linux-toy-tetris)，记得明天再来！

---

via: <https://opensource.com/article/18/12/linux-toy-2048>

作者：[Jason Baker](https://opensource.com/users/jason-baker) 选题：[lujun9972](https://github.com/lujun9972) 译者：[geekpi](https://github.com/geekpi) 校对：[wxy](https://github.com/wxy)

本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


一位开源项目维护者的 5 个决心

Summary: 不管怎么说，好的交流是一个活跃的开源社区的必备品。

不管怎么说，好的交流是一个活跃的开源社区的必备品。

# 一位开源项目维护者的 5 个决心

>
> 不管怎么说，好的交流是一个活跃的开源社区的必备品。
>
>
>

<img src="content_image/album_201901_16_093920o0m0zi1ki1kxukg0.png" alt="" title="">

我通常不会定下大的新年决心。当然，我在自我提升方面没有任何问题，这篇文章我希望锚定的是这个日历中的另外一部分。不过即使是这样，这里也有一些东西要从今年的免费日历上划掉，并将其替换为一些可以激发我的自省的新日历内容。

在 2017 年，我从不在社交媒体上分享我从未阅读过的文章。我一直保持这样的状态，我也认为它让我成为了一个更好的互联网公民。对于 2019 年，我正在考虑让我成为更好的开源软件维护者的决心。

下面是一些我在一些项目中担任维护者或共同维护者时坚持的决心：

### 1、包含行为准则

Jono Bacon 在他的文章“[7 个你可能犯的错误](https://opensource.com/article/17/8/mistakes-open-source-avoid)”中包含了一条“不强制执行行为准则”。当然，要强制执行行为准则，你首先需要有一个行为准则。我打算默认用[贡献者契约](https://www.contributor-covenant.org/)，但是你可以使用其他你喜欢的。关于这个许可协议，最好的方法是使用别人已经写好的，而不是你自己写的。但是重要的是，要找到一些能够定义你希望你的社区执行的，无论它们是什么样子。一旦这些被记录下来并强制执行，人们就能自行决定是否成为他们想象中社区的一份子。

### 2、使许可证清晰且明确

你知道什么真的很烦么？不清晰的许可证。这个软件基于 GPL 授权，如果没有进一步提供更多信息的文字，我无法知道更多信息。基于哪个版本的[GPL](https://opensource.org/licenses/gpl-license)？我可以用它吗？对于项目的非代码部分，“根据知识共享许可证（CC）授权”更糟糕。我喜欢[知识共享许可证](https://creativecommons.org/share-your-work/licensing-types-examples/)，但它有几个不同的许可证包含着不同的权利和义务。因此，我将非常清楚的说明哪个许可证的变种和版本适用于我的项目。我将会在仓库中包含许可的全文，并在其他文件中包含简明的注释。

与此相关的一类问题是使用 [OSI](https://opensource.org/) 批准的许可证。想出一个新的准确的说明了你想要表达什么的许可证是有可能的，但是如果你需要强制推行它，祝你好运。会坚持使用它么？使用您项目的人会理解么？

### 3、快速分类错误报告和问题

在技术领域, 很少有比开源维护者更贫乏的东西了。即使在小型项目中，也很难找到时间去回答每个问题并修复每个错误。但这并不意味着我不能哪怕回应一下，它没必要是多段的回复。即使只是给 GitHub 问题贴了个标签也表明了我看见它了。也许我马上就会处理它，也许一年后我会处理它。但是让社区看到它很重要，是的，这里还有人管。

### 4、如果没有伴随的文档，请不要推送新特性或错误修复

尽管多年来我的开源贡献都围绕着文档，但我的项目并没有反映出我对它的重视。我能推送的提交不多，并不不需要某种形式的文档。新的特性显然应该在他们被提交时甚至是在之前就编写文档。但即使是错误修复，也应该在发行说明中有一个条目提及。如果没有什么意外，推送提交也是很好的改善文档的机会。

### 5、放弃一个项目时，要说清楚

我很不擅长对事情说“不”，我告诉编辑我会为 [Opensource.com](http://Opensource.com) 写一到两篇文章，而现在我有了将近 60 篇文章。哎呀。但在某些时候，曾经我有兴趣的事情也会不再有兴趣。也许该项目是不必要的，因为它的功能被吸收到更大的项目中；也许只是我厌倦了它。但这对社区是不公平的（并且存在潜在的危险，正如最近的 [event-stream 恶意软件注入](https://arstechnica.com/information-technology/2018/11/hacker-backdoors-widely-used-open-source-software-to-steal-bitcoin/)所示），会让该项目陷入困境。维护者有权随时离开，但他们离开时应该说清楚。

无论你是开源维护者还是贡献者，如果你知道项目维护者应该作出的其他决心，请在评论中分享！

---

via: <https://opensource.com/article/18/12/resolutions-open-source-project-maintainers>

作者：[Ben Cotton](https://opensource.com/users/bcotton) 选题：[lujun9972](https://github.com/lujun9972) 译者：[bestony](https://github.com/bestony) 校对：[wxy](https://github.com/wxy)

本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


使用 Ansible 来管理你的工作站：配置自动化

Summary: 学习如何使 Ansible 自动对一系列台式机和笔记本应用配置。

学习如何使 Ansible 自动对一系列台式机和笔记本应用配置。

# 使用 Ansible 来管理你的工作站：配置自动化

>
> 学习如何使 Ansible 自动对一系列台式机和笔记本应用配置。
>
>
>

<img src="content_image/album_201901_16_162446gszu3hzkrewilswr.jpg" alt="" title="">

Ansible 是一个令人惊讶的自动化的配置管理工具。其主要应用在服务器和云部署上，但在工作站上的应用（无论是台式机还是笔记本）却鲜少得到关注，这就是本系列所要关注的。

在这个系列的[第一部分](/article-10434-1.html)，我向你展示了 `ansible-pull` 命令的基本用法，我们创建了一个安装了少量包的剧本。它本身是没有多大的用处的，但是为后续的自动化做了准备。

在这篇文章中，将会达成闭环，而且在最后部分，我们将会有一个针对工作站自动配置的完整的工作解决方案。现在，我们将要设置 Ansible 的配置，这样未来将要做的改变将会自动的部署应用到我们的工作站上。现阶段，假设你已经完成了[第一部分](/article-10434-1.html)的工作。如果没有的话，当你完成的时候回到本文。你应该已经有一个包含第一篇文章中代码的 GitHub 库。我们将直接在之前创建的部分之上继续。

首先，因为我们要做的不仅仅是安装包文件，所以我们要做一些重新的组织工作。现在，我们已经有一个名为 `local.yml` 并包含以下内容的剧本：

```
- hosts: localhost
  become: true
  tasks:
  - name: Install packages
    apt: name={{item}}
    with_items:
      - htop
      - mc
      - tmux
```

如果我们仅仅想实现一个任务那么上面的配置就足够了。随着向我们的配置中不断的添加内容，这个文件将会变的相当的庞大和杂乱。最好能够根据不同类型的配置将我们的<ruby> 动作 <rt>  play </rt></ruby>分为独立的文件。为了达到这个要求，创建一个名为<ruby> 任务手册 <rt>  taskbook </rt></ruby>的东西，它和<ruby> 剧本 <rt>  playbook </rt></ruby>很像但内容更加的流线型。让我们在 Git 库中为任务手册创建一个目录。

```
mkdir tasks
```

`local.yml` 剧本中的代码可以很好地过渡为安装包文件的任务手册。让我们把这个文件移动到刚刚创建好的 `task` 目录中，并重新命名。

```
mv local.yml tasks/packages.yml
```

现在，我们编辑 `packages.yml` 文件将它进行大幅的瘦身，事实上，我们可以精简除了独立任务本身之外的所有内容。让我们把 `packages.yml` 编辑成如下的形式：

```
- name: Install packages
  apt: name={{item}}
  with_items:
    - htop
    - mc
    - tmux
```

正如你所看到的，它使用同样的语法，但我们去掉了对这个任务无用没有必要的所有内容。现在我们有了一个专门安装包文件的任务手册。然而我们仍然需要一个名为 `local.yml` 的文件，因为执行 `ansible-pull` 命令时仍然会去找这个文件。所以我们将在我们库的根目录下（不是在 `task` 目录下）创建一个包含这些内容的全新文件：

```
- hosts: localhost
  become: true
  pre_tasks:
    - name: update repositories
      apt: update_cache=yes
      changed_when: False

  tasks:
    - include: tasks/packages.yml
```

这个新的 `local.yml` 扮演的是导入我们的任务手册的索引的角色。我已经在这个文件中添加了一些你在这个系列中还没见到的内容。首先，在这个文件的开头处，我添加了 `pre_tasks`，这个任务的作用是在其他所有任务运行之前先运行某个任务。在这种情况下，我们给 Ansible 的命令是让它去更新我们的发行版的软件库的索引，下面的配置将执行这个任务要求：

```
apt: update_cache=yes
```

通常 `apt` 模块是用来安装包文件的，但我们也能够让它来更新软件库索引。这样做的目的是让我们的每个动作在 Ansible 运行的时候能够以最新的索引工作。这将确保我们在使用一个老旧的索引安装一个包的时候不会出现问题。因为 `apt` 模块仅仅在 Debian、Ubuntu 及它们的衍生发行版下工作。如果你运行的一个不同的发行版，你要使用特定于你的发行版的模块而不是 `apt`。如果你需要使用一个不同的模块请查看 Ansible 的相关文档。

下面这行也需要进一步解释：

```
changed_when: False
```

在某个任务中的这行阻止了 Ansible 去报告动作改变的结果，即使是它本身在系统中导致的一个改变。在这里，我们不会去在意库索引是否包含新的数据；它几乎总是会的，因为库总是在改变的。我们不会去在意 `apt` 库的改变，因为索引的改变是正常的过程。如果我们删除这行，我们将在过程报告的后面看到所有的变动，即使仅仅库的更新而已。最好忽略这类的改变。

接下来是常规任务的阶段，我们将创建好的任务手册导入。我们每次添加另一个任务手册的时候，要添加下面这一行：

```
tasks:
  - include: tasks/packages.yml
```

如果你现在运行 `ansible-pull` 命令，它应该基本上像上一篇文章中做的一样。不同的是我们已经改进了我们的组织方式，并且能够更有效的扩展它。为了节省你到上一篇文章中去寻找，`ansible-pull` 命令的语法参考如下：

```
sudo ansible-pull -U https://github.com/<github_user>/ansible.git
```

如果你还记得话，`ansible-pull` 的命令拉取一个 Git 仓库并且应用它所包含的配置。

既然我们的基础已经搭建好，我们现在可以扩展我们的 Ansible 并且添加功能。更特别的是，我们将添加配置来自动化的部署对工作站要做的改变。为了支撑这个要求，首先我们要创建一个特殊的账户来应用我们的 Ansible 配置。这个不是必要的，我们仍然能够在我们自己的用户下运行 Ansible 配置。但是使用一个隔离的用户能够将其隔离到不需要我们参与的在后台运行的一个系统进程中，

我们可以使用常规的方式来创建这个用户，但是既然我们正在使用 Ansible，我们应该尽量避开使用手动的改变。替代的是，我们将会创建一个任务手册来处理用户创建任务。这个任务手册目前将会仅仅创建一个用户，但你可以在这个任务手册中添加额外的动作来创建更多的用户。我将这个用户命名为 `ansible`，你可以按照自己的想法来命名（如果你做了这个改变要确保更新所有出现地方）。让我们来创建一个名为 `user.yml` 的任务手册并且将以下代码写进去：

```
- name: create ansible user
  user: name=ansible uid=900
```

下一步，我们需要编辑 `local.yml` 文件，将这个新的任务手册添加进去，像如下这样写：

```
- hosts: localhost
  become: true
  pre_tasks:
    - name: update repositories
      apt: update_cache=yes
      changed_when: False

  tasks:
    - include: tasks/users.yml
    - include: tasks/packages.yml
```

现在当我们运行 `ansible-pull` 命令的时候，一个名为 `ansible` 的用户将会在系统中被创建。注意我特地通过参数 `uid` 为这个用户声明了用户 ID 为 900。这个不是必须的，但建议直接创建好 UID。因为在 1000 以下的 UID 在登录界面是不会显示的，这样是很棒的，因为我们根本没有需要去使用 `ansibe` 账户来登录我们的桌面。UID 900 是随便定的；它应该是在 1000 以下没有被使用的任何一个数值。你可以使用以下命令在系统中去验证 UID 900 是否已经被使用了：

```
cat /etc/passwd |grep 900
```

不过，你使用这个 UID 应该不会遇到什么问题，因为迄今为止在我使用的任何发行版中我还没遇到过它是被默认使用的。

现在，我们已经拥有了一个名为 `ansible` 的账户，它将会在之后的自动化配置中使用。接下来，我们可以创建实际的定时作业来自动操作。我们应该将其分开放到它自己的文件中，而不是将其放置到我们刚刚创建的 `users.yml` 文件中。在任务目录中创建一个名为 `cron.yml` 的任务手册并且将以下的代码写进去：

```
- name: install cron job (ansible-pull)
  cron: user="ansible" name="ansible provision" minute="*/10" job="/usr/bin/ansible-pull -o -U https://github.com/<github_user>/ansible.git > /dev/null"
```

`cron` 模块的语法几乎不需加以说明。通过这个动作，我们创建了一个通过用户 `ansible` 运行的定时作业。这个作业将每隔 10 分钟执行一次，下面是它将要执行的命令：

```
/usr/bin/ansible-pull -o -U https://github.com/<github_user>/ansible.git > /dev/null
```

同样，我们也可以添加想要我们的所有工作站部署的额外的定时作业到这个文件中。我们只需要在新的定时作业中添加额外的动作即可。然而，仅仅是添加一个定时的任务手册是不够的，我们还需要将它添加到 `local.yml` 文件中以便它能够被调用。将下面的一行添加到末尾：

```
- include: tasks/cron.yml
```

现在当 `ansible-pull` 命令执行的时候，它将会以用户 `ansible` 每隔十分钟设置一个新的定时作业。但是，每个十分钟运行一个 Ansible 作业并不是一个好的方式，因为这个将消耗很多的 CPU 资源。每隔十分钟来运行对于 Ansible 来说是毫无意义的，除非我们已经在 Git 仓库中改变一些东西。

然而，我们已经解决了这个问题。注意我在定时作业中的命令 `ansible-pill` 添加的我们之前从未用到过的参数 `-o`。这个参数告诉 Ansible 只有在从上次 `ansible-pull` 被调用以后库有了变化后才会运行。如果库没有任何变化，它将不会做任何事情。通过这个方法，你将不会无端的浪费 CPU 资源。当然在拉取存储库的时候会使用一些 CPU 资源，但不会像再一次应用整个配置的时候使用的那么多。当 `ansible-pull` 执行的时候，它将会遍历剧本和任务手册中的所有任务，但至少它不会毫无目的的运行。

尽管我们已经添加了所有必须的配置要素来自动化 `ansible-pull`，它仍然还不能正常的工作。`ansible-pull` 命令需要 `sudo` 的权限来运行，这将允许它执行系统级的命令。然而我们创建的用户 `ansible` 并没有被设置为以 `sudo` 的权限来执行命令，因此当定时作业触发的时候，执行将会失败。通常我们可以使用命令 `visudo` 来手动的去设置用户 `ansible` 去拥有这个权限。然而我们现在应该以 Ansible 的方式来操作，而且这将会是一个向你展示 `copy` 模块是如何工作的机会。`copy` 模块允许你从库复制一个文件到文件系统的任何位置。在这个案列中，我们将会复制 `sudo` 的一个配置文件到 `/etc/sudoers.d/` 以便用户 `ansible` 能够以管理员的权限执行任务。

打开 `users.yml`，将下面的的动作添加到文件末尾。

```
- name: copy sudoers_ansible
  copy: src=files/sudoers_ansible dest=/etc/sudoers.d/ansible owner=root group=root mode=0440
```

正如我们看到的，`copy`模块从我们的仓库中复制一个文件到其他任何位置。在这个过程中，我们正在抓取一个名为 `sudoers_ansible`（我们将在后续创建）的文件并将它复制为 `/etc/sudoers/ansible`，并且拥有者为 `root`。

接下来，我们需要创建我们将要复制的文件。在你的仓库的根目录下，创建一个名为 `files` 的目录：

```
mkdir files
```

然后，在我们刚刚创建的 `files` 目录里，创建名为 `sudoers_ansible` 的文件，包含以下内容：

```
ansible ALL=(ALL) NOPASSWD: ALL
```

就像我们正在这样做的，在 `/etc/sudoer.d` 目录里创建一个文件允许我们为一个特殊的用户配置 `sudo` 权限。现在我们正在通过 `sudo` 允许用户 `ansible` 不需要密码提示就拥有完全控制权限。这将允许 `ansible-pull` 以后台任务的形式运行而不需要手动去运行。

现在，你可以通过再次运行 `ansible-pull` 来拉取最新的变动：

```
sudo ansible-pull -U https://github.com/<github_user>/ansible.git
```

从这里开始，`ansible-pull` 的定时作业将会在后台每隔十分钟运行一次来检查你的仓库是否有变化，如果它发现有变化，将会运行你的剧本并且应用你的任务手册。

所以现在我们有了一个完整的可工作方案。当你第一次设置一台新的笔记本或者台式机的时候，你要去手动的运行 `ansible-pull` 命令，但仅仅是在第一次的时候。从第一次之后，用户 `ansible` 将会在后台接手后续的运行任务。当你想对你的机器做变动的时候，你只需要简单的去拉取你的 Git 仓库来做变动，然后将这些变化回传到库中。接着，当定时作业下次在每台机器上运行的时候，它将会拉取变动的部分并应用它们。你现在只需要做一次变动，你的所有工作站将会跟着一起变动。这方法尽管有一点不同寻常，通常，你会有一个包含你的机器列表和不同机器所属规则的清单文件。然而，`ansible-pull` 的方法，就像在文章中描述的，是管理工作站配置的非常有效的方法。

我已经在我的 [Github 仓库](https://github.com/jlacroix82/ansible_article.git)中更新了这篇文章中的代码，所以你可以随时去浏览来对比检查你的语法。同时我将前一篇文章中的代码移到了它自己的目录中。

在[第三部分](https://opensource.com/article/18/5/manage-your-workstation-ansible-part-3)，我们将通过介绍使用 Ansible 来配置 GNOME 桌面设置来结束这个系列。我将会告诉你如何设置你的墙纸和锁屏壁纸、应用一个桌面主题以及更多的东西。

同时，到了布置一些作业的时候了，大多数人都有我们所使用的各种应用的配置文件。可能是 Bash、Vim 或者其他你使用的工具的配置文件。现在你可以尝试通过我们在使用的 Ansible 库来自动复制这些配置到你的机器中。在这篇文章中，我已将向你展示了如何去复制文件，所以去尝试以下看看你是都已经能应用这些知识。

---

via: <https://opensource.com/article/18/3/manage-your-workstation-configuration-ansible-part-2>

作者：[Jay LaCroix](https://opensource.com/users/jlacroix) 选题：[lujun9972](https://github.com/lujun9972) 译者：[FelixYFZ](https://github.com/FelixYFZ) 校对：[wxy](https://github.com/wxy)

本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


在 Linux 中打扮你的冬季 Bash 提示符

# 在 Linux 中打扮你的冬季 Bash 提示符

>
> 你的 Linux 终端可能支持 Unicode，那么为何不利用它在提示符中添加季节性的图标呢？
>
>
>

<img src="content_image/album_201901_16_184022d59vyvvkvqaa578v.png" alt="" title="">

欢迎再次来到 Linux 命令行玩具日历的另一篇。如果这是你第一次访问该系列，你甚至可能会问自己什么是命令行玩具？我们对此比较随意：它会是终端上有任何有趣的消遣，对于任何节日主题相关的还有额外的加分。

也许你以前见过其中的一些，也许你没有。不管怎样，我们希望你玩得开心。

今天的玩具非常简单：它是你的 Bash 提示符。你的 Bash 提示符？是的！我们还有几个星期的假期可以盯着它看，在北半球冬天还会再多几周，所以为什么不玩玩它。

目前你的 Bash 提示符号可能是一个简单的美元符号（ `$`），或者更有可能是一个更长的东西。如果你不确定你的 Bash 提示符是什么，你可以在环境变量 `$PS1` 中找到它。要查看它，请输入：

```
echo $PS1
```

对于我而言，它返回：

```
[\u@\h \W]\$
```

`\u`、`\h` 和 `\W` 分别是用户名、主机名和工作目录的特殊字符。你还可以使用其他一些符号。为了帮助构建你的 Bash 提示符，你可以使用 [EzPrompt](http://ezprompt.net/)，这是一个 `PS1` 配置的在线生成器，它包含了许多选项，包括日期和时间、Git 状态等。

你可能还有其他变量来组成 Bash 提示符。对我来说，`$PS2` 包含了我命令提示符的结束括号。有关详细信息，请参阅 [这篇文章](https://access.redhat.com/solutions/505983)。

要更改提示符，只需在终端中设置环境变量，如下所示：

```
$ PS1='\u is cold: '
jehb is cold:
```

要永久设置它，请使用你喜欢的文本编辑器将相同的代码添加到 `/etc/bashrc` 中。

那么这些与冬季化有什么关系呢？好吧，你很有可能有现代一下的机器，你的终端支持 Unicode，所以你不仅限于标准的 ASCII 字符集。你可以使用任何符合 Unicode 规范的 emoji，包括雪花 ❄、雪人 ☃ 或一对滑雪板。你有很多冬季 emoji 可供选择。

* 圣诞树
* 外套
* 鹿手套
* 圣诞夫人
* 圣诞老人
* 围巾
* 滑雪者
* 滑雪板
* 雪花
* 雪人
* 没有雪的雪人
* 包装好的礼物

选择你最喜欢的，享受冬天的欢乐。有趣的事实：现代文件系统也支持文件名中的 Unicode 字符，这意味着技术上你可以将你下个程序命名为 `❄❄❄❄❄.py`。只是说说，不要这么做。

你有特别喜欢的命令行小玩具需要我介绍的吗？这个系列要介绍的小玩具大部分已经有了落实，但还预留了几个空位置。如果你有特别想了解的可以评论留言，我会查看的。如果还有空位置，我会考虑介绍它的。如果没有，但如果我得到了一些很好的意见，我会在最后做一些有价值的提及。

查看昨天的玩具，[在 Linux 终端玩贪吃蛇](https://opensource.com/article/18/12/linux-toy-snake)，记得明天再来！

---

via: <https://opensource.com/article/18/12/linux-toy-bash-prompt>

作者：[Jason Baker](https://opensource.com/users/jason-baker) 选题：[lujun9972](https://github.com/lujun9972) 译者：[geekpi](https://github.com/geekpi) 校对：[wxy](https://github.com/wxy)

本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


Caffeinated 6.828：实验 6：网络驱动程序

# Caffeinated 6.828：实验 6：网络驱动程序

### 简介

这个实验是默认你能够自己完成的最终项目。

现在你已经有了一个文件系统，一个典型的操作系统都应该有一个网络栈。在本实验中，你将继续为一个网卡去写一个驱动程序。这个网卡基于 Intel 82540EM 芯片，也就是众所周知的 E1000 芯片。

#### 预备知识

使用 Git 去提交你的实验 5 的源代码（如果还没有提交的话），获取课程仓库的最新版本，然后创建一个名为 `lab6` 的本地分支，它跟踪我们的远程分支 `origin/lab6`：

```
athena% cd ~/6.828/lab
athena% add git
athena% git commit -am 'my solution to lab5'
nothing to commit (working directory clean)
athena% git pull
Already up-to-date.
athena% git checkout -b lab6 origin/lab6
Branch lab6 set up to track remote branch refs/remotes/origin/lab6.
Switched to a new branch "lab6"
athena% git merge lab5
Merge made by recursive.
 fs/fs.c |   42 +++++++++++++++++++
 1 files changed, 42 insertions(+), 0 deletions(-)
athena%
```

然后，仅有网卡驱动程序并不能够让你的操作系统接入互联网。在新的实验 6 的代码中，我们为你提供了网络栈和一个网络服务器。与以前的实验一样，使用 git 去拉取这个实验的代码，合并到你自己的代码中，并去浏览新的 `net/` 目录中的内容，以及在 `kern/` 中的新文件。

除了写这个驱动程序以外，你还需要去创建一个访问你的驱动程序的系统调用。你将要去实现那些在网络服务器中缺失的代码，以便于在网络栈和你的驱动程序之间传输包。你还需要通过完成一个 web 服务器来将所有的东西连接到一起。你的新 web 服务器还需要你的文件系统来提供所需要的文件。

大部分的内核设备驱动程序代码都需要你自己去从头开始编写。本实验提供的指导比起前面的实验要少一些：没有框架文件、没有现成的系统调用接口、并且很多设计都由你自己决定。因此，我们建议你在开始任何单独练习之前，阅读全部的编写任务。许多学生都反应这个实验比前面的实验都难，因此请根据你的实际情况计划你的时间。

#### 实验要求

与以前一样，你需要做实验中全部的常规练习和至少一个挑战问题。在实验中写出你的详细答案，并将挑战问题的方案描述写入到 `answers-lab6.txt` 文件中。

### QEMU 的虚拟网络

我们将使用 QEMU 的用户模式网络栈，因为它不需要以管理员权限运行。QEMU 的文档的[这里](http://wiki.qemu.org/download/qemu-doc.html#Using-the-user-mode-network-stack)有更多关于用户网络的内容。我们更新后的 makefile 启用了 QEMU 的用户模式网络栈和虚拟的 E1000 网卡。

缺省情况下，QEMU 提供一个运行在 IP 地址 10.2.2.2 上的虚拟路由器，它给 JOS 分配的 IP 地址是 10.0.2.15。为了简单起见，我们在 `net/ns.h` 中将这些缺省值硬编码到网络服务器上。

虽然 QEMU 的虚拟网络允许 JOS 随意连接互联网，但 JOS 的 10.0.2.15 的地址并不能在 QEMU 中的虚拟网络之外使用（也就是说，QEMU 还得做一个 NAT），因此我们并不能直接连接到 JOS 上运行的服务器，即便是从运行 QEMU 的主机上连接也不行。为解决这个问题，我们配置 QEMU 在主机的某些端口上运行一个服务器，这个服务器简单地连接到 JOS 中的一些端口上，并在你的真实主机和虚拟网络之间传递数据。

你将在端口 7（echo）和端口 80（http）上运行 JOS，为避免在共享的 Athena 机器上发生冲突，makefile 将为这些端口基于你的用户 ID 来生成转发端口。你可以运行 `make which-ports` 去找出是哪个 QEMU 端口转发到你的开发主机上。为方便起见，makefile 也提供 `make nc-7` 和 `make nc-80`，它允许你在终端上直接与运行这些端口的服务器去交互。（这些目标仅能连接到一个运行中的 QEMU 实例上；你必须分别去启动它自己的 QEMU）

#### 包检查

makefile 也可以配置 QEMU 的网络栈去记录所有的入站和出站数据包，并将它保存到你的实验目录中的 `qemu.pcap` 文件中。

使用 `tcpdump` 命令去获取一个捕获的 hex/ASCII 包转储：

```
tcpdump -XXnr qemu.pcap
```

或者，你可以使用 [Wireshark](http://www.wireshark.org/) 以图形化界面去检查 pcap 文件。Wireshark 也知道如何去解码和检查成百上千的网络协议。如果你在 Athena 上，你可以使用 Wireshark 的前辈：ethereal，它运行在加锁的保密互联网协议网络中。

#### 调试 E1000

我们非常幸运能够去使用仿真硬件。由于 E1000 是在软件中运行的，仿真的 E1000 能够给我们提供一个人类可读格式的报告、它的内部状态以及它遇到的任何问题。通常情况下，对祼机上做驱动程序开发的人来说，这是非常难能可贵的。

E1000 能够产生一些调试输出，因此你可以去打开一个专门的日志通道。其中一些对你有用的通道如下：

| 标志 | 含义 |
| --- | --- |
| tx | 包发送日志 |
| txerr | 包发送错误日志 |
| rx | 到 RCTL 的日志通道 |
| rxfilter | 入站包过滤日志 |
| rxerr | 接收错误日志 |
| unknown | 未知寄存器的读写日志 |
| eeprom | 读取 EEPROM 的日志 |
| interrupt | 中断和中断寄存器变更日志 |

例如，你可以使用 `make E1000_DEBUG=tx,txerr` 去打开 “tx” 和 “txerr” 日志功能。

注意：`E1000_DEBUG` 标志仅能在打了 6.828 补丁的 QEMU 版本上工作。

你可以使用软件去仿真硬件，来做进一步的调试工作。如果你使用它时卡壳了，不明白为什么 E1000 没有如你预期那样响应你，你可以查看在 `hw/e1000.c` 中的 QEMU 的 E1000 实现。

### 网络服务器

从头开始写一个网络栈是很困难的。因此我们将使用 lwIP，它是一个开源的、轻量级 TCP/IP 协议套件，它能做包括一个网络栈在内的很多事情。你能在 [这里](http://www.sics.se/%7Eadam/lwip/) 找到很多关于 lwIP 的信息。在这个任务中，对我们而言，lwIP 就是一个实现了一个 BSD 套接字接口和拥有一个包输入端口和包输出端口的黑盒子。

一个网络服务器其实就是一个有以下四个环境的混合体：

* 核心网络服务器环境（包括套接字调用派发器和 lwIP）
* 输入环境
* 输出环境
* 定时器环境

下图展示了各个环境和它们之间的关系。下图展示了包括设备驱动的整个系统，我们将在后面详细讲到它。在本实验中，你将去实现图中绿色高亮的部分。

#### 核心网络服务器环境

核心网络服务器环境由套接字调用派发器和 lwIP 自身组成的。套接字调用派发器就像一个文件服务器一样。用户环境使用 stubs（可以在 `lib/nsipc.c` 中找到它）去发送 IPC 消息到核心网络服务器环境。如果你看了 `lib/nsipc.c`，你就会发现核心网络服务器与我们创建的文件服务器 `i386_init` 的工作方式是一样的，`i386_init` 是使用 NSTYPENS 创建的 NS 环境，因此我们检查 `envs`，去查找这个特殊的环境类型。对于每个用户环境的 IPC，网络服务器中的派发器将调用相应的、由 lwIP 提供的、代表用户的 BSD 套接字接口函数。

普通用户环境不能直接使用 `nsipc_*` 调用。而是通过在 `lib/sockets.c` 中的函数来使用它们，这些函数提供了基于文件描述符的套接字 API。以这种方式，用户环境通过文件描述符来引用套接字，就像它们引用磁盘上的文件一样。一些操作（`connect`、`accept` 等等）是特定于套接字的，但 `read`、`write` 和 `close` 是通过 `lib/fd.c` 中一般的文件描述符设备派发代码的。就像文件服务器对所有的打开的文件维护唯一的内部 ID 一样，lwIP 也为所有的打开的套接字生成唯一的 ID。不论是文件服务器还是网络服务器，我们都使用存储在 `struct Fd` 中的信息去映射每个环境的文件描述符到这些唯一的 ID 空间上。

尽管看起来文件服务器的网络服务器的 IPC 派发器行为是一样的，但它们之间还有很重要的差别。BSD 套接字调用（像 `accept` 和 `recv`）能够无限期阻塞。如果派发器让 lwIP 去执行其中一个调用阻塞，派发器也将被阻塞，并且在整个系统中，同一时间只能有一个未完成的网络调用。由于这种情况是无法接受的，所以网络服务器使用用户级线程以避免阻塞整个服务器环境。对于每个入站 IPC 消息，派发器将创建一个线程，然后在新创建的线程上来处理请求。如果线程被阻塞，那么只有那个线程被置入休眠状态，而其它线程仍然处于运行中。

除了核心网络环境外，还有三个辅助环境。核心网络服务器环境除了接收来自用户应用程序的消息之外，它的派发器也接收来自输入环境和定时器环境的消息。

#### 输出环境

在为用户环境套接字调用提供服务时，lwIP 将为网卡生成用于发送的包。lwIP 将使用 `NSREQ_OUTPUT` 去发送在 IPC 消息页参数中附加了包的 IPC 消息。输出环境负责接收这些消息，并通过你稍后创建的系统调用接口来转发这些包到设备驱动程序上。

#### 输入环境

网卡接收到的包需要传递到 lwIP 中。输入环境将每个由设备驱动程序接收到的包拉进内核空间（使用你将要实现的内核系统调用），并使用 `NSREQ_INPUT` IPC 消息将这些包发送到核心网络服务器环境。

包输入功能是独立于核心网络环境的，因为在 JOS 上同时实现接收 IPC 消息并从设备驱动程序中查询或等待包有点困难。我们在 JOS 中没有实现 `select` 系统调用，这是一个允许环境去监视多个输入源以识别准备处理哪个输入的系统调用。

如果你查看了 `net/input.c` 和 `net/output.c`，你将会看到在它们中都需要去实现那个系统调用。这主要是因为实现它要依赖你的系统调用接口。在你实现了驱动程序和系统调用接口之后，你将要为这两个辅助环境写这个代码。

#### 定时器环境

定时器环境周期性发送 `NSREQ_TIMER` 类型的消息到核心服务器，以提醒它那个定时器已过期。lwIP 使用来自线程的定时器消息来实现各种网络超时。

### Part A：初始化和发送包

你的内核还没有一个时间概念，因此我们需要去添加它。这里有一个由硬件产生的每 10 ms 一次的时钟中断。每收到一个时钟中断，我们将增加一个变量值，以表示时间已过去 10 ms。它在 `kern/time.c` 中已实现，但还没有完全集成到你的内核中。

>
> **练习 1**、为 `kern/trap.c` 中的每个时钟中断增加一个到 `time_tick` 的调用。实现 `sys_time_msec` 并增加到 `kern/syscall.c` 中的 `syscall`，以便于用户空间能够访问时间。
>
>
>

使用 `make INIT_CFLAGS=-DTEST_NO_NS run-testtime` 去测试你的代码。你应该会看到环境计数从 5 开始以 1 秒为间隔减少。`-DTEST_NO_NS` 参数禁止在网络服务器环境上启动，因为在当前它将导致 JOS 崩溃。

#### 网卡

写驱动程序要求你必须深入了解硬件和软件中的接口。本实验将给你提供一个如何使用 E1000 接口的高度概括的文档，但是你在写驱动程序时还需要大量去查询 Intel 的手册。

>
> **练习 2**、为开发 E1000 驱动，去浏览 Intel 的 [软件开发者手册](https://pdos.csail.mit.edu/6.828/2018/readings/hardware/8254x_GBe_SDM.pdf)。这个手册涵盖了几个与以太网控制器紧密相关的东西。QEMU 仿真了 82540EM。
>
>
> 现在，你应该去浏览第 2 章，以对设备获得一个整体概念。写驱动程序时，你需要熟悉第 3 到 14 章，以及 4.1（不包括 4.1 的子节）。你也应该去参考第 13 章。其它章涵盖了 E1000 的组件，你的驱动程序并不与这些组件去交互。现在你不用担心过多细节的东西；只需要了解文档的整体结构，以便于你后面需要时容易查找。
>
>
> 在阅读手册时，记住，E1000 是一个拥有很多高级特性的很复杂的设备，一个能让 E1000 工作的驱动程序仅需要它一小部分的特性和 NIC 提供的接口即可。仔细考虑一下，如何使用最简单的方式去使用网卡的接口。我们强烈推荐你在使用高级特性之前，只去写一个基本的、能够让网卡工作的驱动程序即可。
>
>
>

##### PCI 接口

E1000 是一个 PCI 设备，也就是说它是插到主板的 PCI 总线插槽上的。PCI 总线有地址、数据、和中断线，并且 PCI 总线允许 CPU 与 PCI 设备通讯，以及 PCI 设备去读取和写入内存。一个 PCI 设备在它能够被使用之前，需要先发现它并进行初始化。发现 PCI 设备是 PCI 总线查找已安装设备的过程。初始化是分配 I/O 和内存空间、以及协商设备所使用的 IRQ 线的过程。

我们在 `kern/pci.c` 中已经为你提供了使用 PCI 的代码。PCI 初始化是在引导期间执行的，PCI 代码遍历PCI 总线来查找设备。当它找到一个设备时，它读取它的供应商 ID 和设备 ID，然后使用这两个值作为关键字去搜索 `pci_attach_vendor` 数组。这个数组是由像下面这样的 `struct pci_driver` 条目组成：

```
struct pci_driver {
    uint32_t key1, key2;
    int (*attachfn) (struct pci_func *pcif);
};
```

如果发现的设备的供应商 ID 和设备 ID 与数组中条目匹配，那么 PCI 代码将调用那个条目的 `attachfn` 去执行设备初始化。（设备也可以按类别识别，那是通过 `kern/pci.c` 中其它的驱动程序表来实现的。）

绑定函数是传递一个 PCI 函数 去初始化。一个 PCI 卡能够发布多个函数，虽然这个 E1000 仅发布了一个。下面是在 JOS 中如何去表示一个 PCI 函数：

```
struct pci_func {
    struct pci_bus *bus;

    uint32_t dev;
    uint32_t func;

    uint32_t dev_id;
    uint32_t dev_class;

    uint32_t reg_base[6];
    uint32_t reg_size[6];
    uint8_t irq_line;
};
```

上面的结构反映了在 Intel 开发者手册里第 4.1 节的表 4-1 中找到的一些条目。`struct pci_func` 的最后三个条目我们特别感兴趣的，因为它们将记录这个设备协商的内存、I/O、以及中断资源。`reg_base` 和 `reg_size` 数组包含最多六个基址寄存器或 BAR。`reg_base` 为映射到内存中的 I/O 区域（对于 I/O 端口而言是基 I/O 端口）保存了内存的基地址，`reg_size` 包含了以字节表示的大小或来自 `reg_base` 的相关基值的 I/O 端口号，而 `irq_line` 包含了为中断分配给设备的 IRQ 线。在表 4-2 的后半部分给出了 E1000 BAR 的具体涵义。

当设备调用了绑定函数后，设备已经被发现，但没有被启用。这意味着 PCI 代码还没有确定分配给设备的资源，比如地址空间和 IRQ 线，也就是说，`struct pci_func` 结构的最后三个元素还没有被填入。绑定函数将调用 `pci_func_enable`，它将去启用设备、协商这些资源、并在结构 `struct pci_func` 中填入它。

>
> **练习 3**、实现一个绑定函数去初始化 E1000。添加一个条目到 `kern/pci.c` 中的数组 `pci_attach_vendor` 上，如果找到一个匹配的 PCI 设备就去触发你的函数（确保一定要把它放在表末尾的 `{0, 0, 0}` 条目之前）。你在 5.2 节中能找到 QEMU 仿真的 82540EM 的供应商 ID 和设备 ID。在引导期间，当 JOS 扫描 PCI 总线时，你也可以看到列出来的这些信息。
>
>
> 到目前为止，我们通过 `pci_func_enable` 启用了 E1000 设备。通过本实验我们将添加更多的初始化。
>
>
> 我们已经为你提供了 `kern/e1000.c` 和 `kern/e1000.h` 文件，这样你就不会把构建系统搞糊涂了。不过它们现在都是空的；你需要在本练习中去填充它们。你还可能在内核的其它地方包含这个 `e1000.h` 文件。
>
>
> 当你引导你的内核时，你应该会看到它输出的信息显示 E1000 的 PCI 函数已经启用。这时你的代码已经能够通过 `make grade` 的 `pci attach` 测试了。
>
>
>

##### 内存映射的 I/O

软件与 E1000 通过内存映射的 I/O（MMIO）来沟通。你在 JOS 的前面部分可能看到过 MMIO 两次：CGA 控制台和 LAPIC 都是通过写入和读取“内存”来控制和查询设备的。但这些读取和写入不是去往内存芯片的，而是直接到这些设备的。

`pci_func_enable` 为 E1000 协调一个 MMIO 区域，来存储它在 BAR 0 的基址和大小（也就是 `reg_base[0]` 和 `reg_size[0]`），这是一个分配给设备的一段物理内存地址，也就是说你可以通过虚拟地址访问它来做一些事情。由于 MMIO 区域一般分配高位物理地址（一般是 3GB 以上的位置），因此你不能使用 `KADDR` 去访问它们，因为 JOS 被限制为最大使用 256MB。因此，你可以去创建一个新的内存映射。我们将使用 `MMIOBASE`（从实验 4 开始，你的 `mmio_map_region` 区域应该确保不能被 LAPIC 使用的映射所覆盖）以上的部分。由于在 JOS 创建用户环境之前，PCI 设备就已经初始化了，因此你可以在 `kern_pgdir` 处创建映射，并且让它始终可用。

>
> **练习 4**、在你的绑定函数中，通过调用 `mmio_map_region`（它就是你在实验 4 中写的，是为了支持 LAPIC 内存映射）为 E1000 的 BAR 0 创建一个虚拟地址映射。
>
>
> 你将希望在一个变量中记录这个映射的位置，以便于后面访问你映射的寄存器。去看一下 `kern/lapic.c` 中的 `lapic` 变量，它就是一个这样的例子。如果你使用一个指针指向设备寄存器映射，一定要声明它为 `volatile`；否则，编译器将允许缓存它的值，并可以在内存中再次访问它。
>
>
> 为测试你的映射，尝试去输出设备状态寄存器（第 12.4.2 节）。这是一个在寄存器空间中以字节 8 开头的 4 字节寄存器。你应该会得到 `0x80080783`，它表示以 1000 MB/s 的速度启用一个全双工的链路，以及其它信息。
>
>
>

提示：你将需要一些常数，像寄存器位置和掩码位数。如果从开发者手册中复制这些东西很容易出错，并且导致调试过程很痛苦。我们建议你使用 QEMU 的 [e1000\_hw.h](https://pdos.csail.mit.edu/6.828/2018/labs/lab6/e1000_hw.h) 头文件做为基准。我们不建议完全照抄它，因为它定义的值远超过你所需要，并且定义的东西也不见得就是你所需要的，但它仍是一个很好的参考。

##### DMA

你可能会认为是从 E1000 的寄存器中通过写入和读取来传送和接收数据包的，其实这样做会非常慢，并且还要求 E1000 在其中去缓存数据包。相反，E1000 使用直接内存访问（DMA）从内存中直接读取和写入数据包，而且不需要 CPU 参与其中。驱动程序负责为发送和接收队列分配内存、设置 DMA 描述符、以及配置 E1000 使用的队列位置，而在这些设置完成之后的其它工作都是异步方式进行的。发送包的时候，驱动程序复制它到发送队列的下一个 DMA 描述符中，并且通知 E1000 下一个发送包已就绪；当轮到这个包发送时，E1000 将从描述符中复制出数据。同样，当 E1000 接收一个包时，它从接收队列中将它复制到下一个 DMA 描述符中，驱动程序将能在下一次读取到它。

总体来看，接收队列和发送队列非常相似。它们都是由一系列的描述符组成。虽然这些描述符的结构细节有所不同，但每个描述符都包含一些标志和包含了包数据的一个缓存的物理地址（发送到网卡的数据包，或网卡将接收到的数据包写入到由操作系统分配的缓存中）。

队列被实现为一个环形数组，意味着当网卡或驱动到达数组末端时，它将重新回到开始位置。它有一个头指针和尾指针，队列的内容就是这两个指针之间的描述符。硬件就是从头开始移动头指针去消费描述符，在这期间驱动程序不停地添加描述符到尾部，并移动尾指针到最后一个描述符上。发送队列中的描述符表示等待发送的包（因此，在平静状态下，发送队列是空的）。对于接收队列，队列中的描述符是表示网卡能够接收包的空描述符（因此，在平静状态下，接收队列是由所有的可用接收描述符组成的）。正确的更新尾指针寄存器而不让 E1000 产生混乱是很有难度的；要小心！

指向到这些数组及描述符中的包缓存地址的指针都必须是物理地址，因为硬件是直接在物理内存中且不通过 MMU 来执行 DMA 的读写操作的。

#### 发送包

E1000 中的发送和接收功能本质上是独立的，因此我们可以同时进行发送接收。我们首先去攻克简单的数据包发送，因为我们在没有先去发送一个 “I’m here!” 包之前是无法测试接收包功能的。

首先，你需要初始化网卡以准备发送，详细步骤查看 14.5 节（不必着急看子节）。发送初始化的第一步是设置发送队列。队列的详细结构在 3.4 节中，描述符的结构在 3.3.3 节中。我们先不要使用 E1000 的 TCP offload 特性，因此你只需专注于 “传统的发送描述符格式” 即可。你应该现在就去阅读这些章节，并要熟悉这些结构。

##### C 结构

你可以用 C `struct` 很方便地描述 E1000 的结构。正如你在 `struct Trapframe` 中所看到的结构那样，C `struct` 可以让你很方便地在内存中描述准确的数据布局。C 可以在字段中插入数据，但是 E1000 的结构就是这样布局的，这样就不会是个问题。如果你遇到字段对齐问题，进入 GCC 查看它的 “packed” 属性。

查看手册中表 3-8 所给出的一个传统的发送描述符，将它复制到这里作为一个示例：

```
  63            48 47   40 39   32 31   24 23   16 15             0
  +---------------------------------------------------------------+
  |                         Buffer address                        |
  +---------------|-------|-------|-------|-------|---------------+
  |    Special    |  CSS  | Status|  Cmd  |  CSO  |    Length     |
  +---------------|-------|-------|-------|-------|---------------+
```

从结构右上角第一个字节开始，我们将它转变成一个 C 结构，从上到下，从右到左读取。如果你从右往左看，你将看到所有的字段，都非常适合一个标准大小的类型：

```
struct tx_desc
{
    uint64_t addr;
    uint16_t length;
    uint8_t cso;
    uint8_t cmd;
    uint8_t status;
    uint8_t css;
    uint16_t special;
};
```

你的驱动程序将为发送描述符数组去保留内存，并由发送描述符指向到包缓冲区。有几种方式可以做到，从动态分配页到在全局变量中简单地声明它们。无论你如何选择，记住，E1000 是直接访问物理内存的，意味着它能访问的任何缓存区在物理内存中必须是连续的。

处理包缓存也有几种方式。我们推荐从最简单的开始，那就是在驱动程序初始化期间，为每个描述符保留包缓存空间，并简单地将包数据复制进预留的缓冲区中或从其中复制出来。一个以太网包最大的尺寸是 1518 字节，这就限制了这些缓存区的大小。主流的成熟驱动程序都能够动态分配包缓存区（即：当网络使用率很低时，减少内存使用量），或甚至跳过缓存区，直接由用户空间提供（就是“零复制”技术），但我们还是从简单开始为好。

>
> **练习 5**、执行一个 14.5 节中的初始化步骤（它的子节除外）。对于寄存器的初始化过程使用 13 节作为参考，对发送描述符和发送描述符数组参考 3.3.3 节和 3.4 节。
>
>
> 要记住，在发送描述符数组中要求对齐，并且数组长度上有限制。因为 TDLEN 必须是 128 字节对齐的，而每个发送描述符是 16 字节，你的发送描述符数组必须是 8 个发送描述符的倍数。并且不能使用超过 64 个描述符，以及不能在我们的发送环形缓存测试中溢出。
>
>
> 对于 TCTL.COLD，你可以假设为全双工操作。对于 TIPG、IEEE 802.3 标准的 IPG（不要使用 14.5 节中表上的值），参考在 13.4.34 节中表 13-77 中描述的缺省值。
>
>
>

尝试运行 `make E1000_DEBUG=TXERR,TX qemu`。如果你使用的是打了 6.828 补丁的 QEMU，当你设置 TDT（发送描述符尾部）寄存器时你应该会看到一个 “e1000: tx disabled” 的信息，并且不会有更多 “e1000” 信息了。

现在，发送初始化已经完成，你可以写一些代码去发送一个数据包，并且通过一个系统调用使它可以访问用户空间。你可以将要发送的数据包添加到发送队列的尾部，也就是说复制数据包到下一个包缓冲区中，然后更新 TDT 寄存器去通知网卡在发送队列中有另外的数据包。（注意，TDT 是一个进入发送描述符数组的索引，不是一个字节偏移量；关于这一点文档中说明的不是很清楚。）

但是，发送队列只有这么大。如果网卡在发送数据包时卡住或发送队列填满时会发生什么状况？为了检测这种情况，你需要一些来自 E1000 的反馈。不幸的是，你不能只使用 TDH（发送描述符头）寄存器；文档上明确说明，从软件上读取这个寄存器是不可靠的。但是，如果你在发送描述符的命令字段中设置 RS 位，那么，当网卡去发送在那个描述符中的数据包时，网卡将设置描述符中状态字段的 DD 位，如果一个描述符中的 DD 位被设置，你就应该知道那个描述符可以安全地回收，并且可以用它去发送其它数据包。

如果用户调用你的发送系统调用，但是下一个描述符的 DD 位没有设置，表示那个发送队列已满，该怎么办？在这种情况下，你该去决定怎么办了。你可以简单地丢弃数据包。网络协议对这种情况的处理很灵活，但如果你丢弃大量的突发数据包，协议可能不会去重新获得它们。可能需要你替代网络协议告诉用户环境让它重传，就像你在 `sys_ipc_try_send` 中做的那样。在环境上回推产生的数据是有好处的。

>
> **练习 6**、写一个函数去发送一个数据包，它需要检查下一个描述符是否空闲、复制包数据到下一个描述符并更新 TDT。确保你处理的发送队列是满的。
>
>
>

现在，应该去测试你的包发送代码了。通过从内核中直接调用你的发送函数来尝试发送几个包。在测试时，你不需要去创建符合任何特定网络协议的数据包。运行 `make E1000_DEBUG=TXERR,TX qemu` 去测试你的代码。你应该看到类似下面的信息：

```
e1000: index 0: 0x271f00 : 9000002a 0
...
```

在你发送包时，每行都给出了在发送数组中的序号、那个发送的描述符的缓存地址、`cmd/CSO/length` 字段、以及 `special/CSS/status` 字段。如果 QEMU 没有从你的发送描述符中输出你预期的值，检查你的描述符中是否有合适的值和你配置的正确的 TDBAL 和 TDBAH。如果你收到的是 “e1000: TDH wraparound @0, TDT x, TDLEN y” 的信息，意味着 E1000 的发送队列持续不断地运行（如果 QEMU 不去检查它，它将是一个无限循环），这意味着你没有正确地维护 TDT。如果你收到了许多 “e1000: tx disabled” 的信息，那么意味着你没有正确设置发送控制寄存器。

一旦 QEMU 运行，你就可以运行 `tcpdump -XXnr qemu.pcap` 去查看你发送的包数据。如果从 QEMU 中看到预期的 “e1000: index” 信息，但你捕获的包是空的，再次检查你发送的描述符，是否填充了每个必需的字段和位。（E1000 或许已经遍历了你的发送描述符，但它认为不需要去发送）

>
> **练习 7**、添加一个系统调用，让你从用户空间中发送数据包。详细的接口由你来决定。但是不要忘了检查从用户空间传递给内核的所有指针。
>
>
>

#### 发送包：网络服务器

现在，你已经有一个系统调用接口可以发送包到你的设备驱动程序端了。输出辅助环境的目标是在一个循环中做下面的事情：从核心网络服务器中接收 `NSREQ_OUTPUT` IPC 消息，并使用你在上面增加的系统调用去发送伴随这些 IPC 消息的数据包。这个 `NSREQ_OUTPUT` IPC 是通过 `net/lwip/jos/jif/jif.c` 中的 `low_level_output` 函数来发送的。它集成 lwIP 栈到 JOS 的网络系统。每个 IPC 将包含一个页，这个页由一个 `union Nsipc` 和在 `struct jif_pkt pkt` 字段中的一个包组成（查看 `inc/ns.h`）。`struct jif_pkt` 看起来像下面这样：

```
struct jif_pkt {
    int jp_len;
    char jp_data[0];
};
```

`jp_len` 表示包的长度。在 IPC 页上的所有后续字节都是为了包内容。在结构的结尾处使用一个长度为 0 的数组来表示缓存没有一个预先确定的长度（像 `jp_data` 一样），这是一个常见的 C 技巧（也有人说这是一个令人讨厌的做法）。因为 C 并不做数组边界的检查，只要你确保结构后面有足够的未使用内存即可，你可以把 `jp_data` 作为一个任意大小的数组来使用。

当设备驱动程序的发送队列中没有足够的空间时，一定要注意在设备驱动程序、输出环境和核心网络服务器之间的交互。核心网络服务器使用 IPC 发送包到输出环境。如果输出环境在由于一个发送包的系统调用而挂起，导致驱动程序没有足够的缓存去容纳新数据包，这时核心网络服务器将阻塞以等待输出服务器去接收 IPC 调用。

>
> **练习 8**、实现 `net/output.c`。
>
>
>

你可以使用 `net/testoutput.c` 去测试你的输出代码而无需整个网络服务器参与。尝试运行 `make E1000_DEBUG=TXERR,TX run-net_testoutput`。你将看到如下的输出：

```
Transmitting packet 0
e1000: index 0: 0x271f00 : 9000009 0
Transmitting packet 1
e1000: index 1: 0x2724ee : 9000009 0
...
```

运行 `tcpdump -XXnr qemu.pcap` 将输出：

```
reading from file qemu.pcap, link-type EN10MB (Ethernet)
-5:00:00.600186 [|ether]
    0x0000:  5061 636b 6574 2030 30                   Packet.00
-5:00:00.610080 [|ether]
    0x0000:  5061 636b 6574 2030 31                   Packet.01
...
```

使用更多的数据包去测试，可以运行 `make E1000_DEBUG=TXERR,TX NET_CFLAGS=-DTESTOUTPUT_COUNT=100 run-net_testoutput`。如果它导致你的发送队列溢出，再次检查你的 DD 状态位是否正确，以及是否告诉硬件去设置 DD 状态位（使用 RS 命令位）。

你的代码应该会通过 `make grade` 的 `testoutput` 测试。

>
> **问题 1**、你是如何构造你的发送实现的？在实践中，如果发送缓存区满了，你该如何处理？
>
>
>

### Part B：接收包和 web 服务器

#### 接收包

就像你在发送包中做的那样，你将去配置 E1000 去接收数据包，并提供一个接收描述符队列和接收描述符。在 3.2 节中描述了接收包的操作，包括接收队列结构和接收描述符、以及在 14.4 节中描述的详细的初始化过程。

>
> **练习 9**、阅读 3.2 节。你可以忽略关于中断和 offload 校验和方面的内容（如果在后面你想去使用这些特性，可以再返回去阅读），你现在不需要去考虑阈值的细节和网卡内部缓存是如何工作的。
>
>
>

除了接收队列是由一系列的等待入站数据包去填充的空缓存包以外，接收队列的其它部分与发送队列非常相似。所以，当网络空闲时，发送队列是空的（因为所有的包已经被发送出去了），而接收队列是满的（全部都是空缓存包）。

当 E1000 接收一个包时，它首先与网卡的过滤器进行匹配检查（例如，去检查这个包的目标地址是否为这个 E1000 的 MAC 地址），如果这个包不匹配任何过滤器，它将忽略这个包。否则，E1000 尝试从接收队列头部去检索下一个接收描述符。如果头（RDH）追上了尾（RDT），那么说明接收队列已经没有空闲的描述符了，所以网卡将丢弃这个包。如果有空闲的接收描述符，它将复制这个包的数据到描述符指向的缓存中，设置这个描述符的 DD 和 EOP 状态位，并递增 RDH。

如果 E1000 在一个接收描述符中接收到了一个比包缓存还要大的数据包，它将按需从接收队列中检索尽可能多的描述符以保存数据包的全部内容。为表示发生了这种情况，它将在所有的这些描述符上设置 DD 状态位，但仅在这些描述符的最后一个上设置 EOP 状态位。在你的驱动程序上，你可以去处理这种情况，也可以简单地配置网卡拒绝接收这种”长包“（这种包也被称为”巨帧“），你要确保接收缓存有足够的空间尽可能地去存储最大的标准以太网数据包（1518 字节）。

>
> **练习 10**、设置接收队列并按 14.4 节中的流程去配置 E1000。你可以不用支持 ”长包“ 或多播。到目前为止，我们不用去配置网卡使用中断；如果你在后面决定去使用接收中断时可以再去改。另外，配置 E1000 去除以太网的 CRC 校验，因为我们的评级脚本要求必须去掉校验。
>
>
> 默认情况下，网卡将过滤掉所有的数据包。你必须使用网卡的 MAC 地址去配置接收地址寄存器（RAL 和 RAH）以接收发送到这个网卡的数据包。你可以简单地硬编码 QEMU 的默认 MAC 地址 52:54:00:12:34:56（我们已经在 lwIP 中硬编码了这个地址，因此这样做不会有问题）。使用字节顺序时要注意；MAC 地址是从低位字节到高位字节的方式来写的，因此 52:54:00:12 是 MAC 地址的低 32 位，而 34:56 是它的高 16 位。
>
>
> E1000 的接收缓存区大小仅支持几个指定的设置值（在 13.4.22 节中描述的 RCTL.BSIZE 值）。如果你的接收包缓存够大，并且拒绝长包，那你就不用担心跨越多个缓存区的包。另外，要记住的是，和发送一样，接收队列和包缓存必须是连接的物理内存。
>
>
> 你应该使用至少 128 个接收描述符。
>
>
>

现在，你可以做接收功能的基本测试了，甚至都无需写代码去接收包了。运行 `make E1000_DEBUG=TX,TXERR,RX,RXERR,RXFILTER run-net_testinput`。`testinput` 将发送一个 ARP（地址解析协议）通告包（使用你的包发送的系统调用），而 QEMU 将自动回复它，即便是你的驱动尚不能接收这个回复，你也应该会看到一个 “e1000: unicast match[0]: 52:54:00:12:34:56” 的消息，表示 E1000 接收到一个包，并且匹配了配置的接收过滤器。如果你看到的是一个 “e1000: unicast mismatch: 52:54:00:12:34:56” 消息，表示 E1000 过滤掉了这个包，意味着你的 RAL 和 RAH 的配置不正确。确保你按正确的顺序收到了字节，并不要忘记设置 RAH 中的 “Address Valid” 位。如果你没有收到任何 “e1000” 消息，或许是你没有正确地启用接收功能。

现在，你准备去实现接收数据包。为了接收数据包，你的驱动程序必须持续跟踪希望去保存下一下接收到的包的描述符（提示：按你的设计，这个功能或许已经在 E1000 中的一个寄存器来实现了）。与发送类似，官方文档上表示，RDH 寄存器状态并不能从软件中可靠地读取，因为确定一个包是否被发送到描述符的包缓存中，你需要去读取描述符中的 DD 状态位。如果 DD 位被设置，你就可以从那个描述符的缓存中复制出这个数据包，然后通过更新队列的尾索引 RDT 来告诉网卡那个描述符是空闲的。

如果 DD 位没有被设置，表明没有接收到包。这就与发送队列满的情况一样，这时你可以有几种做法。你可以简单地返回一个 ”重传“ 错误来要求对端重发一次。对于满的发送队列，由于那是个临时状况，这种做法还是很好的，但对于空的接收队列来说就不太合理了，因为接收队列可能会保持好长一段时间的空的状态。第二个方法是挂起调用环境，直到在接收队列中处理了这个包为止。这个策略非常类似于 `sys_ipc_recv`。就像在 IPC 的案例中，因为我们每个 CPU 仅有一个内核栈，一旦我们离开内核，栈上的状态就会被丢弃。我们需要设置一个标志去表示那个环境由于接收队列下溢被挂起并记录系统调用参数。这种方法的缺点是过于复杂：E1000 必须被指示去产生接收中断，并且驱动程序为了恢复被阻塞等待一个包的环境，必须处理这个中断。

>
> **练习 11**、写一个函数从 E1000 中接收一个包，然后通过一个系统调用将它发布到用户空间。确保你将接收队列处理成空的。
>
>
>

.

>
> 小挑战！如果发送队列是满的或接收队列是空的，环境和你的驱动程序可能会花费大量的 CPU 周期是轮询、等待一个描述符。一旦完成发送或接收描述符，E1000 能够产生一个中断，以避免轮询。修改你的驱动程序，处理发送和接收队列是以中断而不是轮询的方式进行。
>
>
> 注意，一旦确定为中断，它将一直处于中断状态，直到你的驱动程序明确处理完中断为止。在你的中断服务程序中，一旦处理完成要确保清除掉中断状态。如果你不那样做，从你的中断服务程序中返回后，CPU 将再次跳转到你的中断服务程序中。除了在 E1000 网卡上清除中断外，也需要使用 `lapic_eoi` 在 LAPIC 上清除中断。
>
>
>

#### 接收包：网络服务器

在网络服务器输入环境中，你需要去使用你的新的接收系统调用以接收数据包，并使用 `NSREQ_INPUT` IPC 消息将它传递到核心网络服务器环境。这些 IPC 输入消息应该会有一个页，这个页上绑定了一个 `union Nsipc`，它的 `struct jif_pkt pkt` 字段中有从网络上接收到的包。

>
> **练习 12**、实现 `net/input.c`。
>
>
>

使用 `make E1000_DEBUG=TX,TXERR,RX,RXERR,RXFILTER run-net_testinput` 再次运行 `testinput`，你应该会看到：

```
Sending ARP announcement...
Waiting for packets...
e1000: index 0: 0x26dea0 : 900002a 0
e1000: unicast match[0]: 52:54:00:12:34:56
input: 0000 5254 0012 3456 5255 0a00 0202 0806 0001
input: 0010 0800 0604 0002 5255 0a00 0202 0a00 0202
input: 0020 5254 0012 3456 0a00 020f 0000 0000 0000
input: 0030 0000 0000 0000 0000 0000 0000 0000 0000
```

“input:” 打头的行是一个 QEMU 的 ARP 回复的十六进制转储。

你的代码应该会通过 `make grade` 的 `testinput` 测试。注意，在没有发送至少一个包去通知 QEMU 中的 JOS 的 IP 地址上时，是没法去测试包接收的，因此在你的发送代码中的 bug 可能会导致测试失败。

为彻底地测试你的网络代码，我们提供了一个称为 `echosrv` 的守护程序，它在端口 7 上设置运行 `echo` 的服务器，它将回显通过 TCP 连接发送给它的任何内容。使用 `make E1000_DEBUG=TX,TXERR,RX,RXERR,RXFILTER run-echosrv` 在一个终端中启动 `echo` 服务器，然后在另一个终端中通过 `make nc-7` 去连接它。你输入的每一行都被这个服务器回显出来。每次在仿真的 E1000 上接收到一个包，QEMU 将在控制台上输出像下面这样的内容：

```
e1000: unicast match[0]: 52:54:00:12:34:56
e1000: index 2: 0x26ea7c : 9000036 0
e1000: index 3: 0x26f06a : 9000039 0
e1000: unicast match[0]: 52:54:00:12:34:56
```

做到这一点后，你应该也就能通过 `echosrv` 的测试了。

>
> **问题 2**、你如何构造你的接收实现？在实践中，如果接收队列是空的并且一个用户环境要求下一个入站包，你怎么办？
>
>
>

.

>
> 小挑战！在开发者手册中阅读关于 EEPROM 的内容，并写出从 EEPROM 中加载 E1000 的 MAC 地址的代码。目前，QEMU 的默认 MAC 地址是硬编码到你的接收初始化代码和 lwIP 中的。修复你的初始化代码，让它能够从 EEPROM 中读取 MAC 地址，和增加一个系统调用去传递 MAC 地址到 lwIP 中，并修改 lwIP 去从网卡上读取 MAC 地址。通过配置 QEMU 使用一个不同的 MAC 地址去测试你的变更。
>
>
>

.

>
> 小挑战！修改你的 E1000 驱动程序去使用 零复制 技术。目前，数据包是从用户空间缓存中复制到发送包缓存中，和从接收包缓存中复制回到用户空间缓存中。一个使用 ”零复制“ 技术的驱动程序可以通过直接让用户空间和 E1000 共享包缓存内存来实现。还有许多不同的方法去实现 ”零复制“，包括映射内容分配的结构到用户空间或直接传递用户提供的缓存到 E1000。不论你选择哪种方法，都要注意你如何利用缓存的问题，因为你不能在用户空间代码和 E1000 之间产生争用。
>
>
>

.

>
> 小挑战！把 “零复制” 的概念用到 lwIP 中。
>
>
> 一个典型的包是由许多头构成的。用户发送的数据被发送到 lwIP 中的一个缓存中。TCP 层要添加一个 TCP 包头，IP 层要添加一个 IP 包头，而 MAC 层有一个以太网头。甚至还有更多的部分增加到包上，这些部分要正确地连接到一起，以便于设备驱动程序能够发送最终的包。
>
>
> E1000 的发送描述符设计是非常适合收集分散在内存中的包片段的，像在 lwIP 中创建的包的帧。如果你排队多个发送描述符，但仅设置最后一个描述符的 EOP 命令位，那么 E1000 将在内部把这些描述符串成包缓存，并在它们标记完 EOP 后仅发送串起来的缓存。因此，独立的包片段不需要在内存中把它们连接到一起。
>
>
> 修改你的驱动程序，以使它能够发送由多个缓存且无需复制的片段组成的包，并且修改 lwIP 去避免它合并包片段，因为它现在能够正确处理了。
>
>
>

.

>
> 小挑战！增加你的系统调用接口，以便于它能够为多于一个的用户环境提供服务。如果有多个网络栈（和多个网络服务器）并且它们各自都有自己的 IP 地址运行在用户模式中，这将是非常有用的。接收系统调用将决定它需要哪个环境来转发每个入站的包。
>
>
> 注意，当前的接口并不知道两个包之间有何不同，并且如果多个环境去调用包接收的系统调用，各个环境将得到一个入站包的子集，而那个子集可能并不包含调用环境指定的那个包。
>
>
> 在 [这篇](http://pdos.csail.mit.edu/papers/exo:tocs.pdf) 外内核论文的 2.2 节和 3 节中对这个问题做了深度解释，并解释了在内核中（如 JOS）处理它的一个方法。用这个论文中的方法去解决这个问题，你不需要一个像论文中那么复杂的方案。
>
>
>

#### Web 服务器

一个最简单的 web 服务器类型是发送一个文件的内容到请求的客户端。我们在 `user/httpd.c` 中提供了一个非常简单的 web 服务器的框架代码。这个框架内码处理入站连接并解析请求头。

>
> **练习 13**、这个 web 服务器中缺失了发送一个文件的内容到客户端的处理代码。通过实现 `send_file` 和 `send_data` 完成这个 web 服务器。
>
>
>

在你完成了这个 web 服务器后，启动这个 web 服务器（`make run-httpd-nox`），使用你喜欢的浏览器去浏览 `http://host:port/index.html` 地址。其中 host 是运行 QEMU 的计算机的名字（如果你在 athena 上运行 QEMU，使用 `hostname.mit.edu`（其中 hostname 是在 athena 上运行 `hostname` 命令的输出，或者如果你在运行 QEMU 的机器上运行 web 浏览器的话，直接使用 `localhost`），而 port 是 web 服务器运行 `make which-ports` 命令报告的端口号。你应该会看到一个由运行在 JOS 中的 HTTP 服务器提供的一个 web 页面。

到目前为止，你的评级测试得分应该是 105 分（满分为 105）。

>
> 小挑战！在 JOS 中添加一个简单的聊天服务器，多个人可以连接到这个服务器上，并且任何用户输入的内容都被发送到其它用户。为实现它，你需要找到一个一次与多个套接字通讯的方法，并且在同一时间能够在同一个套接字上同时实现发送和接收。有多个方法可以达到这个目的。lwIP 为 `recv`（查看 `net/lwip/api/sockets.c` 中的 `lwip_recvfrom`）提供了一个 MSG\_DONTWAIT 标志，以便于你不断地轮询所有打开的套接字。注意，虽然网络服务器的 IPC 支持 `recv` 标志，但是通过普通的 `read` 函数并不能访问它们，因此你需要一个方法来传递这个标志。一个更高效的方法是为每个连接去启动一个或多个环境，并且使用 IPC 去协调它们。而且碰巧的是，对于一个套接字，在结构 Fd 中找到的 lwIP 套接字 ID 是全局的（不是每个环境私有的），因此，比如一个 `fork` 的子环境继承了它的父环境的套接字。或者，一个环境通过构建一个包含了正确套接字 ID 的 Fd 就能够发送到另一个环境的套接字上。
>
>
> **问题 3**、由 JOS 的 web 服务器提供的 web 页面显示了什么？
>
>
>

.

>
> **问题 4**、你做这个实验大约花了多长的时间？
>
>
>

**本实验到此结束了。**一如既往，不要忘了运行 `make grade` 并去写下你的答案和挑战问题的解决方案的描述。在你动手之前，使用 `git status` 和 `git diff` 去检查你的变更，并不要忘了去 `git add answers-lab6.txt`。当你完成之后，使用 `git commit -am 'my solutions to lab 6’` 去提交你的变更，然后 `make handin` 并关注它的动向。

---

via: <https://pdos.csail.mit.edu/6.828/2018/labs/lab6/>

作者：[csail.mit](https://pdos.csail.mit.edu) 选题：[lujun9972](https://github.com/lujun9972) 译者：[qhwdw](https://github.com/qhwdw) 校对：[wxy](https://github.com/wxy)

本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


如何在 Linux 中安装微软的 .NET Core SDK

# 如何在 Linux 中安装微软的 .NET Core SDK

**.NET Core** 是微软提供的免费、跨平台和开源的开发框架，可以构建桌面应用程序、移动端应用程序、网络应用程序、物联网应用程序和游戏应用程序等。如果你是 Windows 平台下的 dotnet 开发人员的话，使用 .NET core 可以很轻松就设置好任何 Linux 和类 Unix 操作系统下的开发环境。本分步操作指南文章解释了如何在 Linux 中安装 .NET Core SDK 以及如何使用 .NET 开发出第一个应用程序。

### Linux 中安装 .NET Core SDK

.NET Core 支持 GNU/Linux、Mac OS 和 Windows 系统，可以在主流的 GNU/Linux 操作系统上安装运行，包括 Debian、Fedora、CentOS、Oracle Linux、RHEL、SUSE/openSUSE 和 Ubuntu 。在撰写这篇教程时，其最新版本为 **2.2**。

**Debian 9** 系统上安装 .NET Core SDK，请按如下步骤进行。

首先，需要注册微软的密钥，接着把 .NET 源仓库地址添加进来，运行的命令如下：

```
$ wget -qO- https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor > microsoft.asc.gpg
$ sudo mv microsoft.asc.gpg /etc/apt/trusted.gpg.d/
$ wget -q https://packages.microsoft.com/config/debian/9/prod.list
$ sudo mv prod.list /etc/apt/sources.list.d/microsoft-prod.list
$ sudo chown root:root /etc/apt/trusted.gpg.d/microsoft.asc.gpg
$ sudo chown root:root /etc/apt/sources.list.d/microsoft-prod.list
```

注册好密钥及添加完仓库源后，就可以安装 .NET SDK 了，命令如下：

```
$ sudo apt-get update
$ sudo apt-get install dotnet-sdk-2.2
```

**Debian 8 系统上安装：**

增加微软密钥，添加 .NET 仓库源：

```
$ wget -qO- https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor > microsoft.asc.gpg
$ sudo mv microsoft.asc.gpg /etc/apt/trusted.gpg.d/
$ wget -q https://packages.microsoft.com/config/debian/8/prod.list
$ sudo mv prod.list /etc/apt/sources.list.d/microsoft-prod.list
$ sudo chown root:root /etc/apt/trusted.gpg.d/microsoft.asc.gpg
$ sudo chown root:root /etc/apt/sources.list.d/microsoft-prod.list
```

安装 .NET SDK：

```
$ sudo apt-get update
$ sudo apt-get install dotnet-sdk-2.2
```

**Fedora 28 系统上安装：**

增加微软密钥，添加 .NET 仓库源：

```
$ sudo rpm --import https://packages.microsoft.com/keys/microsoft.asc
$ wget -q https://packages.microsoft.com/config/fedora/27/prod.repo
$ sudo mv prod.repo /etc/yum.repos.d/microsoft-prod.repo
$ sudo chown root:root /etc/yum.repos.d/microsoft-prod.repo
```

现在, 可以安装 .NET SDK 了：

```
$ sudo dnf update
$ sudo dnf install dotnet-sdk-2.2
```

**Fedora 27 系统下：**

增加微软密钥，添加 .NET 仓库源，命令如下：

```
$ sudo rpm --import https://packages.microsoft.com/keys/microsoft.asc
$ wget -q https://packages.microsoft.com/config/fedora/27/prod.repo
$ sudo mv prod.repo /etc/yum.repos.d/microsoft-prod.repo
$ sudo chown root:root /etc/yum.repos.d/microsoft-prod.repo
```

接着安装 .NET SDK ，命令如下：

```
$ sudo dnf update
$ sudo dnf install dotnet-sdk-2.2
```

**CentOS/Oracle 版本的 Linux 系统上：**

增加微软密钥，添加 .NET 仓库源，使其可用：

```
$ sudo rpm -Uvh https://packages.microsoft.com/config/rhel/7/packages-microsoft-prod.rpm
```

更新源仓库，安装 .NET SDK：

```
$ sudo yum update
$ sudo yum install dotnet-sdk-2.2
```

**openSUSE Leap 版本的系统上：**

添加密钥，使仓库源可用，安装必需的依赖包，其命令如下：

```
$ sudo zypper install libicu
$ sudo rpm --import https://packages.microsoft.com/keys/microsoft.asc
$ wget -q https://packages.microsoft.com/config/opensuse/42.2/prod.repo
$ sudo mv prod.repo /etc/zypp/repos.d/microsoft-prod.repo
$ sudo chown root:root /etc/zypp/repos.d/microsoft-prod.repo
```

更新源仓库，安装 .NET SDK，命令如下：

```
$ sudo zypper update
$ sudo zypper install dotnet-sdk-2.2
```

**Ubuntu 18.04 LTS 版本的系统上：**

注册微软的密钥和 .NET Core 仓库源，命令如下：

```
$ wget -q https://packages.microsoft.com/config/ubuntu/18.04/packages-microsoft-prod.deb
$ sudo dpkg -i packages-microsoft-prod.deb
```

使 Universe 仓库可用：

```
$ sudo add-apt-repository universe
```

然后，安装 .NET Core SDK ，命令如下：

```
$ sudo apt-get install apt-transport-https
$sudo apt-get update
$ sudo apt-get install dotnet-sdk-2.2
```

**Ubuntu 16.04 LTS 版本的系统上：**

注册微软的密钥和 .NET Core 仓库源，命令如下：

```
$ wget -q https://packages.microsoft.com/config/ubuntu/16.04/packages-microsoft-prod.deb
$ sudo dpkg -i packages-microsoft-prod.deb
```

然后安装 .NET core SDK：

```
$ sudo apt-get install apt-transport-https
$ sudo apt-get update
$ sudo apt-get install dotnet-sdk-2.2
```

### 创建你的第一个应用程序

我们已经成功的在 Linux 机器中安装了 .NET Core SDK。是时候使用 dotnet 创建第一个应用程序了。

接下来的目的，我们会创建一个名为 ostechnixApp 的应用程序。为此，可以简单的运行如下命令：

```
$ dotnet new console -o ostechnixApp
```

**示例输出：**

```
Welcome to .NET Core!
---------------------
Learn more about .NET Core: https://aka.ms/dotnet-docs
Use 'dotnet --help' to see available commands or visit: https://aka.ms/dotnet-cli-docs

Telemetry
---------
The .NET Core tools collect usage data in order to help us improve your experience. The data is anonymous and doesn't include command-line arguments. The data is collected by Microsoft and shared with the community. You can opt-out of telemetry by setting the DOTNET_CLI_TELEMETRY_OPTOUT environment variable to '1' or 'true' using your favorite shell.

Read more about .NET Core CLI Tools telemetry: https://aka.ms/dotnet-cli-telemetry

ASP.NET Core
------------
Successfully installed the ASP.NET Core HTTPS Development Certificate.
To trust the certificate run 'dotnet dev-certs https --trust' (Windows and macOS only). For establishing trust on other platforms refer to the platform specific documentation.
For more information on configuring HTTPS see https://go.microsoft.com/fwlink/?linkid=848054.
Getting ready...
The template "Console Application" was created successfully.

Processing post-creation actions...
Running 'dotnet restore' on ostechnixApp/ostechnixApp.csproj...
Restoring packages for /home/sk/ostechnixApp/ostechnixApp.csproj...
Generating MSBuild file /home/sk/ostechnixApp/obj/ostechnixApp.csproj.nuget.g.props.
Generating MSBuild file /home/sk/ostechnixApp/obj/ostechnixApp.csproj.nuget.g.targets.
Restore completed in 894.27 ms for /home/sk/ostechnixApp/ostechnixApp.csproj.

Restore succeeded.
```

正如上面的输出所示的，.NET 已经为我们创建一个控制台类型的应用程序。`-o` 参数创建了一个名为 “ostechnixApp” 的目录，其包含有存储此应用程序数据所必需的文件。

让我们切换到 ostechnixApp 目录，看看里面有些什么。

```
$ cd ostechnixApp/
$ ls
obj ostechnixApp.csproj Program.cs
```

可以看到有两个名为 `ostechnixApp.csproj` 和 `Program.cs` 的文件，以及一个名为 `obj` 的目录。默认情况下, `Program.cs` 文件包含有可以在控制台中运行的 “Hello World” 程序代码。可以看看此代码：

```
$ cat Program.cs
using System;

namespace ostechnixApp
{
     class Program
     {
       static void Main(string[] args)
       {
         Console.WriteLine("Hello World!");
       }
   }
}
```

要运行此应用程序，可以简单的使用如下命令：

```
$ dotnet run
Hello World!
```

很简单，对吧？是的，就是如此简单。现在你可以在 `Program.cs` 这文件中写上自己的代码，然后像上面所示的执行。

或者，你可以创建一个新的目录，如例子所示的 `mycode` 目录，命令如下：

```
$ mkdir ~/.mycode
$ cd mycode/
```

然后运行如下命令，使其成为你的新开发环境目录：

```
$ dotnet new console
```

示例输出：

```
The template "Console Application" was created successfully.

Processing post-creation actions...
Running 'dotnet restore' on /home/sk/mycode/mycode.csproj...
Restoring packages for /home/sk/mycode/mycode.csproj...
Generating MSBuild file /home/sk/mycode/obj/mycode.csproj.nuget.g.props.
Generating MSBuild file /home/sk/mycode/obj/mycode.csproj.nuget.g.targets.
Restore completed in 331.87 ms for /home/sk/mycode/mycode.csproj.

Restore succeeded.
```

上的命令会创建两个名叫 `mycode.csproj` 和 `Program.cs` 的文件及一个名为 `obj` 的目录。用你喜欢的编辑器打开 `Program.cs` 文件, 删除或修改原来的 “hello world” 代码段，然后编写自己的代码。

写完代码，保存，关闭 `Program.cs` 文件，然后运行此应用程序，命令如下：

```
$ dotnet run
```

想要查看安装的 .NET core SDK 的版本的话，可以简单的运行：

```
$ dotnet --version
2.2.101
```

要获得帮助，请运行：

```
$ dotnet --help
```

### 使用微软的 Visual Studio Code 编辑器

要编写代码，你可以任选自己喜欢的编辑器。同时微软自己也有一款支持 .NET 的编辑器，其名为 “Microsoft Visual Studio Code”。它是一款开源、轻量级、功能强大的源代码编辑器。其内置了对 JavaScript、TypeScript 和 Node.js 的支持，并为其它语言（如 C++、C#、Python、PHP、Go）和运行时态（如 .NET 和 Unity）提供了丰富的扩展，已经形成一个完整的生态系统。它是一款跨平台的代码编辑器，所以在微软的 Windows 系统、GNU/Linux 系统和 Mac OS X 系统都可以使用。如果对其感兴趣，就可以使用。

想了解如何在 Linux 上安装和使用，请参阅以下指南。

[Linux 中安装 Microsoft Visual Studio Code](https://www.ostechnix.com/install-microsoft-visual-studio-code-linux/)

关于 Visual Studio Code editor 中 .NET Core 和 .NET Core SDK 工具的使用，[此网页](https://docs.microsoft.com/en-us/dotnet/core/tutorials/index)有一些基础的教程。想了解更多就去看看吧。

### Telemetry

默认情况下，.NET core SDK 会采集用户使用情况数据，此功能被称为 Telemetry。采集数据是匿名的，并根据[知识共享署名许可](https://creativecommons.org/licenses/by/4.0/)分享给其开发团队和社区。因此 .NET 团队会知道这些工具的使用状况，然后根据统计做出决策，改进产品。如果你不想分享自己的使用信息的话，可以使用顺手的 shell 工具把名为 `DOTNET_CLI_TELEMETRY_OPTOUT` 的环境变量参数设置为 `1` 或 `true`，这样就简单的关闭此功能了。

就这样。你已经知道如何在各 Linux 平台上安装 .NET Core SDK 以及知道如何创建基本的应用程序了。想了解更多 .NET 使用知识的话，请参阅此文章末尾给出的链接。

会爆出更多干货的。敬请关注！

祝贺下!

### 资源

* [.NET Core](https://dotnet.microsoft.com/)

---

via: <https://www.ostechnix.com/how-to-install-microsoft-net-core-sdk-on-linux/>

作者：[SK](https://www.ostechnix.com/author/sk/) 选题：[lujun9972](https://github.com/lujun9972) 译者：[runningwater](https://github.com/runningwater) 校对：[wxy](https://github.com/wxy)

本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


开始使用 Wekan 吧，一款开源看板软件

# 开始使用 Wekan 吧，一款开源看板软件

>
> 这是开源工具类软件推荐的第二期，本文将让你在 2019 年更具生产力。来，让我们一起看看 Wekan 吧。
>
>
>

每年年初，人们似乎都在想方设法地让自己更具生产力。对新年目标、期待，当然还有“新年新气象”这样的口号等等都促人上进。可大部分生产力软件的推荐都严重偏向闭源的专有软件，但事实上并不用这样。

这是我挑选的 19 款帮助你在 2019 年提升生产力的开源工具中的第 2 个。

### Wekan

[看板](https://en.wikipedia.org/wiki/Kanban)是当今敏捷开发流程中的重要组成部分。我们中的很多人使用它同时管理自己的工作和生活。有些人在用 [Trello](https://www.trello.com) 这样的 APP 来跟踪他们的项目，例如哪些事务正在处理，哪些事务已经完成。

但这些 APP 通常需要连接到一个工作账户或者商业服务中。而 [Wekan](https://wekan.github.io/) 作为一款开源看板工具，你可以让它完全在本地运行，或者使用你自己选择的服务运行它。其他的看板 APP 提供的功能在 Wekan 里几乎都有，例如创建看板、列表、泳道、卡片，在列表间拖放，给指定的用户安排任务，给卡片添加标签等等，基本上你对一款现代看板软件的功能需求它都能提供。

Wekan 的独到之处在于它的内置规则。虽然其他的看板软件支持<ruby> 邮件更新 <rt>  emailing updates </rt></ruby>，但 Wekan 允许用户自行设定触发器，其触发条件可以是卡片变动、清单变动或标签变动等等。

当触发条件满足时， Wekan 可以自动执行如移动卡片、更新标签、添加清单或者发送邮件等操作。

Wekan 的本地搭建可以直接使用 snap 。如果你的桌面环境支持 [Snapcraft](https://snapcraft.io/) 构建的应用，那么只需要一条命令就能安装 Wekan ：

```
sudo snap install wekan
```

此外 Wekan 还支持 Docker 安装，这使它在大部分服务器环境和桌面环境下的搭建变得相当容易。

最后，如果你想寻找一款能自建又好用的看板软件，你已经遇上了 Wekan 。

---

via: <https://opensource.com/article/19/1/productivity-tool-wekan>

作者：[Kevin Sonney](https://opensource.com/users/ksonney "Kevin Sonney") 选题：[lujun9972](https://github.com/lujun9972) 译者：[wwhio](https://github.com/wwhio) 校对：[wxy](https://github.com/wxy)

本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


设计微服务架构前应该了解的 5 项指导原则

Summary: 顶级 CTO 基于五个简单的原则为精心设计的微服务提供建议。

顶级 CTO 基于五个简单的原则为精心设计的微服务提供建议。

# 设计微服务架构前应该了解的 5 项指导原则

>
> 顶级 CTO 基于五个简单的原则为精心设计的微服务提供建议。
>
>
>

<img src="content_image/album_201901_19_002658s8xpfp7qzk1j3jwc.jpg" alt="" title="">

对于从微服务开始的团队来说，最大的挑战之一就是坚持<ruby> 金发女孩原则 <rt>  The Goldilocks principle </rt></ruby>（该典故来自于童话《金发姑娘和三只熊》）：不要太大，不要太小，不能太紧密耦合。之所以是挑战的部分原因是会对究竟什么是设计良好的微服务感到疑惑。

数十位 CTO 通过采访分享了他们的经验，这些对话说明了设计良好的微服务的五个特点。本文将帮助指导团队设计微服务。（有关详细信息，请查看即将出版的书籍 [Microservices for Startups](https://buttercms.com/books/microservices-for-startups/)，LCTT 译注：已可免费下载完整的电子版）。本文将简要介绍微服务的边界和主观的 “规则”，以避免在深入了解五个特征之前就开始指导您的微服务设计。

### 微服务边界

[使用微服务开发新系统的核心优势](https://buttercms.com/books/microservices-for-startups/should-you-always-start-with-a-monolith)之一是该体系结构允许开发人员独立构建和修改各个组件，但在最大限度地减少每个 API 之间的回调数量方面可能会出现问题。根据 [SparkPost](https://www.sparkpost.com/) 工程副总裁 Chris McFadden 所说，解决方案是应用适当的服务边界。

关于边界，与有时难以理解和抽象的领域驱动设计（DDD，一种微服务框架）形成鲜明对比，本文重点介绍了和我们行业的一些顶级 CTO 一同建立的明确定义的微服务边界的实用原则。

### 避免主观的 “规则”

如果您阅读了足够多的关于设计和创建微服务的建议，您一定会遇到下面的一些 “规则”。 尽管将它们用作创建微服务的指南很有吸引力，但加入这些主观规则并不是思考确定微服务的边界的原则性方式。

#### “微服务应该有 X 行代码”

让我们直说：微服务中有多少行代码没有限制。微服务不会因为您写了几行额外的代码而突然变成一个独石应用。关键是要确保服务中的代码具有很高的内聚性（稍后将对此进行更多介绍）。

#### “将每个功能转换为微服务”

如果函数基于三个输入值计算某些内容并返回结果，它是否是微服务的理想候选项？它是否应该是单独可部署应用程序？这确实取决于该函数是什么以及它是如何服务于整个系统。将每个函数转换为微服务在您的情景中可能根本没有意义。

其他主观规则包括不考虑整个情景的规则，例如团队的经验、DevOps 能力、服务正在执行的操作以及数据的可用性需求。

### 精心设计的服务的 5 个特点

如果您读过关于微服务的文章，您无疑会遇到有关设计良好的服务的建议。简单地说，高内聚和低耦合。如果您不熟悉这些概念，有[许多](https://thebojan.ninja/2015/04/08/high-cohesion-loose-coupling/)[文章](https://thebojan.ninja/2015/04/08/high-cohesion-loose-coupling/)关于这些概念的文章。虽然它们提供了合理的建议，但这些概念是相当抽象的。基于与经验丰富的 CTO 们的对话，下面是在创建设计良好的微服务时需要牢记的关键特征。

#### #1：不与其他服务共享数据库表

在 SparkPost 的早期，Chris McFadden 和他的团队必须解决每个 SaaS 业务需要面对的问题：它们需要提供基本服务，如身份验证、帐户管理和计费。

为了解决这个问题，他们创建了两个微服务：用户 API 和帐户 API。用户 API 将处理用户帐户、API 密钥和身份验证，而帐户 API 将处理所有与计费相关的逻辑。这是一个非常合乎逻辑的分离 —— 但没过多久，他们发现了一个问题。

McFadden 解释说，“我们有一个名为‘用户 API’的服务，还有一个名为‘帐户 API’的服务。问题是，他们之间实际上有几个来回的调用。因此，您会在帐户服务中执行一些操作，然后调用并终止于用户服务，反之亦然”

这两个服务的耦合太紧密了。

在设计微服务时，如果您有多个服务引用同一个表，则它是一个危险的信号，因为这可能意味着您的数据库是耦合的源头。

这确实是关于服务与数据的关系，这正是 [Swiftype SRE，Elastic](https://www.elastic.co/solutions/site-search) 的负责人 Oleksiy Kovrin 告诉我。他说，“我们在开发新服务时使用的主要基本原则之一是，它们不应跨越数据库边界。每个服务都应依赖于自己的一组底层数据存储。这使我们能够集中访问控制、审计日志记录、缓存逻辑等。”

Kovrin 接着解释说，如果数据库表的某个子集“与数据集的其余部分没有或很少连接，则这是一个强烈的信号，表明该组件可以被隔离到单独的 API 或单独的服务中”。

[Lead Honestly](https://leadhonestly.com/) 的联合创始人 Darby Frey 与此的观点相呼应：“每个服务都应该有自己的表并且永远不应该共享数据库表。”

#### #2：数据库表数量最小化

微服务的理想尺寸应该足够小，但不能太小。每个服务的数据库表的数量也是如此。

[Scaylr](https://www.scalyr.com/) 的工程主管 Steven Czerwinski 在接受采访时解释说 Scaylr 的最佳选择是“一个或两个服务的数据库表。”

SparkPost 的 Chris McFadden 表示同意：“我们有一个 suppression 微服务，它处理、跟踪数以百万计和数十亿围绕 suppression 的条目，但它们都非常专注于围绕 suppression，所以实际上只有一个或两个表。其他服务也是如此，比如 webhooks。

#### #3：考虑有状态和无状态

在设计微服务时，您需要问问自己它是否需要访问数据库，或者它是否是处理 TB 级数据 (如电子邮件或日志) 的无状态服务。

[Algolia](https://www.algolia.com/) 的 CTO Julien Lemoine 解释说：“我们通过定义服务的输入和输出来定义服务的边界。有时服务是网络 API，但它也可能是使用文件并在数据库中生成记录的进程 (这就是我们的日志处理服务)。”

事先要明确是否有状态，这将引导一个更好的服务设计。

#### #4：考虑数据可用性需求

在设计微服务时，请记住哪些服务将依赖于此新服务，以及在该数据不可用时的整个系统的影响。考虑到这一点，您可以正确地设计此服务的数据备份和恢复系统。

Steven Czerwinski 提到，在 Scaylr 由于关键客户行空间映射数据的重要性，它将以不同的方式复制和分离。

相比之下，他补充说，“每个分片信息，都在自己的小分区里。如果部分客户群体因为没有可用日志而停止服务那很糟糕，但它只影响 5% 的客户，而不是100% 的客户。”

#### #5：单一的真实来源

设计服务，使其成为系统中某些内容的唯一真实来源。

例如，当您从电子商务网站订购内容时，则会生成订单 ID，其他服务可以使用此订单 ID 来查询订单服务，以获取有关订单的完整信息。使用 [发布/订阅模式](https://en.wikipedia.org/wiki/Publish%E2%80%93subscribe_pattern)，在服务之间传递的数据应该是订单 ID ，而不是订单本身的属性信息。只有订单服务具有订单的完整信息，并且是给定订单信息的唯一真实来源。

### 大型团队的注意事项

考虑到上面列出的五个注意事项，较大的团队应了解其组织结构对微服务边界的影响。

对于较大的组织，整个团队可以专门拥有服务，在确定服务边界时，组织性就会发挥作用。还有两个需要考虑的因素：**独立的发布计划**和**不同的正常运行时间**的重要性。

[Cloud66.](https://www.cloud66.com/) 的 CEO Khash Sajadi 说：“我们所看到的微服务最成功的实现要么基于类似领域驱动设计这样的软件设计原则 (如面向服务的体系结构)，要么基于反映组织方法的设计原则。”

“所以 (对于) 支付团队” Sajadi 说，“他们有支付服务或信用卡验证服务，这就是他们向外界提供的服务。所以这不一定是关于软件的。这主要是关于为外界提供更多服务的业务单位。”

### 双披萨原理

Amazon 是一个拥有多个团队的大型组织的完美示例。正如在一篇发表于 [API Evangelist](https://apievangelist.com/2012/01/12/the-secret-to-amazons-success-internal-apis/) 的文章中所提到的，Jeff Bezos 向所有员工发布一项要求，告知他们公司内的每个团队都必须通过 API 进行沟通。任何不这样做的人都会被解雇。

这样，所有数据和功能都通过该接口公开。Bezos 还设法让每个团队解耦，定义他们的资源，并通过 API 提供。Amazon 正在从头建立一个系统。这使得公司内的每一支团队都能成为彼此的合作伙伴。

我与 [Iron.io](https://www.iron.io/) 的 CTO Travis Reeder 谈到了 Bezos 的内部倡议。

“Jeff Bezos 规定所有团队都必须构建 API 才能与其他团队进行沟通，” Reeder 说。“他也是提出‘双披萨’规则的人：一支团队不应该比两个比萨饼能养活的大。”

“我认为这里也可以适用同样的方法：无论一个小型团队是否能够开发、管理和富有成效。如果它开始变得笨重或开始变慢，它可能变得太大了。” Reeder 告诉我。

### 最后注意事项: 您的服务是否具有合适的大小和正确的定义？

在微服务系统的测试和实施阶段，有一些指标需要记住。

#### 指标 #1: 服务之间是否存在过度依赖？

如果两个服务不断地相互回调，那么这就是强烈的耦合信号，也是它们可能更好地合并为一个服务的信号。

回到 Chris McFadden 的例子， 他有两个 API 服务，帐户服务和用户服务不断地相互通信， McFadden 提出了一个合并服务的想法，并决定将其称为 “账户用户 API”。事实证明，这是一项富有成效的战略。

“我们开始做的是消除这些内部 API 之间调用的链接，” McFadden 告诉我。“这有助于简化代码。”

#### 指标 #2: 设置服务的开销是否超过了服务独立的好处？

Darby Frey 解释说，“每个应用都需要将其日志聚合到某个位置，并需要进行监视。你需要设置它的警报。你需要有标准的操作程序，和在出现问题时的操作手册。您必须管理 SSH 对它的访问。只是为了让一个应用运行起来，就有大量的基础性工作必须存在。”

### 关键要点

设计微服务往往会让人感觉更像是一门艺术，而不是一门科学。对工程师来说，这可能并不顺利。有很多一般性的建议，但有时可能有点太抽象了。让我们回顾一下在设计下一组微服务时要注意的五个具体特征：

1. 不与其他服务共享数据库表
2. 数据库表数量最小化
3. 考虑有状态和无状态
4. 考虑数据可用性需求
5. 单一的真实来源

下次设计一组微服务并确定服务边界时，回顾这些原则应该会使任务变得更容易。

---

via: <https://opensource.com/article/18/4/guide-design-microservices>

作者：[Jake Lumetta](https://opensource.com/users/jakelumetta) 选题：[lujun9972](https://github.com/lujun9972) 译者：[lixinyuxx](https://github.com/lixinyuxx) 校对：[wxy](https://github.com/wxy)

本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


Ubuntu PPA 使用指南

写的非常好，赞一个。

内容全面

is nice

very good

非常不错~！！！

很赞的文章，爱了爱了，原来学习Linux这个网站比CSDN好用啊

写得非常好

太感谢您的讲解了

如果说这篇文章翻译腔能再小一点儿，那就完美了

# Ubuntu PPA 使用指南

>
> 一篇涵盖了在 Ubuntu 和其他 Linux 发行版中使用 PPA 的几乎所有问题的深入的文章。
>
>
>

如果你一直在使用 Ubuntu 或基于 Ubuntu 的其他 Linux 发行版，例如 Linux Mint、Linux Lite、Zorin OS 等，你可能会遇到以下三种神奇的命令：

```
sudo add-apt-repository ppa:dr-akulavich/lighttable
sudo apt-get update
sudo apt-get install lighttable-installer
```

许多网站推荐使用类似于以上几行的形式 [在 Ubuntu 中安装应用程序](https://itsfoss.com/remove-install-software-ubuntu/)。这就是所谓的使用 PPA 安装应用程序。

但什么是 PPA？为什么要用它？使用 PPA 安全吗？如何正确使用 PPA？如何删除 PPA？

我将在这个详细的指南中回答上述所有问题。即使你已经了解了一些关于 PPA 的事情，我相信这篇文章仍然会让你了解这方面的更多知识。

请注意我正在使用 Ubuntu 撰写本文。因此，我几乎在各个地方都使用了 Ubuntu 这个术语，但文中的说明和步骤也适用于其他基于 Debian/Ubuntu 的发行版。

### 什么是 PPA？为什么要使用 PPA？

PPA 表示<ruby> 个人软件包存档 <rt>  Personal Package Archive </rt></ruby>。

这样说容易理解吗？可能不是很容易。

在了解 PPA 之前，你应该了解 Linux 中软件仓库的概念。关于软件仓库，在这里我不会详述。

#### 软件仓库和包管理的概念

软件仓库是一组文件，其中包含各种软件及其版本的信息，以及校验和等其他一些详细信息。每个版本的 Ubuntu 都有自己的四个官方软件仓库：

* Main - Canonical 支持的自由开源软件。
* Universe - 社区维护的自由开源软件。
* Restricted - 设备的专有驱动程序。
* Multiverse - 受版权或法律问题限制的软件。

你可以在 [这里](http://archive.ubuntu.com/ubuntu/dists/) 看到所有版本的 Ubuntu 的软件仓库。你可以浏览并转到各个仓库。例如，可以在 [这里](http://archive.ubuntu.com/ubuntu/dists/xenial/main/) 找到 Ubuntu 16.04 的主存储库（Main）。

所以，PPA 基本上是一个包含软件信息的网址。那你的系统又是如何知道这些仓库的位置的呢？

这些信息存储在 `/etc/apt` 目录中的 `sources.list` 文件中。如果查看此文件的内容，你就会看到里面有软件仓库的网址。`#` 开头的行将被忽略。

这样的话，当你运行 `sudo apt update` 命令时，你的系统将使用 [APT 工具](https://wiki.debian.org/Apt) 来检查软件仓库并将软件及其版本信息存储在缓存中。当你使用 `sudo apt install package_name` 命令时，它通过该信息从实际存储软件的网址获取该软件包。

如果软件仓库中没有关于某个包的信息，你将看到如下错误：

```
E: Unable to locate package
```

此时，建议阅读我的 [apt 命令使用指南](https://itsfoss.com/apt-command-guide/) 一文，这将帮你更好地理解 `apt`、`update` 等命令。

以上是关于软件仓库的内容。但什么是 PPA？PPA 和软件仓库又有什么关联呢？

#### 为什么要用 PPA？

如你所见，Ubuntu 对系统中的软件进行管理，更重要的是控制你在系统上获得哪个版本的软件。但想象一下开发人员发布了软件的新版本的情况。

Ubuntu 不会立即提供该新版本的软件。需要一个步骤来检查此新版本的软件是否与系统兼容，从而可以确保系统的稳定性。

但这也意味着它需要经过几周才能在 Ubuntu 上可用，在某些情况下，这可能需要几个月的时间。不是每个人都想等待那么长时间才能获得他们最喜欢的软件的新版本。

类似地，假设有人开发了一款软件，并希望 Ubuntu 将该软件包含在官方软件仓库中。在 Ubuntu 做出决定并将其包含在官方存软件仓库之前，还需要几个月的时间。

另一种情况是在 beta 测试阶段。即使官方软件仓库中提供了稳定版本的软件，软件开发人员也可能希望某些终端用户测试他们即将发布的版本。他们是如何使终端用户对即将发布的版本进行 beta 测试的呢？

通过 PPA！

### 如何使用 PPA？PPA 是怎样工作的？

正如我已经告诉过你的那样，[PPA](https://launchpad.net/ubuntu/+ppas) 代表<ruby> 个人软件包存档 <rt>  Personal Package Archive </rt></ruby>。在这里注意 “个人” 这个词，它暗示了这是开发人员独有的东西，并没有得到分发的正式许可。

Ubuntu 提供了一个名为 Launchpad 的平台，使软件开发人员能够创建自己的软件仓库。终端用户，也就是你，可以将 PPA 仓库添加到 `sources.list` 文件中，当你更新系统时，你的系统会知道这个新软件的可用性，然后你可以使用标准的 `sudo apt install` 命令安装它。

```
sudo add-apt-repository ppa:dr-akulavich/lighttable
sudo apt-get update
sudo apt-get install lighttable-installer
```

概括一下上面三个命令：

* `sudo add-apt-repository <PPA_info>` <- 此命令将 PPA 仓库添加到列表中。
* `sudo apt-get update` <- 此命令更新可以在当前系统上安装的软件包列表。
* `sudo apt-get install <package_in_PPA>` <- 此命令安装软件包。

你会发现使用 `sudo apt update` 命令非常重要，否则你的系统将无法知道新软件包何时可用。

现在让我们更详细地看一下第一个命令。

```
sudo add-apt-repository ppa:dr-akulavich/lighttable
```

你会注意到此命令没有软件仓库的 URL。这是因为该工具被设计成将 URL 信息抽象之后再展示给你。

小小注意一下：如果你添加的是 `ppa:dr-akulavich/lighttable`，你会得到 Light Table。但是如果你添加 `ppa:dr-akulavich`，你将得到 “上层软件仓库” 中的所有仓库或软件包。它是按层级划分的。

基本上，当您使用 `add-apt-repository` 添加 PPA 时，它将执行与手动运行这些命令相同的操作：

```
deb http://ppa.launchpad.net/dr-akulavich/lighttable/ubuntu YOUR_UBUNTU_VERSION_HERE main
deb-src http://ppa.launchpad.net/dr-akulavich/lighttable/ubuntu YOUR_UBUNTU_VERSION_HERE main
```

以上两行是将任何软件仓库添加到你系统的 `sources.list` 文件的传统方法。但 PPA 会自动为你完成这些工作，无需考虑确切的软件仓库 URL 和操作系统版本。

此处不那么重要的一点是，当你使用 PPA 时，它不会更改原始的 `sources.list` 文件。相反，它在 `/etc/apt/sources.d` 目录中创建了两个文件，一个 `.list` 文件和一个带有 `.save` 后缀的备份文件。

*PPA 创建了单独的 `sources.list` 文件*

带有后缀 `.list` 的文件含有添加软件仓库的信息的命令。

*一个 PPA 的 `source.list` 文件的内容*

这是一种安全措施，可以确保添加的 PPA 不会和原始的 `sources.list` 文件弄混，它还有助于移除 PPA。

#### 为什么使用 PPA？为何不用 DEB 包

你可能会问为什么要使用 PPA，PPA 需要通过命令行使用，而不是每个人都喜欢用命令行。为什么不直接分发可以图形方式安装的 DEB 包呢？

答案在于更新的过程。如果使用 DEB 包安装软件，将无法保证在运行 `sudo apt update` 和 `sudo apt upgrade` 命令时，已安装的软件会被更新为较新的版本。

这是因为 `apt` 的升级过程依赖于 `sources.list` 文件。如果文件中没有相应的软件条目，则不会通过标准软件更新程序获得更新。

那么这是否意味着使用 DEB 安装的软件永远不会得到更新？不是的。这取决于 DEB 包的创建方式。

一些开发人员会自动在 `sources.list` 中添加一个条目，这样软件就可以像普通软件一样更新。谷歌 Chrome 浏览器就是这样一个例子。

某些软件会在运行时通知你有新版本可用。你必须下载新的 DEB 包并再次运行，来将当前软件更新为较新版本。Oracle Virtual Box 就是这样一个例子。

对于其余的 DEB 软件包，你必须手动查找更新，这很不方便，尤其是在你的软件面向 Beta 测试者时，你需要频繁的添加很多更新。这正是 PPA 要解决的问题。

#### 官方 PPA vs 非官方 PPA

你或许听过官方 PPA 或非官方 PPA 这个词，二者有什么不同呢？

开发人员为他们的软件创建的 PPA 称为官方 PPA。很明显，这是因为它来自项目开发者。

但有时，个人会创建由其他开发人员所创建的项目的 PPA。

为什么会有人这样做？ 因为许多开发人员只提供软件的源代码，而且你也知道 [在 Linux 中从源代码安装软件](/article-9172-1.html) 是一件痛苦的事情，并不是每个人都可以或者会这样做。

这就是志愿者自己从这些源代码创建 PPA 以便其他用户可以轻松安装软件的原因。毕竟，使用这 3 行命令比从源代码安装要容易得多。

#### 确保你的 Linux 发行版本可以使用 PPA

当在 Ubuntu 或任何其他基于 Debian 的发行版中使用 PPA 时，你应该记住一些事情。

并非每个 PPA 都适用于你的特定版本。你应该知道正在使用 [哪个版本的 Ubuntu](https://itsfoss.com/how-to-know-ubuntu-unity-version/)。版本的开发代号很重要，因为当你访问某个 PPA 的页面时，你可以看到该 PPA 都支持哪些版本的 Ubuntu。

对于其他基于 Ubuntu 的发行版，你可以查看 `/etc/os-release` 的内容来 [找出 Ubuntu 版本](https://itsfoss.com/how-to-know-ubuntu-unity-version/) 的信息。

*检查 PPA 是否适用于你的 Ubuntu 版本*

如何知道 PPA 的网址呢？只需在网上搜索 PPA 的名称，如 `ppa:dr-akulavich/lighttable`，第一个搜索结果来自 [Launchpad](https://launchpad.net/)，这是托管 PPA 的官方平台。你也可以转到 Launchpad 并直接在那里搜索所需的 PPA。

如果不验证是否适用当前的版本就添加 PPA，当尝试安装不适用于你的系统版本的软件时，可能会看到类似下面的错误。

```
E: Unable to locate package
```

更糟糕的是，因为它已经添加到你的 `source.list` 中，每次运行软件更新程序时，你都会看到 “[无法下载软件仓库信息](https://itsfoss.com/failed-to-download-repository-information-ubuntu-13-04/)” 的错误。

如果你在终端中运行 `sudo apt update`，错误提示将包含导致此问题的仓库的更多详细信息。你可以在 `sudo apt update` 的输出内容结尾看到类似的内容：

```
W: Failed to fetch http://ppa.launchpad.net/venerix/pkg/ubuntu/dists/raring/main/binary-i386/Packages  404  Not Found
E: Some index files failed to download. They have been ignored, or old ones used instead.
```

上面的错误提示说的很明白，是因为系统找不到当前版本对应的仓库。还记得我们之前看到的仓库结构吗？APT 将尝试在 `http://ppa.launchpad.net/<PPA_NAME>/ubuntu/dists/<Ubuntu_Version>` 中寻找软件信息。

如果特定版本的 PPA 不可用，它将永远无法打开 URL，你会看到著名的 404 错误。

#### 为什么 PPA 不适用于所有 Ubuntu 发行版？

这是因为 PPA 的作者必须编译软件并在特定版本上创建 PPA。考虑到每六个月发布一个新的 Ubuntu 版本，为每个版本的 Ubuntu 更新 PPA 是一项繁琐的任务，并非所有开发人员都有时间这样做。

#### 如果 PPA 不适用于你的系统版本，该如何安装应用程序？

尽管 PPA 不适用于你的 Ubuntu 版本，你仍然可以下载 DEB 文件并安装应用程序。

比如说，你访问 Light Table 的 PPA 页面，使用刚刚学到的有关 PPA 的知识，你会发现 PPA 不适用于你的特定 Ubuntu 版本。

你可以点击 “查看软件包详细信息”。

在这里，你可以单击软件包以显示更多详细信息，还可以在此处找到包的源代码和 DEB 文件。

我建议 [使用 Gdebi 安装这些 DEB 文件](https://itsfoss.com/gdebi-default-ubuntu-software-center/) 而不是通过软件中心，因为 Gdebi 在处理依赖项方面要好得多。

请注意，以这种方式安装的软件包可能无法获得任何将来的更新。

我认为你已经阅读了足够多的关于添加 PPA 的内容，那么如何删除 PPA 及其安装的软件呢？

### 如何删除 PPA？

我过去曾写过 [删除 PPA](https://itsfoss.com/how-to-remove-or-delete-ppas-quick-tip/) 的教程，这里写的也是同样的方法。

我建议在删除 PPA 之前删除从 PPA 安装的软件。如果只是删除 PPA，则已安装的软件仍保留在系统中，但不会获得任何更新。这不是你想要的，不是吗？

那么，问题来了，如何知道是哪个 PPA 安装了哪个应用程序？

#### 查找 PPA 安装的软件包并将其移除

Ubuntu 软件中心无法移除 PPA 安装的软件包，你必须使用具有更多高级功能的 Synaptic 包管理器。

可以从软件中心安装 Synaptic 或使用以下命令进行安装：

```
sudo apt install synaptic
```

安装后，启动 Synaptic 包管理器并选择 “Origin”。你会看到添加到系统的各种软件仓库。PPA 条目将以前缀 PPA 进行标识，单击以查看 PPA 可用的包。已安装的软件前面会有恰当的符号进行标识。

*查找通过 PPA 安装的软件包*

找到包后，你可以从 Synaptic 删除它们。此外，也始终可以选择使用命令行进行移除：

```
sudo apt remove package_name
```

删除 PPA 安装的软件包后，你可以继续从 `sources.list` 中删除PPA。

#### 以图形界面的方式删除 PPA

在设置中打开 “软件和更新”，然后点击 “其他软件” 选项卡。查找要删除的 PPA：

此处你可以进项两项操作，可以取消选择 PPA 或选择 “删除” 选项。

区别在于，当你取消选择 PPA 条目时，系统将在 `/etc/apt/sources.list.d` 中的`ppa_name.list` 文件中注释掉仓库条目；但如果选择 “删除” 选项，将会删除 `/etc/apt/sources.list.d`目录中 `ppa_name.list` 文件里的仓库条目。

在这两种情况下，文件 `ppa_name.list` 都保留在所在的目录中，即使它是空的。

### 使用 PPA 安全吗？

这是一个主观问题。纯粹主义者厌恶 PPA，因为大多数时候 PPA 来自第三方开发者。但与此同时，PPA 在 Debian/Ubuntu 世界中很受欢迎，因为它们提供了更简单的安装选项。

就安全性而言，很少见到因为使用 PPA 之后你的 Linux 系统被黑客攻击或注入恶意软件。到目前为止，我不记得发生过这样的事件。

官方 PPA 可以不加考虑的使用，使用非官方 PPA 完全是你自己的决定。

根据经验，如果程序需要 sudo 权限，则应避免通过第三方 PPA 进行安装。

### 你如何看待使用 PPA？

我知道这篇文章需要挺长时间来阅读，但我想让你更好地了解 PPA。我希望这份详细指南能够回答你关于使用 PPA 的大部分问题。

如果你对 PPA 有更多疑问，请随时在评论区提问。

如果你发现任何技术或语法错误，或者有改进的建议，请告诉我。

---

via: <https://itsfoss.com/ppa-guide/>

作者：[Abhishek Prakash](https://itsfoss.com/author/abhishek/) 选题：[lujun9972](https://github.com/lujun9972) 译者：[jlztan](https://github.com/jlztan) 校对：[wxy](https://github.com/wxy)

本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


Turtl：Evernote 的开源替代品

# Turtl：Evernote 的开源替代品

>
> 如果你正在寻找一个 Evernote 和 Google Keep 的替代品，那么 Turtl 是一个可靠的记笔记工具。
>
>
>

我认识的每个人都会记笔记，许多人使用在线笔记应用，如 Evernote、Simplenote 或 Google Keep。这些都是很好的工具，但你不得不担忧信息的安全性和隐私性 —— 特别是考虑到 [Evernote 2016 年的隐私策略变更](https://blog.evernote.com/blog/2016/12/15/evernote-revisits-privacy-policy/)。如果你想要更好地控制笔记和数据，你需要转向开源工具。

无论你离开 Evernote 的原因是什么，都有开源替代品。让我们来看看其中一个选择：Turtl。

### 入门

[Turtl](https://turtlapp.com/) 背后的开发人员希望你将其视为“具有绝对隐私的 Evernote”。说实话，我不能保证 Turtl 提供的隐私级别，但它是一个非常好的笔记工具。

要开始使用 Turtl，[下载](https://turtlapp.com/download/)适用于 Linux、Mac OS 或 Windows 的桌面客户端，或者获取它的 [Android 应用](https://turtlapp.com/download/)。安装它，然后启动客户端或应用。系统会要求你输入用户名和密码。Turtl 使用密码来生成加密密钥，根据开发人员的说法，加密密钥会在将笔记存储在设备或服务器上之前对其进行加密。

### 使用 Turtl

你可以使用 Turtl 创建以下类型的笔记：

* 密码
* 档案
* 图片
* 书签
* 文字笔记

无论你选择何种类型的笔记，你都可以在类似的窗口中创建：

*在 Turtl 中创建新笔记*

添加笔记标题、文字并（如果你正在创建文件或图像笔记）附加文件或图像等信息。然后单击“保存”。

你可以通过 [Markdown](https://en.wikipedia.org/wiki/Markdown) 为你的笔记添加格式。因为没有工具栏快捷方式，所以你需要手动添加格式。

如果你需要整理笔记，可以将它们添加到“白板”中。白板就像 Evernote 中的笔记本。要创建新的白板，请单击 “Board” 选项卡，然后单击“创建”按钮。输入白板的标题，然后单击“创建”。

*在 Turtl 中创建新的白板*

要向白板中添加笔记，请创建或编辑笔记，然后单击笔记底部的“此笔记不在任何白板”的链接。选择一个或多个白板，然后单击“完成”。

要为笔记添加标记，请单击记事本底部的“标记”图标，输入一个或多个以逗号分隔的关键字，然后单击“完成”。

### 跨设备同步你的笔记

例如，如果你在多台计算机和 Android 设备上使用 Turtl，Turtl 会在你上线时同步你的笔记。但是，我在同步时遇到了一个小问题：我手机上创建的笔记经常不会同步到我的笔记本电脑上。我尝试通过单击窗口左上角的图标手动同步，然后单击立即同步，但这并不总是有效。我发现偶尔需要单击“设置”图标，然后单击“清除本地数据”。然后我需要重新登录 Turtl，但所有数据都能正确同步了。

### 一个疑问和一些问题

当我开始使用 Turtl 时，我被一个疑问所困扰：我的笔记保存在哪里？事实证明，Turtl 背后的开发人员位于美国，这也是他们服务器的所在地。虽然 Turtl 使用的加密[非常强大](https://turtlapp.com/docs/security/encryption-specifics/)并且你的笔记在服务器上加密，但我偏执地认为你不应该在 Turtl 中保存任何敏感内容（或在任何类似的在线笔记中）。

Turtl 以平铺视图显示笔记，让人想起 Google Keep：

*Turtl 中的一系列笔记*

无法在桌面或 Android 应用上将其更改为列表视图。这对我来说不是问题，但我听说有些人因为它没有列表视图而不喜欢 Turtl。

说到 Android 应用，它并不差。但是，它没有与 Android 共享菜单集成。如果你想把在其他应用中看到或阅读的内容添加到 Turtl 笔记中，则需要手动复制并粘贴。

我已经在我的 Linux 笔记本，[运行 GalliumOS 的 Chromebook](https://opensource.com/article/17/4/linux-chromebook-gallium-os)，还有一台 Android 手机上使用 Turtl 几个月了。对所有这些设备来说这都是一种非常无缝的体验。虽然它不是我最喜欢的开源笔记工具，但 Turtl 做得非常好。试试看它，它可能是你正在寻找的简单的笔记工具。

---

via: <https://opensource.com/article/17/12/using-turtl-open-source-alternative-evernote>

作者：[Scott Nesbitt](https://opensource.com/users/scottnesbitt) 译者：[geekpi](https://github.com/geekpi) 校对：[wxy](https://github.com/wxy)

本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


计算机实验室之树莓派：课程 1 OK01

# 计算机实验室之树莓派：课程 1 OK01

OK01 课程讲解了树莓派如何入门，以及在树莓派上如何启用靠近 RCA 和 USB 端口的 OK 或 ACT 的 LED 指示灯。这个指示灯最初是为了指示 OK 状态的，但它在第二版的树莓派上被改名为 ACT。

### 1、入门

我们假设你已经访问了[下载](https://www.cl.cam.ac.uk/projects/raspberrypi/tutorials/os/downloads.html)页面，并且已经获得了必需的 GNU 工具链。也下载了一个称为操作系统模板的文件。请下载这个文件并在一个新目录中解开它。

### 2、开始

现在，你已经展开了这个模板文件，在 `source` 目录中创建一个名为 `main.s` 的文件。这个文件包含了这个操作系统的代码。具体来看，这个文件夹的结构应该像下面这样：

```
build/
   (empty)
source/
   main.s
kernel.ld
LICENSE
Makefile
```

用文本编辑器打开 `main.s` 文件，这样我们就可以输入汇编代码了。树莓派使用了称为 ARMv6 的汇编代码变体，这就是我们即将要写的汇编代码类型。

>
> 扩展名为 `.s` 的文件一般是汇编代码，需要记住的是，在这里它是 ARMv6 的汇编代码。
>
>
>

首先，我们复制下面的这些命令。

```
.section .init
.globl _start
_start:
```

实际上，上面这些指令并没有在树莓派上做任何事情，它们是提供给汇编器的指令。汇编器是一个转换程序，它将我们能够理解的汇编代码转换成树莓派能够理解的机器代码。在汇编代码中，每个行都是一个新的命令。上面的第一行告诉汇编器    1  在哪里放我们的代码。我们提供的模板中将它放到一个名为 `.init` 的节中的原因是，它是输出的起始点。这很重要，因为我们希望确保我们能够控制哪个代码首先运行。如果不这样做，首先运行的代码将是按字母顺序排在前面的代码！`.section` 命令简单地告诉汇编器，哪个节中放置代码，从这个点开始，直到下一个 `.section` 或文件结束为止。

>
> 在汇编代码中，你可以跳行、在命令前或后放置空格去提升可读性。
>
>
>

接下来两行是停止一个警告消息，它们并不重要。   2

### 3、第一行代码

现在，我们正式开始写代码。计算机执行汇编代码时，是简单地一行一行按顺序执行每个指令，除非明确告诉它不这样做。每个指令都是开始于一个新行。

复制下列指令。

```
ldr r0,=0x20200000
```

>
> `ldr reg,=val` 将数字 `val` 加载到名为 `reg` 的寄存器中。
>
>
>

那是我们的第一个命令。它告诉处理器将数字 `0x20200000` 保存到寄存器 `r0` 中。在这里我需要去回答两个问题，<ruby> 寄存器 <rt>  register </rt></ruby>是什么？`0x20200000` 是一个什么样的数字？

寄存器在处理器中就是一个极小的内存块，它是处理器保存正在处理的数字的地方。处理器中有很多寄存器，很多都有专门的用途，我们在后面会一一接触到它们。最重要的有十三个（命名为 `r0`、`r1`、`r2`、…、`r9`、`r10`、`r11`、`r12`），它们被称为通用寄存器，你可以使用它们做任何计算。由于是写我们的第一行代码，我们在示例中使用了 `r0`，当然你可以使用它们中的任何一个。只要后面始终如一就没有问题。

>
> 树莓派上的一个单独的寄存器能够保存任何介于 `0` 到 `4,294,967,295`（含）之间的任意整数，它可能看起来像一个很大的内存，实际上它仅有 32 个二进制比特。
>
>
>

`0x20200000` 确实是一个数字。只不过它是以十六进制表示的。下面的内容详细解释了十六进制的相关信息：

>
> 延伸阅读：十六进制解释
>
>
> 十六进制是另一种表示数字的方式。你或许只知道十进制的数字表示方法，十进制共有十个数字：`0`、`1`、`2`、`3`、`4`、`5`、`6`、`7`、`8` 和 `9`。十六进制共有十六个数字：`0`、`1`、`2`、`3`、`4`、`5`、`6`、`7`、`8`、`9`、`a`、`b`、`c`、`d`、`e` 和 `f`。
>
>
> 你可能还记得十进制是如何用位制来表示的。即最右侧的数字是个位，紧接着的左边一位是十位，再接着的左边一位是百位，依此类推。也就是说，它的值是 100 × 百位的数字，再加上 10 × 十位的数字，再加上 1 × 个位的数字。
>
>
>
>
>
> 从数学的角度来看，我们可以发现规律，最右侧的数字是 10 0 = 1s，紧接着的左边一位是 10 1 = 10s，再接着是 10 2 = 100s，依此类推。我们设定在系统中，0 是最低位，紧接着是 1，依此类推。但如果我们使用一个不同于 10 的数字为幂底会是什么样呢？我们在系统中使用的十六进制就是这样的一个数字。
>
>
>
>
>
>
>
>
> 上面的数学等式表明，十进制的数字 567 等于十六进制的数字 237。通常我们需要在系统中明确它们，我们使用下标  10 表示它是十进制数字，用下标  16 表示它是十六进制数字。由于在汇编代码中写上下标的小数字很困难，因此我们使用 0x 来表示它是一个十六进制的数字，因此 0x237 的意思就是 237 16 。
>
>
> 那么，后面的 `a`、`b`、`c`、`d`、`e` 和 `f` 又是什么呢？好问题！在十六进制中为了能够写每个数字，我们就需要额外的东西。例如 9 16 = 9×16 0 = 9 10 ，但是 10 16 = 1×16 1 + 1×16 0 = 16 10 。因此，如果我们只使用 0、1、2、3、4、5、6、7、8 和 9，我们就无法写出 10 10 、11 10 、12 10 、13 10 、14 10 、15 10 。因此我们引入了 6 个新的数字，这样 a 16 = 10 10 、b 16 = 11 10 、c 16 = 12 10 、d 16 = 13 10 、e 16 = 14 10 、f 16 = 15 10 。
>
>
> 所以，我们就有了另一种写数字的方式。但是我们为什么要这么麻烦呢？好问题！由于计算机总是工作在二进制中，事实证明，十六进制是非常有用的，因为每个十六进制数字正好是四个二进制数字的长度。这种方法还有另外一个好处，那就是许多计算机的数字都是十六进制的整数倍，而不是十进制的整数倍。比如，我在上面的汇编代码中使用的一个数字 20200000 16 。如果我们用十进制来写，它就是一个不太好记住的数字 538968064 10 。
>
>
> 我们可以用下面的简单方法将十进制转换成十六进制：
>
>
>
>
>
> 1. 我们以十进制数字 567 为例来说明。
> 2. 将十进制数字 567 除以 16 并计算其余数。例如 567 ÷ 16 = 35 余数为 7。
> 3. 在十六进制中余数就是答案中的最后一位数字，在我们的例子中它是 7。
> 4. 重复第 2 步和第 3 步，直到除法结果的整数部分为 0。例如 35 ÷ 16 = 2 余数为 3，因此 3 就是答案中的下一位。2 ÷ 16 = 0 余数为 2，因此 2 就是答案的接下来一位。
> 5. 一旦除法结果的整数部分为 0 就结束了。答案就是反序的余数，因此 567 10 = 237 16。
>
>
> 转换十六进制数字为十进制，也很容易，将数字展开即可，因此 237 16 = 2×16 2 + 3×16 1 +7 ×16 0 = 2×256 + 3×16 + 7×1 = 512 + 48 + 7 = 567。
>
>
>

因此，我们所写的第一个汇编命令是将数字 20200000 16 加载到寄存器 `r0` 中。那个命令看起来似乎没有什么用，但事实并非如此。在计算机中，有大量的内存块和设备。为了能够访问它们，我们给每个内存块和设备指定了一个地址。就像邮政地址或网站地址一样，它用于标识我们想去访问的内存块或设备的位置。计算机中的地址就是一串数字，因此上面的数字 20200000 16 就是 GPIO 控制器的地址。这个地址是由制造商的设计所决定的，他们也可以使用其它地址（只要不与其它的冲突即可）。我之所以知道这个地址是 GPIO 控制器的地址是因为我看了它的手册，   3  地址的使用没有专门的规范（除了它们都是以十六进制表示的大数以外）。

### 4、启用输出

阅读了手册可以得知，我们需要给 GPIO 控制器发送两个消息。我们必须用它的语言告诉它，如果我们这样做了，它将非常乐意实现我们的意图，去打开 OK 的 LED 指示灯。幸运的是，它是一个非常简单的芯片，为了让它能够理解我们要做什么，只需要给它设定几个数字即可。

```
mov r1,#1
lsl r1,#18
str r1,[r0,#4]
```

>
> `mov reg,#val` 将数字 `val` 放到名为 `reg` 的寄存器中。
>
>
> `lsl reg,#val` 将寄存器 `reg` 中的二进制操作数左移 `val` 位。
>
>
> `str reg,[dest,#val]` 将寄存器 `reg` 中的数字保存到地址 `dest + val` 上。
>
>
>

这些命令的作用是在 GPIO 的第 16 号插针上启用输出。首先我们在寄存器 `r1` 中获取一个必需的值，接着将这个值发送到 GPIO 控制器。因此，前两个命令是尝试取值到寄存器 `r1` 中，我们可以像前面一样使用另一个命令 `ldr` 来实现，但 `lsl` 命令对我们后面能够设置任何给定的 GPIO 针比较有用，因此从一个公式中推导出值要比直接写入来好一些。表示 OK 的 LED 灯是直接连线到 GPIO 的第 16 号针脚上的，因此我们需要发送一个命令去启用第 16 号针脚。

寄存器 `r1` 中的值是启用 LED 针所需要的。第一行命令将数字 1 10 放到 `r1` 中。在这个操作中 `mov` 命令要比 `ldr` 命令快很多，因为它不需要与内存交互，而 `ldr` 命令是将需要的值从内存中加载到寄存器中。尽管如此，`mov` 命令仅能用于加载某些值。   4  在 ARM 汇编代码中，基本上每个指令都使用一个三字母代码表示。它们被称为助记词，用于表示操作的用途。`mov` 是 “move” 的简写，而 `ldr` 是 “load register” 的简写。`mov` 是将第二个参数 `#1` 移动到前面的 `r1` 寄存器中。一般情况下，`#` 肯定是表示一个数字，但我们已经看到了不符合这种情况的一个反例。

第二个指令是 `lsl`（逻辑左移）。它的意思是将第一个参数的二进制操作数向左移第二个参数所表示的位数。在这个案例中，将 1 10 （即 1 2 ）向左移 18 位（将它变成 1000000000000000000 2=262144 10 ）。

如果你不熟悉二进制表示法，可以看下面的内容：

>
> 延伸阅读： 二进制解释
>
>
> 与十六进制一样，二进制是写数字的另一种方法。在二进制中只有两个数字，即 `0` 和 `1`。它在计算机中非常有用，因为我们可以用电路来实现它，即电流能够通过电路表示为 `1`，而电流不能通过电路表示为 `0`。这就是计算机能够完成真实工作和做数学运算的原理。尽管二进制只有两个数字，但它却能够表示任何一个数字，只是写起来有点长而已。
>
>
>
>
>
> 这个图片展示了 567 10 的二进制表示是 1000110111 2 。我们使用下标 2 来表示这个数字是用二进制写的。
>
>
> 我们在汇编代码中大量使用二进制的其中一个巧合之处是，数字可以很容易地被 `2` 的幂（即 `1`、`2`、`4`、`8`、`16`）乘或除。通常乘法和除法都是非常难的，而在某些特殊情况下却变得非常容易，所以二进制非常重要。
>
>
>
>
>
> 将一个二进制数字左移 `n` 位就相当于将这个数字乘以 2 n。因此，如果我们想将一个数乘以 4，我们只需要将这个数字左移 2 位。如果我们想将它乘以 256，我们只需要将它左移 8 位。如果我们想将一个数乘以 12 这样的数字，我们可以有一个替代做法，就是先将这个数乘以 8，然后再将那个数乘以 4，最后将两次相乘的结果相加即可得到最终结果（N × 12 = N × (8 + 4) = N × 8 + N × 4）。
>
>
>
>
>
> 右移一个二进制数 `n` 位就相当于这个数除以 2 n 。在右移操作中，除法的余数位将被丢弃。不幸的是，如果对一个不能被 2 的幂次方除尽的二进制数字做除法是非常难的，这将在 [课程 9 Screen04](https://www.cl.cam.ac.uk/projects/raspberrypi/tutorials/os/screen04.html) 中讲到。
>
>
>
>
>
> 这个图展示了二进制常用的术语。一个<ruby> 比特 <rt>  bit </rt></ruby>就是一个单独的二进制位。一个“<ruby> 半字节 <rt>  nibble </rt></ruby>“ 是 4 个二进制位。一个<ruby> 字节 <rt>  byte </rt></ruby>是 2 个半字节，也就是 8 个比特。<ruby> 半字 <rt>  half </rt></ruby>是指一个字长度的一半，这里是 2 个字节。<ruby> 字 <rt>  word </rt></ruby>是指处理器上寄存器的大小，因此，树莓派的字长是 4 字节。按惯例，将一个字最高有效位标识为 31，而将最低有效位标识为 0。顶部或最高位表示最高有效位，而底部或最低位表示最低有效位。一个 kilobyte（KB）就是 1000 字节，一个 megabyte 就是 1000 KB。这样表示会导致一些困惑，到底应该是 1000 还是 1024（二进制中的整数）。鉴于这种情况，新的国际标准规定，一个 KB 等于 1000 字节，而一个 Kibibyte（KiB）是 1024 字节。一个 Kb 是 1000 比特，而一个 Kib 是 1024 比特。
>
>
> 树莓派默认采用小端法，也就是说，从你刚才写的地址上加载一个字节时，是从一个字的低位字节开始加载的。
>
>
>

再强调一次，我们只有去阅读手册才能知道我们所需要的值。手册上说，GPIO 控制器中有一个 24 字节的集合，由它来决定 GPIO 针脚的设置。第一个 4 字节与前 10 个 GPIO 针脚有关，第二个 4 字节与接下来的 10 个针脚有关，依此类推。总共有 54 个 GPIO 针脚，因此，我们需要 6 个 4 字节的一个集合，总共是 24 个字节。在每个 4 字节中，每 3 个比特与一个特定的 GPIO 针脚有关。我们想去启用的是第 16 号 GPIO 针脚，因此我们需要去设置第二组 4 字节，因为第二组的 4 字节用于处理 GPIO 针脚的第 10-19 号，而我们需要第 6 组 3 比特，它在上面的代码中的编号是 18（6×3）。

最后的 `str`（“store register”）命令去保存第一个参数中的值，将寄存器 `r1` 中的值保存到后面的表达式计算出来的地址上。这个表达式可以是一个寄存器，在上面的例子中是 `r0`，我们知道 `r0` 中保存了 GPIO 控制器的地址，而另一个值是加到它上面的，在这个例子中是 `#4`。它的意思是将 GPIO 控制器地址加上 `4` 得到一个新的地址，并将寄存器 `r1` 中的值写到那个地址上。那个地址就是我们前面提到的第二组 4 字节的位置，因此，我们发送我们的第一个消息到 GPIO 控制器上，告诉它准备启用 GPIO 第 16 号针脚的输出。

### 5、生命的信号

现在，LED 已经做好了打开准备，我们还需要实际去打开它。意味着需要给 GPIO 控制器发送一个消息去关闭 16 号针脚。是的，你没有看错，就是要发送一个关闭的消息。芯片制造商认为，在 GPIO 针脚关闭时打开 LED 更有意义。   5  硬件工程师经常做这种反常理的决策，似乎是为了让操作系统开发者保持警觉。可以认为是给自己的一个警告。

```
mov r1,#1
lsl r1,#16
str r1,[r0,#40]
```

希望你能够认识上面全部的命令，先不要管它的值。第一个命令和前面一样，是将值 `1` 推入到寄存器 `r1` 中。第二个命令是将二进制的 `1` 左移 16 位。由于我们是希望关闭 GPIO 的 16 号针脚，我们需要在下一个消息中将第 16 比特设置为 1（想设置其它针脚只需要改变相应的比特位即可）。最后，我们写这个值到 GPIO 控制器地址加上 40 10 的地址上，这将使那个针脚关闭（加上 28 将打开针脚）。

### 6、永远幸福快乐

似乎我们现在就可以结束了，但不幸的是，处理器并不知道我们做了什么。事实上，处理器只要通电，它就永不停止地运转。因此，我们需要给它一个任务，让它一直运转下去，否则，树莓派将进入休眠（本示例中不会，LED 灯会一直亮着）。

```
loop$:
b loop$

```

>
> `name:` 下一行的名字。
>
>
> `b label` 下一行将去标签 `label` 处运行。
>
>
>

第一行不是一个命令，而是一个标签。它给下一行命名为 `loop$`，这意味着我们能够通过名字来指向到该行。这就称为一个标签。当代码被转换成二进制后，标签将被丢弃，但这对我们通过名字而不是数字（地址）找到行比较有用。按惯例，我们使用一个 `$` 表示这个标签只对这个代码块中的代码起作用，让其它人知道，它不对整个程序起作用。`b`（“branch”）命令将去运行指定的标签中的命令，而不是去运行它后面的下一个命令。因此，下一行将再次去运行这个 `b` 命令，这将导致永远循环下去。因此处理器将进入一个无限循环中，直到它安全关闭为止。

代码块结尾的一个空行是有意这样写的。GNU 工具链要求所有的汇编代码文件都是以空行结束的，因此，这就可以你确实是要结束了，并且文件没有被截断。如果你不这样处理，在汇编器运行时，你将收到烦人的警告。

### 7、树莓派上场

由于我们已经写完了代码，现在，我们可以将它上传到树莓派中了。在你的计算机上打开一个终端，改变当前工作目录为 `source` 文件夹的父级目录。输入 `make` 然后回车。如果报错，请参考排错章节。如果没有报错，你将生成三个文件。 `kernel.img` 是你的编译后的操作系统镜像。`kernel.list` 是你写的汇编代码的一个清单，它实际上是生成的。这在将来检查程序是否正确时非常有用。`kernel.map` 文件包含所有标签结束位置的一个映射，这对于跟踪值非常有用。

为安装你的操作系统，需要先有一个已经安装了树莓派操作系统的 SD 卡。如果你浏览 SD 卡中的文件，你应该能看到一个名为 `kernel.img` 的文件。将这个文件重命名为其它名字，比如 `kernel_linux.img`。然后，复制你编译的 `kernel.img` 文件到 SD 卡中原来的位置，这将用你的操作系统镜像文件替换现在的树莓派操作系统镜像。想切换回来时，只需要简单地删除你自己的 `kernel.img` 文件，然后将前面重命名的文件改回 `kernel.img` 即可。我发现，保留一个原始的树莓派操作系统的备份是非常有用的，万一你要用到它呢。

将这个 SD 卡插入到树莓派，并打开它的电源。这个 OK 的 LED 灯将亮起来。如果不是这样，请查看故障排除页面。如果一切如愿，恭喜你，你已经写出了你的第一个操作系统。[课程 2 OK02](https://www.cl.cam.ac.uk/projects/raspberrypi/tutorials/os/ok02.html) 将指导你让 LED 灯闪烁和关闭闪烁。

---

1. 是的，我说错了，它告诉的是链接器，它是另一个程序，用于将汇编器转换过的几个代码文件链接到一起。直接说是汇编器也没有大问题。 [↩](#fnref1)
2. 其实它们对你很重要。由于 GNU 工具链主要用于开发操作系统，它要求入口点必须是名为 `_start` 的地方。由于我们是开发一个操作系统，无论什么时候，它总是从 `_start` 开时的，而我们可以使用 `.section .init` 命令去设置它。因此，如果我们没有告诉它入口点在哪里，就会使工具链困惑而产生警告消息。所以，我们先定义一个名为 `_start` 的符号，它是所有人可见的（全局的），紧接着在下一行生成符号 `_start` 的地址。我们很快就讲到这个地址了。 [↩](#fnref2)
3. 本教程的设计减少了你阅读树莓派开发手册的难度，但是，如果你必须要阅读它，你可以在这里 [SoC-Peripherals.pdf](https://www.cl.cam.ac.uk/projects/raspberrypi/tutorials/os/downloads/SoC-Peripherals.pdf) 找到它。由于添加了混淆，手册中 GPIO 使用了不同的地址系统。我们的操作系统中的地址 0x20200000 对应到手册中是 0x7E200000。 [↩](#fnref3)
4. `mov` 能够加载的值只有前 8 位是 `1` 的二进制表示的值。换句话说就是一个 0 后面紧跟着 8 个 `1` 或 `0`。 [↩](#fnref4)
5. 一个很友好的硬件工程师是这样向我解释这个问题的： [↩](#fnref5)

原因是现在的芯片都是用一种称为 CMOS 的技术来制成的，它是互补金属氧化物半导体的简称。互补的意思是每个信号都连接到两个晶体管上，一个是使用 N 型半导体的材料制成，它用于将电压拉低，而另一个使用 P 型半导体材料制成，它用于将电压升高。在任何时刻，仅有一个半导体是打开的，否则将会短路。P 型材料的导电性能不如 N 型材料。这意味着三倍大的 P 型半导体材料才能提供与 N 型半导体材料相同的电流。这就是为什么 LED 总是通过降低为低电压来打开它，因为 N 型半导体拉低电压比 P 型半导体拉高电压的性能更强。

还有一个原因。早在上世纪七十年代，芯片完全是由 N 型材料制成的（NMOS），P 型材料部分使用了一个电阻来代替。这意味着当信号为低电压时，即便它什么事都没有做，芯片仍然在消耗能量（并发热）。你的电话装在口袋里什么事都不做，它仍然会发热并消耗你的电池电量，这不是好的设计。因此，信号设计成 “活动时低”，而不活动时为高电压，这样就不会消耗能源了。虽然我们现在已经不使用 NMOS 了，但由于 N 型材料的低电压信号比 P 型材料的高电压信号要快，所以仍然使用了这种设计。通常在一个 “活动时低” 信号名字上方会有一个条型标记，或者写作 `SIGNAL_n` 或 `/SIGNAL`。但是即便这样，仍然很让人困惑，那怕是硬件工程师，也不可避免这种困惑！

---

via: <https://www.cl.cam.ac.uk/projects/raspberrypi/tutorials/os/ok01.html>

作者：[Robert Mullins](http://www.cl.cam.ac.uk/%7Erdm34) 选题：[lujun9972](https://github.com/lujun9972) 译者：[qhwdw](https://github.com/qhwdw) 校对：[wxy](https://github.com/wxy)

本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出