在 Wireshark 中过滤数据包

Summary: 数据包过滤可让你专注于你感兴趣的确定数据集。如你所见，Wireshark 默认会抓取所有数据包。这可能会妨碍你寻找具体的数据。 Wireshark 提供了两个功能强大的过滤工具，让你简单而无痛地获得精确的数据。

![](/data/attachment/album/201708/19/123750v3fqtsis8v9gc3fs.jpg)

### 介绍

数据包过滤可让你专注于你感兴趣的确定数据集。如你所见，Wireshark 默认会抓取*所有*数据包。这可能会妨碍你寻找具体的数据。 Wireshark 提供了两个功能强大的过滤工具，让你简单而无痛地获得精确的数据。

Wireshark 可以通过两种方式过滤数据包。它可以通过只收集某些数据包来过滤，或者在抓取数据包后进行过滤。当然，这些可以彼此结合使用，并且它们各自的用处取决于收集的数据和信息的多少。

### 布尔表达式和比较运算符

Wireshark 有很多很棒的内置过滤器。当开始输入任何一个过滤器字段时，你将看到它们会自动补完。这些过滤器大多数对应于用户对数据包的常见分组方式，比如仅过滤 HTTP 请求就是一个很好的例子。

对于其他的，Wireshark 使用布尔表达式和/或比较运算符。如果你曾经做过任何编程，你应该熟悉布尔表达式。他们是使用 `and`、`or`、`not` 来验证声明或表达式的真假。比较运算符要简单得多，它们只是确定两件或更多件事情是否彼此相等、大于或小于。

### 过滤抓包

在深入自定义抓包过滤器之前，请先查看 Wireshark 已经内置的内容。单击顶部菜单上的 “Capture” 选项卡，然后点击 “Options”。可用接口下面是可以编写抓包过滤器的行。直接移到左边一个标有 “Capture Filter” 的按钮上。点击它，你将看到一个新的对话框，其中包含内置的抓包过滤器列表。看看里面有些什么。

![Wireshark dialog for creating a capture filter](/data/attachment/album/201708/19/123807wy6kt2sq8f8fls8t.jpg)

在对话框的底部，有一个用于创建并保存抓包过滤器的表单。按左边的 “New” 按钮。它将创建一个填充有默认数据的新的抓包过滤器。要保存新的过滤器，只需将实际需要的名称和表达式替换原来的默认值，然后单击“Ok”。过滤器将被保存并应用。使用此工具，你可以编写并保存多个不同的过滤器，以便它们将来可以再次使用。

抓包有自己的过滤语法。对于比较，它不使用等于号，并使用 `>` 和 `<` 来用于大于或小于。对于布尔值来说，它使用 `and`、`or` 和 `not`。

例如，如果你只想监听 80 端口的流量，你可以使用这样的表达式：`port 80`。如果你只想从特定的 IP 监听端口 80，你可以使用 `port 80 and host 192.168.1.20`。如你所见，抓包过滤器有特定的关键字。这些关键字用于告诉 Wireshark 如何监控数据包以及哪一个数据是要找的。例如，`host` 用于查看来自 IP 的所有流量。`src` 用于查看源自该 IP 的流量。与之相反，`dst` 只监听目标到这个 IP 的流量。要查看一组 IP 或网络上的流量，请使用 `net`。

### 过滤结果

界面的底部菜单栏是专门用于过滤结果的菜单栏。此过滤器不会更改 Wireshark 收集的数据，它只允许你更轻松地对其进行排序。有一个文本字段用于输入新的过滤器表达式，并带有一个下拉箭头以查看以前输入的过滤器。旁边是一个标为 “Expression” 的按钮，另外还有一些用于清除和保存当前表达式的按钮。

点击 “Expression” 按钮。你将看到一个小窗口，其中包含多个选项。左边一栏有大量的条目，每个都有附加的折叠子列表。你可以用这些来过滤所有不同的协议、字段和信息。你不可能看完所有，所以最好是大概看下。你应该注意到了一些熟悉的选项，如 HTTP、SSL 和 TCP。

![Wireshark dailog for creating a results filter](/data/attachment/album/201708/19/123809we838dvp88nx63e6.jpg)

子列表包含可以过滤的不同部分和请求方法。你可以看到通过 GET 和 POST 请求过滤 HTTP 请求。

你还可以在中间看到运算符列表。通过从每列中选择条目，你可以使用此窗口创建过滤器，而不用记住 Wireshark 可以过滤的每个条目。对于过滤结果，比较运算符使用一组特定的符号。 `==` 用于确定是否相等。`>` 用于确定一件东西是否大于另一个东西，`<` 找出是否小一些。 `>=` 和 `<=` 分别用于大于等于和小于等于。它们可用于确定数据包是否包含正确的值或按大小过滤。使用 `==` 仅过滤 HTTP GET 请求的示例如下：`http.request.method == "GET"`。

布尔运算符基于多个条件将小的表达式串到一起。不像是抓包所使用的单词，它使用三个基本的符号来做到这一点。`&&` 代表 “与”。当使用时，`&&` 两边的两个语句都必须为真值才行，以便 Wireshark 来过滤这些包。`||` 表示 “或”。只要两个表达式任何一个为真值，它就会被过滤。如果你正在查找所有的 GET 和 POST 请求，你可以这样使用 `||`：`(http.request.method == "GET") || (http.request.method == "POST")`。`!` 是 “非” 运算符。它会寻找除了指定的东西之外的所有东西。例如，`!http` 将展示除了 HTTP 请求之外的所有东西。

### 总结思考

过滤 Wireshark 可以让你有效监控网络流量。熟悉可以使用的选项并习惯你可以创建过滤器的强大表达式需要一些时间。然而一旦你学会了，你将能够快速收集和查找你要的网络数据，而无需梳理长长的数据包或进行大量的工作。

---

via: <https://linuxconfig.org/filtering-packets-in-wireshark-on-kali-linux>

作者：[Nick Congleton](https://linuxconfig.org/filtering-packets-in-wireshark-on-kali-linux) 译者：[geekpi](https://github.com/geekpi) 校对：[wxy](https://github.com/wxy)

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


了解 7z 命令开关（一）

Summary: 7z 无疑是一个功能强大的强大的归档工具（声称提供最高的压缩比）。

7z 无疑是一个功能强大的强大的归档工具（声称提供最高的压缩比）。

![](/data/attachment/album/201708/19/143244kypyp9rln99qyl9v.png)

7z 无疑是一个功能强大的强大的归档工具（声称提供最高的压缩比）。在 HowtoForge 中，我们已经[已经讨论过](https://www.howtoforge.com/tutorial/how-to-install-and-use-7zip-file-archiver-on-ubuntu-linux/)如何安装和使用它。但讨论仅限于你可以使用该工具提供的“功能字母”来使用基本功能。

在本教程中，我们将扩展对这个工具的说明，我们会讨论一些 7z 提供的“开关”。 但在继续之前，需要说明的是，本教程中提到的所有说明和命令都已在 Ubuntu 16.04 LTS 上进行了测试。

**注意**：我们将使用以下截图中显示的文件来执行使用 7zip 的各种操作。

 [![ls from test directory](/data/attachment/album/201708/19/143302xyqgjbbptpfwf3pu.png)](https://www.howtoforge.com/images/understanding_7z_command_switches_part_i/big/ls.png)

### 包含文件

7z 工具允许你有选择地将文件包含在归档中。可以使用 `-i` 开关来使用此功能。

语法：

```
-i[r[-|0]]{@listfile|!wildcard}

```

比如，如果你想在归档中只包含 “.txt” 文件，你可以使用下面的命令：

```
$ 7z a ‘-i!*.txt’ include.7z

```

这是输出：

 [![add files to 7zip](/data/attachment/album/201708/19/143304tyy5g74w77ypm5ah.png)](https://www.howtoforge.com/images/understanding_7z_command_switches_part_i/big/include.png)

现在，检查新创建的归档是否只包含 “.txt” 文件，你可以使用下面的命令：

```
$ 7z l include.7z

```

这是输出：

 [![Result](/data/attachment/album/201708/19/143305c0gbebnwsrbb0l1k.png)](https://www.howtoforge.com/images/understanding_7z_command_switches_part_i/big/includelist.png)

在上面的截图中，你可以看到 “testfile.txt” 已经包含到归档中了。

### 排除文件

如果你想要，你可以排除不想要的文件。可以使用 `-x` 开关做到。

语法：

```
-x[r[-|0]]]{@listfile|!wildcard}

```

比如，如果你想在要创建的归档中排除 “abc.7z” ，你可以使用下面的命令：

```
$ 7z a ‘-x!abc.7z’ exclude.7z

```

这是输出：

 [![exclude files from 7zip](/data/attachment/album/201708/19/143306hxsm44cls1vmc1md.png)](https://www.howtoforge.com/images/understanding_7z_command_switches_part_i/big/exclude.png)

要检查最后的归档是否排除了 “abc.7z”, 你可以使用下面的命令：

```
$ 7z l exclude.7z

```

这是输出：

 [![result of file exclusion](/data/attachment/album/201708/19/143309famooayyvjs0u1o0.png)](https://www.howtoforge.com/images/understanding_7z_command_switches_part_i/big/excludelist.png)

上面的截图中，你可以看到 “abc.7z” 已经从新的归档中排除了。

**专业提示**：假设任务是排除以 “t” 开头的所有 .7z 文件，并且包含以字母 “a” 开头的所有 .7z 文件。这可以通过以下方式组合 `-i` 和 `-x` 开关来实现：

```
$ 7z a '-x!t*.7z' '-i!a*.7z' combination.7z

```

### 设置归档密码

7z 同样也支持用密码保护你的归档文件。这个功能可以使用 `-p` 开关来实现。

```
$ 7z a [archive-filename] -p[your-password] -mhe=[on/off]

```

**注意**：`-mhe` 选项用来启用或者禁用归档头加密（默认是“off”）。

例子：

```
$ 7z a password.7z -pHTF -mhe=on

```

无需多说，当你解压密码保护的归档时，工具会向你询问密码。要解压一个密码保护的文件，使用 `e` 功能字母。下面是例子：

```
$ 7z e password.7z

```

 [![protect 7zip archive with a password](/data/attachment/album/201708/19/143311wspxbbnnpln572pp.png)](https://www.howtoforge.com/images/understanding_7z_command_switches_part_i/big/password.png)

### 设置输出目录

工具同样支持解压文件到你选择的目录中。这可以使用 `-o` 开关。无需多说，这个开关只在含有 `e` 或者 `x` 功能字母的时候有用。

```
$ 7z [e/x] [existing-archive-filename] -o[path-of-directory]

```

比如，假设下面命令工作在当前的工作目录中：

```
$ 7z e output.7z -ohow/to/forge

```

如 `-o` 开关的值所指的那样，它的目标是解压文件到 ./how/to/forge 中。

这是输出：

 [![7zip output directory](/data/attachment/album/201708/19/143315xpy5s5mysko8p484.png)](https://www.howtoforge.com/images/understanding_7z_command_switches_part_i/big/output.png)

在上面的截图中，你可以看到归档文件的所有内容都已经解压了。但是在哪里？要检查文件是否被解压到 ./how/to/forge，我们可以使用 `ls -R` 命令。

 [![result](/data/attachment/album/201708/19/143318au8pirl8oz8rte8l.png)](https://www.howtoforge.com/images/understanding_7z_command_switches_part_i/big/ls_-R.png)

在上面的截图中，我们可以看到 .7z 中的内容都被解压到 ./how/to/forge 中。

### 创建多个卷

借助 7z 工具，你可以为归档创建多个卷（较小的子档案）。当通过网络或 USB 传输大文件时，这是非常有用的。可以使用 `-v` 开关使用此功能。这个开关需要指定子档案的大小。

我们可以以字节（b）、千字节（k）、兆字节（m）和千兆字节（g）指定子档案大小。

```
$ 7z a [archive-filename] [files-to-archive] -v[size-of-sub-archive1] -v[size-of-sub-archive2] ....

```

让我们用一个例子来理解这个。请注意，我们将使用一个新的目录来执行 `-v` 开关的操作。

这是目录内容的截图：

 [![7zip volumes](/data/attachment/album/201708/19/143319xwdwkof1ks4sk0kd.png)](https://www.howtoforge.com/images/understanding_7z_command_switches_part_i/big/volumels.png)

现在，我们运行下面的命令来为一个归档文件创建多个卷（每个大小 100b）：

```
7z a volume.7z * -v100b

```

这是截图：

 [![compressing volumes](/data/attachment/album/201708/19/143322z0cdkdg275zpdezu.png)](https://www.howtoforge.com/images/understanding_7z_command_switches_part_i/big/volume.png)

现在，要查看创建的子归档，使用 `ls` 命令。

 [![list of archives](/data/attachment/album/201708/19/143324hz2um0i3wzbr2sbu.png)](https://www.howtoforge.com/images/understanding_7z_command_switches_part_i/big/volumels2.png)

如下截图所示，一共创建四个卷 - volume.7z.001、volume.7z.002、volume.7z.003 和 volume.7z.004

**注意**：你可以使用 .7z.001 归档文件来解压。但是，要这么做，其他所有的卷都应该在同一个目录内。

### 设置归档的压缩级别

7z 允许你设置归档的压缩级别。这个功能可以使用 `-m` 开关。7z 中有不同的压缩级别，比如：`-mx0`、`-mx1`、`-mx3`、`-mx5`、`-mx7` 和 `-mx9`。

这是这些压缩级别的简要说明：

* `mx0` = 完全不压缩 - 只是复制文件到归档中。
* `mx1` = 消耗最少时间，但是压缩最小。
* `mx3` = 比 `-mx1` 好。
* `mx5` = 这是默认级别 （常规压缩）。
* `mx7` = 最大化压缩。
* `mx9` = 极端压缩。

**注意**：关于这些压缩级别的更多信息，阅读[这里](http://askubuntu.com/questions/491223/7z-ultra-settings-for-zip-format)。

```
$ 7z a [archive-filename] [files-to-archive] -mx=[0,1,3,5,7,9]

```

例如，我们在目录中有一堆文件和文件夹，我们每次尝试使用不同的压缩级别进行压缩。作为一个例子，这是当使用压缩级别 “0” 时创建存档时使用的命令。

```
$ 7z a compression(-mx0).7z * -mx=0

```

相似地，其他命令也这样执行。

以下是输出档案（使用 “ls” 命令生成）的列表，其名称表示其创建中使用的压缩级别，输出中的第五列显示压缩级别对其大小的影响。

 [![7zip compression level](/data/attachment/album/201708/19/143326b7l7pzmm1lxx7w0j.png)](https://www.howtoforge.com/images/understanding_7z_command_switches_part_i/big/compression.png)

### 显示归档的技术信息

如果需要，7z 还可以在标准输出中显示归档的技术信息 - 类型、物理大小、头大小等。可以使用 `-slt` 开关使用此功能。 此开关仅适用于带有 `l` 功能字母的情况下。

```
$ 7z l -slt [archive-filename]

```

比如：

```
$ 7z l -slt abc.7z

```

这是输出：

 [![](/data/attachment/album/201708/19/143328p2gkd0z2vtnok0s2.png)](https://www.howtoforge.com/images/understanding_7z_command_switches_part_i/big/slt.png)

### 指定创建归档的类型

如果你想要创建一个非 7z 的归档文件（这是默认的创建类型），你可以使用 `-t` 开关来指定。

```
$ 7z a -t[specify-type-of-archive] [archive-filename] [file-to-archive]

```

下面的例子展示创建了一个 .zip 文件：

```
7z a -tzip howtoforge *

```

输出的文件是 “howtoforge.zip”。要交叉验证它的类型，使用 `file` 命令：

 [![](/data/attachment/album/201708/19/143329hoq7skkmo1k7o8mn.png)](https://www.howtoforge.com/images/understanding_7z_command_switches_part_i/big/type.png)

因此，howtoforge.zip 的确是一个 ZIP 文件。相似地，你可以创建其他 7z 支持的归档。

### 总结

你将会认识到， 7z 的 “功能字母” 以及 “开关” 的知识可以让你充分利用这个工具。我们还没有完成开关的部分 - 其余部分将在第 2 部分中讨论。

---

via: <https://www.howtoforge.com/tutorial/understanding-7z-command-switches/>

作者：[Himanshu Arora](https://www.howtoforge.com/tutorial/understanding-7z-command-switches/) 译者：[geekpi](https://github.com/geekpi) 校对：[wxy](https://github.com/wxy)

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


极客漫画：最大之数

Summary: 人们总是在说 Java 要完了，然后 Java 年复一年的还继续活着……

interesting

今年Java真的药丸了

为啥？愿闻其详

2019了，java还活着。。。。。

2019了，java还活着。。。。。

2019了，java还活着。。。。。

2019了，java还活着。。。。。

9102年了. java好好的呢

202了，java还活着。。。。。

2021了，java非常顽强。。。

2021了，java非常顽强。。。

人们总是在说 Java 要完了，然后 Java 年复一年的还继续活着……

![Big Numbers](/data/attachment/album/201708/21/135134fespkmiv2kgivls8.png)

人们总是在说 Java 要完了，然后 Java 年复一年的还继续活着……

---

via: <http://turnoff.us/geek/big-numbers/>

作者：[Daniel Stori](http://turnoff.us/about/) 译者：[wxy](https://github.com/wxy)

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


Linux 容器轻松应对性能工程

Summary: 容器在性能方面表现出色，而即将到来的 “serverless” 计算（也称为功能即服务 - Function as a Service（FaaS））的浪潮将扩大容器的优势。

SaaS不是软件即服务吗？

![](/data/attachment/album/201708/20/184447hw12rkwjvhwscxsw.jpg)

应用程序的性能决定了软件能多快完成预期任务。这回答有关应用程序的几个问题，例如：

* 峰值负载下的响应时间
* 与替代方案相比，它易于使用，受支持的功能和用例
* 运营成本（CPU 使用率、内存需求、数据吞吐量、带宽等）

该性能分析的价值超出了服务负载所需的计算资源或满足峰值需求所需的应用实例数量的估计。性能显然与成功企业的基本要素挂钩。它揭示了用户的总体体验，包括确定什么会拖慢客户预期的响应时间，通过设计满足带宽要求的内容交付来提高客户粘性，选择最佳设备，最终帮助企业发展业务。

### 问题

当然，这是对业务服务的性能工程价值的过度简化。为了理解在完成我刚刚所描述事情背后的挑战，让我们把它放到一个真实的稍微有点复杂的场景中。

现实世界的应用程序可能托管在云端。应用程序可以利用非常大（或概念上是无穷大）的计算资源。在硬件和软件方面的需求将通过云来满足。从事开发工作的开发人员将使用云交付功能来实现更快的编码和部署。云托管不是免费的，但成本开销与应用程序的资源需求成正比。

除了<ruby> 搜索即服务 <rt>  Search as a Service </rt></ruby>（SaaS）、<ruby> 平台即服务 <rt>  Platform as a Service </rt></ruby>（PaaS）、<ruby> 基础设施即服务 <rt>  Infrastructure as a Service </rt></ruby>（IaaS）以及<ruby> 负载平衡即服务 <rt>  Load Balancing as a Service </rt></ruby>（LBaaS）之外，当云端管理托管程序的流量时，开发人员可能还会使用这些快速增长的云服务中的一个或多个：

* <ruby> 安全即服务 <rt>  Security as a Service </rt></ruby> （SECaaS），可满足软件和用户的安全需求
* <ruby> 数据即服务 <rt>  Data as a Service </rt></ruby> （DaaS），为应用提供了用户需求的数据
* <ruby> 登录即服务 <rt>  Logging as a Service </rt></ruby> （LaaS），DaaS 的近亲，提供了日志传递和使用的分析指标
* <ruby> 搜索即服务 <rt>  Search as a Service </rt></ruby> （SaaS），用于应用程序的分析和大数据需求
* <ruby> 网络即服务 <rt>  Network as a Service </rt></ruby> （NaaS），用于通过公共网络发送和接收数据

云服务也呈指数级增长，因为它们使得开发人员更容易编写复杂的应用程序。除了软件复杂性之外，所有这些分布式组件的相互作用变得越来越多。用户群变得更加多元化。该软件的需求列表变得更长。对其他服务的依赖性变大。由于这些因素，这个生态系统的缺陷会引发性能问题的多米诺效应。

例如，假设你有一个精心编写的应用程序，它遵循安全编码实践，旨在满足不同的负载要求，并经过彻底测试。另外假设你已经将基础架构和分析工作结合起来，以支持基本的性能要求。在系统的实现、设计和架构中建立性能标准需要做些什么？软件如何跟上不断变化的市场需求和新兴技术？如何测量关键参数以调整系统以获得最佳性能？如何使系统具有弹性和自我恢复能力？你如何更快地识别任何潜在的性能问题，并尽早解决？

### 进入容器

软件[容器](https://opensource.com/resources/what-are-linux-containers)以[微服务](https://opensource.com/resources/what-are-microservices)设计或面向服务的架构（SoA）的优点为基础，提高了性能，因为包含更小的、自足的代码块的系统更容易编码，对其它系统组件有更清晰、定义良好的依赖。测试更容易，包括围绕资源利用和内存过度消耗的问题比在宏架构中更容易确定。

当扩容系统以增加负载能力时，容器应用程序的复制快速而简单。安全漏洞能更好地隔离。补丁可以独立版本化并快速部署。性能监控更有针对性，测量更可靠。你还可以重写和“改版”资源密集型代码，以满足不断变化的性能要求。

容器启动快速，停止也快速。它比虚拟机（VM）有更高效资源利用和更好的进程隔离。容器没有空闲内存和 CPU 闲置。它们允许多个应用程序共享机器，而不会丢失数据或性能。容器使应用程序可移植，因此开发人员可以构建并将应用程序发送到任何支持容器技术的 Linux 服务器上，而不必担心性能损失。容器生存在其内，并遵守其集群管理器（如 Cloud Foundry 的 Diego、[Kubernetes](https://opensource.com/resources/what-is-kubernetes)、Apache Mesos 和 Docker Swarm）所规定的配额（比如包括存储、计算和对象计数配额）。

容器在性能方面表现出色，而即将到来的 “serverless” 计算（也称为<ruby> 功能即服务 <rt>  Function as a Service </rt></ruby>（FaaS））的浪潮将扩大容器的优势。在 FaaS 时代，这些临时性或短期的容器将带来超越应用程序性能的优势，直接转化为在云中托管的间接成本的节省。如果容器的工作更快，那么它的寿命就会更短，而且计算量负载纯粹是按需的。

---

作者简介：

Garima 是 Red Hat 的工程经理，专注于 OpenShift 容器平台。在加入 Red Hat 之前，Garima 帮助 Akamai Technologies＆MathWorks Inc. 开创了创新。

---

via: <https://opensource.com/article/17/2/performance-container-world>

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

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


如何解决 VLC 视频嵌入字幕中遇到的错误

这会是一个有点奇怪的教程。背景故事如下。最近，我创作了一堆 [Risitas y las paelleras](https://www.youtube.com/watch?v=cDphUib5iG4) 素材中[sweet](https://www.youtube.com/watch?v=MpDdGOKZ3dg) [parody](https://www.youtube.com/watch?v=KHG6fXEba0A) [的片段](https://www.youtube.com/watch?v=TXw5lRi97YY)，以主角 Risitas 疯狂的笑声而闻名。和往常一样，我把它们上传到了 Youtube，但是从当我决定使用字幕起，到最终在网上可以观看时，我经历了一个漫长而曲折的历程。

在本指南中，我想介绍几个你可能会在创作自己的媒体时会遇到的典型问题，主要是使用字幕方面，然后上传到媒体共享门户网站，特别是 Youtube 中，以及如何解决这些问题。跟我来。

### 背景故事

我选择的视频编辑软件是 Kdenlive，当我创建那愚蠢的 [Frankenstein](http://www.dedoimedo.com/computers/frankenstein-media.html) 片段时开始使用这个软件，从那以后它一直是我的忠实伙伴。通常，我将文件交给带有 VP8 视频编解码器和 Vorbis 音频编解码器的 WebM 容器来渲染，因为这是 Google 所喜欢的格式。事实上，我在过去七年里上传的大约 40 个不同的片段中都没有问题。

![Kdenlive, create project](/data/attachment/album/201708/21/090014fvkplsj0av1fk521.jpg)

![Kdenlive, render](/data/attachment/album/201708/21/090015brl9l99rxqm9rlgl.png)

但是，在完成了我的 Risitas＆Linux 项目之后，我遇到了一个困难。视频文件和字幕文件仍然是两个独立的实体，我需要以某种方式将它们放在一起。我最初关于字幕的文章提到了 Avidemux 和 Handbrake，这两个都是有效的选项。

但是，我对它们任何一个的输出都并不满意，而且由于种种原因，有些东西有所偏移。 Avidemux 不能很好处理视频编码，而 Handbrake 在最终输出中省略了几行字幕，而且字体是丑陋的。这个可以解决，但这不是今天的话题。

因此，我决定使用 VideoLAN（VLC） 将字幕嵌入视频。有几种方法可以做到这一点。你可以使用 “Media > Convert/Save” 选项，但这不能达到我们需要的。相反，你应该使用 “Media > Stream”，它带有一个更完整的向导，它还提供了一个我们需要的可编辑的代码转换选项 - 请参阅我的[教程](http://www.dedoimedo.com/computers/vlc-subtitles.html)关于字幕的部分。

### 错误！

嵌入字幕的过程并没那么简单的。你有可能遇到几个问题。本指南应该能帮助你解决这些问题，所以你可以专注于你的工作，而不是浪费时间调试怪异的软件错误。无论如何，在使用 VLC 中的字幕时，你将会遇到一小部分可能会遇到的问题。尝试以及出错，还有书呆子的设计。

### 没有可播放的流

你可能选择了奇怪的输出设置。你要仔细检查你是否选择了正确的视频和音频编解码器。另外，请记住，一些媒体播放器可能没有所有的编解码器。此外，确保在所有要播放的系统中都测试过了。

![No playable streams](/data/attachment/album/201708/21/090015a8p4og4o6ocd78b9.png)

### 字幕叠加两次

如果在第一步的流媒体向导中选择了 “Use a subtitle file”，则可能会发生这种情况。只需选择所需的文件，然后单击 “Stream”。取消选中该框。

![Select file](/data/attachment/album/201708/21/090016tcavdc9qgv34odci.png)

### 字幕没有输出

这可能是两个主要原因。一、你选择了错误的封装格式。在进行编辑之前，请确保在配置文件页面上正确标记了字幕。如果格式不支持字幕，它可能无法正常工作。

![Encapsulation](/data/attachment/album/201708/21/090016hfl365lln6zn77pj.png)

二、你可能已经在最终输出中启用了字幕编解码器渲染功能。你不需要这个。你只需要将字幕叠加到视频片段上。在单击 “Stream” 按钮之前，请检查生成的流输出字符串并删除 “scodec=” 的选项。

![Remove text from output string](/data/attachment/album/201708/21/090016f8izwyld2zinjdnz.png)

### 缺少编解码器的解决方法

这是一个常见的 [bug](https://trac.videolan.org/vlc/ticket/6184)，取决于编码器的实现的实验性，如果你选择以下配置文件，你将很有可能会看到它：“Video - H.264 + AAC (MP4)”。该文件将被渲染，如果你选择了字幕，它们也会被叠加上，但没有任何音频。但是，我们可以用技巧来解决这个问题。

![AAC codec](/data/attachment/album/201708/21/090017tkjkttuaq3ubdy6n.png)

![MP4A error](/data/attachment/album/201708/21/090017x9jm0n8jb0hkaaj9.png)

一个可能的技巧是从命令行使用 “--sout-ffmpeg-strict=-2” 选项（可能有用）启动 VLC。另一个更安全的解决方法是采用无音频视频，但是带有字幕叠加，并将不带字幕的原始项目作为音频源用 Kdenlive 渲染。听上去很复杂，下面是详细步骤：

* 将现有片段（包含音频）从视频移动到音频。删除其余的。
* 或者，使用渲染过的 WebM 文件作为你的音频源。
* 添加新的片段 - 带有字幕，并且没有音频。
* 将片段放置为新视频。
* 再次渲染为 WebM。

![Repeat render](/data/attachment/album/201708/21/090017z00ruk8l5xuh6uu8.jpg)

使用其他类型的音频编解码器将很有可能可用（例如 MP3），你将拥有一个包含视频、音频和字幕的完整项目。如果你很高兴没有遗漏，你可以现在上传到 Youtube 上。但是之后 ...

### Youtube 视频管理器和未知格式

如果你尝试上传非 WebM 片段（例如 MP4），则可能会收到未指定的错误，你的片段不符合媒体格式要求。我不知道为什么 VLC 会生成一个不符合 YouTube 规定的文件。但是，修复很容易。使用 Kdenlive 重新创建视频，将会生成带有所有正确的元字段和 Youtube 喜欢的文件。回到我原来的故事，我有 40 多个片段使用 Kdenlive 以这种方式创建。

P.S. 如果你的片段有有效的音频，则只需通过 Kdenlive 重新运行它。如果没有，重做视频/音频。根据需要将片段静音。最终，这就像叠加一样，除了你使用的视频来自于一个片段，而音频来自于另一个片段。工作完成。

### 更多阅读

我不想用链接重复自己或垃圾信息。在“软件与安全”部分，我有 VLC 上的片段，因此你可能需要咨询。前面提到的关于 VLC 和字幕的文章已经链接到大约六个相关教程，涵盖了其他主题，如流媒体、日志记录、视频旋转、远程文件访问等等。我相信你可以像专业人员一样使用搜索引擎。

### 总结

我希望你觉得本指南有帮助。它涵盖了很多，我试图使其直接而简单，并解决流媒体爱好者和字幕爱好者在使用 VLC 时可能遇到的许多陷阱。这都与容器和编解码器相关，而且媒体世界几乎没有标准的事实，当你从一种格式转换到另一种格式时，有时你可能会遇到边际情况。

如果你遇到了一些错误，这里的提示和技巧应该可以至少帮助你解决一些，包括无法播放的流、丢失或重复的字幕、缺少编解码器和 Kdenlive 解决方法、YouTube 上传错误、隐藏的 VLC 命令行选项，还有一些其他东西。是的，这些对于一段文字来说是很多的。幸运的是，这些都是好东西。保重，互联网的孩子们。如果你有任何其他要求，我将来的 VLC 文章应该会涵盖，请随意给我发邮件。

干杯。

---

via: <http://www.dedoimedo.com/computers/vlc-subtitles-errors.html>

作者：[Dedoimedo](http://www.dedoimedo.com/faq.html) 译者：[geekpi](https://github.com/geekpi) 校对：[wxy](https://github.com/wxy)

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


11 个使用 GNOME 3 桌面环境的理由

没有介绍 GNOME Shell Extensions 是这文章一大败笔

简单说就是扩展 GNOME 的程序或小工具.

没用过这个的, 哪里算用过了 GNOME

https://extensions.gnome.org/

然而我已经从gnome3转到kde plasma了，

我也已经从gnome3转到kde plasma了。

我个人还是觉得GNOME3需要一个能上推屏幕至屏幕下黄金分割线的全键触摸键盘。搞好了应该比平板的触控键盘好一点点！！！！这是我觉得目前有触控屏幕最大的困扰！！！

如果可以忍受

1) 比 GNOME 更耗电脑资源

2) 能用的 widgets 且满意的, 寥寥无几

用 KDE 又何妨..

你这就偏激了。桌面小插件我是一个没用，KDE Plasma 5 是跑在虚拟机里面的，要知道我那个笔记本还同时跑了 N 个其他的系统，虽然每个系统不一定都处在高负载。一次忘记接电源，就这样笔记本还是持续运行了几个小时，大概 4 小时吧，机器还是 4 年前的。至于内存消耗，虚拟机 RAM 总共提供 2.5 GiB, 开机进入桌面时，swap 零消耗，随着使用，最多的占用是浏览器。如果是 4GiB RAM，那 swap 最多就几十 MiB，少有达到 1NN MiB。

我没说用 KDE 不好呀,

我只是点出它的问题而已. 而且是经证实的.

到底谁偏激了?

不信你可以正式安装 Ubuntu Gnome 和 Kubuntu

各用一段时间才再来想想我说的对否..

同一台机, 不要用虚拟机. 还有安装后, 注意内存的消耗. 还要尝试各不同插件.

我更偏向使用gnome的原因是g系列软件的风格简约，我看到kde一堆选项就头大，g系列软件呈现的是一种不打扰的姿态……

找两个对应的软件，你就能感受到这一点

当然这是个人喜好，你爱用那个用那个

Gnome 有个问题, 就是应该强迫安装

显示系统内存消耗的插件 (显示在 panel),

不然内存不够用了也不知道, 很多人还以为造成系统卡的原因都是 Gnome 作怪.

你之前的回复

"

如果可以忍受

1) 比 GNOME 更耗电脑资源

2) 能用的 widgets 且满意的, 寥寥无几

"

难道这两句不是主观化，情绪化的回复？ 还没有技术支撑。

我昨天的回复里提到的 RAM 4GiB 是台式机。在笔记本上使用虚拟机是一个非常不错的解决方案，可以让用户免去驱动适配的烦恼，Linux 运行在一个比较理想的环境，也可避免电量的过多消耗。如果我愿意，可以为运行在虚拟机里的 Linux 划分 12 GiB RAM，外加足够大的虚拟硬盘。

笔记本附带的家庭版 Windows 能够满足我日常的需求，当初换硬盘还特地用 wim+diskpart，把原硬盘的系统迁

移到新硬盘上。

最初正式使用 Linux 系统时，花了一段时间从 Ubuntu Unity 到其他知名还是不知名的 DE，经过不断尝试评估，最终在“美德”二字里选择了‘德’，‘美’确实不对我胃口。P.S. 当初确认的版本还是 Debian 7 上的 KDE 4.x

linux看似很多选择，其实真正开箱即用，稳定好用的桌面几乎没有，到最后用户还得自己依个人习惯安装各种插件，时间成本真的是太高太高了。<br />
gnome3 无论用多牛X的硬件，输入用户名，密码都要等几秒进入桌面，无法关闭的shield, nautilus 搜索很慢，文件超过1W+越来越明显。估计官方gnome开发人员根本不用nautilus吧。文件管理器现在凑和用的也就thunar和nemo不过多文件选择慢的问题存在了好多年，一直反应也没人解决。nemo文件管理器目前用着还好，不过它源自gnome2 当然也继承了gnome 的缺点，那就是gnome捆绑性太高了，脱离了 gnome nemo

不管你信不信, 我已经像要求你的那样做了, 而不是只是说而已.

所以说, 是经验之谈所得到的结论. 不管你喜不喜欢, 这说的是事实.

如果你无法做到我要求的, 你跟本上就不能给予这2个平台公平的评论.

还虚拟机.. 做不到我说的, 就乖乖去睡觉去吧. 少废话连篇.

在我看来你只是不敢验证别人所说的, 就否定了. 我说东,你说西.

身为一位学者应该以 [开明] 的态度接受事物. 你有吗?

一直说别人偏激的人, 我觉得应先检讨自己下吧.

目前的 linux 安装还没有人工智能, 可以知道你要什么..

看来目前也只有命令行适合阁下..

有兴趣知道你说的 shield 是什么?

我之前评论中内存部分，已经是对你的所谓 KDE Plasma 占用内存高的回复。KDE 最初我是 05 年开始通过 knoppix 首先接触的。无论是虚拟机，还是物理机都是属于资源有限情况下（即便我的笔记本内存多，但也不能光给一个程序用），根据日常使用的体验，确认其内存包括 CPU 消耗都是自己能接受的。别看不起虚拟机，虚拟机是现在能够在一台机器同时使用 N 个操作系统的唯一方法。而且虚拟机的性能也不是你想的那么低，它甚至能勉强（不是幻灯片那种效果）在低于 4K 原生分辨率的屏幕，回放每秒 30 hz  的视频，当然音画是不同步的，不过不影响

“在我看来你只是不敢验证别人所说的, 就否定了. 我说东,你说西.

身为一位学者应该以 [开明] 的态度接受事物. 你有吗?

一直说别人偏激的人, 我觉得先检讨自己下吧.” -- 还没验证？没看我之前的回复？知道那个回复里 “美德” 二字分别指代的是那两个 DE 不？

一没有那么多时间精力，二又没有测试农场？叫人怎么做从底层框架到上层 DE 的测试？

作为一个普通用户，依据自己实际使用体验那评判一个产品，不是一种很合乎情理的做法？

观看。若换为 60hz 的 1080p 视频则完全没问题。

少废话,

英雄帖已发, 内容我也写得很清楚了.

yes /  no

就那么简单. 不然就睡你的觉去. 别浪费大家的时间.

+1，没有Extensions的Gnome3真心没法用。。。<br />
默认的Gnome，说得好听叫简洁，说得难听叫简陋，做减法做过头了。

其实Plasma5之后内存资源消耗没有以前那么大了，Plasma5基于KF5，基本所有组件都基于Qt5的KF5重写了。

反倒是Gnome3得得gtk3越来越重。

据我在Gentoo和ArchLinux上的使用情况来看，Gnome3和Plasma5在仅安装核心组件(gnome-shell/plasma-desktop)的情况下，基本开销并没有太大差异。

只是现阶段Plasma5的很多组件刚迁移到KF5，稳定性欠佳(经常有莫名其妙地奔溃)，跟已经迭代更新了好几年的Gnome3不能比。

很早就尝试过GNOME3，几年后仍然没什么改进，真是最糟糕的Linux桌面环境。最喜欢Unity节省了很多可用空间而且很漂亮，但是已被官方放弃吗，根据以后支持情况准备转KDE了。KDE可配置项太多了，反而很容易把界面弄乱，倒腾很久还不如默认的(lll￢ω￢)

虽然配置项过多，但也不是非得所有都要改。Windows 的组策略设置涉及到界面的也多，但是需要改的数量，也不是很多，大部分还是用默认设置。况且 KDE 有的设置还有预览，更方便查看更改后的效果。

我觉得elementory os 是一个不错的桌面选择

说的 好，我选择KDE

说的好，我选择KDE

>
> GNOME 3 桌面的设计目的是简单、易于访问和可靠。GNOME 的受欢迎程度证明达成了这些目标。
>
>
>

![11 reasons to use the GNOME desktop environment](/data/attachment/album/201708/22/114336gms3mx5u1dppx3ze.png "11 reasons to use the GNOME desktop environment")

去年年底，在我升级到 Fedora 25 后，新版本 [KDE](https://opensource.com/life/15/4/9-reasons-to-use-kde) Plasma 出现了一些问题，这使我难以完成任何工作。所以我决定尝试其他的 Linux 桌面环境有两个原因。首先，我需要完成我的工作。第二，多年来一直使用 KDE，我想可能是尝试一些不同的桌面的时候了。

我尝试了几个星期的第一个替代桌面是我在 1 月份文章中写到的 [Cinnamon](/article-8606-1.html)，然后我写了用了大约八个星期的[LXDE](/article-8434-1.html)，我发现这里有很多事情我都喜欢。我用了 [GNOME 3](https://www.gnome.org/gnome-3/) 几个星期来写了这篇文章。

像几乎所有的网络世界一样，GNOME 是缩写；它代表 “GNU 网络对象模型”（GNU Network Object Model）。GNOME 3 桌面设计的目的是简单、易于访问和可靠。GNOME 的受欢迎程度证明达成了这些目标。

GNOME 3 在需要大量屏幕空间的环境中非常有用。这意味着两个具有高分辨率的大屏幕，并最大限度地减少桌面小部件、面板和用来启动新程序之类任务的图标所需的空间。GNOME 项目有一套人机接口指南（HIG），用来定义人类应该如何与计算机交互的 GNOME 哲学。

### 我使用 GNOME 3 的十一个原因

1、 **诸多选择：** GNOME 以多种形式出现在我个人喜爱的 Fedora 等一些发行版上。你可以选择的桌面登录选项有 GNOME Classic、Xorg 上的 GNOME、GNOME 和 GNOME（Wayland）。从表面上看，启动后这些都是一样的，但它们使用不同的 X 服务器，或者使用不同的工具包构建。Wayland 在小细节上提供了更多的功能，例如动态滚动，拖放和中键粘贴。

2、 **入门教程：** 在用户第一次登录时会显示入门教程。它向你展示了如何执行常见任务，并提供了大量的帮助链接。教程在首次启动后关闭后也可轻松访问，以便随时访问该教程。教程非常简单直观，这为 GNOME 新用户提供了一个简单明了开始。要之后返回本教程，请点击 **Activities**，然后点击会显示程序的有九个点的正方形。然后找到并点击标为救生员图标的 **Help**。

3、 **桌面整洁：** 对桌面环境采用极简方法以减少杂乱，GNOME 设计为仅提供具备可用环境所必需的最低限度。你应该只能看到顶部栏（是的，它就叫这个），其他所有的都被隐藏，直到需要才显示。目的是允许用户专注于手头的任务，并尽量减少桌面上其他东西造成的干扰。

4、 **顶部栏：** 无论你想做什么，顶部栏总是开始的地方。你可以启动应用程序、注销、关闭电源、启动或停止网络等。不管你想做什么都很简单。除了当前应用程序之外，顶栏通常是桌面上唯一的其他对象。

5、 **dash：** 如下所示，在默认情况下, dash 包含三个图标。在开始使用应用程序时，会将它们添加到 dash 中，以便在其中显示最常用的应用程序。你也可以从应用程序查看器中将应用程序图标添加到 dash 中。

![gnome_fig_2.png](/data/attachment/album/201708/22/114337bds4o6uyg1ul42ge.png)

6、 **应用程序浏览器：** 我真的很喜欢这个可以从位于 GNOME 桌面左侧的垂直条上访问应用程序浏览器。除非有一个正在运行的程序，GNOME 桌面通常没有任何东西，所以你必须点击顶部栏上的 **Activities** 选区，点击 dash 底部的九个点组成的正方形，它是应用程序浏览器的图标。

![gnome_fig_1.png](/data/attachment/album/201708/22/114338f3v5wwv262a26bme.png)

如上所示，浏览器本身是一个由已安装的应用程序的图标组成的矩阵。矩阵下方有一对互斥的按钮，**Frequent** 和 **All**。默认情况下，应用程序浏览器会显示所有安装的应用。点击 **Frequent** 按钮，它会只显示最常用的应用程序。向上和向下滚动以找到要启动的应用程序。应用程序按名称按字母顺序显示。

[GNOME](https://www.gnome.org/gnome-3/) 官网和内置的帮助有更多关于浏览器的细节。

7、 **应用程序就绪通知：** 当新启动的应用程序的窗口打开并准备就绪时，GNOME 会在屏幕顶部显示一个整齐的通知。只需点击通知即可切换到该窗口。与在其他桌面上搜索新打开的应用程序窗口相比，这节省了一些时间。

8、 **应用程序显示：** 为了访问不可见的其它运行的应用程序，点击 **Activities** 菜单。这将在桌面上的矩阵中显示所有正在运行的应用程序。点击所需的应用程序将其带到前台。虽然当前应用程序显示在顶栏中，但其他正在运行的应用程序不会。

9、 **最小的窗口装饰：** 桌面上打开窗口也很简单。标题栏上唯一显示的按钮是关闭窗口的 “X”。所有其他功能，如最小化、最大化、移动到另一个桌面等，可以通过在标题栏上右键单击来访问。

10、 **自动创建的新桌面：** 在使用下一空桌面的时候将自动创建的新的空桌面。这意味着总是有一个空的桌面在需要时可以使用。我使用的所有其他的桌面系统都可以让你在桌面活动时设置桌面数量，但必须使用系统设置手动完成。

11、 **兼容性：** 与我所使用的所有其他桌面一样，为其他桌面创建的应用程序可在 GNOME 上正常工作。这功能让我有可能测试这些桌面，以便我可以写出它们。

### 最后的思考

GNOME 不像我以前用过的桌面。它的主要指导是“简单”。其他一切都要以简单易用为前提。如果你从入门教程开始，学习如何使用 GNOME 需要很少的时间。这并不意味着 GNOME 有所不足。它是一款始终保持不变的功能强大且灵活的桌面。

（题图：[Gunnar Wortmann](https://pixabay.com/en/users/karpartenhund-3077375/) 通过 [Pixabay](https://pixabay.com/en/garden-gnome-black-and-white-f%C3%B6hr-1584401/)。由 Opensource.com 修改。[CC BY-SA 4.0](https://creativecommons.org/licenses/by-sa/4.0/)）

---

作者简介：

David Both - David Both 是位于北卡罗来纳州罗利的 Linux 和开源倡导者。他已经在 IT 行业工作了四十多年，并为 IBM 教授 OS/2 超过 20 年。在 IBM，他在 1981 年为初始的 IBM PC 写了第一个培训课程。他为红帽教授 RHCE 课程，曾在 MCI Worldcom、思科和北卡罗来纳州工作。他一直在使用 Linux 和开源软件近 20 年。

---

via: <https://opensource.com/article/17/5/reasons-gnome>

作者：[David Both](https://opensource.com/users/dboth) 译者：[geekpi](https://github.com/geekpi) 校对：[wxy](https://github.com/wxy)

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


一文了解 Kubernetes 是什么？

Summary:
这是一篇 Kubernetes 的概览。

Kubernetes 是一个自动化部署、伸缩和操作应用程序容器的开源平台。

![](/data/attachment/album/201708/22/115549zy13hgl2n33gow00.png)

这是一篇 Kubernetes 的概览。

Kubernetes 是一个[自动化部署、伸缩和操作应用程序容器的开源平台](http://www.slideshare.net/BrianGrant11/wso2con-us-2015-kubernetes-a-platform-for-automating-deployment-scaling-and-operations)。

使用 Kubernetes，你可以快速、高效地满足用户以下的需求：

* 快速精准地部署应用程序
* 即时伸缩你的应用程序
* 无缝展现新特征
* 限制硬件用量仅为所需资源

我们的目标是培育一个工具和组件的生态系统，以减缓在公有云或私有云中运行的程序的压力。

#### Kubernetes 的优势

* **可移动**: 公有云、私有云、混合云、多态云
* **可扩展**: 模块化、插件化、可挂载、可组合
* **自修复**: 自动部署、自动重启、自动复制、自动伸缩

Google 公司于 2014 年启动了 Kubernetes 项目。Kubernetes 是在 [Google 的长达 15 年的成规模的产品级任务的经验下](https://research.google.com/pubs/pub43438.html)构建的，结合了来自社区的最佳创意和实践经验。

### 为什么选择容器?

想要知道你为什么要选择使用 [容器](https://aucouranton.com/2014/06/13/linux-containers-parallels-lxc-openvz-docker-and-more/)?

![](/data/attachment/album/201708/22/115217h7sbftbcbb8fby6u.jpg)

程序部署的*传统方法*是指通过操作系统包管理器在主机上安装程序。这样做的缺点是，容易混淆程序之间以及程序和主机系统之间的可执行文件、配置文件、库、生命周期。为了达到精准展现和精准回撤，你可以搭建一台不可变的虚拟机镜像。但是虚拟机体量往往过于庞大而且不可转移。

容器部署的*新的方式*是基于操作系统级别的虚拟化，而非硬件虚拟化。容器彼此是隔离的，与宿主机也是隔离的：它们有自己的文件系统，彼此之间不能看到对方的进程，分配到的计算资源都是有限制的。它们比虚拟机更容易搭建。并且由于和基础架构、宿主机文件系统是解耦的，它们可以在不同类型的云上或操作系统上转移。

正因为容器又小又快，每一个容器镜像都可以打包装载一个程序。这种一对一的“程序 - 镜像”联系带给了容器诸多便捷。有了容器，静态容器镜像可以在编译/发布时期创建，而非部署时期。因此，每个应用不必再等待和整个应用栈其它部分进行整合，也不必和产品基础架构环境之间进行妥协。在编译/发布时期生成容器镜像建立了一个持续地把开发转化为产品的环境。相似地，容器远比虚拟机更加透明，尤其在设备监控和管理上。这一点，在容器的进程生命周期被基础架构管理而非被容器内的进程监督器隐藏掉时，尤为显著。最终，随着每个容器内都装载了单一的程序，管理容器就等于管理或部署整个应用。

容器优势总结：

* **敏捷的应用创建与部署**：相比虚拟机镜像，容器镜像的创建更简便、更高效。
* **持续的开发、集成，以及部署**：在快速回滚下提供可靠、高频的容器镜像编译和部署（基于镜像的不可变性）。
* **开发与运营的关注点分离**：由于容器镜像是在编译/发布期创建的，因此整个过程与基础架构解耦。
* **跨开发、测试、产品阶段的环境稳定性**：在笔记本电脑上的运行结果和在云上完全一致。
* **在云平台与 OS 上分发的可转移性**：可以在 Ubuntu、RHEL、CoreOS、预置系统、Google 容器引擎，乃至其它各类平台上运行。
* **以应用为核心的管理**: 从在虚拟硬件上运行系统，到在利用逻辑资源的系统上运行程序，从而提升了系统的抽象层级。
* **松散耦联、分布式、弹性、无拘束的[微服务](https://martinfowler.com/articles/microservices.html)**：整个应用被分散为更小、更独立的模块，并且这些模块可以被动态地部署和管理，而不再是存储在大型的单用途机器上的臃肿的单一应用栈。
* **资源隔离**：增加程序表现的可预见性。
* **资源利用率**：高效且密集。

#### 为什么我需要 Kubernetes，它能做什么?

至少，Kubernetes 能在实体机或虚拟机集群上调度和运行程序容器。而且，Kubernetes 也能让开发者斩断联系着实体机或虚拟机的“锁链”，从**以主机为中心**的架构跃至**以容器为中心**的架构。该架构最终提供给开发者诸多内在的优势和便利。Kubernetes 提供给基础架构以真正的**以容器为中心**的开发环境。

Kubernetes 满足了一系列产品内运行程序的普通需求，诸如：

* [协调辅助进程](https://kubernetes.io/docs/concepts/workloads/pods/pod/)，协助应用程序整合，维护一对一“程序 - 镜像”模型。
* [挂载存储系统](https://kubernetes.io/docs/concepts/storage/volumes/)
* [分布式机密信息](https://kubernetes.io/docs/concepts/configuration/secret/)
* [检查程序状态](https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-probes/)
* [复制应用实例](https://kubernetes.io/docs/concepts/workloads/controllers/replicationcontroller/)
* [使用横向荚式自动缩放](https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/)
* [命名与发现](https://kubernetes.io/docs/concepts/services-networking/connect-applications-service/)
* [负载均衡](https://kubernetes.io/docs/concepts/services-networking/service/)
* [滚动更新](https://kubernetes.io/docs/tasks/run-application/rolling-update-replication-controller/)
* [资源监控](https://kubernetes.io/docs/tasks/debug-application-cluster/resource-usage-monitoring/)
* [访问并读取日志](https://kubernetes.io/docs/concepts/cluster-administration/logging/)
* [程序调试](https://kubernetes.io/docs/tasks/debug-application-cluster/debug-application-introspection/)
* [提供验证与授权](https://kubernetes.io/docs/admin/authorization/)

以上兼具平台即服务（PaaS）的简化和基础架构即服务（IaaS）的灵活，并促进了在平台服务提供商之间的迁移。

#### Kubernetes 是一个什么样的平台?

虽然 Kubernetes 提供了非常多的功能，总会有更多受益于新特性的新场景出现。针对特定应用的工作流程，能被流水线化以加速开发速度。特别的编排起初是可接受的，这往往需要拥有健壮的大规模自动化机制。这也是为什么 Kubernetes 也被设计为一个构建组件和工具的生态系统的平台，使其更容易地部署、缩放、管理应用程序。

<ruby>   标签  <rp>  （ </rp> <rt>  label </rt> <rp>  ） </rp></ruby>可以让用户按照自己的喜好组织资源。 <ruby>   注释  <rp>  （ </rp> <rt>  annotation </rt> <rp>  ） </rp></ruby>让用户在资源里添加客户信息，以优化工作流程，为管理工具提供一个标示调试状态的简单方法。

此外，[Kubernetes 控制面板](https://kubernetes.io/docs/concepts/overview/components/)是由开发者和用户均可使用的同样的 [API](https://kubernetes.io/docs/reference/api-overview/) 构建的。用户可以编写自己的控制器，比如 <ruby>   调度器  <rp>  （ </rp> <rt>  scheduler </rt> <rp>  ） </rp></ruby>，使用可以被通用的[命令行工具](https://kubernetes.io/docs/user-guide/kubectl-overview/)识别的[他们自己的 API](https://git.k8s.io/community/contributors/design-proposals/extending-api.md)。

这种[设计](https://github.com/kubernetes/community/blob/master/contributors/design-proposals/principles.md)让大量的其它系统也能构建于 Kubernetes 之上。

#### Kubernetes 不是什么？

Kubernetes 不是传统的、全包容的平台即服务（Paas）系统。它尊重用户的选择，这很重要。

Kubernetes：

* 并不限制支持的程序类型。它并不检测程序的框架 (例如，[Wildfly](http://wildfly.org/))，也不限制运行时支持的语言集合 (比如， Java、Python、Ruby)，也不仅仅迎合 [12 因子应用程序](https://12factor.net/)，也不区分 *应用* 与 *服务* 。Kubernetes 旨在支持尽可能多种类的工作负载，包括无状态的、有状态的和处理数据的工作负载。如果某程序在容器内运行良好，它在 Kubernetes 上只可能运行地更好。
* 不提供中间件（例如消息总线）、数据处理框架（例如 Spark）、数据库（例如 mysql），也不把集群存储系统（例如 Ceph）作为内置服务。但是以上程序都可以在 Kubernetes 上运行。
* 没有“点击即部署”这类的服务市场存在。
* 不部署源代码，也不编译程序。持续集成 (CI) 工作流程是不同的用户和项目拥有其各自不同的需求和表现的地方。所以，Kubernetes 支持分层 CI 工作流程，却并不监听每层的工作状态。
* 允许用户自行选择日志、监控、预警系统。（ Kubernetes 提供一些集成工具以保证这一概念得到执行）
* 不提供也不管理一套完整的应用程序配置语言/系统（例如 [jsonnet](https://github.com/google/jsonnet)）。
* 不提供也不配合任何完整的机器配置、维护、管理、自我修复系统。

另一方面，大量的 PaaS 系统运行*在* Kubernetes 上，诸如 [Openshift](https://www.openshift.org/)、[Deis](http://deis.io/)，以及 [Eldarion](http://eldarion.cloud/)。你也可以开发你的自定义 PaaS，整合上你自选的 CI 系统，或者只在 Kubernetes 上部署容器镜像。

因为 Kubernetes 运营在应用程序层面而不是在硬件层面，它提供了一些 PaaS 所通常提供的常见的适用功能，比如部署、伸缩、负载平衡、日志和监控。然而，Kubernetes 并非铁板一块，这些默认的解决方案是可供选择，可自行增加或删除的。

而且， Kubernetes 不只是一个*编排系统* 。事实上，它满足了编排的需求。 *编排* 的技术定义是，一个定义好的工作流程的执行：先做 A，再做 B，最后做 C。相反地， Kubernetes 囊括了一系列独立、可组合的控制流程，它们持续驱动当前状态向需求的状态发展。从 A 到 C 的具体过程并不唯一。集中化控制也并不是必须的；这种方式更像是*编舞*。这将使系统更易用、更高效、更健壮、复用性、扩展性更强。

#### Kubernetes 这个单词的含义？k8s？

**Kubernetes** 这个单词来自于希腊语，含义是 *舵手* 或 *领航员* 。其词根是 *governor* 和 [cybernetic](http://www.etymonline.com/index.php?term=cybernetics)。 *K8s* 是它的缩写，用 8 字替代了“ubernete”。

---

via: <https://kubernetes.io/docs/concepts/overview/what-is-kubernetes/>

作者：[kubernetes.io](https://kubernetes.io/) 译者：[songshuang00](https://github.com/songsuhang00) 校对：[wxy](https://github.com/wxy)

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


免费学习 Docker 的最佳方法：Play-with-docker（PWD）

Summary: PWD 像是一个 Docker 游乐场，用户在几秒钟内就可以运行 Docker 命令。 还可以在浏览器中安装免费的 Alpine Linux 虚拟机，然后在虚拟机里面构建和运行 Docker 容器，甚至可以使用 Docker 集群模式创建集群。

![](/data/attachment/album/201708/22/141737c0buuo823dfyzxoz.jpg)

去年在柏林的分布式系统峰会上，Docker 的负责人 [Marcos Nils](https://www.twitter.com/marcosnils) 和 [Jonathan Leibiusky](https://www.twitter.com/xetorthio) 宣称已经开始研究浏览器内置 Docker 的方案，帮助人们学习 Docker。 几天后，[Play-with-docker](http://play-with-docker.com/)（PWD）就诞生了。

PWD 像是一个 Docker 游乐场，用户在几秒钟内就可以运行 Docker 命令。 还可以在浏览器中安装免费的 Alpine Linux 虚拟机，然后在虚拟机里面构建和运行 Docker 容器，甚至可以使用 [Docker 集群模式](https://docs.docker.com/engine/swarm/)创建集群。 有了 Docker-in-Docker（DinD）引擎，甚至可以体验到多个虚拟机/个人电脑的效果。 除了 Docker 游乐场外，PWD 还包括一个培训站点 [training.play-with-docker.com](http://training.play-with-docker.com/)，该站点提供大量的难度各异的 Docker 实验和测验。

如果你错过了峰会，Marcos 和 Jonathan 在最后一场 DockerCon Moby Cool Hack 会议中展示了 PWD。 观看下面的视频，深入了解其基础结构和发展路线图。

在过去几个月里，Docker 团队与 Marcos、Jonathan，还有 Docker 社区的其他活跃成员展开了密切合作，为项目添加了新功能，为培训部分增加了 Docker 实验室。

### PWD: 游乐场

以下快速的概括了游乐场的新功能：

#### 1、 PWD Docker Machine 驱动和 SSH

随着 PWD 成功的成长，社区开始问他们是否可以使用 PWD 来运行自己的 Docker 研讨会和培训。 因此，对项目进行的第一次改进之一就是创建 [PWD Docker Machine 驱动](https://github.com/play-with-docker/docker-machine-driver-pwd/releases/tag/v0.0.5)，从而用户可以通过自己喜爱的终端轻松创建管理 PWD 主机，包括使用 SSH 相关命令的选项。 下面是它的工作原理：

![Play With Docker](/data/attachment/album/201708/22/141830o5beyvabi2i161w2.gif)

#### 2、 支持文件上传

Marcos 和 Jonathan 还带来了另一个炫酷的功能就是可以在 PWD 实例中通过拖放文件的方式将 Dockerfile 直接上传到 PWD 窗口。

![](/data/attachment/album/201708/22/141838yg4jvqggv4vddvcg.gif)

#### 3、 模板会话

除了文件上传之外，PWD 还有一个功能，可以使用预定义的模板在几秒钟内启动 5 个节点的群集。

![Play with Docker](/data/attachment/album/201708/22/141901xw8q5mqu07m1r8wb.gif)

#### 4、 一键使用 Docker 展示你的应用程序

PWD 附带的另一个很酷的功能是它的内嵌按钮，你可以在你的站点中使用它来设置 PWD 环境，并快速部署一个构建好的堆栈，另外还有一个 [chrome 扩展](https://chrome.google.com/webstore/detail/play-with-docker/kibbhpioncdhmamhflnnmfonadknnoan) ，可以将 “Try in PWD” 按钮添加 DockerHub 最流行的镜像中。 以下是扩展程序的一个简短演示：

![Play with Docker](/data/attachment/album/201708/22/141902r7794c409jbk0c7m.gif)

### PWD 培训站点

[training.play-with-docker.com](http://training.play-with-docker.com/) 站点提供了大量新的实验。有一些值得注意的两点，包括两个来源于奥斯丁召开的 DockerCon 中的动手实践的实验，还有两个是 Docker 17.06CE 版本中亮眼的新功能：

* [可以动手实践的 Docker 网络实验](http://training.play-with-docker.com/docker-networking-hol/)
* [可以动手实践的 Docker 编排实验](http://training.play-with-docker.com/orchestration-hol/)
* [多阶段构建](http://training.play-with-docker.com/multi-stage/)
* [Docker 集群配置文件](http://training.play-with-docker.com/swarm-config/)

总而言之，现在有 36 个实验，而且一直在增加。 如果你想贡献实验，请从查看 [GitHub 仓库](https://github.com/play-with-docker/play-with-docker.github.io)开始。

### PWD 用例

根据网站访问量和我们收到的反馈，很可观的说，PWD 现在有很大的吸引力。下面是一些最常见的用例：

* 紧跟最新开发版本，尝试新功能。
* 快速建立集群并启动复制服务。
* 通过互动教程学习： [training.play-with-docker.com](http://training.play-with-docker.com/)。
* 在会议和集会上做演讲。
* 召开需要复杂配置的高级研讨会，例如 Jérôme’的 [Docker 编排高级研讨会](https://github.com/docker/labs/tree/master/Docker-Orchestration)。
* 和社区成员协作诊断问题检测问题。

参与 PWD：

* 通过[向 PWD 提交 PR](https://github.com/play-with-docker/) 做贡献
* 向 [PWD 培训站点](https://github.com/play-with-docker/training)贡献

---

作者简介：

Victor 是 Docker, Inc. 的高级社区营销经理。他喜欢优质的葡萄酒、象棋和足球，上述爱好不分先后顺序。 Victor 的 tweet：@vcoisne。

---

via: <https://blog.docker.com/2017/07/best-way-learn-docker-free-play-docker-pwd/>

作者：[Victor](https://blog.docker.com/author/victor_c/) 译者：[Flowsnow](https://github.com/Flowsnow) 校对：[wxy](https://github.com/wxy)

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


物联网助长了 Linux 恶意软件

Summary: 针对 Linux 系统的恶意软件正在增长，这主要是由于连接到物联网设备的激增。“制造商对他们如何配置这些设备不屑一顾，所以他们使这些设备的利用变得非常轻易。”

这些玩意都不是GPL滴！！！。所以不要和Linux混为一谈！！！GPL本身可以高效的检出恶意代码。。。还是那句名言说的好，只要有更多的人关注代码，代码的安全就会更高！！！

“只要有更多的人关注代码，代码的安全就会更高！！！”事实证明，这只是一种非常理想化的想象，现实中没有太多的人自愿来进行代码审核。

“只要有更多的人关注代码，代码的安全就会更高”

![linus-iot-security](/data/attachment/album/201708/22/233549sdvoy3kzgq9vfkkk.jpg)

针对 Linux 系统的恶意软件正在增长，这主要是由于连接到物联网设备的激增。

这是网络安全设备制造商 [WatchGuard Technologies](http://www.watchguard.com/) 上周发布的的一篇报告中所披露的。该报告分析了全球 26,000 多件设备收集到的数据，今年第一季度的前 10 名恶意软件中发现了三个针对 Linux 的恶意软件，而上一季度仅有一个。

WatchGuard 的 CTO Corey Nachreiner 和安全威胁分析师 Marc Laliberte 写道：“Linux 上的攻击和恶意软件正在兴起。我们相信这是因为 IoT 设备的系统性弱点与其快速增长相结合的结果，它正在引导僵尸网络的作者们转向 Linux 平台。”

他们建议“阻止入站的 Telnet 和 SSH，以及使用复杂的管理密码，可以防止绝大多数潜在的攻击”。

### 黑客的新大道

Laliberte 观察到，Linux 恶意软件在去年年底随着 Mirai 僵尸网络开始增长。Mirai 在九月份曾经用来攻击部分互联网的基础设施，迫使数百万用户断线。

他告诉 LinuxInsider，“现在，随着物联网设备的飞速发展，一条全新的大道正在向攻击者们开放。我们相信，随着互联网上新目标的出现，Linux 恶意软件会逐渐增多。”Laliberte 继续说，物联网设备制造商并没有对安全性表现出很大的关注。他们的目标是使他们的设备能够使用、便宜，能够快速制造。

他说：“开发过程中他们真的不关心安全。”

### 轻易捕获

[Alert Logic](http://www.alertlogic.com/) 的网络安全布道师 Paul Fletcher 说，大多数物联网制造商都使用 Linux 的裁剪版本，因为操作系统需要最少的系统资源来运行。

他告诉 LinuxInsider，“当你将大量与互联网连接的物联网设备结合在一起时，这相当于在线大量的 Linux 系统，它们可用于攻击。”

为了使设备易于使用，制造商使用的协议对黑客来说也是用户友好的。Fletcher 说：“攻击者可以访问这些易受攻击的接口，然后上传并执行他们选择的恶意代码。”

他指出，厂商经常给他们的设备很差的默认设置。Fletcher 说：“通常，管理员帐户是空密码或易于猜测的默认密码，例如 ‘password123’。”

[SANS 研究所](http://www.sans.org/) 首席研究员 Johannes B. Ullrich 表示，安全问题通常“本身不是 Linux 特有的”。他告诉 LinuxInsider，“制造商对他们如何配置这些设备不屑一顾，所以他们使这些设备的利用变得非常轻易。”

### 10 大恶意软件

这些 Linux 恶意软件在 WatchGuard 的第一季度的统计数据中占据了前 10 名的位置：

* Linux/Exploit，它使用几种木马来扫描可以加入僵尸网络的设备。
* Linux/Downloader，它使用恶意的 Linux shell 脚本。Linux 可以运行在许多不同的架构上，如 ARM、MIPS 和传统的 x86 芯片组。报告解释说，一个为某个架构编译的可执行文件不能在不同架构的设备上运行。因此，一些 Linux 攻击利用 dropper shell 脚本下载并安装适合它们所要感染的体系架构的恶意组件。
* Linux/Flooder，它使用了 Linux 分布式拒绝服务工具，如 Tsunami，用于执行 DDoS 放大攻击，以及 Linux 僵尸网络（如 Mirai）使用的 DDoS 工具。报告指出：“正如 Mirai 僵尸网络向我们展示的，基于 Linux 的物联网设备是僵尸网络军队的主要目标。

### Web 服务器战场

WatchGuard 报告指出，敌人攻击网络的方式发生了变化。

公司发现，到 2016 年底，73％ 的 Web 攻击针对客户端 - 浏览器和配套软件。今年头三个月发生了彻底改变，82％ 的 Web 攻击集中在 Web 服务器或基于 Web 的服务上。报告合著者 Nachreiner 和 Laliberte 写道：“我们不认为下载式的攻击将会消失，但似乎攻击者已经集中力量和工具来试图利用 Web 服务器攻击。”

他们也发现，自 2006 年底以来，杀毒软件的有效性有所下降。Nachreiner 和 Laliberte 报道说：“连续的第二个季度，我们看到使用传统的杀毒软件解决方案漏掉了使用我们更先进的解决方案可以捕获的大量恶意软件，实际上已经从 30％ 上升到了 38％ 漏掉了。”

他说：“如今网络犯罪分子使用许多精妙的技巧来重新包装恶意软件，从而避免了基于签名的检测。这就是为什么使用基本的杀毒软件的许多网络成为诸如赎金软件之类威胁的受害者。”

---

作者简介:

John P. Mello Jr.自 2003 年以来一直是 ECT 新闻网记者。他的重点领域包括网络安全、IT问题、隐私权、电子商务、社交媒体、人工智能、大数据和消费电子。 他撰写和编辑了众多出版物，包括“波士顿商业杂志”、“波士顿凤凰”、“Megapixel.Net” 和 “政府安全新闻”。

---

via: <http://www.linuxinsider.com/story/84652.html>

作者：[John P. Mello Jr](john.mello@newsroom.ectnews.com) 译者：[geekpi](https://github.com/geekpi) 校对：[wxy](https://github.com/wxy)

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


一个时代的结束：Solaris 系统的那些年，那些事

Summary: 现在看来，Oracle 公司正在通过取消 Solaris 12 而终止 Solaris 的功能开发，这里我们要回顾下多年来在 Phoronix 上最受欢迎的 Solaris 重大事件和新闻。

oracle干死太多项目了。。

我没有生在那个最好的时代，感到有点可惜呢

我2009年，接触过120万用户量的DHCP服务“设计及部署，实施，运维”项目，给上海电信做IPTV机顶盒分配DHCP地址。

那时候20多台DHCP服务器，底层系统就是5.10 的Solaris。

整套系统，包括数据库，网络，服务器我一个人维护了3年，直到跳槽离职，中途还接了很多私活，移动一些dhcp服务器和广电的，我用的都是open Solaris架设的

对这个系统，还是挺有感情的。

![](/data/attachment/album/201708/23/133658b8kossou8xs63s4u.jpg)

现在看来，Oracle 公司正在通过取消 Solaris 12 而[终止 Solaris 的功能开发](http://www.phoronix.com/scan.php?page=news_item&px=No-Solaris-12)，这里我们要回顾下多年来在 Phoronix 上最受欢迎的 Solaris 重大事件和新闻。

这里有许多关于 Solaris 的有趣／重要的回忆。

![](/data/attachment/album/201708/23/134253x46h33nc3322fha3.jpg)

在 Sun Microsystems 时期，我真的对 Solaris 很感兴趣。在 Phoronix 上我们一直重点关注 Linux 的同时，经常也有 Solaris 的文章出现。 Solaris 玩起来很有趣，OpenSolaris／SXCE 是伟大的产物，我将 Phoronix 测试套件移植到 Solaris 上，我们与 Sun Microsystems 人员有密切的联系，也出现在 Sun 的许多活动中。

![](/data/attachment/album/201708/23/133843a5ykil00ubzlt5iu.jpeg)

*在那些日子里 Sun 有一些相当独特的活动...*

不幸的是，自从 Oracle 公司收购了 Sun 公司， Solaris 就如坠入深渊一样。最大的打击大概是 Oracle 结束了 OpenSolaris ，并将所有 Solaris 的工作转移到专有模式...

![](/data/attachment/album/201708/23/134142v1vvfg20ezfae70v.jpeg)

在 Sun 时代的 Solaris 有很多美好的回忆，所以 Oracle 在其计划中抹去了 Solaris 12 之后，我经常在 Phoronix 上翻回去看一些之前 Solaris 的经典文章，期待着能从 Oracle 听到 “Solaris 11” 下一代的消息，重启 Solaris 项目的开发。

![](/data/attachment/album/201708/23/134154whskhzwdpzswlzgl.jpeg)

虽然在后 Solaris 的世界中，看到 Oracle 对 ZFS 所做的事情以及他们在基于 RHEL 的 Oracle Enterprise Linux 上下的重注将会很有趣，但时间将会告诉我们一切。

![](/data/attachment/album/201708/23/134206dytcc5zuy1tpqqoc.jpeg)

无论如何，这是回顾自 2004 年以来我们最受欢迎的 Solaris 文章：

### 2016/12/1 [Oracle 或许会罐藏 Solaris](http://www.phoronix.com/scan.php?page=news_item&px=Oracle-Solaris-Demise-Rumors)

Oracle 可能正在拔掉 Solaris 的电源插头，据一些新的传闻说。

### 2013/6/9 [OpenSXCE 2013.05 拯救 Solaris 社区](http://www.phoronix.com/scan.php?page=news_item&px=MTM4Njc)

作为 Solaris 社区版的社区复兴，OpenSXCE 2013.05 出现在网上。

### 2013/2/2 [Solaris 12 可能最终带来 Radeon KMS 驱动程序](http://www.phoronix.com/scan.php?page=news_item&px=MTI5MTU)

看起来，Oracle 可能正在准备发布自己的 AMD Radeon 内核模式设置（KMS）驱动程序，并引入到 Oracle Solaris 12 中。

### 2012/10/4 [Oracle Solaris 11.1 提供 300 个以上增强功能](http://www.phoronix.com/scan.php?page=news_item&px=MTE5OTQ)

Oracle昨天在旧金山的 Oracle OpenWorld 会议上发布了 Solaris 11.1 。

![](/data/attachment/album/201708/23/134218q56mf58mzvwnnvr6.jpeg)

### 2012/1/9 [Oracle 尚未澄清 Solaris 11 内核来源](http://www.phoronix.com/scan.php?page=news_item&px=MTAzOTc)

一个月前，Phoronix 是第一个注意到 Solaris 11 内核源代码通过 Torrent 站点泄漏到网上的信息。一个月后，甲骨文还没有正式评论这个情况。

### 2011/12/19 [Oracle Solaris 11 内核源代码泄漏](http://www.phoronix.com/scan.php?page=news_item&px=MTAzMDE)

似乎 Solaris 11的内核源代码在过去的一个周末被泄露到了网上。

### 2011/8/25 [对于 BSD，Solaris 的 GPU 驱动程序的悲惨状态](http://www.phoronix.com/scan.php?page=news_item&px=OTgzNA)

昨天在邮件列表上出现了关于干掉所有旧式 Mesa 驱动程序的讨论。这些旧驱动程序没有被积极维护，支持复古的图形处理器，并且没有更新支持新的 Mesa 功能。英特尔和其他开发人员正在努力清理 Mesa 核心，以将来增强这一开源图形库。这种清理 Mesa，对 BSD 和 Solaris 用户也有一些影响。

### 2010/8/13 [告别 OpenSolaris，Oracle 刚刚把它干掉](http://www.phoronix.com/scan.php?page=news_item&px=ODUwNQ)

Oracle 终于宣布了他们对 Solaris 操作系统和 OpenSolaris 平台的计划，而且不是好消息。OpenSolaris 将实际死亡，未来将不会有更多的 Solaris 版本出现 - 包括长期延期的 2010 年版本。Solaris 仍然会继续存在，现在 Oracle 正在忙于明年发布的 Solaris 11，但仅在 Oracle 的企业版之后才会发布 “Solaris 11 Express” 作为 OpenSolaris 的类似产品。

### 2010/2/22 [Oracle 仍然要对 OpenSolaris 进行更改](http://www.phoronix.com/scan.php?page=news_item&px=ODAwNg)

自从 Oracle 完成对 Sun Microsystems 的收购以来，已经有了许多变化，这个 Sun 最初支持的开源项目现在已经不再被 Oracle 支持，并且对其余的开源产品进行了重大改变。 Oracle 表现出并不太开放的意图的开源项目之一是 OpenSolaris 。 Solaris Express 社区版（SXCE）上个月已经关闭，并且也没有预计 3 月份发布的下一个 OpenSolaris 版本（OpenSolaris 2010.03）的信息流出。

### 2007/9/10 [Solaris Express 社区版 Build 72](http://www.phoronix.com/scan.php?page=news_item&px=NjA0Nw)

对于那些想要在 “印第安纳项目” 发布之前尝试 OpenSolaris 软件中最新最好的软件的人来说，现在可以使用 Solaris Express 社区版 Build 72。Solaris Express 社区版（SXCE）Build 72 可以从 OpenSolaris.org 下载。同时，预计将在下个月推出 Sun 的 “印第安纳项目” 项目的预览版。

### 2007/9/6 [ATI R500/600 驱动要支持 Solaris 了?](http://www.phoronix.com/scan.php?page=news_item&px=NjA0Mg)

虽然没有可用于 Solaris/OpenSolaris 或 \* BSD 的 ATI fglrx 驱动程序，现在 AMD 将向 X.Org 开发人员和开源驱动程序交付规范，但对于任何使用 ATI 的 Radeon X1000 “R500” 或者 HD 2000“R600” 系列的 Solaris 用户来说，这肯定是有希望的。将于下周发布的开源 X.Org 驱动程序距离成熟尚远，但应该能够相对容易地移植到使用 X.Org 的 Solaris 和其他操作系统上。 AMD 今天宣布的针对的是 Linux 社区，但它也可以帮助使用 ATI 硬件的 Solaris/OpenSolaris 用户。特别是随着印第安纳项目的即将推出，开源 R500/600 驱动程序移植就只是时间问题了。

### 2007/9/5 [Solaris Express 社区版 Build 71](http://www.phoronix.com/scan.php?page=news_item&px=NjAzNQ)

Solaris Express 社区版（SXCE）现已推出 Build 71。您可以在 OpenSolaris.org 中找到有关 Solaris Express 社区版 Build 71 的更多信息。另外，在 Linux 内核峰会上，AMD 将提供 GPU 规格的消息，由此产生的 X.Org 驱动程序将来可能会导致 ATI 硬件上 Solaris/OpenSolaris 有所改善。

### 2007/8/27 [Linux 的 Solaris 容器](http://www.phoronix.com/scan.php?page=news_item&px=NjAxMQ)

Sun Microsystems 已经宣布，他们将很快支持适用于 Linux 应用程序的 Solaris 容器。这样可以在 Solaris 下运行 Linux 应用程序，而无需对二进制包进行任何修改。适用于 Linux 的 Solaris 容器将允许从 Linux 到 Solaris 的平滑迁移，协助跨平台开发以及其他优势。当该支持到来时，这个时代就“快到了”。

### 2007/8/23 [OpenSolaris 开发者峰会](http://www.phoronix.com/scan.php?page=news_item&px=NjAwNA)

今天早些时候在 OpenSolaris 论坛上发布了第一次 OpenSolaris 开发人员峰会的消息。这次峰会将在十月份在加州大学圣克鲁斯分校举行。 Sara Dornsife 将这次峰会描述为“不是与演示文稿或参展商举行会议，而是一个亲自参与的协作工作会议，以计划下一期的印第安纳项目。” 伊恩·默多克（Ian Murdock） 将在这个“印第安纳项目”中进行主题演讲，但除此之外，该计划仍在计划之中。 Phoronix 可能会继续跟踪此事件，您可以在 Solaris 论坛上讨论此次峰会。

### 2007/8/18 [Solaris Express 社区版 Build 70](http://www.phoronix.com/scan.php?page=news_item&px=NTk4Nw)

名叫 "Nevada" 的 Solaris Express 社区版 Build 70 (SXCE snv\_70) 现在已经发布。有关下载链接的通知可以在 OpenSolaris 论坛中找到。还有公布了其网络存储的 Build 71 版本，包括来自 Qlogic 的光纤通道 HBA 驱动程序的源代码。

### 2007/8/16 [IBM 使用 Sun Solaris 的系统](http://www.phoronix.com/scan.php?page=news_item&px=NTk4NA)

Sun Microsystems 和 IBM正在举行电话会议，他们刚刚宣布，IBM 将开始在服务器上使用 Sun 的 Solaris 操作系统。这些 IBM 服务器包括基于 x86 的服务器系统以及 Blade Center 服务器。官方新闻稿刚刚发布，可以在 sun 新闻室阅读。

### 2007/8/9 [OpenSolaris 不会与 Linux 合并](http://www.phoronix.com/scan.php?page=news_item&px=NTk2Ng)

在旧金山的 LinuxWorld 2007 上，Andrew Morton 在主题演讲中表示， OpenSolaris 的关键组件不会出现在 Linux 内核中。事实上，莫顿甚至表示 “非常遗憾 OpenSolaris 活着”。OpenSolaris 的一些关键组件包括 Zones、ZFS 和 DTrace 。虽然印第安纳州项目有可能将这些项目转变为 GPLv3 项目... 更多信息参见 ZDNET。

### 2007/7/27 [Solaris Xen 已经更新](http://www.phoronix.com/scan.php?page=news_item&px=NTkzMQ)

已经有一段时间了，Solaris Xen 终于更新了。约翰·莱文（John Levon）表示，这一最新版本基于 Xen 3.0.4 和 Solaris “Nevada” Build 66。这一最新版本的改进包括 PAE 支持、HVM 支持、新的 virt-manager 工具、改进的调试支持以及管理域支持。可以在 Sun 的网站上找到 2007 年 7 月 Solaris Xen 更新的下载。

### 2007/7/25 [Solaris 10 7/07 HW 版本](http://www.phoronix.com/scan.php?page=news_item&px=NTkyMA)

Solaris 10 7/07 HW 版本的文档已经上线。如 Solaris 发行注记中所述，Solaris 10 7/07 仅适用于 SPARC Enterprise M4000-M9000 服务器，并且没有 x86/x64 版本可用。所有平台的最新 Solaris 更新是 Solaris 10 11/06 。您可以在 Phoronix 论坛中讨论 Solaris 7/07。

### 2007/7/16 [来自英特尔的 Solaris 电信服务器](http://www.phoronix.com/scan.php?page=news_item&px=NTg5Nw)

今天宣布推出符合 NEBS、ETSI 和 ATCA 合规性的英特尔体系的 Sun Solaris 电信机架服务器和刀片服务器。在这些新的运营商级平台中，英特尔运营商级机架式服务器 TIGW1U 支持 Linux 和 Solaris 10，而 Intel NetStructure MPCBL0050 SBC 也将支持这两种操作系统。今天的新闻稿可以在这里阅读。

然后是 Solaris 分类中最受欢迎的特色文章：

### [Ubuntu vs. OpenSolaris vs. FreeBSD 基准测试](http://www.phoronix.com/vr.php?view=13149)

在过去的几个星期里，我们提供了几篇关于 Ubuntu Linux 性能的深入文章。我们已经开始提供 Ubuntu 7.04 到 8.10 的基准测试，并且发现这款受欢迎的 Linux 发行版的性能随着时间的推移而变慢，随之而来的是 Mac OS X 10.5 对比 Ubuntu 8.10 的基准测试和其他文章。在本文中，我们正在比较 Ubuntu 8.10 的 64 位性能与 OpenSolaris 2008.11 和 FreeBSD 7.1 的最新测试版本。

### [NVIDIA 的性能：Windows vs. Linux vs. Solaris](http://www.phoronix.com/vr.php?view=11968)

本周早些时候，我们预览了 Quadro FX1700，它是 NVIDIA 的中端工作站显卡之一，基于 G84GL 内核，而 G84GL 内核又源于消费级 GeForce 8600 系列。该 PCI Express 显卡提供 512MB 的视频内存，具有两个双链路 DVI 连接，并支持 OpenGL 2.1 ，同时保持最大功耗仅为 42 瓦。正如我们在预览文章中提到的，我们将不仅在 Linux 下查看此显卡的性能，还要在 Microsoft Windows 和 Sun 的 Solaris 中测试此工作站解决方案。在今天的这篇文章中，我们正在这样做，因为我们测试了 NVIDIA Quadro FX1700 512MB 与这些操作系统及其各自的二进制显示驱动程序。

### [FreeBSD 8.0 对比 Linux、OpenSolaris](http://www.phoronix.com/vr.php?view=14407)

在 FreeBSD 8.0 的稳定版本发布的上周，我们终于可以把它放在测试台上，并用 Phoronix 测试套件进行了全面的了解。我们将 FreeBSD 8.0 的性能与早期的 FreeBSD 7.2 版本以及 Fedora 12 和 Ubuntu 9.10 还有 Sun OS 端的 OpenSolaris 2010.02 b127 快照进行了比较。

### [Fedora、Debian、FreeBSD、OpenBSD、OpenSolaris 基准测试](http://www.phoronix.com/vr.php?view=14533)

 上周我们发布了第一个 Debian GNU/kFreeBSD 基准测试，将 FreeBSD 内核捆绑在 Debian GNU 用户的 Debian GNU/Linux 上，比较了这款 Debian 系统的 32 位和 64 位性能。 我们现在扩展了这个比较，使许多其他操作系统与 Debian GNU/Linux 和 Debian GNU/kFreeBSD 的 6.0 Squeeze 快照直接进行比较，如 Fedora 12，FreeBSD 7.2，FreeBSD 8.0，OpenBSD 4.6 和 OpenSolaris 2009.06 。

### [AMD 上海皓龙：Linux vs. OpenSolaris 基准测试](http://www.phoronix.com/vr.php?view=13475)

 1月份，当我们研究了四款皓龙 2384 型号时，我们在 Linux 上发布了关于 AMD 上海皓龙 CPU 的综述。与早期的 AMD 巴塞罗那处理器 Ubuntu Linux 相比，这些 45nm 四核工作站服务器处理器的性能非常好，但是在运行 Sun OpenSolaris 操作系统时，性能如何？今天浏览的是 AMD 双核的基准测试，运行 OpenSolaris 2008.11、Ubuntu 8.10 和即将推出的 Ubuntu 9.04 版本。

### [OpenSolaris vs. Linux 内核基准](http://www.phoronix.com/vr.php?view=13826)

本周早些时候，我们提供了 Ubuntu 9.04 与 Mac OS X 10.5.6 的基准测试，发现 Leopard 操作系统（Mac）在大多数测试中的表现要优于 Jaunty Jackalope （Ubuntu），至少在 Ubuntu 32 位是这样的。我们今天又回过来进行更多的操作系统基准测试，但这次我们正在比较 Linux 和 Sun OpenSolaris 内核的性能。我们使用的 Nexenta Core 2 操作系统将 OpenSolaris 内核与 GNU/Ubuntu 用户界面组合在同一个 Ubuntu 软件包中，但使用了 Linux 内核的 32 位和 64 位 Ubuntu 服务器安装进行测试。

### [Netbook 性能：Ubuntu vs. OpenSolaris](http://www.phoronix.com/vr.php?view=14039)

过去，我们已经发布了 OpenSolaris vs. Linux Kernel 基准测试以及类似的文章，关注 Sun 的 OpenSolaris 与流行的 Linux 发行版的性能。我们已经看过高端 AMD 工作站的性能，但是我们从来没有比较上网本上的 OpenSolaris 和 Linux 性能。直到今天，在本文中，我们将比较戴尔 Inspiron Mini 9 上网本上的 OpenSolaris 2009.06 和 Ubuntu 9.04 的结果。

### [NVIDIA 图形：Linux vs. Solaris](http://www.phoronix.com/vr.php?view=10301)

在 Phoronix，我们不断探索 Linux 下的不同显示驱动程序，在我们评估了 Sun 的检查工具并测试了 Solaris 主板以及覆盖其他几个领域之后，我们还没有执行图形驱动程序 Linux 和 Solaris 之间的比较。直到今天。由于印第安纳州项目，我们对 Solaris 更感兴趣，我们决定终于通过 NVIDIA 专有驱动程序提供我们在 Linux 和 Solaris 之间的第一次定量图形比较。

### [OpenSolaris 2008.05 向 Solaris 提供了一个新面孔](http://www.phoronix.com/vr.php?view=12269)

2月初，Sun Microsystems 发布了印第安纳项目的第二个预览版本。对于那些人来说，印第安纳州项目是 Sun 的 Ian Murdock 领导的项目的代号，旨在通过解决 Solaris 的长期可用性问题，将 OpenSolaris 推向更多的台式机和笔记本电脑。我们没有对预览 2 留下什么深刻印象，因为它没有比普通用户感兴趣的 GNU/Linux 桌面更有优势。然而，随着 5 月份推出的 OpenSolaris 2008.05 印第安纳项目发布，Sun Microsystems 今天发布了该操作系统的最终测试副本。当最后看到项目印第安纳时， 我们对这个新的 OpenSolaris 版本的最初体验是远远优于我们不到三月前的体验的。

### [快速概览 Oracle Solaris 11](http://www.phoronix.com/vr.php?view=16681)

Solaris 11 在周三发布，是七年来这个前 Sun 操作系统的第一个主要更新。在过去七年中，Solaris 家族发生了很大变化，OpenSolaris 在那个时候已经到来，但在本文中，简要介绍了全新的 Oracle Solaris 11 版本。

### [OpenSolaris、BSD & Linux 的新基准测试](http://www.phoronix.com/vr.php?view=15476)

 今天早些时候，我们对以原生的内核模块支持的 Linux 上的 ZFS 进行了基准测试，该原生模块将被公开提供，以将这个 Sun/Oracle 文件系统覆盖到更多的 Linux 用户。现在，尽管作为一个附加奖励，我们碰巧有了基于 OpenSolaris 的最新发行版的新基准，包括 OpenSolaris、OpenIndiana 和 Augustiner-Schweinshaxe，与 PC-BSD、Fedora 和 Ubuntu相比。

### [FreeBSD/PC-BSD 9.1 针对 Linux、Solaris、BSD 的基准](http://www.phoronix.com/vr.php?view=18291)

虽然 FreeBSD 9.1 尚未正式发布，但是基于 FreeBSD 的 PC-BSD 9.1 “Isotope”版本本月已经可用。本文中的性能指标是 64 位版本的 PC-BSD 9.1 与 DragonFlyBSD 3.0.3、Oracle Solaris Express 11.1、CentOS 6.3、Ubuntu 12.10 以及 Ubuntu 13.04 开发快照的比较。

---

作者简介：

Michael Larabel 是 Phoronix.com 的作者，并于 2004 年创立了该网站，该网站重点是丰富多样的 Linux 硬件体验。 Michael 撰写了超过10,000 篇文章，涵盖了 Linux 硬件支持，Linux 性能，图形驱动程序等主题。 Michael 也是 Phoronix 测试套件、 Phoromatic 和 OpenBenchmarking.org 自动化基准测试软件的主要开发人员。可以通过 Twitter 关注他或通过 MichaelLarabel.com 联系他。

---

via: <https://www.phoronix.com/scan.php?page=news_item&px=Solaris-2017-Look-Back>

作者：[Michael Larabel](http://www.michaellarabel.com/) 译者：[MonkeyDEcho](https://github.com/MonkeyDEcho) 校对：[wxy](https://github.com/wxy)

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


极客漫画：Deadline

Summary: 当期限到来时……

貌似穿了一件docker鲸鱼的T恤

![Deadline](/data/attachment/album/201708/23/145201fekjepxkvv2ufxju.png)

DeLorean：美国经典科幻电影系列《回到未来》中的时光汽车。

Tardis：英国科幻电视剧《Doctor Who》中的时间机器和宇宙飞船，它是时间和空间的相对维度（Time And Relative Dimension(s) In Space）的缩写。

---

via: <http://turnoff.us/geek/deadline/>

作者：[Daniel Stori](http://turnoff.us/about/) 译者：[zionfuo](https://github.com/zionfuo) 合成：[zionfuo](https://github.com/zionfuo)

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


Linux 开机引导和启动过程详解

赞一个

好文@！~

粗看题图，还以为是个在流口水的吃豆人，简直吓尿

SYSTEMD还是应该保留很多SYSV的配置方法。太激进不好

赞一个！

不错哦，很详细，赞一个

"图 1 中，以 * 开头的目标态是通用的启动状态。"<br />
为什么我在图1中找不到带*的内容？

哦，这是一个疏漏，马上补上

看不懂

牛逼

ubuntu~ [Chrome Mobile 40.0|Android 6.0]

zhendeniupi

zhendeniupi

zhendeniupi

zhendeniupi

写的非常好，必须赞一个

非常棒

看不懂，但是看起来很厉害！

盗别人的网页资料，连话术和标题都一模一样，你咋这么优秀呢

你看懂了吗？还非常好，漏洞百出

你看懂了吗？还非常好，漏洞百出

的地方

感谢分享

>
> 你是否曾经对操作系统为何能够执行应用程序而感到疑惑？那么本文将为你揭开操作系统引导与启动的面纱。
>
>
>

![](/data/attachment/album/201708/24/123711cam1fjf4eaefajnz.jpg)

理解操作系统开机引导和启动过程对于配置操作系统和解决相关启动问题是至关重要的。该文章陈述了 [GRUB2 引导装载程序](https://en.wikipedia.org/wiki/GNU_GRUB)开机引导装载内核的过程和 [systemd 初始化系统](https://en.wikipedia.org/wiki/Systemd)执行开机启动操作系统的过程。

事实上，操作系统的启动分为两个阶段：<ruby> 引导 <rt>  boot </rt></ruby>和<ruby> 启动 <rt>  startup </rt></ruby>。引导阶段开始于打开电源开关，结束于内核初始化完成和 systemd 进程成功运行。启动阶段接管了剩余工作，直到操作系统进入可操作状态。

总体来说，Linux 的开机引导和启动过程是相当容易理解，下文将分节对于不同步骤进行详细说明。

* BIOS 上电自检（POST）
* 引导装载程序 (GRUB2)
* 内核初始化
* 启动 systemd，其是所有进程之父。

注意，本文以 GRUB2 和 systemd 为载体讲述操作系统的开机引导和启动过程，是因为这二者是目前主流的 linux 发行版本所使用的引导装载程序和初始化软件。当然另外一些过去使用的相关软件仍然在一些 Linux 发行版本中使用。

### 引导过程

引导过程能以两种方式之一初始化。其一，如果系统处于关机状态，那么打开电源按钮将开启系统引导过程。其二，如果操作系统已经运行在一个本地用户（该用户可以是 root 或其他非特权用户），那么用户可以借助图形界面或命令行界面通过编程方式发起一个重启操作，从而触发系统引导过程。重启包括了一个关机和重新开始的操作。

#### BIOS 上电自检（POST）

上电自检过程中其实 Linux 没有什么也没做，上电自检主要由硬件的部分来完成，这对于所有操作系统都一样。当电脑接通电源，电脑开始执行 BIOS（<ruby> 基本输入输出系统 <rt>  Basic I/O System </rt></ruby>）的 POST（<ruby> 上电自检 <rt>  Power On Self Test </rt></ruby>）过程。

在 1981 年，IBM 设计的第一台个人电脑中，BIOS 被设计为用来初始化硬件组件。POST 作为 BIOS 的组成部分，用于检验电脑硬件基本功能是否正常。如果 POST 失败，那么这个电脑就不能使用，引导过程也将就此中断。

BIOS 上电自检确认硬件的基本功能正常，然后产生一个 BIOS [中断](https://en.wikipedia.org/wiki/BIOS_interrupt_call) INT 13H，该中断指向某个接入的可引导设备的引导扇区。它所找到的包含有效的引导记录的第一个引导扇区将被装载到内存中，并且控制权也将从引导扇区转移到此段代码。

引导扇区是引导加载器真正的第一阶段。大多数 Linux 发行版本使用的引导加载器有三种：GRUB、GRUB2 和 LILO。GRUB2 是最新的，也是相对于其他老的同类程序使用最广泛的。

#### GRUB2

GRUB2 全称是 GRand Unified BootLoader，Version 2（第二版大一统引导装载程序）。它是目前流行的大部分 Linux 发行版本的主要引导加载程序。GRUB2 是一个用于计算机寻找操作系统内核并加载其到内存的智能程序。由于 GRUB 这个单词比 GRUB2 更易于书写和阅读，在下文中，除特殊指明以外，GRUB 将代指 GRUB2。

GRUB 被设计为兼容操作系统[多重引导规范](https://en.wikipedia.org/wiki/Multiboot_Specification)，它能够用来引导不同版本的 Linux 和其他的开源操作系统；它还能链式加载专有操作系统的引导记录。

GRUB 允许用户从任何给定的 Linux 发行版本的几个不同内核中选择一个进行引导。这个特性使得操作系统，在因为关键软件不兼容或其它某些原因升级失败时，具备引导到先前版本的内核的能力。GRUB 能够通过文件 `/boot/grub/grub.conf` 进行配置。（LCTT 译注：此处指 GRUB1）

GRUB1 现在已经逐步被弃用，在大多数现代发行版上它已经被 GRUB2 所替换，GRUB2 是在 GRUB1 的基础上重写完成。基于 Red Hat 的发行版大约是在 Fedora 15 和 CentOS/RHEL 7 时升级到 GRUB2 的。GRUB2 提供了与 GRUB1 同样的引导功能，但是 GRUB2 也是一个类似主框架（mainframe）系统上的基于命令行的前置操作系统（Pre-OS）环境，使得在预引导阶段配置更为方便和易操作。GRUB2 通过 `/boot/grub2/grub.cfg` 进行配置。

两个 GRUB 的最主要作用都是将内核加载到内存并运行。两个版本的 GRUB 的基本工作方式一致，其主要阶段也保持相同，都可分为 3 个阶段。在本文将以 GRUB2 为例进行讨论其工作过程。GRUB 或 GRUB2 的配置，以及 GRUB2 的命令使用均超过本文范围，不会在文中进行介绍。

虽然 GRUB2 并未在其三个引导阶段中正式使用这些<ruby> 阶段 <rt>  stage </rt></ruby>名词，但是为了讨论方便，我们在本文中使用它们。

##### 阶段 1

如上文 POST（上电自检）阶段提到的，在 POST 阶段结束时，BIOS 将查找在接入的磁盘中查找引导记录，其通常位于 MBR（<ruby> 主引导记录 <rt>  Master Boot Record </rt></ruby>），它加载它找到的第一个引导记录中到内存中，并开始执行此代码。引导代码（及阶段 1 代码）必须非常小，因为它必须连同分区表放到硬盘的第一个 512 字节的扇区中。 在[传统的常规 MBR](https://en.wikipedia.org/wiki/Master_boot_record) 中，引导代码实际所占用的空间大小为 446 字节。这个阶段 1 的 446 字节的文件通常被叫做引导镜像（boot.img），其中不包含设备的分区信息，分区是一般单独添加到引导记录中。

由于引导记录必须非常的小，它不可能非常智能，且不能理解文件系统结构。因此阶段 1 的唯一功能就是定位并加载阶段 1.5 的代码。为了完成此任务，阶段 1.5 的代码必须位于引导记录与设备第一个分区之间的位置。在加载阶段 1.5 代码进入内存后，控制权将由阶段 1 转移到阶段 1.5。

##### 阶段 1.5

如上所述，阶段 1.5 的代码必须位于引导记录与设备第一个分区之间的位置。该空间由于历史上的技术原因而空闲。第一个分区的开始位置在扇区 63 和 MBR（扇区 0）之间遗留下 62 个 512 字节的扇区（共 31744 字节），该区域用于存储阶段 1.5 的代码镜像 core.img 文件。该文件大小为 25389 字节，故此区域有足够大小的空间用来存储 core.img。

因为有更大的存储空间用于阶段 1.5，且该空间足够容纳一些通用的文件系统驱动程序，如标准的 EXT 和其它的 Linux 文件系统，如 FAT 和 NTFS 等。GRUB2 的 core.img 远比更老的 GRUB1 阶段 1.5 更复杂且更强大。这意味着 GRUB2 的阶段 2 能够放在标准的 EXT 文件系统内，但是不能放在逻辑卷内。故阶段 2 的文件可以存放于 `/boot` 文件系统中，一般在 `/boot/grub2` 目录下。

注意 `/boot` 目录必须放在一个 GRUB 所支持的文件系统（并不是所有的文件系统均可）。阶段 1.5 的功能是开始执行存放阶段 2 文件的 `/boot` 文件系统的驱动程序，并加载相关的驱动程序。

##### 阶段 2

GRUB 阶段 2 所有的文件都已存放于 `/boot/grub2` 目录及其几个子目录之下。该阶段没有一个类似于阶段 1 与阶段 1.5 的镜像文件。相应地，该阶段主要需要从 `/boot/grub2/i386-pc` 目录下加载一些内核运行时模块。

GRUB 阶段 2 的主要功能是定位和加载 Linux 内核到内存中，并转移控制权到内核。内核的相关文件位于 `/boot` 目录下，这些内核文件可以通过其文件名进行识别，其文件名均带有前缀 vmlinuz。你可以列出 `/boot` 目录中的内容来查看操作系统中当前已经安装的内核。

GRUB2 跟 GRUB1 类似，支持从 Linux 内核选择之一引导启动。Red Hat 包管理器（DNF）支持保留多个内核版本，以防最新版本内核发生问题而无法启动时，可以恢复老版本的内核。默认情况下，GRUB 提供了一个已安装内核的预引导菜单，其中包括问题诊断菜单（recuse）以及恢复菜单（如果配置已经设置恢复镜像）。

阶段 2 加载选定的内核到内存中，并转移控制权到内核代码。

#### 内核

内核文件都是以一种自解压的压缩格式存储以节省空间，它与一个初始化的内存映像和存储设备映射表都存储于 `/boot` 目录之下。

在选定的内核加载到内存中并开始执行后，在其进行任何工作之前，内核文件首先必须从压缩格式解压自身。一旦内核自解压完成，则加载 [systemd](https://en.wikipedia.org/wiki/Systemd) 进程（其是老式 System V 系统的 [init](https://en.wikipedia.org/wiki/Init#SysV-style) 程序的替代品)，并转移控制权到 systemd。

这就是引导过程的结束。此刻，Linux 内核和 systemd 处于运行状态，但是由于没有其他任何程序在执行，故其不能执行任何有关用户的功能性任务。

### 启动过程

启动过程紧随引导过程之后，启动过程使 Linux 系统进入可操作状态，并能够执行用户功能性任务。

#### systemd

systemd 是所有进程的父进程。它负责将 Linux 主机带到一个用户可操作状态（可以执行功能任务）。systemd 的一些功能远较旧式 init 程序更丰富，可以管理运行中的 Linux 主机的许多方面，包括挂载文件系统，以及开启和管理 Linux 主机的系统服务等。但是 systemd 的任何与系统启动过程无关的功能均不在此文的讨论范围。

首先，systemd 挂载在 `/etc/fstab` 中配置的文件系统，包括内存交换文件或分区。据此，systemd 必须能够访问位于 `/etc` 目录下的配置文件，包括它自己的。systemd 借助其配置文件 `/etc/systemd/system/default.target` 决定 Linux 系统应该启动达到哪个状态（或<ruby> 目标态 <rt>  target </rt></ruby>）。`default.target` 是一个真实的 target 文件的符号链接。对于桌面系统，其链接到 `graphical.target`，该文件相当于旧式 systemV init 方式的 **runlevel 5**。对于一个服务器操作系统来说，`default.target` 更多是默认链接到 `multi-user.target`， 相当于 systemV 系统的 **runlevel 3**。 `emergency.target` 相当于单用户模式。

（LCTT 译注：“target” 是 systemd 新引入的概念，目前尚未发现有官方的准确译名，考虑到其作用和使用的上下文环境，我们认为翻译为“目标态”比较贴切。以及，“unit” 是指 systemd 中服务和目标态等各个对象/文件，在此依照语境译作“单元”。）

注意，所有的<ruby> 目标态 <rt>  target </rt></ruby>和<ruby> 服务 <rt>  service </rt></ruby>均是 systemd 的<ruby> 单元 <rt>  unit </rt></ruby>。

如下表 1 是 systemd 启动的<ruby> 目标态 <rt>  target </rt></ruby>和老版 systemV init 启动<ruby> 运行级别 <rt>  runlevel </rt></ruby>的对比。这个 **systemd 目标态别名** 是为了 systemd 向前兼容 systemV 而提供。这个目标态别名允许系统管理员（包括我自己）用 systemV 命令（例如 `init 3`）改变运行级别。当然，该 systemV 命令是被转发到 systemd 进行解释和执行的。

| SystemV 运行级别 | systemd 目标态 | systemd 目标态别名 | 描述 |
| --- | --- | --- | --- |
|  | `halt.target` |  | 停止系统运行但不切断电源。 |
| 0 | `poweroff.target` | `runlevel0.target` | 停止系统运行并切断电源. |
| S | `emergency.target` |  | 单用户模式，没有服务进程运行，文件系统也没挂载。这是一个最基本的运行级别，仅在主控制台上提供一个 shell 用于用户与系统进行交互。 |
| 1 | `rescue.target` | `runlevel1.target` | 挂载了文件系统，仅运行了最基本的服务进程的基本系统，并在主控制台启动了一个 shell 访问入口用于诊断。 |
| 2 |  | `runlevel2.target` | 多用户，没有挂载 NFS 文件系统，但是所有的非图形界面的服务进程已经运行。 |
| 3 | `multi-user.target` | `runlevel3.target` | 所有服务都已运行，但只支持命令行接口访问。 |
| 4 |  | `runlevel4.target` | 未使用。 |
| 5 | `graphical.target` | `runlevel5.target` | 多用户，且支持图形界面接口。 |
| 6 | `reboot.target` | `runlevel6.target` | 重启。 |
|  | `default.target` |  | 这个<ruby> 目标态 <rt>  target </rt></ruby>是总是 `multi-user.target` 或 `graphical.target` 的一个符号链接的别名。systemd 总是通过 `default.target` 启动系统。`default.target` 绝不应该指向 `halt.target`、 `poweroff.target` 或 `reboot.target`。 |

*表 1 老版本 systemV 的 运行级别与 systemd 与<ruby> 目标态 <rt>  target </rt></ruby>或目标态别名的比较*

每个<ruby> 目标态 <rt>  target </rt></ruby>有一个在其配置文件中描述的依赖集，systemd 需要首先启动其所需依赖，这些依赖服务是 Linux 主机运行在特定的功能级别所要求的服务。当配置文件中所有的依赖服务都加载并运行后，即说明系统运行于该目标级别。

systemd 也会查看老式的 systemV init 目录中是否存在相关启动文件，若存在，则 systemd 根据这些配置文件的内容启动对应的服务。在 Fedora 系统中，过时的网络服务就是通过该方式启动的一个实例。

如下图 1 是直接从 bootup 的 man 页面拷贝而来。它展示了在 systemd 启动过程中一般的事件序列和确保成功的启动的基本的顺序要求。

`sysinit.target` 和 `basic.target` 目标态可以被视作启动过程中的状态检查点。尽管 systemd 的设计初衷是并行启动系统服务，但是部分服务或功能目标态是其它服务或目标态的启动的前提。系统将暂停于检查点直到其所要求的服务和目标态都满足为止。

`sysinit.target` 状态的到达是以其所依赖的所有资源模块都正常启动为前提的，所有其它的单元，如文件系统挂载、交换文件设置、设备管理器的启动、随机数生成器种子设置、低级别系统服务初始化、加解密服务启动（如果一个或者多个文件系统加密的话）等都必须完成，但是在 **sysinit.target** 中这些服务与模块是可以并行启动的。

`sysinit.target` 启动所有的低级别服务和系统初具功能所需的单元，这些都是进入下一阶段 basic.target 的必要前提。

![](/data/attachment/album/201708/31/154048a1v4ev3huvq53574.jpg)

*图 1：systemd 的启动流程*

在 `sysinit.target` 的条件满足以后，systemd 接下来启动 `basic.target`，启动其所要求的所有单元。 `basic.target` 通过启动下一目标态所需的单元而提供了更多的功能，这包括各种可执行文件的目录路径、通信 sockets，以及定时器等。

最后，用户级目标态（`multi-user.target` 或 `graphical.target`） 可以初始化了，应该注意的是 `multi-user.target` 必须在满足图形化目标态 `graphical.target` 的依赖项之前先达成。

图 1 中，以 `*` 开头的目标态是通用的启动状态。当到达其中的某一目标态，则说明系统已经启动完成了。如果 `multi-user.target` 是默认的目标态，则成功启动的系统将以命令行登录界面呈现于用户。如果 `graphical.target` 是默认的目标态，则成功启动的系统将以图形登录界面呈现于用户，界面的具体样式将根据系统所配置的[显示管理器](https://opensource.com/article/16/12/yearbook-best-couple-2016-display-manager-and-window-manager)而定。

### 故障讨论

最近我需要改变一台使用 GRUB2 的 Linux 电脑的默认引导内核。我发现一些 GRUB2 的命令在我的系统上不能用，也可能是我使用方法不正确。至今，我仍然不知道是何原因导致，此问题需要进一步探究。

`grub2-set-default` 命令没能在配置文件 `/etc/default/grub` 中成功地设置默认内核索引，以至于期望的替代内核并没有被引导启动。故在该配置文件中我手动更改 `GRUB_DEFAULT=saved` 为 `GRUB_DEFAULT=2`，2 是我需要引导的安装好的内核文件的索引。然后我执行命令 `grub2-mkconfig > /boot/grub2/grub.cfg` 创建了新的 GRUB 配置文件，该方法如预期的规避了问题，并成功引导了替代的内核。

### 结论

GRUB2、systemd 初始化系统是大多数现代 Linux 发行版引导和启动的关键组件。尽管在实际中，systemd 的使用还存在一些争议，但是 GRUB2 与 systemd 可以密切地配合先加载内核，然后启动一个业务系统所需要的系统服务。

尽管 GRUB2 和 systemd 都比其前任要更加复杂，但是它们更加容易学习和管理。在 man 页面有大量关于 systemd 的帮助说明，freedesktop.org 也在线收录了完整的此[帮助说明](https://www.freedesktop.org/software/systemd/man/index.html)。下面有更多相关信息链接。

### 附加资源

* [GNU GRUB](https://en.wikipedia.org/wiki/GNU_GRUB) (Wikipedia)
* [GNU GRUB Manual](https://www.gnu.org/software/grub/manual/grub.html) (GNU.org)
* [Master Boot Record](https://en.wikipedia.org/wiki/Master_boot_record) (Wikipedia)
* [Multiboot specification](https://en.wikipedia.org/wiki/Multiboot_Specification) (Wikipedia)
* [systemd](https://en.wikipedia.org/wiki/Systemd) (Wikipedia)
* [systemd bootup process](https://www.freedesktop.org/software/systemd/man/bootup.html) (Freedesktop.org)
* [systemd index of man pages](https://www.freedesktop.org/software/systemd/man/index.html) (Freedesktop.org)

---

作者简介：

David Both 居住在美国北卡罗纳州的首府罗利，是一个 Linux 开源贡献者。他已经从事 IT 行业 40 余年，在 IBM 教授 OS/2 20余年。1981 年，他在 IBM 开发了第一个关于最初的 IBM 个人电脑的培训课程。他也曾在 Red Hat 教授 RHCE 课程，也曾供职于 MCI worldcom，Cico 以及北卡罗纳州等。他已经为 Linux 开源社区工作近 20 年。

---

via: <https://opensource.com/article/17/2/linux-boot-and-startup>

作者：[David Both](https://opensource.com/users/dboth) 译者： [penghuster](https://github.com/penghuster) 校对：[wxy](https://github.com/wxy)

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


给中级 Meld 用户的有用技巧

Summary: Meld 是 Linux 上功能丰富的可视化比较和合并工具。在本教程中，我们将讨论一些非常有用的技巧，这将让你使用工具的体验更好。

不考虑开源还是闭源，beyond compare 给我留下的映像最深。

没有linux版本吧？windows上beyond compare还是很牛逼的

目前即是开源又是跨平台的比较， 拆分，合并类软件，流行读最高的应该是 git 的吧？

我觉得araxis merge 最厉害。

有linux版本

哪里下载？

![](/data/attachment/album/201708/25/081212g4ub334ub4am4a04.jpg)

Meld 是 Linux 上功能丰富的可视化比较和合并工具。如果你是第一次接触，你可以进入我们的[初学者指南](/article-8402-1.html)，了解该程序的工作原理，如果你已经阅读过或正在使用 Meld 进行基本的比较/合并任务，你将很高兴了解本教程的东西，在本教程中，我们将讨论一些非常有用的技巧，这将让你使用工具的体验更好。

*但在我们跳到安装和解释部分之前，值得一提的是，本教程中介绍的所有说明和示例已在 Ubuntu 14.04 上进行了测试，而我们使用的 Meld 版本为 3.14.2*。

### 1、 跳转

你可能已经知道（我们也在初学者指南中也提到过这一点），标准滚动不是在使用 Meld 时在更改之间跳转的唯一方法 - 你可以使用向上和向下箭头键轻松地从一个更改跳转到另一个更改位于编辑区域上方的窗格中：

 [![Navigating in Meld](/data/attachment/album/201708/25/083846b58ldk85olo3389n.png)](https://www.howtoforge.com/images/beginners-guide-to-visual-merge-tool-meld-on-linux-part-2/big/meld-go-next-prev-9.png)

但是，这需要你将鼠标指针移动到这些箭头，然后再次单击其中一个（取决于你要去哪里 - 向上或向下）。你会很高兴知道，存在另一种更简单的方式来跳转：只需使用鼠标的滚轮即可在鼠标指针位于中央更改栏上时进行滚动。

 [![The change bar](/data/attachment/album/201708/25/083847dsmshxwbmmbbbkwf.png)](https://www.howtoforge.com/images/beginners-guide-to-visual-merge-tool-meld-on-linux-part-2/big/meld-center-area-scrolling.png)

这样，你就可以在视线不离开或者分心的情况下进行跳转，

### 2、 可以对更改进行的操作

看下上一节的最后一个屏幕截图。你知道那些黑箭头做什么吧？默认情况下，它们允许你执行合并/更改操作 - 当没有冲突时进行合并，并在同一行发生冲突时进行更改。

但是你知道你可以根据需要删除个别的更改么？是的，这是可能的。为此，你需要做的是在处理更改时按下 Shift 键。你会观察到箭头被变成了十字架。

 [![Things you can do with changes](/data/attachment/album/201708/25/083847goh999xy9rapzyxn.png)](https://www.howtoforge.com/images/beginners-guide-to-visual-merge-tool-meld-on-linux-part-2/big/meld-delete-changes.png)

只需点击其中任何一个，相应的更改将被删除。

不仅是删除，你还可以确保冲突的更改不会在合并时更改行。例如，以下是一个冲突变化的例子：

 [![Delete changes in Meld](/data/attachment/album/201708/25/083852mxhmzbfnhqh1bhtb.png)](https://www.howtoforge.com/images/beginners-guide-to-visual-merge-tool-meld-on-linux-part-2/big/meld-conflicting-change.png)

现在，如果你点击任意两个黑色箭头，箭头指向的行将被改变，并且将变得与其他文件的相应行相似。只要你想这样做，这是没问题的。但是，如果你不想要更改任何行呢？相反，目的是将更改的行在相应行的上方或下方插入到其他文件中。

我想说的是，例如，在上面的截图中，需要在 “test23” 之上或之下添加 “test 2”，而不是将 “test23” 更改为 “test2”。你会很高兴知道在 Meld 中这是可能的。就像你按下 Shift 键删除注释一样，在这种情况下，你必须按下 Ctrl 键。

你会观察到当前操作将被更改为插入 - 双箭头图标将确认这一点 。

 [![Change actions](/data/attachment/album/201708/25/083852rlyuju9zjyctcbjj.png)](https://www.howtoforge.com/images/beginners-guide-to-visual-merge-tool-meld-on-linux-part-2/big/meld-ctrl-insert.png)

从箭头的方向看，此操作可帮助用户将当前更改插入到其他文件中的相应更改 (如所选择的)。

### 3、 自定义文件在 Meld 的编辑器区域中显示的方式

有时候，你希望 Meld 的编辑区域中的文字大小变大（为了更好或更舒适的浏览），或者你希望文本行被包含而不是脱离视觉区域（意味着你不要想使用底部的水平滚动条）。

Meld 在 *Editor* 选项卡（*Edit->Preferences->Editor*）的 *Preferences* 菜单中提供了一些显示和字体相关的自定义选项，你可以进行这些调整：

 [![Meld preferences](/data/attachment/album/201708/25/083856u3ut540fu6z43fyc.png)](https://www.howtoforge.com/images/beginners-guide-to-visual-merge-tool-meld-on-linux-part-2/big/meld-editor-tab.png)

在这里你可以看到，默认情况下，Meld 使用系统定义的字体宽度。只需取消选中 *Font* 类别下的框，你将有大量的字体类型和大小选项可供选择。

然后在 *Display* 部分，你将看到我们正在讨论的所有自定义选项：你可以设置 Tab 宽度、告诉工具是否插入空格而不是 tab、启用/禁用文本换行、使Meld显示行号和空白（在某些情况下非常有用）以及使用语法突出显示。

### 4、 过滤文本

有时候，并不是所有的修改都是对你很重要的。例如，在比较两个 C 编程文件时，你可能不希望 Meld 显示注释中的更改，因为你只想专注于与代码相关的更改。因此，在这种情况下，你可以告诉 Meld 过滤（或忽略）与注释相关的更改。

例如，这里是 Meld 中的一个比较，其中由工具高亮了注释相关更改：

 [![Filter Text in Meld](/data/attachment/album/201708/25/083857wjailj1xjggbubcb.png)](https://www.howtoforge.com/images/beginners-guide-to-visual-merge-tool-meld-on-linux-part-2/big/meld-changes-with-comments.png)

而在这种情况下，Meld 忽略了相同的变化，仅关注与代码相关的变更：

 [![Ignore Changes in Meld](/data/attachment/album/201708/25/083858tdv7ovhpviz9xxxk.png)](https://www.howtoforge.com/images/beginners-guide-to-visual-merge-tool-meld-on-linux-part-2/big/meld-changes-without-comments.png)

很酷，不是吗？那么这是怎么回事？为此，我是在 “*Edit->Preferences->Text Filters*” 标签中启用了 “C comments” 文本过滤器：

 [![C-Comments in Meld](/data/attachment/album/201708/25/083900bc6vnhnc68hn18tk.png)](https://www.howtoforge.com/images/beginners-guide-to-visual-merge-tool-meld-on-linux-part-2/big/meld-text-filters.png)

如你所见，除了 “C comments” 之外，你还可以过滤掉 C++ 注释、脚本注释、引导或所有的空格等。此外，你还可以为你处理的任何特定情况定义自定义文本过滤器。例如，如果你正在处理日志文件，并且不希望 Meld 高亮显示特定模式开头的行中的更改，则可以为该情况定义自定义文本过滤器。

但是，请记住，要定义一个新的文本过滤器，你需要了解 Python 语言以及如何使用该语言创建正则表达式。

### 总结

这里讨论的所有四个技巧都不是很难理解和使用（当然，除了你想立即创建自定义文本过滤器），一旦你开始使用它们，你会认为他们是真的有好处。这里的关键是要继续练习，否则你学到的任何技巧不久后都会忘记。

你还知道或者使用其他任何中级 Meld 的贴士和技巧么？如果有的话，欢迎你在下面的评论中分享。

---

via: <https://www.howtoforge.com/tutorial/beginners-guide-to-visual-merge-tool-meld-on-linux-part-2/>

作者：[Ansh](https://www.howtoforge.com/tutorial/beginners-guide-to-visual-merge-tool-meld-on-linux-part-2/) 译者：[geekpi](https://github.com/geekpi) 校对：[wxy](https://github.com/wxy)

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


开源优先：私营公司宣言

![Open Source First: A manifesto for private companies](/data/attachment/album/201708/26/090105bc7a8cpdu8jnznfu.png "Open Source First: A manifesto for private companies")

这是一个宣言，任何私人组织都可以用来构建其协作转型。请阅读并让我知道你的看法。

我[在 Linux TODO 小组中作了一个演讲](https://sarob.com/2017/01/todo-open-source-presentation-17-january-2017/)使用了这篇文章作为我的材料。对于那些不熟悉 TODO 小组的人，他们是在商业公司支持开源领导力的组织。相互依赖是很重要的，因为法律、安全和其他共享的知识对于开源社区向前推进是非常重要的。尤其是因为我们需要同时代表商业和公共社区的最佳利益。

“开源优先”意味着我们在考虑供应商出品的产品以满足我们的需求之前，首先考虑开源。要正确使用开源技术，你需要做的不仅仅是消费，还需要你的参与，以确保开源技术长期存在。要参与开源工作，你需要将工程师的工作时间分别分配给你的公司和开源项目。我们期望将开源贡献意图以及内部协作带到私营公司。我们需要定义、建立和维护一种贡献、协作和择优工作的文化。

### 开放花园开发

我们的私营公司致力于通过对技术界的贡献，成为技术的领导者。这不仅仅是使用开源代码，成为领导者需要参与。成为领导者还需要与公司以外的团体（社区）进行各种类型的参与。这些社区围绕一个特定的研发项目进行组织。每个社区的参与就像为公司工作一样。重大成果需要大量的参与。

### 编码更多，生活更好

我们必须对计算资源慷慨，对空间吝啬，并鼓励由此产生的凌乱而有创造力的结果。允许人们使用他们的业务的这些工具将改变他们。我们必须有自发的互动。我们必须通过协作来构建鼓励创造性的线上以及线下空间。无法实时联系对方，协作就不能进行。

### 通过精英体制创新

我们必须创建一个精英阶层。思想素质要超过群体结构和在其中的职位任期。按业绩晋升鼓励每个人都成为更好的人和雇员。当我们成为最好的坏人时, 充满激情的人之间的争论将会发生。我们的文化应该有鼓励异议的义务。强烈的意见和想法将会变成热情的职业道德。这些想法和意见可以来自而且应该来自所有人。它不应该改变你是谁，而是应该关心你做什么。随着精英体制的进行，我们会投资未经许可就能正确行事的团队。

### 项目到产品

由于我们的私营公司拥抱开源贡献，我们还必须在研发项目中的上游工作和实现最终产品之间实现明确的分离。项目是研发工作，快速失败以及开发功能是常态。产品是你投入生产，拥有 SLA，并使用研发项目的成果。分离至少需要分离项目和产品的仓库。正常的分离包含在项目和产品上工作的不同社区。每个社区都需要大量的贡献和参与。为了使这些活动保持独立，需要有一个客户功能以及项目到产品的 bug 修复请求的工作流程。

接下来，我们会强调在私营公司创建、支持和扩展开源中的主要步骤。

### 技术上有天赋的人的学校

高手必须指导没有经验的人。当你学习新技能时，你将它们传给下一个人。当你训练下一个人时，你会面临新的挑战。永远不要期待在一个位置很长时间。获得技能，变得强大，通过学习，然后继续前进。

### 找到最适合你的人

我们热爱我们的工作。我们非常喜欢它，我们想和我们的朋友一起工作。我们是一个比我们公司大的社区的一部分。我们应该永远记住招募最好的人与我们一起工作。即使不是为我们公司工作，我们将会为我们周围的人找到很棒的工作。这样的想法使雇用很棒的人成为一种生活方式。随着招聘变得普遍，那么审查和帮助新员工就会变得容易了。

### 即将写的

我将在我的博客上发布关于每个宗旨的[更多细节](https://sarob.com/2017/02/open-source-first-project-product/)，敬请关注。

*这篇文章最初发表在 [Sean Robert 的博客](https://sarob.com/2017/01/open-source-first/)上。CC BY 许可。*

（题图： opensource.com）

---

作者简介：

Sean A Roberts - -以同理心为主导，同时专注于结果。我实践精英体制。在这里发现的智慧。

---

via: <https://opensource.com/article/17/2/open-source-first>

作者：[Sean A Roberts](https://opensource.com/users/sarob) 译者：[geekpi](https://github.com/geekpi) 校对：[wxy](https://github.com/wxy)

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


使用 snapcraft 将 snap 包发布到商店

Summary: 是时候让你的 snap 包进入商店了！

别又搞分裂。

无论是Snap还是Flatpak都不可能流行的。

是时候让你的 snap 包进入商店了！

![snaps](/data/attachment/album/201708/26/100735o1ynjs4en2iykguz.jpg)

Ubuntu Core 已经正式发布（LCTT 译注：指 2016 年 11 月发布的 Ubuntu Snappy Core 16 ），也许是时候让你的 snap 包进入商店了！

### 交付和商店的概念

首先回顾一下我们是怎么通过商店管理 snap 包的吧。

每次你上传 snap 包，商店都会为其分配一个修订版本号，并且商店中针对特定 snap 包 的版本号都是唯一的。

但是第一次上传 snap 包的时候，我们首先要为其注册一个还没有被使用的名字，这很容易。

商店中所有的修订版本都可以释放到多个通道中，这些通道只是概念上定义的，以便给用户一个稳定或风险等级的参照，这些通道有:

* 稳定（stable）
* 候选（candidate）
* 测试（beta）
* 边缘（edge）

理想情况下，如果我们设置了 CI/CD 过程，那么每天或在每次更新源码时都会将其推送到边缘通道。在此过程中有两件事需要考虑。

首先在开始的时候，你最好制作一个不受限制的 snap 包，因为在这种新范例下，snap 包的大部分功能都能不受限制地工作。考虑到这一点，你的项目开始时 `confinement` 将被设置为 `devmode`（LCTT 译注：这是 `snapcraft.yaml` 中的一个键及其可选值）。这使得你在开发的早期阶段，仍然可以让你的 snap 包进入商店。一旦所有的东西都得到了 snap 包运行的安全模型的充分支持，那么就可以将 `confinement` 修改为 `strict`。

好了，假设你在限制方面已经做好了，并且也开始了一个对应边缘通道的 CI/CD 过程，但是如果你也想确保在某些情况下，早期版本 master 分支新的迭代永远也不会进入稳定或候选通道，那么我们可以使用 `gadge` 设置。如果 snap 包的 `gadge` 设置为 `devel` （LCTT注：这是 `snapcraft.yaml` 中的一个键及其可选值），商店将会永远禁止你将 snap 包释放到稳定和候选通道。

在这个过程中，我们有时可能想要发布一个修订版本到测试通道，以便让有些用户更愿意去跟踪它（一个好的发布管理流程应该比一个随机的日常构建更有用）。这个阶段结束后，如果希望人们仍然能保持更新，我们可以选择关闭测试通道，从一个特定的时间点开始我们只计划发布到候选和稳定通道，通过关闭测试通道我们将使该通道跟随稳定列表中的下一个开放通道，在这里是候选通道。而如果候选通道跟随的是稳定通道后，那么最终得到是稳定通道了。

### 进入 Snapcraft

那么所有这些给定的概念是如何在 snapcraft 中配合使用的？首先我们需要登录:

```
$ snapcraft login
Enter your Ubuntu One SSO credentials.
Email: sxxxxx.sxxxxxx@canonical.com
Password: **************
Second-factor auth: 123456

```

在登录之后，我们就可以开始注册 snap 了。例如，我们想要注册一个虚构的 snap 包 awesome-database：

```
$ snapcraft register awesome-database
We always want to ensure that users get the software they expect
for a particular name.

If needed, we will rename snaps to ensure that a particular name
reflects the software most widely expected by our community.

For example, most people would expect ‘thunderbird’ to be published by
Mozilla. They would also expect to be able to get other snaps of
Thunderbird as 'thunderbird-sergiusens'.

Would you say that MOST users will expect 'a' to come from
you, and be the software you intend to publish there? [y/N]: y

You are now the publisher for 'awesome-database'

```

假设我们已经构建了 snap 包，接下来我们要做的就是把它上传到商店。我们可以在同一个命令中使用快捷方式和 `--release` 选项：

```
$ snapcraft push awesome-databse_0.1_amd64.snap --release edge
Uploading awesome-database_0.1_amd64.snap [=================] 100%
Processing....
Revision 1 of 'awesome-database' created.

Channel    Version    Revision
stable     -          -
candidate  -          -
beta       -          -
edge       0.1        1

The edge channel is now open.

```

如果我们试图将其发布到稳定通道，商店将会阻止我们：

```
$ snapcraft release awesome-database 1 stable
Revision 1 (devmode) cannot target a stable channel (stable, grade: devel)

```

这样我们不会搞砸，也不会让我们的忠实用户使用它。现在，我们将最终推出一个值得发布到稳定通道的修订版本：

```
$ snapcraft push awesome-databse_0.1_amd64.snap
Uploading awesome-database_0.1_amd64.snap [=================] 100%
Processing....
Revision 10 of 'awesome-database' created.

```

注意，<ruby> 版本号 <rt>  version </rt></ruby>（LCTT 译注：这里指的是 snap 包名中 `0.1` 这个版本号）只是一个友好的标识符，真正重要的是商店为我们生成的<ruby> 修订版本号 <rt>  Revision </rt></ruby>（LCTT 译注：这里生成的修订版本号为 `10`）。现在让我们把它释放到稳定通道：

```
$ snapcraft release awesome-database 10 stable
Channel    Version    Revision
stable     0.1        10
candidate  ^          ^
beta       ^          ^
edge       0.1        10

The 'stable' channel is now open.

```

在这个针对我们正在使用架构最终的通道映射视图中，可以看到边缘通道将会被固定在修订版本 10 上，并且测试和候选通道将会跟随现在修订版本为 10 的稳定通道。由于某些原因，我们决定将专注于稳定性并让我们的 CI/CD 推送到测试通道。这意味着我们的边缘通道将会略微过时，为了避免这种情况，我们可以关闭这个通道：

```
 $ snapcraft close awesome-database edge
Arch    Channel    Version    Revision
amd64   stable     0.1        10
        candidate  ^          ^
        beta       ^          ^
        edge       ^          ^

The edge channel is now closed.

```

在当前状态下，所有通道都跟随着稳定通道，因此订阅了候选、测试和边缘通道的人也将跟踪稳定通道的改动。比如就算修订版本 11 只发布到稳定通道，其他通道的人们也能看到它。

这个清单还提供了完整的体系结构视图，在本例中，我们只使用了 amd64。

### 获得更多的信息

有时过了一段时间，我们想知道商店中的某个 snap 包的历史记录和现在的状态是什么样的，这里有两个命令，一个是直截了当输出当前的状态，它会给我们一个熟悉的结果:

```
 $ snapcraft status awesome-database
Arch    Channel    Version    Revision
amd64   stable     0.1        10
        candidate  ^          ^
        beta       ^          ^
        edge       ^          ^

```

我们也可以通过下面的命令获得完整的历史记录：

```
 $ snapcraft history awesome-database
Rev.    Uploaded              Arch       Version    Channels
3       2016-09-30T12:46:21Z  amd64      0.1        stable*
...
...
...
2       2016-09-30T12:38:20Z  amd64      0.1        -
1       2016-09-30T12:33:55Z  amd64      0.1        -

```

### 结束语

希望这篇文章能让你对 Snap 商店能做的事情有一个大概的了解，并让更多的人开始使用它！

---

via: <https://insights.ubuntu.com/2016/11/15/making-your-snaps-available-to-the-store-using-snapcraft/>

*译者简介：*

>
> snapcraft.io 的钉子户，对 Ubuntu Core、Snaps 和 Snapcraft 有着浓厚的兴趣，并致力于将这些还在快速发展的新技术通过翻译或原创的方式介绍到中文世界。有兴趣的小伙伴也可以关注译者个人的公众号： `Snapcraft`，近期会在上面连载几篇有关 Core snap 发布策略、交付流程和验证流程的文章，欢迎围观 :）
>
>
>

作者：[Sergio Schvezov](https://insights.ubuntu.com/author/sergio-schvezov/) 译者：[Snapcrafter](https://github.com/Snapcrafter) 校对：[wxy](https://github.com/wxy)

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


Linux 容器演化史

Summary: 我希望这份深入的介绍能够帮助你理解 Linux 容器的演化过程。Linux 容器曾经陷入一种各自为营的困境，Docker 建立起了镜像创建的事实标准，简化了容器的使用工具。OCI 则意味着业界在核心镜像格式与运行时方面的合作。

>
> 容器在过去几年内取得很大的进展。现在我们来回顾它发展的时间线。
>
>
>

![](/data/attachment/album/201708/26/161009ab5ggfgbg0hb10f5.jpg)

### Linux 容器是如何演变的

在过去几年内，容器不仅成为了开发者们热议的话题，还受到了企业的关注。持续增长的关注使得在它的安全性、可扩展性以及互用性等方面的需求也得以增长。满足这些需求需要很大的工程量，下面我们讲讲在红帽这样的企业级这些工程是如何发展的。

我在 2013 年秋季第一次遇到 Docker 公司（Docker.io）的代表，那时我们在设法使 Red Hat Enterprise Linux (RHEL) 支持 Docker 容器（现在 Docker 项目的一部分已经更名为 *Moby*）的运行。在移植过程中，我们遇到了一些问题。处理容器镜像分层所需的写时拷贝（COW）文件系统成了我们第一个重大阻碍。Red Hat 最终贡献了一些 COW 文件系统实现，包括 [Device Mapper](https://access.redhat.com/documentation/en-US/Red_Hat_Enterprise_Linux/6/html/Logical_Volume_Manager_Administration/device_mapper.html)、[btrf](https://btrfs.wiki.kernel.org/index.php/Main_Page)，以及 [OverlayFS](https://www.kernel.org/doc/Documentation/filesystems/overlayfs.txt) 的第一个版本。在 RHEL 上，我们默认使用 Device Mapper， 但是我们在 OverlayFS 上也已经取得了很大进展。

我们在用于启动容器的工具上遇到了第二个主要障碍。那时的上游 docker 使用 [LXC](https://linuxcontainers.org/) 工具来启动容器，然而我们不想在 RHEL 上支持 LXC 工具集。而且在与上游 docker 合作之前，我们已经与 [libvrit](https://libvirt.org/) 团队携手构建了 [virt-sandbox](http://sandbox.libvirt.org/) 工具，它使用 `libvrit-lxc` 来启动容器。

在那时，红帽里有员工提到一个好办法，换掉 LXC 工具集而添加桥接器，以便 docker 守护进程通过 `libvirt-lxc` 与 libvirt 通讯来启动容器。这个方案也有一些顾虑。考虑下面这个例子，使用 Docker 客户端（`docker-cli`）来启动容器，各层调用会在容器进程（`pid1OfContainer`）之前依次启动：

>
> **docker-cli → docker-daemon → libvirt-lxc → pid1OfContainer**
>
>
>

我不是很喜欢这个方案，因为它在启动容器的工具与最终的容器进程之间有两个守护进程。

我的团队与上游 docker 开发者合作实现了一个原生的 [Go 编程语言](https://opensource.com/article/17/6/getting-started-go) 版本的容器运行时，叫作 [libcontainer](https://github.com/opencontainers/runc/tree/master/libcontainer)。这个库作为 [OCI 运行时规范]的最初版实现与 runc 一同发布。

>
> **docker-cli → docker-daemon @ pid1OfContainer**
>
>
>

大多数人误认为当他们执行一个容器时，容器进程是作为 `docker-cli` 的子进程运行的。实际上他们执行的是一个客户端/服务端请求操作，容器进程是在一个完全单独的环境作为子进程运行的。这个客户端/服务端请求会导致不稳定性和潜在的安全问题，而且会阻碍一些实用特性的实现。举个例子，[systemd](https://opensource.com/business/15/10/lisa15-interview-alison-chaiken-mentor-graphics) 有个叫做套接字唤醒的特性，你可以将一个守护进程设置成仅当相应的套结字被连接时才启动。这意味着你的系统可以节约内存并按需执行服务。套结字唤醒的工作原理是 systemd 代为监听 TCP 套结字，并在数据包到达套结字时启动相应的服务。一旦服务启动完毕，systemd 将套结字交给新启动的守护进程。如果将守护进程运行在基于 docker 的容器中就会出现问题。systemd 的 unit 文件通过 Docker CLI 执行容器，然而这时 systemd 却无法简单地经由 Docker CLI 将套结字转交给 Docker 守护进程。

类似这样的问题让我们意识到我们需要一个运行容器的替代方案。

#### 容器编排问题

上游的 docker 项目简化了容器的使用过程，同时也是一个绝佳的 Linux 容器学习工具。你可以通过一条简单的命令快速地体验如何启动一个容器，例如运行 `docker run -ti fedora sh` 然后你就立即处于一个容器之中。

当开始把许多容器组织成一个功能更为强大的应用时，你才能体会到容器真正的能力。但是问题在于伴随多容器应用而来的高复杂度使得简单的 Docker 命令无法胜任编排工作。你要如何管理容器应用在有限资源的集群节点间的布局与编排？如何管理它们的生命周期等等？

在第一届 DockerCon，至少有 7 种不同的公司/开源项目展示了其容器的编排方案。红帽演示了 [OpenShift](https://www.openshift.com/) 的 [geard](https://openshift.github.io/geard/) 项目，它基于 OpenShift v2 的容器（叫作 gears）。红帽觉得我们需要重新审视容器编排，而且可能要与开源社区的其他人合作。

Google 则演示了 Kubernetes 容器编排工具，它来源于 Google 对其自内部架构进行编排时所积累的知识经验。OpenShift 决定放弃 Gear 项目，开始和 Google 一同开发 Kubernetes。 现在 Kubernetes 是 GitHub 上最大的社区项目之一。

#### Kubernetes

Kubernetes 原先被设计成使用 Google 的 [lmctfy](https://github.com/google/lmctfy) 容器运行时环境来完成工作。在 2014 年夏天，lmctfy 兼容了 docker。Kubernetes 还会在 kubernetes 集群的每个节点运行一个 [kubelet](https://kubernetes.io/docs/admin/kubelet/) 守护进程，这意味着原先使用 docker 1.8 的 kubernetes 工作流看起来是这样的：

>
> **kubelet → dockerdaemon @ PID1**
>
>
>

回退到了双守护进程的模式。

然而更糟糕的是，每次 docker 的新版本发布都使得 kubernetes 无法工作。Docker 1.10 切换镜像底层存储方案导致所有镜像重建。而 Docker 1.11 开始使用 `runc` 来启动镜像：

>
> **kubelet → dockerdaemon @ runc @PID1**
>
>
>

Docker 1.12 则增加了一个容器守护进程用于启动容器。其主要目的是为了支持 Docker Swarm （Kubernetes 的竞争者之一）：

>
> **kubelet → dockerdaemon → containerd @runc @ pid1**
>
>
>

如上所述，*每一次* docker 发布都破坏了 Kubernetes 的功能，这也是为什么 Kubernetes 和 OpenShift 请求我们为他们提供老版本 Docker 的原因。

现在我们有了一个三守护进程的系统，只要任何一个出现问题，整个系统都将崩溃。

### 走向容器标准化

#### CoreOS、rkt 和其它替代运行时

因为 docker 运行时带来的问题，几个组织都在寻求一个替代的运行时。CoreOS 就是其中之一。他们提供了一个 docker 容器运行时的替代品，叫 *rkt* （rocket）。他们同时还引入一个标准容器规范，称作 *appc* （App Container）。从根本上讲，他们是希望能使得所有人都使用一个标准规范来管理容器镜像中的应用。

这一行为为标准化工作树立了一面旗帜。当我第一次开始和上游 docker 合作时，我最大的担忧就是最终我们会分裂出多个标准。我不希望类似 RPM 和 DEB 之间的战争影响接下来 20 年的 Linux 软件部署。appc 的一个成果是它说服了上游 docker 与开源社区合作创建了一个称作<ruby> <a href="https://www.opencontainers.org/">  开放容器计划 </a> <rp>  （ </rp> <rt>  Open Container Initiative </rt> <rp>  ） </rp></ruby>(OCI) 的标准团体。

OCI 已经着手制定两个规范：

[OCI 运行时规范](https://github.com/opencontainers/runtime-spec/blob/master/spec.md)：OCI 运行时规范“旨在规范容器的配置、执行环境以及生命周期”。它定义了容器的磁盘存储，描述容器内运行的应用的 JSON 文件，容器的生成和执行方式。上游 docker 贡献了 libcontainer 并构建了 runc 作为 OCI 运行时规范的默认实现。

[OCI 镜像文件格式规范](https://github.com/opencontainers/image-spec/blob/master/spec.md)：镜像文件格式规范主要基于上游 docker 所使用的镜像格式，定义了容器仓库中实际存储的容器镜像格式。该规范使得应用开发者能为应用使用单一的标准化格式。一些 appc 中描述的概念被加入到 OCI 镜像格式规范中得以保留。这两份规范 1.0 版本的发布已经临近（LCTT 译注：[已经发布](/article-8778-1.html)）。上游 docker 已经同意在 OCI 镜像规范定案后支持该规范。Rkt 现在既支持运行 OCI 镜像也支持传统的上游 docker 镜像。

OCI 通过为工业界提供容器镜像与运行时标准化的环境，帮助在工具与编排领域解放创新的力量。

#### 抽象运行时接口

得益于标准化工作， Kubernetes 编排领域也有所创新。作为 Kubernetes 的一大支持者，CoreOS 提交了一堆补丁，使 Kubernetes 除了 docker 引擎外还能通过 rkt 运行容器并且与容器通讯。Google 和 Kubernetes 上游预见到增加这些补丁和将来可能添加的容器运行时接口将给 Kubernetes 带来的代码复杂度，他们决定实现一个叫作<ruby> 容器运行时接口 <rp>  （ </rp> <rt>  Container Runtime Interface </rt> <rp>  ） </rp></ruby>(CRI) 的 API 协议规范。于是他们将 Kubernetes 由原来的直接调用 docker 引擎改为调用 CRI，这样任何人都可以通过实现服务器端的 CRI 来创建支持 Kubernetes 的容器运行时。Kubernetes 上游还为 CRI 开发者们创建了一个大型测试集以验证他们的运行时对 Kubernetes 的支持情况。开发者们还在努力地移除 Kubernetes 对 docker 引擎的调用并将它们隐藏在一个叫作 docker-shim 的薄抽象层后。

### 容器工具的创新

#### 伴随 skopeo 而来的容器仓库创新

几年前我们正与 Atomic 项目团队合作构建 [atomic CLI](https://github.com/projectatomic/atomic)。我们希望实现一个功能，在镜像还在镜像仓库时查看它的细节。在那时，查看仓库中的容器镜像相关 JSON 文件的唯一方法是将镜像拉取到本地服务器再通过 `docker inspect` 来查看 JSON 文件。这些镜像可能会很大，上至几个 GiB。为了允许用户在不拉取镜像的情况下查看镜像细节，我们希望在 `docker inspect` 接口添加新的 `--remote` 参数。上游 docker 拒绝了我们的代码拉取请求（PR），告知我们他们不希望将 Docker CLI 复杂化，我们可以构建我们自己的工具去实现相同的功能。

我们的团队在 [Antonio Murdaca](https://twitter.com/runc0m) 的领导下执行这个提议，构建了 [skopeo](https://github.com/projectatomic/skopeo)。Antonio 没有止步于拉取镜像相关的 JSON 文件，而是决定实现一个完整的协议，用于在容器仓库与本地主机之间拉取与推送容器镜像。

skopeo 现在被 atomic CLI 大量用于类似检查容器更新的功能以及 [atomic 扫描](https://developers.redhat.com/blog/2016/05/02/introducing-atomic-scan-container-vulnerability-detection/) 当中。Atomic 也使用 skopeo 取代上游 docker 守护进程拉取和推送镜像的功能。

#### Containers/image

我们也曾和 CoreOS 讨论过在 rkt 中使用 skopeo 的可能，然而他们表示不希望运行一个外部的协助程序，但是会考虑使用 skopeo 所使用的代码库。于是我们决定将 skopeo 分离为一个代码库和一个可执行程序，创建了 [image](https://github.com/containers/image) 代码库。

[containers/images](https://github.com/containers/image) 代码库和 skopeo 被几个其它上游项目和云基础设施工具所使用。Skopeo 和 containers/image 已经支持 docker 和多个存储后端，而且能够在容器仓库之间移动容器镜像，还拥有许多酷炫的特性。[skopeo 的一个优点](http://rhelblog.redhat.com/2017/05/11/skopeo-copy-to-the-rescue/)是它不需要任何守护进程的协助来完成任务。Containers/image 代码库的诞生使得类似[容器镜像签名](https://access.redhat.com/articles/2750891)等增强功能得以实现。

#### 镜像处理与扫描的创新

我在前文提到 atomic CLI。我们构建这个工具是为了给容器添加不适合 docker CLI 或者我们无法在上游 docker 中实现的特性。我们也希望获得足够灵活性，将其用于开发额外的容器运行时、工具和存储系统。Skopeo 就是一例。

我们想要在 atomic 实现的一个功能是 `atomic mount`。从根本上讲，我们希望从 Docker 镜像存储（上游 docker 称之为 graph driver）中获取内容，把镜像挂在到某处，以便用工具来查看该镜像。如果你使用上游的 docker，查看镜像内容的唯一方法就是启动该容器。如果其中有不可信的内容，执行容器中的代码来查看它会有潜在危险。通过启动容器查看镜像内容的另一个问题是所需的工具可能没有被包含在容器镜像当中。

大多数容器镜像扫描器遵循以下流程：它们连接到 Docker 的套结字，执行一个 `docker save` 来创建一个 tar 打包文件，然后在磁盘上分解这个打包文件，最后查看其中的内容。这是一个很慢的过程。

通过 `atomic mount`，我们希望直接使用 Docker graph driver 挂载镜像。如果 docker 守护进程使用 device mapper，我们将挂载这个设备。如果它使用 overlay，我们会挂载 overlay。这个操作很快而且满足我们的需求。现在你可以执行：

```
# atomic mount fedora /mnt
# cd /mnt

```

然后开始探查内容。你完成相应工作后，执行：

```
# atomic umount /mnt

```

我们在 `atomic scan` 中使用了这一特性，实现了一个快速的容器扫描器。

#### 工具协作的问题

其中一个严重的问题是 `atomic mount` 隐式地执行这些工作。Docker 守护进程不知道有另一个进程在使用这个镜像。这会导致一些问题（例如，如果你先挂载了 Fedora 镜像，然后某个人执行了 `docker rmi fedora` 命令，docker 守护进程移除镜像时就会产生奇怪的操作失败，同时报告说相应的资源忙碌）。Docker 守护进程可能因此进入一个奇怪的状态。

#### 容器存储系统

为了解决这个问题，我们开始尝试将从上游 docker 守护进程剥离出来的 graph driver 代码拉取到我们的代码库中。Docker 守护进程在内存中为 graph driver 完成所有锁的获取。我们想要将这些锁操作转移到文件系统中，这样我们可以支持多个不同的进程来同时操作容器的存储系统，而不用通过单一的守护进程。

我们创建了 [containers/storage](https://github.com/containers/storage) 项目，实现了容器运行、构建、存储所需的所有写时拷贝（COW）特性，同时不再需要一个单一进程来控制和监控这个过程（也就是不需要守护进程）。现在 skopeo 以及其它工具和项目可以直接利用镜像的存储系统。其它开源项目也开始使用 containers/storage，在某些时候，我们也会把这些项目合并回上游 docker 项目。

### 驶向创新

当 Kubernetes 在一个节点上使用 docker 守护进程运行容器时会发生什么？首先，Kubernetes 执行一条类似如下的命令：

```
kubelet run nginx -image=nginx

```

这个命令告诉 kubelet 在节点上运行 NGINX 应用程序。kubelet 调用 CRI 请求启动 NGINX 应用程序。在这时，实现了 CRI 规范的容器运行时必须执行以下步骤：

1. 检查本地是否存在名为 `nginx` 的容器。如果没有，容器运行时会在容器仓库中搜索标准的容器镜像。
2. 如果镜像不存在于本地，从容器仓库下载到本地系统。
3. 使用容器存储系统（通常是写时拷贝存储系统）解析下载的容器镜像并挂载它。
4. 使用标准的容器运行时执行容器。

让我们看看上述过程使用到的特性：

1. OCI 镜像格式规范定义了容器仓库存储的标准镜像格式。
2. Containers/image 代码库实现了从容器仓库拉取镜像到容器主机所需的所有特性。
3. Containers/storage 提供了在写时拷贝的存储系统上探查并处理 OCI 镜像格式的代码库。
4. OCI 运行时规范以及 `runc` 提供了执行容器的工具（同时也是 docker 守护进程用来运行容器的工具）。

这意味着我们可以利用这些工具来使用容器，而无需一个大型的容器守护进程。

在中等到大规模的基于 DevOps 的持续集成/持续交付环境下，效率、速度和安全性至关重要。只要你的工具遵循 OCI 规范，开发者和执行者就能在持续集成、持续交付到生产环境的自动化中自然地使用最佳的工具。大多数的容器工具被隐藏在容器编排或上层容器平台技术之下。我们预想着有朝一日，运行时和镜像工具的选择会变成容器平台的一个安装选项。

#### 系统（独立）容器

在 Atomic 项目中我们引入了<ruby> 原子主机 <rt>  atomic host </rt></ruby>，一种新的操作系统构建方式：所有的软件可以被“原子地”升级并且大多数应用以容器的形式运行在操作系统中。这个平台的目的是证明将来所有的软件都能部署在 OCI 镜像格式中并且使用标准协议从容器仓库中拉取，然后安装到系统上。用容器镜像的形式发布软件允许你以不同的速度升级应用程序和操作系统。传统的 RPM/yum/DNF 包分发方式把应用更新锁定在操作系统的生命周期中。

在以容器部署基础设施时多数会遇到一个问题——有时一些应用必须在容器运行时执行之前启动。我们看一个使用 docker 的 Kubernetes 的例子：Kubernetes 为了将 pods 或者容器部署在独立的网络中，要求先建立一个网络。现在默认用于创建网络的守护进程是 [flanneld](https://github.com/coreos/flannel)，而它必须在 docker 守护进程之前启动，以支持 docker 网络接口来运行 Kubernetes 的 pods。而且，flanneld 使用 [etcd](https://github.com/coreos/etcd) 来存储数据，这个守护进程必须在 flanneld 启动之前运行。

如果你想把 etcd 和 flanneld 部署到容器镜像中，那就陷入了鸡与鸡蛋的困境中。我们需要容器运行时来启动容器化的应用，但这些应用又需要在容器运行时之前启动。我见过几个取巧的方法尝试解决这个问题，但这些方法都不太干净利落。而且 docker 守护进程当前没有合适的方法来配置容器启动的优先级顺序。我见过一些提议，但它们看起来和 SysVInit 所使用的启动服务的方式相似（我们知道它带来的复杂度）。

#### systemd

用 systemd 替代 SysVInit 的原因之一就是为了处理服务启动的优先级和顺序，我们为什么不充分利用这种技术呢？在 Atomic 项目中我们决定在让它在没有容器运行时的情况下也能启动容器，尤其是在系统启动早期。我们增强了 atomic CLI 的功能，让用户可以安装容器镜像。当你执行 `atomic install --system etc`，它将利用 skopeo 从外部的容器仓库拉取 etcd 的 OCI 镜像，然后把它分解（扩展）为 OSTree 底层存储。因为 etcd 运行在生产环境中，我们把镜像处理为只读。接着 `atomic` 命令抓取容器镜像中的 systemd 的 unit 文件模板，用它在磁盘上创建 unit 文件来启动镜像。这个 unit 文件实际上使用 `runc` 来在主机上启动容器（虽然 `runc` 不是必需的）。

执行 `atomic install --system flanneld` 时会进行相似的过程，但是这时 flanneld 的 unit 文件中会指明它依赖 etcd。

在系统引导时，systemd 会保证 etcd 先于 flanneld 运行，并且直到 flanneld 启动完毕后再启动容器运行时。这样我们就能把 docker 守护进程和 Kubernetes 部署到系统容器当中。这也意味着你可以启动一台原子主机或者使用传统的基于 rpm 的操作系统，让整个容器编排工具栈运行在容器中。这是一个强大的特性，因为用户往往希望改动容器主机时不受这些组件影响。而且，它保持了主机的操作系统的占用最小化。

大家甚至讨论把传统的应用程序部署到独立/系统容器或者被编排的容器中。设想一下，可以用 `atomic install --system httpd` 命令安装一个 Apache 容器，这个容器可以和用 RPM 安装的 httpd 服务以相同的方式启动（`systemctl start httpd` ，区别是这个容器 httpd 运行在一个容器中）。存储系统可以是本地的，换言之，`/var/www` 是从宿主机挂载到容器当中的，而容器监听着本地网络的 80 端口。这表明了我们可以在不使用容器守护进程的情况下将传统的负载组件部署到一个容器中。

### 构建容器镜像

在我看来，在过去 4 年来容器发展方面最让人失落的是缺少容器镜像构建机制上的创新。容器镜像不过是将一些 tar 包文件与 JSON 文件一起打包形成的文件。基础镜像则是一个 rootfs 与一个描述该基础镜像的 JSON 文件。然后当你增加镜像层时，层与层之间的差异会被打包，同时 JSON 文件会做出相应修改。这些镜像层与基础文件一起被打包，共同构成一个容器镜像。

现在几乎所有人都使用 `docker build` 与 Dockerfile 格式来构建镜像。上游 docker 已经在几年前停止了接受修改或改进 Dockerfile 格式的拉取请求（PR）了。Dockerfile 在容器的演进过程中扮演了重要角色，开发者和管理员/运维人员可以通过简单直接的方式来构建镜像；然而我觉得 Dockerfile 就像一个简陋的 bash 脚本，还带来了一些尚未解决的问题，例如：

* 使用 Dockerfile 创建容器镜像要求运行着 Docker 守护进程。
	+ 没有可以独立于 docker 命令的标准工具用于创建 OCI 镜像。
	+ 甚至类似 `ansible-containers` 和 OpenShift S2I (Source2Image) 的工具也在底层使用 `docker-engine`。
* Dockerfile 中的每一行都会创建一个新的镜像，这有助于创建容器的开发过程，这是因为构建工具能够识别 Dockerfile 中的未改动行，复用已经存在的镜像从而避免了未改动行的重复执行。但这个特性会产生*大量*的镜像层。
	+ 因此，不少人希望构建机制能压制镜像消除这些镜像层。我猜想上游 docker 最后应该接受了一些提交满足了这个需求。
* 要从受保护的站点拉取内容到容器镜像，你往往需要某种密钥。比如你为了添加 RHEL 的内容到镜像中，就需要访问 RHEL 的证书和订阅。
	+ 这些密钥最终会被以层的方式保存在镜像中。开发者要费很大工夫去移除它们。
	+ 为了允许在 docker 构建过程中挂载数据卷，我们在我们维护的 projectatomic/docker 中加入了 `-v volume` 选项，但是这些修改没有被上游 docker 接受。
* 构建过程的中间产物最终会保留在容器镜像中，所以尽管 Dockerfile 易于学习，当你想要了解你要构建的镜像时甚至可以在笔记本上构建容器，但它在大规模企业环境下还不够高效。然而在自动化容器平台下，你应该不会关心用于构建 OCI 镜像的方式是否高效。

### Buildah 起航

在 DevConf.cz 2017，我让我们团队的 [Nalin Dahyabhai](https://twitter.com/nalind) 考虑构建被我称为 `containers-coreutils` 的工具，它基本上就是基于 containers/storage 和 containers/image 库构建的一系列可以使用类似 Dockerfile 语法的命令行工具。Nalin 为了取笑我的波士顿口音，决定把它叫做 [buildah](https://github.com/projectatomic/buildah)。我们只需要少量的 buildah 原语就可以构建一个容器镜像：

* 最小化 OS 镜像、消除不必要的工具是主要的安全原则之一。因为黑客在攻击应用时需要一些工具，如果类似 `gcc`，`make`，`dnf` 这样的工具根本不存在，就能阻碍攻击者的行动。
* 减小容器的体积总是有益的，因为这些镜像会通过互联网拉取与推送。
* 使用 Docker 进行构建的基本原理是在容器构建的根目录下利用命令安装或编译软件。
* 执行 `run` 命令要求所有的可执行文件都包含在容器镜像内。只是在容器镜像中使用 `dnf` 就需要完整的 Python 栈，即使在应用中从未使用到 Python。
* `ctr=$(buildah from fedora)`:
	+ 使用 containers/image 从容器仓库拉取 Fedora 镜像。
	+ 返回一个容器 ID （`ctr`）。
* `mnt=$(buildah mount $ctr)`:
	+ 挂载新建的容器镜像（`$ctr`）.
	+ 返回挂载点路径。
	+ 现在你可以使用挂载点来写入内容。
* `dnf install httpd –installroot=$mnt`:
	+ 你可以使用主机上的命令把内容重定向到容器中，这样你可以把密钥保留在主机而不导入到容器内，同时构建所用的工具也仅仅存在于主机上。
	+ 容器内不需要包含 `dnf` 或者 Python 栈，除非你的应用用到它们。
* `cp foobar $mnt/dir`:
	+ 你可以使用任何 bash 中可用的命令来构造镜像。
* `buildah commit $ctr`:
	+ 你可以随时创建一个镜像层，镜像的分层由用户而不是工具来决定。
* `buildah config --env container=oci --entrypoint /usr/bin/httpd $ctr`:
	+ Buildah 支持所有 Dockerfile 的命令。
* `buildah run $ctr dnf -y install httpd`:
	+ Buildah 支持 `run` 命令，但它是在一个锁定的容器内利用 `runc` 执行命令，而不依赖容器运行时守护进程。
* `buildah build-using-dockerfile -f Dockerfile .`：
	+ 我们希望将移植类似 `ansible-containers` 和 OpenShift S2I 这样的工具，改用 `buildah` 以去除对容器运行时守护进程的依赖。
	+ 使用与生产环境相同的容器运行时构建容器镜像会遇到另一个大问题。为了保证安全性，我们需要把权限限制到支持容器构建与运行所需的最小权限。构建容器比起运行容器往往需要更多额外的权限。举个例子，我们默认允许 `mknod` 权限，这会允许进程创建设备节点。有些包的安装会尝试创建设备节点，然而在生产环境中的应用几乎都不会这么做。如果默认移除生产环境中容器的 `mknod` 特权会让系统更为安全。
	+ 另一个例子是，容器镜像默认是可读写的，因为安装过程意味着向 `/usr` 存入软件包。然而在生产环境中，我强烈建议把所有容器设为只读模式，仅仅允许它们写入 tmpfs 或者是挂载了数据卷的目录。通过分离容器的构建与运行环境，我们可以更改这些默认设置，提供一个更为安全的环境。
	+ 当然，buildah 可以使用 Dockerfile 构建容器镜像。

### CRI-O ：一个 Kubernetes 的运行时抽象

Kubernetes 添加了<ruby> 容器运行时接口 <rt>  Container Runtime Interface </rt></ruby>（CRI）接口，使 pod 可以在任何运行时上工作。虽然我不是很喜欢在我的系统上运行太多的守护进程，然而我们还是加了一个。我的团队在 [Mrunal Patel](https://twitter.com/mrunalp) 的领导下于 2016 年后期开始构建 [CRI-O] 守护进程。这是一个用来运行 OCI 应用程序的 OCI 守护进程。理论上，将来我们能够把 CRI-O 的代码直接并入 kubelet 中从而消除这个多余的守护进程。

不像其它容器运行时，CRI-O 的唯一目的就只是为了满足 Kubernetes 的需求。记得前文描述的 Kubernetes 运行容器的条件。

Kubernetes 传递消息给 kubelet 告知其运行 NGINX 服务器：

1. kubelet 唤醒 CRI-O 并告知它运行 NGINX。
2. CRI-O 回应 CRI 请求。
3. CRI-O 在容器仓库查找 OCI 镜像。
4. CRI-O 使用 containers/image 从仓库拉取镜像到主机。
5. CRI-O 使用 containers/storage 解压镜像到本地磁盘。
6. CRI-O 按照 OCI 运行时规范（通常使用 `runc`）启动容器。如前文所述，Docker 守护进程也同样使用 `runc` 启动它的容器。
7. 按照需要，kubelet 也可以使用替代的运行时启动容器，例如 Clear Containers `runcv`。

CRI-O 旨在成为稳定的 Kubernetes 运行平台。只有通过完整的 Kubernetes 测试集后，新版本的 CRI-O 才会被推出。所有提交到 <https://github.com/Kubernetes-incubator/cri-o> 的拉取请求都会运行完整的 Kubernetes 测试集。没有通过测试集的拉取请求都不会被接受。CRI-O 是完全开放的，我们已经收到了来自 Intel、SUSE、IBM、Google、Hyper.sh 等公司的代码贡献。即使不是红帽想要的特性，只要通过一定数量维护者的同意，提交给 CRI-O 的补丁就会被接受。

### 小结

我希望这份深入的介绍能够帮助你理解 Linux 容器的演化过程。Linux 容器曾经陷入一种各自为营的困境，Docker 建立起了镜像创建的事实标准，简化了容器的使用工具。OCI 则意味着业界在核心镜像格式与运行时方面的合作，这促进了工具在自动化效率、安全性、高可扩展性、易用性方面的创新。容器使我们能够以一种新奇的方式部署软件——无论是运行于主机上的传统应用还是部署在云端的微服务。而在许多方面，这一切还仅仅是个开始。

---

作者简介：

Daniel J Walsh - Daniel 有将近 30 年的计算机安全领域工作经验。他在 2001 年 8 月加入 Red Hat。

via: <https://opensource.com/article/17/7/how-linux-containers-evolved>

作者：[Daniel J Walsh](https://opensource.com/users/rhatdan) 译者：[haoqixu](https://github.com/haoqixu) 校对：[wxy](https://github.com/wxy)

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


开发一个 Linux 调试器（五）：源码和信号

![](/data/attachment/album/201708/26/175338u3rarihzal14s8sg.jpg)

在上一部分我们学习了关于 DWARF 的信息，以及它如何被用于读取变量和将被执行的机器码与我们的高级语言的源码联系起来。在这一部分，我们将进入实践，实现一些我们调试器后面会使用的 DWARF 原语。我们也会利用这个机会，使我们的调试器可以在命中一个断点时打印出当前的源码上下文。

### 系列文章索引

随着后面文章的发布，这些链接会逐渐生效。

1. [准备环境](/article-8626-1.html)
2. [断点](/article-8645-1.html)
3. [寄存器和内存](/article-8663-1.html)
4. [Elves 和 dwarves](/article-8719-1.html)
5. [源码和信号](https://blog.tartanllama.xyz/c++/2017/04/24/writing-a-linux-debugger-source-signal/)
6. [源码级逐步执行](https://blog.tartanllama.xyz/c++/2017/05/06/writing-a-linux-debugger-dwarf-step/)
7. 源码级断点
8. 调用栈展开
9. 读取变量
10. 下一步

### 设置我们的 DWARF 解析器

正如我在这系列文章开始时备注的，我们会使用 [libelfin](https://github.com/TartanLlama/libelfin/tree/fbreg) 来处理我们的 DWARF 信息。希望你已经在[第一部分](/article-8626-1.html)设置好了这些，如果没有的话，现在做吧，确保你使用我仓库的 `fbreg` 分支。

一旦你构建好了 `libelfin`，就可以把它添加到我们的调试器。第一步是解析我们的 ELF 可执行程序并从中提取 DWARF 信息。使用 `libelfin` 可以轻易实现，只需要对`调试器`作以下更改：

```
class debugger {
public:
    debugger (std::string prog_name, pid_t pid)
         : m_prog_name{std::move(prog_name)}, m_pid{pid} {
        auto fd = open(m_prog_name.c_str(), O_RDONLY);

        m_elf = elf::elf{elf::create_mmap_loader(fd)};
        m_dwarf = dwarf::dwarf{dwarf::elf::create_loader(m_elf)};
    }
    //...

private:
    //...
    dwarf::dwarf m_dwarf;
    elf::elf m_elf;
};

```

我们使用了 `open` 而不是 `std::ifstream`，因为 elf 加载器需要传递一个 UNIX 文件描述符给 `mmap`，从而可以将文件映射到内存而不是每次读取一部分。

### 调试信息原语

下一步我们可以实现从程序计数器的值中提取行条目（line entry）以及函数 DWARF 信息条目（function DIE）的函数。我们从 `get_function_from_pc` 开始：

```
dwarf::die debugger::get_function_from_pc(uint64_t pc) {
    for (auto &cu : m_dwarf.compilation_units()) {
        if (die_pc_range(cu.root()).contains(pc)) {
            for (const auto& die : cu.root()) {
                if (die.tag == dwarf::DW_TAG::subprogram) {
                    if (die_pc_range(die).contains(pc)) {
                        return die;
                    }
                }
            }
        }
    }

    throw std::out_of_range{"Cannot find function"};
}

```

这里我采用了朴素的方法，迭代遍历编译单元直到找到一个包含程序计数器的，然后迭代遍历它的子节点直到我们找到相关函数（`DW_TAG_subprogram`）。正如我在上一篇中提到的，如果你想要的话你可以处理类似的成员函数或者内联等情况。

接下来是 `get_line_entry_from_pc`：

```
dwarf::line_table::iterator debugger::get_line_entry_from_pc(uint64_t pc) {
    for (auto &cu : m_dwarf.compilation_units()) {
        if (die_pc_range(cu.root()).contains(pc)) {
            auto &lt = cu.get_line_table();
            auto it = lt.find_address(pc);
            if (it == lt.end()) {
                throw std::out_of_range{"Cannot find line entry"};
            }
            else {
                return it;
            }
        }
    }

    throw std::out_of_range{"Cannot find line entry"};
}

```

同样，我们可以简单地找到正确的编译单元，然后查询行表获取相关的条目。

### 打印源码

当我们命中一个断点或者逐步执行我们的代码时，我们会想知道处于源码中的什么位置。

```
void debugger::print_source(const std::string& file_name, unsigned line, unsigned n_lines_context) {
    std::ifstream file {file_name};

    //获得一个所需行附近的窗口
    auto start_line = line <= n_lines_context ? 1 : line - n_lines_context;
    auto end_line = line + n_lines_context + (line < n_lines_context ? n_lines_context - line : 0) + 1;

    char c{};
    auto current_line = 1u;
    //跳过 start_line 之前的行
    while (current_line != start_line && file.get(c)) {
        if (c == '\n') {
            ++current_line;
        }
    }

    //如果我们在当前行则输出光标
    std::cout << (current_line==line ? "> " : "  ");

    //输出行直到 end_line
    while (current_line <= end_line && file.get(c)) {
        std::cout << c;
        if (c == '\n') {
            ++current_line;
            //如果我们在当前行则输出光标
            std::cout << (current_line==line ? "> " : "  ");
        }
    }

    //输出换行确保恰当地清空了流
    std::cout << std::endl;
}

```

现在我们可以打印出源码了，我们需要将这些通过钩子添加到我们的调试器。实现这个的一个好地方是当调试器从一个断点或者（最终）逐步执行得到一个信号时。到了这里，我们可能想要给我们的调试器添加一些更好的信号处理。

### 更好的信号处理

我们希望能够得知什么信号被发送给了进程，同样我们也想知道它是如何产生的。例如，我们希望能够得知是否由于命中了一个断点从而获得一个 `SIGTRAP`，还是由于逐步执行完成、或者是产生了一个新线程等等导致的。幸运的是，我们可以再一次使用 `ptrace`。可以给 `ptrace` 的一个命令是 `PTRACE_GETSIGINFO`，它会给你被发送给进程的最后一个信号的信息。我们类似这样使用它：

```
siginfo_t debugger::get_signal_info() {
    siginfo_t info;
    ptrace(PTRACE_GETSIGINFO, m_pid, nullptr, &info);
    return info;
}

```

这会给我们一个 `siginfo_t` 对象，它能提供以下信息：

```
siginfo_t {
    int      si_signo;     /* 信号编号 */
    int      si_errno;     /* errno 值 */
    int      si_code;      /* 信号代码 */
    int      si_trapno;    /* 导致生成硬件信号的陷阱编号
                              （大部分架构中都没有使用） */
    pid_t    si_pid;       /* 发送信号的进程 ID */
    uid_t    si_uid;       /* 发送信号进程的用户 ID */
    int      si_status;    /* 退出值或信号 */
    clock_t  si_utime;     /* 消耗的用户时间 */
    clock_t  si_stime;     /* 消耗的系统时间 */
    sigval_t si_value;     /* 信号值 */
    int      si_int;       /* POSIX.1b 信号 */
    void    *si_ptr;       /* POSIX.1b 信号 */
    int      si_overrun;   /* 计时器 overrun 计数;
                              POSIX.1b 计时器 */
    int      si_timerid;   /* 计时器 ID; POSIX.1b 计时器 */
    void    *si_addr;      /* 导致错误的内存地址 */
    long     si_band;      /* Band event (在 glibc 2.3.2 和之前版本中是 int 类型) */
    int      si_fd;        /* 文件描述符 */
    short    si_addr_lsb;  /* 地址的最不重要位
                              (自 Linux 2.6.32) */
    void    *si_lower;     /* 出现地址违规的下限 (自 Linux 3.19) */
    void    *si_upper;     /* 出现地址违规的上限 (自 Linux 3.19) */
    int      si_pkey;      /* PTE 上导致错误的保护键 (自 Linux 4.6) */
    void    *si_call_addr; /* 系统调用指令的地址
                              (自 Linux 3.5) */
    int      si_syscall;   /* 系统调用尝试次数
                              (自 Linux 3.5) */
    unsigned int si_arch;  /* 尝试系统调用的架构
                              (自 Linux 3.5) */
}

```

我只需要使用 `si_signo` 就可以找到被发送的信号，使用 `si_code` 来获取更多关于信号的信息。放置这些代码的最好位置是我们的 `wait_for_signal` 函数：

```
void debugger::wait_for_signal() {
    int wait_status;
    auto options = 0;
    waitpid(m_pid, &wait_status, options);

    auto siginfo = get_signal_info();

    switch (siginfo.si_signo) {
    case SIGTRAP:
        handle_sigtrap(siginfo);
        break;
    case SIGSEGV:
        std::cout << "Yay, segfault. Reason: " << siginfo.si_code << std::endl;
        break;
    default:
        std::cout << "Got signal " << strsignal(siginfo.si_signo) << std::endl;
    }
}

```

现在再来处理 `SIGTRAP`。知道当命中一个断点时会发送 `SI_KERNEL` 或 `TRAP_BRKPT`，而逐步执行结束时会发送 `TRAP_TRACE` 就足够了：

```
void debugger::handle_sigtrap(siginfo_t info) {
    switch (info.si_code) {
    //如果命中了一个断点其中的一个会被设置
    case SI_KERNEL:
    case TRAP_BRKPT:
    {
        set_pc(get_pc()-1); //将程序计数器的值设置为它应该指向的地方
        std::cout << "Hit breakpoint at address 0x" << std::hex << get_pc() << std::endl;
        auto line_entry = get_line_entry_from_pc(get_pc());
        print_source(line_entry->file->path, line_entry->line);
        return;
    }
    //如果信号是由逐步执行发送的，这会被设置
    case TRAP_TRACE:
        return;
    default:
        std::cout << "Unknown SIGTRAP code " << info.si_code << std::endl;
        return;
    }
}

```

这里有一大堆不同风格的信号你可以处理。查看 `man sigaction` 获取更多信息。

由于当我们收到 `SIGTRAP` 信号时我们已经修正了程序计数器的值，我们可以从 `step_over_breakpoint` 中移除这些代码，现在它看起来类似：

```
void debugger::step_over_breakpoint() {
    if (m_breakpoints.count(get_pc())) {
        auto& bp = m_breakpoints[get_pc()];
        if (bp.is_enabled()) {
            bp.disable();
            ptrace(PTRACE_SINGLESTEP, m_pid, nullptr, nullptr);
            wait_for_signal();
            bp.enable();
        }
    }
}

```

### 测试

现在你应该可以在某个地址设置断点，运行程序然后看到打印出了源码，而且正在被执行的行被光标标记了出来。

后面我们会添加设置源码级别断点的功能。同时，你可以从[这里](https://github.com/TartanLlama/minidbg/tree/tut_source)获取该博文的代码。

---

via: <https://blog.tartanllama.xyz/c++/2017/04/24/writing-a-linux-debugger-source-signal/>

作者：[TartanLlama](https://www.twitter.com/TartanLlama) 译者：[ictlyh](https://github.com/ictlyh) 校对：[wxy](https://github.com/wxy)

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


开发一个 Linux 调试器（六）：源码级逐步执行

![](/data/attachment/album/201708/26/182207drf4dydz6xpyc5jd.jpg)

在前几篇博文中我们学习了 DWARF 信息以及它如何使我们将机器码和上层源码联系起来。这一次我们通过为我们的调试器添加源码级逐步调试将该知识应用于实际。

### 系列文章索引

随着后面文章的发布，这些链接会逐渐生效。

1. [准备环境](/article-8626-1.html)
2. [断点](/article-8645-1.html)
3. [寄存器和内存](/article-8579-1.html)
4. [Elves 和 dwarves](/article-8719-1.html)
5. [源码和信号](/article-8812-1.html)
6. [源码级逐步执行](https://blog.tartanllama.xyz/c++/2017/05/06/writing-a-linux-debugger-dwarf-step/)
7. 源码级断点
8. 调用栈展开
9. 读取变量
10. 下一步

### 揭秘指令级逐步执行

我们正在超越了自我。首先让我们通过用户接口揭秘指令级单步执行。我决定将它切分为能被其它部分代码利用的 `single_step_instruction` 和确保是否启用了某个断点的 `single_step_instruction_with_breakpoint_check` 两个函数。

```
void debugger::single_step_instruction() {
    ptrace(PTRACE_SINGLESTEP, m_pid, nullptr, nullptr);
    wait_for_signal();
}

void debugger::single_step_instruction_with_breakpoint_check() {
    //首先，检查我们是否需要停用或者启用某个断点
    if (m_breakpoints.count(get_pc())) {
        step_over_breakpoint();
    }
    else {
        single_step_instruction();
    }
}

```

正如以往，另一个命令被集成到我们的 `handle_command` 函数：

```
else if(is_prefix(command, "stepi")) {
    single_step_instruction_with_breakpoint_check();
    auto line_entry = get_line_entry_from_pc(get_pc());
    print_source(line_entry->file->path, line_entry->line);
 }

```

利用新增的这些函数我们可以开始实现我们的源码级逐步执行函数。

### 实现逐步执行

我们打算编写这些函数非常简单的版本，但真正的调试器有 *thread plan* 的概念，它封装了所有的单步信息。例如，调试器可能有一些复杂的逻辑去决定断点的位置，然后有一些回调函数用于判断单步操作是否完成。这其中有非常多的基础设施，我们只采用一种朴素的方法。我们可能会意外地跳过断点，但如果你愿意的话，你可以花一些时间把所有的细节都处理好。

对于跳出 `step_out`，我们只是在函数的返回地址处设一个断点然后继续执行。我暂时还不想考虑调用栈展开的细节 - 这些都会在后面的部分介绍 - 但可以说返回地址就保存在栈帧开始的后 8 个字节中。因此我们会读取栈指针然后在内存相对应的地址读取值：

```
void debugger::step_out() {
    auto frame_pointer = get_register_value(m_pid, reg::rbp);
    auto return_address = read_memory(frame_pointer+8);

    bool should_remove_breakpoint = false;
    if (!m_breakpoints.count(return_address)) {
        set_breakpoint_at_address(return_address);
        should_remove_breakpoint = true;
    }

    continue_execution();

    if (should_remove_breakpoint) {
        remove_breakpoint(return_address);
    }
}

```

`remove_breakpoint` 是一个小的帮助函数：

```
void debugger::remove_breakpoint(std::intptr_t addr) {
    if (m_breakpoints.at(addr).is_enabled()) {
        m_breakpoints.at(addr).disable();
    }
    m_breakpoints.erase(addr);
}

```

接下来是跳入 `step_in`。一个简单的算法是继续逐步执行指令直到新的一行。

```
void debugger::step_in() {
   auto line = get_line_entry_from_pc(get_pc())->line;

    while (get_line_entry_from_pc(get_pc())->line == line) {
        single_step_instruction_with_breakpoint_check();
    }

    auto line_entry = get_line_entry_from_pc(get_pc());
    print_source(line_entry->file->path, line_entry->line);
}

```

跳过 `step_over` 对于我们来说是三个中最难的。理论上，解决方法就是在下一行源码中设置一个断点，但下一行源码是什么呢？它可能不是当前行后续的那一行，因为我们可能处于一个循环、或者某种条件结构之中。真正的调试器一般会检查当前正在执行什么指令然后计算出所有可能的分支目标，然后在所有分支目标中设置断点。对于一个小的项目，我不打算实现或者集成一个 x86 指令模拟器，因此我们要想一个更简单的解决办法。有几个可怕的选择，一个是一直逐步执行直到当前函数新的一行，或者在当前函数的每一行都设置一个断点。如果我们是要跳过一个函数调用，前者将会相当的低效，因为我们需要逐步执行那个调用图中的每个指令，因此我会采用第二种方法。

```
void debugger::step_over() {
    auto func = get_function_from_pc(get_pc());
    auto func_entry = at_low_pc(func);
    auto func_end = at_high_pc(func);

    auto line = get_line_entry_from_pc(func_entry);
    auto start_line = get_line_entry_from_pc(get_pc());

    std::vector<std::intptr_t> to_delete{};

    while (line->address < func_end) {
        if (line->address != start_line->address && !m_breakpoints.count(line->address)) {
            set_breakpoint_at_address(line->address);
            to_delete.push_back(line->address);
        }
        ++line;
    }

    auto frame_pointer = get_register_value(m_pid, reg::rbp);
    auto return_address = read_memory(frame_pointer+8);
    if (!m_breakpoints.count(return_address)) {
        set_breakpoint_at_address(return_address);
        to_delete.push_back(return_address);
    }

    continue_execution();

    for (auto addr : to_delete) {
        remove_breakpoint(addr);
    }
}

```

这个函数有一点复杂，我们将它拆开来看。

```
    auto func = get_function_from_pc(get_pc());
    auto func_entry = at_low_pc(func);
    auto func_end = at_high_pc(func);

```

`at_low_pc` 和 `at_high_pc` 是 `libelfin` 中的函数，它们能给我们指定函数 DWARF 信息条目的最小和最大程序计数器值。

```
    auto line = get_line_entry_from_pc(func_entry);
    auto start_line = get_line_entry_from_pc(get_pc());

    std::vector<std::intptr_t> breakpoints_to_remove{};

    while (line->address < func_end) {
        if (line->address != start_line->address && !m_breakpoints.count(line->address)) {
            set_breakpoint_at_address(line->address);
            breakpoints_to_remove.push_back(line->address);
        }
        ++line;
    }

```

我们需要移除我们设置的所有断点，以便不会泄露出我们的逐步执行函数，为此我们把它们保存到一个 `std::vector` 中。为了设置所有断点，我们循环遍历行表条目直到找到一个不在我们函数范围内的。对于每一个，我们都要确保它不是我们当前所在的行，而且在这个位置还没有设置任何断点。

```
    auto frame_pointer = get_register_value(m_pid, reg::rbp);
    auto return_address = read_memory(frame_pointer+8);
    if (!m_breakpoints.count(return_address)) {
        set_breakpoint_at_address(return_address);
        to_delete.push_back(return_address);
    }

```

这里我们在函数的返回地址处设置一个断点，正如跳出 `step_out`。

```
    continue_execution();

    for (auto addr : to_delete) {
        remove_breakpoint(addr);
    }

```

最后，我们继续执行直到命中它们中的其中一个断点，然后移除所有我们设置的临时断点。

它并不美观，但暂时先这样吧。

当然，我们还需要将这个新功能添加到用户界面：

```
    else if(is_prefix(command, "step")) {
        step_in();
    }
    else if(is_prefix(command, "next")) {
        step_over();
    }
    else if(is_prefix(command, "finish")) {
        step_out();
    }

```

### 测试

我通过实现一个调用一系列不同函数的简单函数来进行测试：

```
void a() {
    int foo = 1;
}

void b() {
    int foo = 2;
    a();
}

void c() {
    int foo = 3;
    b();
}

void d() {
    int foo = 4;
    c();
}

void e() {
    int foo = 5;
    d();
}

void f() {
    int foo = 6;
    e();
}

int main() {
    f();
}

```

你应该可以在 `main` 地址处设置一个断点，然后在整个程序中跳入、跳过、跳出函数。如果你尝试跳出 `main` 函数或者跳入任何动态链接库，就会出现意料之外的事情。

你可以在[这里](https://github.com/TartanLlama/minidbg/tree/tut_dwarf_step)找到这篇博文的相关代码。下次我们会利用我们新的 DWARF 技巧来实现源码级断点。

---

via: <https://blog.tartanllama.xyz/c++/2017/05/06/writing-a-linux-debugger-dwarf-step/>

作者：[Simon Brand](https://www.twitter.com/TartanLlama) 译者：[ictlyh](https://github.com/ictlyh) 校对：[wxy](https://github.com/wxy)

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