mirror of
https://github.com/LCTT/TranslateProject.git
synced 2025-02-03 23:40:14 +08:00
Merge branch 'master' of https://github.com/LCTT/TranslateProject into translating
This commit is contained in:
commit
e424b1ca91
@ -1,8 +1,8 @@
|
||||
[#]: collector: (lujun9972)
|
||||
[#]: translator: (gxlct008)
|
||||
[#]: reviewer: ( )
|
||||
[#]: publisher: ( )
|
||||
[#]: url: ( )
|
||||
[#]: reviewer: (wxy)
|
||||
[#]: publisher: (wxy)
|
||||
[#]: url: (https://linux.cn/article-12704-1.html)
|
||||
[#]: subject: (Building a Messenger App: Access Page)
|
||||
[#]: via: (https://nicolasparada.netlify.com/posts/go-messenger-access-page/)
|
||||
[#]: author: (Nicolás Parada https://nicolasparada.netlify.com/)
|
||||
@ -10,6 +10,8 @@
|
||||
构建一个即时消息应用(七):Access 页面
|
||||
======
|
||||
|
||||
![](https://img.linux.net.cn/data/attachment/album/202010/10/101345zj7gfybyee2g9x9e.jpg)
|
||||
|
||||
本文是该系列的第七篇。
|
||||
|
||||
* [第一篇: 模式][1]
|
||||
@ -38,7 +40,7 @@
|
||||
</html>
|
||||
```
|
||||
|
||||
这个 HTML 文件必须为每个 URL 提供服务,并且将使用 JavaScript 负责呈现正确的页面。
|
||||
这个 HTML 文件必须为每个 URL 提供服务,并且使用 JavaScript 负责呈现正确的页面。
|
||||
|
||||
因此,让我们将注意力转到 `main.go` 片刻,然后在 `main()` 函数中添加以下路由:
|
||||
|
||||
@ -96,11 +98,9 @@ function view(pageName) {
|
||||
}
|
||||
```
|
||||
|
||||
如果您是这个博客的关注者,您已经知道它是如何工作的了。 该路由器就是在 [这里][7] 显示的那个。 只需从 [@nicolasparada/router][8] 下载并保存到 `static/router.js` 即可。
|
||||
如果你是这个博客的关注者,你已经知道它是如何工作的了。 该路由器就是在 [这里][7] 显示的那个。 只需从 [@nicolasparada/router][8] 下载并保存到 `static/router.js` 即可。
|
||||
|
||||
We registered four routes. At the root `/` we show the home or access page whether the user is authenticated. At `/callback` we show the callback page. On `/conversations/{conversationID}` we show the conversation or access page whether the user is authenticated and for every other URL, we show a not found page.
|
||||
|
||||
我们注册了四条路由。 在根路由 `/` 处,我们展示 home 或 access 页面,无论用户是否通过身份验证。 在 `/callback` 中,我们展示 callback 页面。 在 `/conversations/{conversationID}` 上,我们展示对话或 access 页面,无论用户是否通过验证,对于其他 URL,我们展示一个 not found 页面。
|
||||
我们注册了四条路由。 在根路由 `/` 处,我们展示 `home` 或 `access` 页面,无论用户是否通过身份验证。 在 `/callback` 中,我们展示 `callback` 页面。 在 `/conversations/{conversationID}` 上,我们展示对话或 `access` 页面,无论用户是否通过验证,对于其他 URL,我们展示一个 `not-found` 页面。
|
||||
|
||||
我们告诉路由器将结果渲染为文档主体,并在离开之前向每个页面调度一个 `disconnect` 事件。
|
||||
|
||||
@ -108,8 +108,7 @@ We registered four routes. At the root `/` we show the home or access page wheth
|
||||
|
||||
### 身份验证
|
||||
|
||||
`guard()` 是一个函数,给它两个函数作为参数,如果用户通过了身份验证,则执行第一个函数,否则执行第二个。
|
||||
它来自 `auth.js`,所以我们创建一个包含以下内容的 `static/auth.js` 文件:
|
||||
`guard()` 是一个函数,给它两个函数作为参数,如果用户通过了身份验证,则执行第一个函数,否则执行第二个。它来自 `auth.js`,所以我们创建一个包含以下内容的 `static/auth.js` 文件:
|
||||
|
||||
```javascript
|
||||
export function isAuthenticated() {
|
||||
@ -151,15 +150,15 @@ export function getAuthUser() {
|
||||
}
|
||||
```
|
||||
|
||||
`isAuthenticated()` 检查 localStorage 中的 `token` 和 `expires_at`,以判断用户是否已通过身份验证。`getAuthUser()` 从 localStorage 中获取经过身份验证的用户。
|
||||
`isAuthenticated()` 检查 `localStorage` 中的 `token` 和 `expires_at`,以判断用户是否已通过身份验证。`getAuthUser()` 从 `localStorage` 中获取经过身份验证的用户。
|
||||
|
||||
当我们登录时,我们会将所有的数据保存到 localStorage,这样才有意义。
|
||||
当我们登录时,我们会将所有的数据保存到 `localStorage`,这样才有意义。
|
||||
|
||||
### Access 页面
|
||||
|
||||
![access page screenshot][9]
|
||||
|
||||
让我们从 access 页面开始。 创建一个包含以下内容的文件 `static/pages/access-page.js`:
|
||||
让我们从 `access` 页面开始。 创建一个包含以下内容的文件 `static/pages/access-page.js`:
|
||||
|
||||
```javascript
|
||||
const template = document.createElement('template')
|
||||
@ -175,7 +174,7 @@ export default function accessPage() {
|
||||
|
||||
因为路由器会拦截所有链接点击来进行导航,所以我们必须特别阻止此链接的事件传播。
|
||||
|
||||
单击该链接会将我们重定向到后端,然后重定向到 GitHub,再重定向到后端,然后再次重定向到前端; 到 callback 页面。
|
||||
单击该链接会将我们重定向到后端,然后重定向到 GitHub,再重定向到后端,然后再次重定向到前端; 到 `callback` 页面。
|
||||
|
||||
### Callback 页面
|
||||
|
||||
@ -212,7 +211,7 @@ function getAuthUser(token) {
|
||||
}
|
||||
```
|
||||
|
||||
callback 页面不呈现任何内容。这是一个异步函数,它使用 URL 查询字符串中的 token 向 `/api/auth_user` 发出 GET 请求,并将所有数据保存到 localStorage。 然后重定向到 `/`。
|
||||
`callback` 页面不呈现任何内容。这是一个异步函数,它使用 URL 查询字符串中的 token 向 `/api/auth_user` 发出 GET 请求,并将所有数据保存到 `localStorage`。 然后重定向到 `/`。
|
||||
|
||||
### HTTP
|
||||
|
||||
@ -304,7 +303,7 @@ export default {
|
||||
|
||||
![home page screenshot][12]
|
||||
|
||||
因此,当用户登录时,将显示主页。 创建一个具有以下内容的 `static/pages/home-page.js` 文件:
|
||||
因此,当用户登录时,将显示 `home` 页。 创建一个具有以下内容的 `static/pages/home-page.js` 文件:
|
||||
|
||||
```javascript
|
||||
import { getAuthUser } from '../auth.js'
|
||||
@ -335,9 +334,9 @@ function onLogoutClick() {
|
||||
}
|
||||
```
|
||||
|
||||
对于这篇文章,这是我们在主页上呈现的唯一内容。我们显示当前经过身份验证的用户和注销按钮。
|
||||
对于这篇文章,这是我们在 `home` 页上呈现的唯一内容。我们显示当前经过身份验证的用户和注销按钮。
|
||||
|
||||
当用户单击注销时,我们清除 localStorage 中的所有内容并重新加载页面。
|
||||
当用户单击注销时,我们清除 `localStorage` 中的所有内容并重新加载页面。
|
||||
|
||||
### Avatar
|
||||
|
||||
@ -351,11 +350,9 @@ export function avatar(user) {
|
||||
}
|
||||
```
|
||||
|
||||
We use a small figure with the user’s initial in case the avatar URL is null.
|
||||
如果头像网址为 null,我们将使用用户的姓名首字母作为初始头像。
|
||||
如果头像网址为 `null`,我们将使用用户的姓名首字母作为初始头像。
|
||||
|
||||
您可以使用 `attr()` 函数显示带有少量 CSS 样式的首字母。
|
||||
You can show the initial with a little of CSS using the `attr()` function.
|
||||
你可以使用 `attr()` 函数显示带有少量 CSS 样式的首字母。
|
||||
|
||||
```css
|
||||
.avatar[data-initial]::after {
|
||||
@ -367,7 +364,7 @@ You can show the initial with a little of CSS using the `attr()` function.
|
||||
|
||||
![access page with login form screenshot][13]
|
||||
|
||||
在上一篇文章中,我们为编写了一个登录代码。让我们在 access 页面中为此添加一个表单。 进入 `static/ages/access-page.js`,稍微修改一下。
|
||||
在上一篇文章中,我们为编写了一个登录代码。让我们在 `access` 页面中为此添加一个表单。 进入 `static/ages/access-page.js`,稍微修改一下。
|
||||
|
||||
```javascript
|
||||
import http from '../http.js'
|
||||
@ -423,7 +420,7 @@ function login(username) {
|
||||
}
|
||||
```
|
||||
|
||||
我添加了一个登录表单。当用户提交表单时。它使用用户名对 `/api/login` 进行 POST 请求。将所有数据保存到 localStorage 并重新加载页面。
|
||||
我添加了一个登录表单。当用户提交表单时。它使用用户名对 `/api/login` 进行 POST 请求。将所有数据保存到 `localStorage` 并重新加载页面。
|
||||
|
||||
记住在前端完成后删除此表单。
|
||||
|
||||
@ -431,7 +428,7 @@ function login(username) {
|
||||
|
||||
这就是这篇文章的全部内容。在下一篇文章中,我们将继续使用主页添加一个表单来开始对话,并显示包含最新对话的列表。
|
||||
|
||||
[Souce Code][14]
|
||||
- [源代码][14]
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
@ -439,19 +436,19 @@ via: https://nicolasparada.netlify.com/posts/go-messenger-access-page/
|
||||
|
||||
作者:[Nicolás Parada][a]
|
||||
选题:[lujun9972][b]
|
||||
译者:[译者ID](https://github.com/gxlct008)
|
||||
校对:[校对者ID](https://github.com/校对者ID)
|
||||
译者:[gxlct008](https://github.com/gxlct008)
|
||||
校对:[wxy](https://github.com/wxy)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]: https://nicolasparada.netlify.com/
|
||||
[b]: https://github.com/lujun9972
|
||||
[1]: https://nicolasparada.netlify.com/posts/go-messenger-schema/
|
||||
[2]: https://nicolasparada.netlify.com/posts/go-messenger-oauth/
|
||||
[3]: https://nicolasparada.netlify.com/posts/go-messenger-conversations/
|
||||
[4]: https://nicolasparada.netlify.com/posts/go-messenger-messages/
|
||||
[5]: https://nicolasparada.netlify.com/posts/go-messenger-realtime-messages/
|
||||
[6]: https://nicolasparada.netlify.com/posts/go-messenger-dev-login/
|
||||
[1]: https://linux.cn/article-11396-1.html
|
||||
[2]: https://linux.cn/article-11510-1.html
|
||||
[3]: https://linux.cn/article-12056-1.html
|
||||
[4]: https://linux.cn/article-12680-1.html
|
||||
[5]: https://linux.cn/article-12685-1.html
|
||||
[6]: https://linux.cn/article-12692-1.html
|
||||
[7]: https://nicolasparada.netlify.com/posts/js-router/
|
||||
[8]: https://unpkg.com/@nicolasparada/router
|
||||
[9]: https://nicolasparada.netlify.com/img/go-messenger-access-page/access-page.png
|
@ -1,8 +1,8 @@
|
||||
[#]: collector: (lujun9972)
|
||||
[#]: translator: (rakino)
|
||||
[#]: reviewer: ( )
|
||||
[#]: publisher: ( )
|
||||
[#]: url: ( )
|
||||
[#]: reviewer: (wxy)
|
||||
[#]: publisher: (wxy)
|
||||
[#]: url: (https://linux.cn/article-12706-1.html)
|
||||
[#]: subject: (Things You Didn't Know About GNU Readline)
|
||||
[#]: via: (https://twobithistory.org/2019/08/22/readline.html)
|
||||
[#]: author: (Two-Bit History https://twobithistory.org)
|
||||
@ -10,17 +10,19 @@
|
||||
你所不知的 GNU Readline
|
||||
======
|
||||
|
||||
有时我会觉得自己的计算机是一栋非常大的房子,我每天都会访问这栋房子,也对一楼的大部分房间都了如指掌,但仍然还是有卧室我没有去过,有衣柜我没有打开过,有犄角旮旯我没有探索过。我感到有必要更多地了解我的计算机了,就像任何人都会觉得有必要看看自己家里从未去过的房间一样。
|
||||
![](https://img.linux.net.cn/data/attachment/album/202010/10/222755etdndudtu97wddz7.jpg)
|
||||
|
||||
GNU Readline 是个不起眼的小软件库,我依赖了它多年却没有意识到它的存在,也许有成千上万的人每天都在不经意间使用它。如果你用 Bash shell 的话,每当你补全一个文件名,或者在一行文本输入中移动光标,以及搜索之前命令的历史记录时,你都在使用 GNU Readline;当你在 Postgres(`psql`)或是 Ruby REPL(`irb`)的命令行界面中进行同样的操作时,你依然在使用 GNU Readline。很多软件都依赖 GNU Readline 库来实现用户所期望的功能,不过这些功能是如此的辅助与不显眼,以至于在我看来很少有人会停下来去想它是从哪里来的。
|
||||
有时我会觉得自己的计算机是一栋非常大的房子,我每天都会访问这栋房子,也对一楼的大部分房间都了如指掌,但仍然还是有我没有去过的卧室,有我没有打开过的衣柜,有我没有探索过的犄角旮旯。我感到有必要更多地了解我的计算机了,就像任何人都会觉得有必要看看自己家里从未去过的房间一样。
|
||||
|
||||
GNU Readline 最初是自由软件基金会在 20 世纪 80 年代创建的,如今作为每个人的基础计算设施的重要组成部分的它,由一位志愿者维护。
|
||||
GNU Readline 是个不起眼的小软件库,我依赖了它多年却没有意识到它的存在,也许有成千上万的人每天都在不经意间使用它。如果你用 Bash shell 的话,每当你自动补全一个文件名,或者在输入的一行文本中移动光标,以及搜索之前命令的历史记录时,你都在使用 GNU Readline;当你在 Postgres(`psql`)或是 Ruby REPL(`irb`)的命令行界面中进行同样的操作时,你依然在使用 GNU Readline。很多软件都依赖 GNU Readline 库来实现用户所期望的功能,不过这些功能是如此的辅助与不显眼,以至于在我看来很少有人会停下来去想它是从哪里来的。
|
||||
|
||||
GNU Readline 最初是自由软件基金会在 20 世纪 80 年代创建的,如今作为每个人的基础计算设施的重要的、甚至看不见的组成部分的它,由一位志愿者维护。
|
||||
|
||||
### 充满特色
|
||||
|
||||
GNU Readline 库的存在,主要是为了用一组通用的按键来增强任何命令行界面,从而使你可以在一行输入中移动和编辑。例如,在 Bash 提示符中按下 `Ctrl-A`,你的光标会跳到行首,而按下 `Ctrl-E` 则会跳到行末;另一个有用的命令是 `Ctrl-U`,它会删除该行中光标之前的所有内容。
|
||||
GNU Readline 库的存在,主要是为了增强各种命令行界面,它提供了一组通用的按键,使你可以在一个单行输入中移动和编辑。例如,在 Bash 提示符中按下 `Ctrl-A`,你的光标会跳到行首,而按下 `Ctrl-E` 则会跳到行末;另一个有用的命令是 `Ctrl-U`,它会删除该行中光标之前的所有内容。
|
||||
|
||||
有很长一段时间,我通过反复敲击方向键来在命令行上移动,如今看来这十分尴尬,也不知道为什么,当时的我从来没有想过可以有一种更快的方法。当然了,没有一个熟悉 Vim 或 Emacs 这种文本编辑器的程序员愿意长时间地击打方向键,所以像 Readline 这样的东西必然会被创造出来;不过在 Readline 上可以做的绝非仅仅跳来跳去,你可以像使用文本编辑器那样编辑单行文本——这里有删除单词、换位、大写单词、复制和粘贴字符等命令。Readline 的大部分按键/快捷键都是基于 Emacs 的,它基本上就是一个单行文本版的 Emacs 了,甚至还有录制和重放宏的功能。
|
||||
有很长一段时间,我通过反复敲击方向键来在命令行上移动,如今看来这十分尴尬,也不知道为什么,当时的我从来没有想过可以有一种更快的方法。当然了,没有哪一个熟悉 Vim 或 Emacs 这种文本编辑器的程序员愿意长时间地击打方向键,所以像 Readline 这样的东西必然会被创造出来。在 Readline 上可以做的绝非仅仅跳来跳去,你可以像使用文本编辑器那样编辑单行文本——这里有删除单词、单词换位、大写单词、复制和粘贴字符等命令。Readline 的大部分按键/快捷键都是基于 Emacs 的,它基本上就是一个单行文本版的 Emacs 了,甚至还有录制和重放宏的功能。
|
||||
|
||||
我从来没有用过 Emacs,所以很难记住所有不同的 Readline 命令。不过 Readline 有着很巧妙的一点,那就是能够切换到基于 Vim 的模式,在 Bash 中可以使用内置的 `set` 命令来这样做。下面会让 Readline 在当前的 shell 中使用 Vim 风格的命令:
|
||||
|
||||
@ -30,17 +32,17 @@ $ set -o vi
|
||||
|
||||
该选项启用后,就可以使用 `dw` 等命令来删除单词了,此时相当于 Emacs 模式下的 `Ctrl-U` 的命令是 `d0`。
|
||||
|
||||
我第一次知道有这个功能的时候很兴奋地想尝试一下,但它对我来说并不是那么好用。我很高兴知道有这种对 Vim 用户的让步,在使用这个功能上你可能会比我更幸运,尤其是你还没有使用 Readline 的默认按键的话;我的问题在于,我听说有基于 Vim 的界面时已经学会了几种默认按键,因此即使启用了 Vim 的选项,也一直在错误地用着默认的按键;另外因为没有某种指示器,所以 Vim 的多模态设计在这里会很尴尬——你很容易就忘记了自己处于哪个模式,就因为这样,我卡在了一种虽然使用 Vim 作为文本编辑器,但却在 Readline 上用着 Emacs 风格的命令的情况里,我猜其他很多人也是这样的。
|
||||
我第一次知道有这个功能的时候很兴奋地想尝试一下,但它对我来说并不是那么好用。我很高兴知道有这种对 Vim 用户的让步,在使用这个功能上你可能会比我更幸运,尤其是你还没有使用 Readline 的默认按键的话;我的问题在于,我听说有基于 Vim 的界面时已经学会了几种默认按键,因此即使启用了 Vim 的选项,也一直在错误地用着默认的按键;另外因为没有某种指示器,所以 Vim 的模态设计在这里会很尴尬——你很容易就忘记了自己处于哪个模式,就因为这样,我卡在了一种虽然使用 Vim 作为文本编辑器,但却在 Readline 上用着 Emacs 风格的命令的情况里,我猜其他很多人也是这样的。
|
||||
|
||||
如果你觉得 Vim 和 Emacs 的键盘命令系统诡异而神秘,你可以按照喜欢的方式自定义 Readline 的键绑定,这并不难。Readline 在启动时会读取文件 `~/.inputrc`,它可以用来配置各种选项与键绑定,我做的一件事是重新配置了 `Ctrl-K`:通常情况下该命令会从光标处删除到行末,但我很少这样做,所以我在 `~/.inputrc` 中添加了以下内容,把它绑定为直接删除整行:
|
||||
如果你觉得 Vim 和 Emacs 的键盘命令系统诡异而神秘(这并不是没有道理的),你可以按照喜欢的方式自定义 Readline 的键绑定。Readline 在启动时会读取文件 `~/.inputrc`,它可以用来配置各种选项与键绑定,我做的一件事是重新配置了 `Ctrl-K`:通常情况下该命令会从光标处删除到行末,但我很少这样做,所以我在 `~/.inputrc` 中添加了以下内容,把它绑定为直接删除整行:
|
||||
|
||||
```
|
||||
Control-k: kill-whole-line
|
||||
```
|
||||
|
||||
每个 Readline 命令(文档中称它们为 _函数_ )都有一个名称,你可以用这种方式将其与一个键序联系起来。如果你在 Vim 中编辑 `~/.inputrc`,就会发现 Vim 知道这种文件类型,还会帮你高亮显示有效的函数名,而不高亮无效的函数名。
|
||||
每个 Readline 命令(文档中称它们为 “函数” )都有一个名称,你可以用这种方式将其与一个键序列联系起来。如果你在 Vim 中编辑 `~/.inputrc`,就会发现 Vim 知道这种文件类型,还会帮你高亮显示有效的函数名,而不高亮无效的函数名。
|
||||
|
||||
`~/.inputrc` 可以做的另一件事是通过将键序映射到输入字符串上来创建预制宏。[Readline 手册][1]给出了一个我认为特别有用的例子:我经常想把一个程序的输出保存到文件中,这意味着我得经常在 Bash 命令中追加类似 `> output.txt` 这样的东西,为了节省时间,可以把它做成一个 Readline 宏。
|
||||
`~/.inputrc` 可以做的另一件事是通过将键序列映射到输入字符串上来创建预制宏。[Readline 手册][1]给出了一个我认为特别有用的例子:我经常想把一个程序的输出保存到文件中,这意味着我得经常在 Bash 命令中追加类似 `> output.txt` 这样的东西,为了节省时间,可以把它做成一个 Readline 宏:
|
||||
|
||||
```
|
||||
Control-o: "> output.txt"
|
||||
@ -48,35 +50,35 @@ Control-o: "> output.txt"
|
||||
|
||||
这样每当你按下 `Ctrl-O` 时,你都会看到 `> output.txt` 被添加到了命令行光标的后面,这样很不错!
|
||||
|
||||
不过你可以用宏做的可不仅仅是为文本串创建快捷方式;在 `~/.inputrc` 中使用以下条目意味着每次按下 `Ctrl-J` 时,行内已有的文本都会被 `$(` 和 `)` 包裹住。该宏先用 `Ctrl-A` 移动到行首,添加 `$(` ,然后再用 `Ctrl-E` 移动到行尾,添加 `)`。
|
||||
不过你可以用宏做的可不仅仅是为文本串创建快捷方式;在 `~/.inputrc` 中使用以下条目意味着每次按下 `Ctrl-J` 时,行内已有的文本都会被 `$(` 和 `)` 包裹住。该宏先用 `Ctrl-A` 移动到行首,添加 `$(` ,然后再用 `Ctrl-E` 移动到行尾,添加 `)`:
|
||||
|
||||
```
|
||||
Control-j: "\C-a$(\C-e)"
|
||||
```
|
||||
|
||||
如果你经常需要像下面这样把一个命令的输出用于另一个命令的话,这个宏可能会对你有帮助。
|
||||
如果你经常需要像下面这样把一个命令的输出用于另一个命令的话,这个宏可能会对你有帮助:
|
||||
|
||||
```
|
||||
$ cd $(brew --prefix)
|
||||
```
|
||||
|
||||
`~/.inputrc` 文件也允许你为 Readline 手册中所谓的 _变量_ 设置不同的值,这些变量会启用或禁用某些 Readline 行为,你也可以使用这些变量来改变 Readline 中像是自动补全或者历史搜索这些行为的工作方式。我建议开启的一个变量是 `revert-all-at-newline`,它是默认关闭的,当这个变量关闭时,如果你使用反向搜索功能从命令历史记录中提取一行并编辑,但随后又决定搜索另一行,那么你所做的编辑会被保存在历史记录中。我觉得这样会很混乱,因为这会导致你的 Bash 命令历史中出现从未运行过的行。所以在你的 `~/.inputrc` 中加入这个:
|
||||
`~/.inputrc` 文件也允许你为 Readline 手册中所谓的 “变量” 设置不同的值,这些变量会启用或禁用某些 Readline 行为,你也可以使用这些变量来改变 Readline 中像是自动补全或者历史搜索这些行为的工作方式。我建议开启的一个变量是 `revert-all-at-newline`,它是默认关闭的,当这个变量关闭时,如果你使用反向搜索功能从命令历史记录中提取一行并编辑,但随后又决定搜索另一行,那么你所做的编辑会被保存在历史记录中。我觉得这样会很混乱,因为这会导致你的 Bash 命令历史中出现从未运行过的行。所以在你的 `~/.inputrc` 中加入这个:
|
||||
|
||||
```
|
||||
set revert-all-at-newline on
|
||||
```
|
||||
|
||||
在你用 `~/.inputrc` 设置了选项或键绑定以后,它们会适用于任何使用 Readline 库的地方,显然 Bash 包括在内,不过你也会在其它像是 `irb` 和 `psql` 这样的程序中受益。如果你经常使用关系型数据库的命令行界面,一个用于插入 `SELECT * FROM` 的 Readline 宏可能会很有用。
|
||||
在你用 `~/.inputrc` 设置了选项或键绑定以后,它们会适用于任何使用 Readline 库的地方,显然 Bash 也包括在内,不过你也会在其它像是 `irb` 和 `psql` 这样的程序中受益。如果你经常使用关系型数据库的命令行界面,一个用于插入 `SELECT * FROM` 的 Readline 宏可能会很有用。
|
||||
|
||||
### Chet Ramey
|
||||
|
||||
GNU Readline 如今由凯斯西储大学的高级技术架构师 Chet Ramey 维护,Ramey 同时还负责维护 Bash shell;这两个项目都是由一位名叫 Brian Fox 的自由软件基金会员工在 1988 年开始编写的,但从 1994 年左右开始,Ramey 一直是它们唯一的维护者。
|
||||
|
||||
Ramey 通过电子邮件告诉我,Readline 远非一个原创的想法,它是为了实现 POSIX 规范所规定的功能而被创建的,而 POSIX 规范又是在 20 世纪 80 年代末被制定的。许多早期的 shell,包括 Korn shell 和至少一个版本的 Unix System V shell,都包含行编辑功能。1988 年版的 Korn shell(`ksh88`)提供了 Emacs 风格和 Vi/Vim 风格的编辑模式。据我从[手册页][2]中得知,Korn shell 会通过查看 `VISUAL` 和 `EDITOR` 环境变量来决定你使用的模式,这一点非常巧妙。POSIX 中指定 shell 功能的部分近似于 `ksh88` 的实现,所以 GNU Bash 也要实现一个类似的灵活的行编辑系统来保持兼容,因此就有了Readline。
|
||||
Ramey 通过电子邮件告诉我,Readline 远非一个原创的想法,它是为了实现 POSIX 规范所规定的功能而被创建的,而 POSIX 规范又是在 20 世纪 80 年代末被制定的。许多早期的 shell,包括 Korn shell 和至少一个版本的 Unix System V shell,都包含行编辑功能。1988 年版的 Korn shell(`ksh88`)提供了 Emacs 风格和 Vi/Vim 风格的编辑模式。据我从[手册页][2]中得知,Korn shell 会通过查看 `VISUAL` 和 `EDITOR` 环境变量来决定你使用的模式,这一点非常巧妙。POSIX 中指定 shell 功能的部分近似于 `ksh88` 的实现,所以 GNU Bash 也要实现一个类似的灵活的行编辑系统来保持兼容,因此就有了 Readline。
|
||||
|
||||
Ramey 第一次参与 Bash 开发时,Readline 还是 Bash 项目目录下的一个源文件,它真的只是 Bash 的一部分;随着时间的推移,Readline 文件慢慢地成为了独立的项目,不过还要等到 1994 年(Readline 的 2.0 版本)Readline 才完全成为了一个独立的库。
|
||||
Ramey 第一次参与 Bash 开发时,Readline 还是 Bash 项目目录下的一个单一的源文件,它其实只是 Bash 的一部分;随着时间的推移,Readline 文件慢慢地成为了独立的项目,不过直到 1994 年(Readline 2.0 版本发布),Readline 才完全成为了一个独立的库。
|
||||
|
||||
Readline 与 Bash 密切相关,Ramey 也通常把 Readline 与 Bash 的发布配对,但正如我上面提到的,Readline 是一个可以被任何有命令行接口的软件使用的库,而且它真的很容易使用。下面是一个例子,虽然简单,但这就是在 C 程序中使用 Readline 的方法。向 `readline()` 函数传递的字符串参数就是你希望 Readline 向用户显示的提示符:
|
||||
Readline 与 Bash 密切相关,Ramey 也通常把 Readline 与 Bash 的发布配对,但正如我上面提到的,Readline 是一个可以被任何有命令行界面的软件使用的库,而且它真的很容易使用。下面是一个例子,虽然简单,但这就是在 C 程序中使用 Readline 的方法。向 `readline()` 函数传递的字符串参数就是你希望 Readline 向用户显示的提示符:
|
||||
|
||||
```
|
||||
#include <stdio.h>
|
||||
@ -100,20 +102,14 @@ int main(int argc, char** argv)
|
||||
$ gcc main.c -lreadline
|
||||
```
|
||||
|
||||
当然,Readline 的 API 比起那个单一的函数要丰富得多,任何使用它的人都可以对库的行为进行各种调整,库的用户(开发者)甚至可以添加新的函数,来让最终用户可以通过 `~/.inputrc` 来配置它们,这意味着 Readline 非常容易扩展。但是据我所知,即使是 Bash ,虽然事先有很多配置,最终也会像上面的例子一样调用简单的 `readline()` 函数来获取输入。(参见 GNU Bash 源代码中的[这一行][3],Bash 似乎在这里将获取输入的责任交给了 Readline)。
|
||||
当然,Readline 的 API 比起那个单一的函数要丰富得多,任何使用它的人都可以对库的行为进行各种调整,库的用户(开发者)甚至可以添加新的函数,来让最终用户可以通过 `~/.inputrc` 来配置它们,这意味着 Readline 非常容易扩展。但是据我所知,即使是 Bash ,虽然事先有很多配置,最终也会像上面的例子一样调用简单的 `readline()` 函数来获取输入。(参见 GNU Bash 源代码中的[这一行][3],Bash 似乎在这里将获取输入的责任交给了 Readline)。
|
||||
|
||||
Ramey 现在已经在 Bash 和 Readline 上工作了十多年,但他的工作却从来没有得到过报酬——他一直都是一名志愿者。尽管 Ramey 说 Readline 的变化比 Bash 慢得多,但 Bash 和 Readline 仍然在积极开发中。我问 Ramey 作为这么多人使用的软件唯一的维护者是什么感觉,他说可能有几百万人在不知不觉中使用 Bash(因为每个苹果设备都运行 Bash),这让他担心一个突破性的变化会造成多大的破坏,不过他已经慢慢习惯了所有这些人的想法。他还说他会继续在 Bash 和 Readline 上工作,因为在这一点上他已经深深地投入了,而且他也只是单纯地喜欢把有用的软件提供给世界。
|
||||
Ramey 现在已经在 Bash 和 Readline 上工作了二十多年,但他的工作却从来没有得到过报酬 —— 他一直都是一名志愿者。Bash 和 Readline 仍然在积极开发中,尽管 Ramey 说 Readline 的变化比 Bash 慢得多。我问 Ramey 作为这么多人使用的软件唯一的维护者是什么感觉,他说可能有几百万人在不知不觉中使用 Bash(因为每个苹果设备都运行 Bash),这让他担心一个破坏性的变化会造成多大的混乱,不过他已经慢慢习惯了所有这些人的想法。他还说他会继续在 Bash 和 Readline 上工作,因为在这一点上他已经深深地投入了,而且他也只是单纯地喜欢把有用的软件提供给世界。
|
||||
|
||||
_你可以在 [Chet Ramey 的网站][4]上找到更多关于他的信息。_
|
||||
|
||||
_喜欢这篇文章吗?我会每四周写出一篇像这样的文章。关注推特帐号 [@TwoBitHistory][5] 或者[订阅 RSS][6] 来获取更新吧!_
|
||||
|
||||
_TwoBitHistory 的上一条消息:_
|
||||
|
||||
> 请欣赏我拖欠已久的新文章,我在里面以 BBC Micro 和计算机认知计划的故事作为出发点抱怨了一下 Codecademy。<https://t.co/PiWlKljDjK>
|
||||
>
|
||||
> — TwoBitHistory (@TwoBitHistory) [三月 31,2019][7]
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
via: https://twobithistory.org/2019/08/22/readline.html
|
||||
@ -121,7 +117,7 @@ via: https://twobithistory.org/2019/08/22/readline.html
|
||||
作者:[Two-Bit History][a]
|
||||
选题:[lujun9972][b]
|
||||
译者:[rakino](https://github.com/rakino)
|
||||
校对:[校对者ID](https://github.com/校对者ID)
|
||||
校对:[wxy](https://github.com/wxy)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
304
published/20200811 TCP window scaling, timestamps and SACK.md
Normal file
304
published/20200811 TCP window scaling, timestamps and SACK.md
Normal file
@ -0,0 +1,304 @@
|
||||
[#]: collector: (lujun9972)
|
||||
[#]: translator: (gxlct008)
|
||||
[#]: reviewer: (wxy)
|
||||
[#]: publisher: (wxy)
|
||||
[#]: url: (https://linux.cn/article-12710-1.html)
|
||||
[#]: subject: (TCP window scaling, timestamps and SACK)
|
||||
[#]: via: (https://fedoramagazine.org/tcp-window-scaling-timestamps-and-sack/)
|
||||
[#]: author: (Florian Westphal https://fedoramagazine.org/author/strlen/)
|
||||
|
||||
TCP 窗口缩放、时间戳和 SACK
|
||||
======
|
||||
|
||||
![](https://img.linux.net.cn/data/attachment/album/202010/12/114050up4695djpw6n4tu9.jpg)
|
||||
|
||||
Linux TCP 协议栈具有无数个可以更改其行为的 `sysctl` 旋钮。 这包括可用于接收或发送操作的内存量、套接字的最大数量、可选的特性和协议扩展。
|
||||
|
||||
有很多文章出于各种“性能调优”或“安全性”原因,建议禁用 TCP 扩展,比如时间戳或<ruby>选择性确认<rt>Selective ACKnowledgments</rt></ruby>(SACK)。
|
||||
|
||||
本文提供了这些扩展功能的背景,为什么会默认启用,它们之间是如何关联的,以及为什么通常情况下将它们关闭是个坏主意。
|
||||
|
||||
### TCP 窗口缩放
|
||||
|
||||
TCP 可以承受的数据传输速率受到几个因素的限制。其中包括:
|
||||
|
||||
* <ruby>往返时间<rt>Round trip time</rt></ruby>(RTT)。
|
||||
|
||||
这是数据包到达目的地并返回回复所花费的时间。越低越好。
|
||||
* 所涉及的网络路径的最低链路速度。
|
||||
* 丢包频率。
|
||||
* 新数据可用于传输的速度。
|
||||
|
||||
例如,CPU 需要能够以足够快的速度将数据传递到网络适配器。如果 CPU 需要首先加密数据,则适配器可能必须等待新数据。同样地,如果磁盘存储不能足够快地读取数据,则磁盘存储可能会成为瓶颈。
|
||||
* TCP 接收窗口的最大可能大小。
|
||||
|
||||
接收窗口决定了 TCP 在必须等待接收方报告接收到该数据之前可以传输多少数据(以字节为单位)。这是由接收方宣布的。接收方将在读取并确认接收到传入数据时不断更新此值。接收窗口的当前值包含在 [TCP 报头][2] 中,它是 TCP 发送的每个数据段的一部分。因此,只要发送方接收到来自对等方的确认,它就知道当前的接收窗口。这意味着往返时间(RTT)越长,发送方获得接收窗口更新所需的时间就越长。
|
||||
|
||||
TCP 的未确认(正在传输)数据被限制为最多 64KB。在大多数网络场景中,这甚至还不足以维持一个像样的数据速率。让我们看看一些例子。
|
||||
|
||||
**理论数据速率**
|
||||
|
||||
在往返时间(RTT)为 100 毫秒的情况下,TCP 每秒最多可以传输 640KB。在延迟为 1 秒的情况下,最大理论数据速率降至只有 64KB/s。
|
||||
|
||||
这是因为接收窗口的原因。一旦发送了 64KB 的数据,接收窗口就已经满了。发送方必须等待,直到对等方通知它应用程序已经读取了至少一部分数据。
|
||||
|
||||
发送的第一个段会把 TCP 窗口缩减去该段的大小。在接收窗口值的更新信息可用之前,需要往返一次。当更新以 1 秒的延迟到达时,即使链路有足够的可用带宽,也会导致 64KB 的限制。
|
||||
|
||||
为了充分利用一个具有几毫秒延迟的快速网络,必须有一个比传统 TCP 支持的窗口更大的窗口。“64KB 限制”是协议规范的产物:TCP 头只为接收窗口大小保留了 16 个位。这允许接收窗口最大为 64KB。在 TCP 协议最初设计时,这个大小并没有被视为一个限制。
|
||||
|
||||
不幸的是,想通过仅仅更改 TCP 头来支持更大的最大窗口值是不可能的。如果这样做就意味着 TCP 的所有实现都必须同时更新,否则它们将无法相互理解。为了解决这个问题,我们改变了对接收窗口值的解释。
|
||||
|
||||
“窗口缩放选项”允许你改变这个解释,同时保持与现有实现的兼容性。
|
||||
|
||||
#### TCP 选项:向后兼容的协议扩展
|
||||
|
||||
TCP 支持可选扩展。这允许使用新特性增强协议,而无需立即更新所有实现。当 TCP 发起方连接到对等方时,它还会发送一个支持的扩展列表。所有扩展都遵循相同的格式:一个唯一的选项号,后跟选项的长度以及选项数据本身。
|
||||
|
||||
TCP 响应方检查连接请求中包含的所有选项号。如果它遇到一个不能理解的选项号,则会跳过
|
||||
该选项号附带的“长度”字节的数据,并检查下一个选项号。响应方忽略了从答复中无法理解的内容。这使发送方和接收方都够理解所支持的公共选项集。
|
||||
|
||||
使用窗口缩放时,选项数据总是由单个数字组成。
|
||||
|
||||
### 窗口缩放选项
|
||||
|
||||
```
|
||||
Window Scale option (WSopt): Kind: 3, Length: 3
|
||||
+---------+---------+---------+
|
||||
| Kind=3 |Length=3 |shift.cnt|
|
||||
+---------+---------+---------+
|
||||
1 1 1
|
||||
```
|
||||
|
||||
[窗口缩放][3] 选项告诉对等方,应该使用给定的数字缩放 TCP 标头中的接收窗口值,以获取实际大小。
|
||||
|
||||
例如,一个宣告窗口缩放因子为 7 的 TCP 发起方试图指示响应方,任何将来携带接收窗口值为 512 的数据包实际上都会宣告 65536 字节的窗口。增加了 128 倍(2^7)。这将允许最大为 8MB 的 TCP 窗口。
|
||||
|
||||
不能理解此选项的 TCP 响应方将会忽略它,为响应连接请求而发送的 TCP 数据包(SYN-ACK)不会包含该窗口缩放选项。在这种情况下,双方只能使用 64k 的窗口大小。幸运的是,默认情况下,几乎每个 TCP 栈都支持并默认启用了此选项,包括 Linux。
|
||||
|
||||
响应方包括了它自己所需的缩放因子。两个对等方可以使用不同的因子。宣布缩放因子为 0 也是合法的。这意味着对等方应该如实处理它接收到的接收窗口值,但它允许应答方向上的缩放值,然后接收方可以使用更大的接收窗口。
|
||||
|
||||
与 SACK 或 TCP 时间戳不同,窗口缩放选项仅出现在 TCP 连接的前两个数据包中,之后无法更改。也不可能通过查看不包含初始连接三次握手的连接的数据包捕获来确定缩放因子。
|
||||
|
||||
支持的最大缩放因子为 14。这将允许 TCP 窗口的大小高达 1GB。
|
||||
|
||||
**窗口缩放的缺点**
|
||||
|
||||
在非常特殊的情况下,它可能导致数据损坏。但在你禁用该选项之前,要知道通常情况下是不可能损坏的。还有一种解决方案可以防止这种情况。不幸的是,有些人在没有意识到它与窗口缩放的关系的情况下禁用了该解决方案。首先,让我们看一下需要解决的实际问题。想象以下事件序列:
|
||||
|
||||
1. 发送方发送段:s_1、s_2、s_3、... s_n。
|
||||
2. 接收方看到:s_1、s_3、... s_n,并发送对 s_1 的确认。
|
||||
3. 发送方认为 s_2 丢失,然后再次发送。它还发送了段 s_n+1 中包含的新数据。
|
||||
4. 接收方然后看到:s_2、s_n+1,s_2:数据包 s_2 被接收两次。
|
||||
|
||||
当发送方过早触发重新传输时,可能会发生这种情况。在正常情况下,即使使用窗口缩放,这种错误的重传也绝不会成为问题。接收方将只丢弃重复项。
|
||||
|
||||
#### 从旧数据到新数据
|
||||
|
||||
TCP 序列号最多可以为 4GB。如果它变得大于此值,则该序列会回绕到 0,然后再次增加。这本身不是问题,但是如果这种问题发生得足够快,则上述情况可能会造成歧义。
|
||||
|
||||
如果在正确的时刻发生回绕,则序列号 s_2(重新发送的数据包)可能已经大于 s_n+1。因此,在最后的步骤(4)中,接收方可以将其解释为:s_2、s_n+1、s_n+m,即它可以将 “旧” 数据包 s_2 视为包含新数据。
|
||||
|
||||
通常,这不会发生,因为即使在高带宽链接上,“回绕”也只会每隔几秒钟或几分钟发生一次。原始数据包和不需要的重传的数据包之间的间隔将小得多。
|
||||
|
||||
例如,对于 50MB/s 的传输速度,重复项要迟到一分钟以上才会成为问题。序列号的回绕速度没有快到让小的延迟会导致这个问题。
|
||||
|
||||
一旦 TCP 达到 “GB/s” 的吞吐率,序列号的回绕速度就会非常快,以至于即使只有几毫秒的延迟也可能会造成 TCP 无法检测出的重复项。通过解决接收窗口太小的问题,TCP 现在可以用于以前无法实现的网络速度,这会产生一个新的,尽管很少见的问题。为了在 RTT 非常低的环境中安全使用 GB/s 的速度,接收方必须能够检测到这些旧的重复项,而不必仅依赖序列号。
|
||||
|
||||
### TCP 时间戳
|
||||
|
||||
#### 最佳截止日期
|
||||
|
||||
用最简单的术语来说,[TCP 时间戳][3]只是在数据包上添加时间戳,以解决由非常快速的序列号回绕引起的歧义。如果一个段看起来包含新数据,但其时间戳早于上一个在接收窗口内的数据包,则该序列号已被重新回绕,而“新”数据包实际上是一个较旧的重复项。这解决了即使在极端情况下重传的歧义。
|
||||
|
||||
但是,该扩展不仅仅是检测旧数据包。TCP 时间戳的另一个主要功能是更精确的往返时间测量(RTTm)。
|
||||
|
||||
#### 需要准确的 RTT 估算
|
||||
|
||||
当两个对等方都支持时间戳时,每个 TCP 段都携带两个附加数字:时间戳值和回显时间戳。
|
||||
|
||||
```
|
||||
TCP Timestamp option (TSopt): Kind: 8, Length: 10
|
||||
+-------+----+----------------+-----------------+
|
||||
|Kind=8 | 10 |TS Value (TSval)|EchoReply (TSecr)|
|
||||
+-------+----+----------------+-----------------+
|
||||
1 1 4 4
|
||||
```
|
||||
|
||||
准确的 RTT 估算对于 TCP 性能至关重要。TCP 会自动重新发送未确认的数据。重传由计时器触发:如果超时,则 TCP 会将尚未收到确认的一个或多个数据包视为丢失。然后再发送一次。
|
||||
|
||||
但是,“尚未得到确认” 并不意味着该段已丢失。也有可能是接收方到目前为止没有发送确认,或者确认仍在传输中。这就造成了一个两难的困境:TCP 必须等待足够长的时间,才能让这种轻微的延迟变得无关紧要,但它也不能等待太久。
|
||||
|
||||
**低网络延迟 VS 高网络延迟**
|
||||
|
||||
在延迟较高的网络中,如果计时器触发过快,TCP 经常会将时间和带宽浪费在不必要的重发上。
|
||||
|
||||
然而,在延迟较低的网络中,等待太长时间会导致真正发生数据包丢失时吞吐量降低。因此,在低延迟网络中,计时器应该比高延迟网络中更早到期。所以,TCP 重传超时不能使用固定常量值作为超时。它需要根据其在网络中所经历的延迟来调整该值。
|
||||
|
||||
**往返时间的测量**
|
||||
|
||||
TCP 选择基于预期的往返时间(RTT)的重传超时。RTT 事先是未知的。它是通过测量发送的段与 TCP 接收到该段所承载数据的确认之间的增量来估算的。
|
||||
|
||||
由于多种因素使其而变得复杂。
|
||||
|
||||
* 出于性能原因,TCP 不会为收到的每个数据包生成新的确认。它等待的时间非常短:如果有更多的数据段到达,则可以通过单个 ACK 数据包确认其接收。这称为<ruby>“累积确认”<rt>cumulative ACK</rt></ruby>。
|
||||
* 往返时间并不恒定。这是有多种因素造成的。例如,客户端可能是一部移动电话,随其移动而切换到不同的基站。也可能是当链路或 CPU 的利用率提高时,数据包交换花费了更长的时间。
|
||||
* 必须重新发送的数据包在计算过程中必须被忽略。这是因为发送方无法判断重传数据段的 ACK 是在确认原来的传输数据(毕竟已到达)还是在确认重传数据。
|
||||
|
||||
最后一点很重要:当 TCP 忙于从丢失中恢复时,它可能仅接收到重传段的 ACK。这样,它就无法在此恢复阶段测量(更新)RTT。所以,它无法调整重传超时,然后超时将以指数级增长。那是一种非常具体的情况(它假设其他机制,如快速重传或 SACK 不起作用)。但是,使用 TCP 时间戳,即使在这种情况下也会进行 RTT 评估。
|
||||
|
||||
如果使用了扩展,则对等方将从 TCP 段的扩展空间中读取时间戳值并将其存储在本地。然后,它将该值作为 “回显时间戳” 放入发回的所有数据段中。
|
||||
|
||||
因此,该选项带有两个时间戳:它的发送方自己的时间戳和它从对等方收到的最新时间戳。原始发送方使用 “回显时间戳” 来计算 RTT。它是当前时间戳时钟与 “回显时间戳” 中所反映的值之间的增量。
|
||||
|
||||
**时间戳的其他用途**
|
||||
|
||||
TCP 时间戳甚至还有除 PAWS(<ruby>防止序列号回绕<rt>Protection Against Wrapped Sequences</rt></ruby>) 和 RTT 测量以外的其他用途。例如,可以检测是否不需要重发。如果该确认携带较旧的回显时间戳,则该确认针对的是初始数据包,而不是重新发送的数据包。
|
||||
|
||||
TCP 时间戳的另一个更晦涩的用例与 TCP [syn cookie][4] 功能有关。
|
||||
|
||||
**在服务器端建立 TCP 连接**
|
||||
|
||||
当连接请求到达的速度快于服务器应用程序可以接受新的传入连接的速度时,连接积压最终将达到其极限。这可能是由于系统配置错误或应用程序中的错误引起的。当一个或多个客户端发送连接请求而不对 “SYN ACK” 响应做出反应时,也会发生这种情况。这将用不完整的连接填充连接队列。这些条目需要几秒钟才会超时。这被称为<ruby>“同步泛洪攻击”<rt>syn flood attack</rt></ruby>。
|
||||
|
||||
**TCP 时间戳和 TCP Syn Cookie**
|
||||
|
||||
即使队列已满,某些 TCP 协议栈也允许继续接受新连接。发生这种情况时,Linux 内核将在系统日志中打印一条突出的消息:
|
||||
|
||||
> 端口 P 上可能发生 SYN 泛洪。正在发送 Cookie。检查 SNMP 计数器。
|
||||
|
||||
此机制将完全绕过连接队列。通常存储在连接队列中的信息被编码到 SYN/ACK 响应 TCP 序列号中。当 ACK 返回时,可以根据序列号重建队列条目。
|
||||
|
||||
序列号只有有限的空间来存储信息。因此,使用 “TCP Syn Cookie” 机制建立的连接不能支持 TCP 选项。
|
||||
|
||||
但是,对两个对等点都通用的 TCP 选项可以存储在时间戳中。ACK 数据包在回显时间戳字段中反映了该值,这也允许恢复已达成共识的 TCP 选项。否则,cookie 连接受标准的 64KB 接收窗口限制。
|
||||
|
||||
**常见误区 —— 时间戳不利于性能**
|
||||
|
||||
不幸的是,一些指南建议禁用 TCP 时间戳,以减少内核访问时间戳时钟来获取当前时间所需的次数。这是不正确的。如前所述,RTT 估算是 TCP 的必要部分。因此,内核在接收/发送数据包时总是采用微秒级的时间戳。
|
||||
|
||||
在包处理步骤的其余部分中,Linux 会重用 RTT 估算所需的时钟时间戳。这还避免了将时间戳添加到传出 TCP 数据包的额外时钟访问。
|
||||
|
||||
整个时间戳选项在每个数据包中仅需要 10 个字节的 TCP 选项空间,这不会显著减少可用于数据包有效负载的空间。
|
||||
|
||||
**常见误区 —— 时间戳是个安全问题**
|
||||
|
||||
一些安全审计工具和(较旧的)博客文章建议禁用 TCP 时间戳,因为据称它们泄露了系统正常运行时间:这样一来,便可以估算系统/内核的补丁级别。这在过去是正确的:时间戳时钟基于不断增加的值,该值在每次系统引导时都以固定值开始。时间戳值可以估计机器已经运行了多长时间(正常运行时间 `uptime`)。
|
||||
|
||||
从 Linux 4.12 开始,TCP 时间戳不再显示正常运行时间。发送的所有时间戳值都使用对等设备特定的偏移量。时间戳值也每 49 天回绕一次。
|
||||
|
||||
换句话说,从地址 “A” 出发,或者终到地址 “A” 的连接看到的时间戳与到远程地址 “B” 的连接看到的时间戳不同。
|
||||
|
||||
运行 `sysctl net.ipv4.tcp_timeamp=2` 以禁用随机化偏移。这使得分析由诸如 `wireshark` 或 `tcpdump` 之类的工具记录的数据包跟踪变得更容易 —— 从主机发送的数据包在其 TCP 选项时间戳中都具有相同的时钟基准。因此,对于正常操作,默认设置应保持不变。
|
||||
|
||||
### 选择性确认
|
||||
|
||||
如果同一数据窗口中的多个数据包丢失了,TCP 将会出现问题。这是因为 TCP 确认是累积的,但仅适用于按顺序到达的数据包。例如:
|
||||
|
||||
* 发送方发送段 s_1、s_2、s_3、... s_n
|
||||
* 发送方收到 s_2 的 ACK
|
||||
* 这意味着 s_1 和 s_2 都已收到,并且发送方不再需要保留这些段。
|
||||
* s_3 是否应该重新发送? s_4 呢? s_n?
|
||||
|
||||
发送方等待 “重传超时” 或 “重复 ACK” 以使 s_2 到达。如果发生重传超时或到达了 s_2 的多个重复 ACK,则发送方再次发送 s_3。
|
||||
|
||||
如果发送方收到对 s_n 的确认,则 s_3 是唯一丢失的数据包。这是理想的情况。仅发送单个丢失的数据包。
|
||||
|
||||
如果发送方收到的确认段小于 s_n,例如 s_4,则意味着丢失了多个数据包。发送方也需要重传下一个数据段。
|
||||
|
||||
**重传策略**
|
||||
|
||||
可能只是重复相同的序列:重新发送下一个数据包,直到接收方指示它已处理了直至 s_n 的所有数据包为止。这种方法的问题在于,它需要一个 RTT,直到发送方知道接下来必须重新发送的数据包为止。尽管这种策略可以避免不必要的重传,但要等到 TCP 重新发送整个数据窗口后,它可能要花几秒钟甚至更长的时间。
|
||||
|
||||
另一种方法是一次重新发送几个数据包。当丢失了几个数据包时,此方法可使 TCP 恢复更快。在上面的示例中,TCP 重新发送了 s_3、s_4、s_5、...,但是只能确保已丢失 s_3。
|
||||
|
||||
从延迟的角度来看,这两种策略都不是最佳的。如果只有一个数据包需要重新发送,第一种策略是快速的,但是当多个数据包丢失时,它花费的时间太长。
|
||||
|
||||
即使必须重新发送多个数据包,第二个也是快速的,但是以浪费带宽为代价。此外,这样的 TCP 发送方在进行不必要的重传时可能已经发送了新数据。
|
||||
|
||||
通过可用信息,TCP 无法知道丢失了哪些数据包。这就是 TCP [选择性确认][5](SACK)的用武之地了。就像窗口缩放和时间戳一样,它是另一个可选的但非常有用的 TCP 特性。
|
||||
|
||||
**SACK 选项**
|
||||
|
||||
```
|
||||
TCP Sack-Permitted Option: Kind: 4, Length 2
|
||||
+---------+---------+
|
||||
| Kind=4 | Length=2|
|
||||
+---------+---------+
|
||||
```
|
||||
|
||||
支持此扩展的发送方在连接请求中包括 “允许 SACK” 选项。如果两个端点都支持该扩展,则检测到数据流中丢失数据包的对等方可以将此信息通知发送方。
|
||||
|
||||
```
|
||||
TCP SACK Option: Kind: 5, Length: Variable
|
||||
+--------+--------+
|
||||
| Kind=5 | Length |
|
||||
+--------+--------+--------+--------+
|
||||
| Left Edge of 1st Block |
|
||||
+--------+--------+--------+--------+
|
||||
| Right Edge of 1st Block |
|
||||
+--------+--------+--------+--------+
|
||||
| |
|
||||
/ . . . /
|
||||
| |
|
||||
+--------+--------+--------+--------+
|
||||
| Left Edge of nth Block |
|
||||
+--------+--------+--------+--------+
|
||||
| Right Edge of nth Block |
|
||||
+--------+--------+--------+--------+
|
||||
```
|
||||
|
||||
接收方遇到 s_2 后跟 s_5 ... s_n,则在发送对 s_2 的确认时将包括一个 SACK 块:
|
||||
|
||||
```
|
||||
|
||||
+--------+-------+
|
||||
| Kind=5 | 10 |
|
||||
+--------+------+--------+-------+
|
||||
| Left edge: s_5 |
|
||||
+--------+--------+-------+------+
|
||||
| Right edge: s_n |
|
||||
+--------+-------+-------+-------+
|
||||
```
|
||||
|
||||
这告诉发送方到 s_2 的段都是按顺序到达的,但也让发送方知道段 s_5 至 s_n 也已收到。然后,发送方可以重新发送那两个数据包(s_3、s_4),并继续发送新数据。
|
||||
|
||||
**神话般的无损网络**
|
||||
|
||||
从理论上讲,如果连接不会丢包,那么 SACK 就没有任何优势。或者连接具有如此低的延迟,甚至等待一个完整的 RTT 都无关紧要。
|
||||
|
||||
在实践中,无损行为几乎是不可能保证的。即使网络及其所有交换机和路由器具有足够的带宽和缓冲区空间,数据包仍然可能丢失:
|
||||
|
||||
* 主机操作系统可能面临内存压力并丢弃数据包。请记住,一台主机可能同时处理数万个数据包流。
|
||||
* CPU 可能无法足够快地消耗掉来自网络接口的传入数据包。这会导致网络适配器本身中的数据包丢失。
|
||||
* 如果 TCP 时间戳不可用,即使一个非常小的 RTT 的连接也可能在丢失恢复期间暂时停止。
|
||||
|
||||
使用 SACK 不会增加 TCP 数据包的大小,除非连接遇到数据包丢失。因此,几乎没有理由禁用此功能。几乎所有的 TCP 协议栈都支持 SACK —— 它通常只在不进行 TCP 批量数据传输的低功耗 IOT 类的设备上才不存在。
|
||||
|
||||
当 Linux 系统接受来自此类设备的连接时,TCP 会自动为受影响的连接禁用 SACK。
|
||||
|
||||
### 总结
|
||||
|
||||
本文中研究的三个 TCP 扩展都与 TCP 性能有关,最好都保留其默认设置:启用。
|
||||
|
||||
TCP 握手可确保仅使用双方都可以理解的扩展,因此,永远不需因为对等方可能不支持而全局禁用该扩展。
|
||||
|
||||
关闭这些扩展会导致严重的性能损失,尤其是 TCP 窗口缩放和 SACK。可以禁用 TCP 时间戳而不会立即造成不利影响,但是现在没有令人信服的理由这样做了。启用它们还可以支持 TCP 选项,即使在 SYN cookie 生效时也是如此。
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
via: https://fedoramagazine.org/tcp-window-scaling-timestamps-and-sack/
|
||||
|
||||
作者:[Florian Westphal][a]
|
||||
选题:[lujun9972][b]
|
||||
译者:[gxlct008](https://github.com/gxlct008)
|
||||
校对:[wxy](https://github.com/wxy)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]: https://fedoramagazine.org/author/strlen/
|
||||
[b]: https://github.com/lujun9972
|
||||
[1]: https://fedoramagazine.org/wp-content/uploads/2020/08/tcp-window-scaling-816x346.png
|
||||
[2]: https://en.wikipedia.org/wiki/Transmission_Control_Protocol#TCP_segment_structure
|
||||
[3]: https://www.rfc-editor.org/info/rfc7323
|
||||
[4]: https://en.wikipedia.org/wiki/SYN_cookies
|
||||
[5]: https://www.rfc-editor.org/info/rfc2018
|
@ -1,8 +1,8 @@
|
||||
[#]: collector: (lujun9972)
|
||||
[#]: translator: (MjSeven)
|
||||
[#]: reviewer: ( )
|
||||
[#]: publisher: ( )
|
||||
[#]: url: ( )
|
||||
[#]: reviewer: (wxy)
|
||||
[#]: publisher: (wxy)
|
||||
[#]: url: (https://linux.cn/article-12703-1.html)
|
||||
[#]: subject: (How to install software with Ansible)
|
||||
[#]: via: (https://opensource.com/article/20/9/install-packages-ansible)
|
||||
[#]: author: (Seth Kenlon https://opensource.com/users/seth)
|
||||
@ -10,51 +10,55 @@
|
||||
如何使用 Ansible 安装软件
|
||||
======
|
||||
|
||||
使用 Ansible 剧本自动安装和更新设备上的软件。
|
||||
![Puzzle pieces coming together to form a computer screen][1]
|
||||
> 使用 Ansible 剧本自动安装和更新设备上的软件。
|
||||
|
||||
Ansible 是系统管理员和开发人员用来保持计算机系统处于最佳状态的一种流行的自动化工具。与可扩展框架一样,[Ansible][2] 本身功能有限,它真正的功能体现在许多模块中。在某种程度上,Ansible 模块是 [Linux][3] 系统的命令。它们针对特定问题提供解决方案。维护计算机时的一项常见任务是使所有计算机的更新和一致。
|
||||
![](https://img.linux.net.cn/data/attachment/album/202010/10/095024hh65atkh6cc8ntn9.jpg)
|
||||
|
||||
我曾经使用软件包的文本列表来保持系统或多或少同步:我会列出笔记本电脑上安装的软件包,然后将其与台式机或另一台服务器之间进行交叉引用,手动弥补差异。当然,在 Linux 机器上安装和维护应用程序是 Ansible 的一项基本功能,这意味着你可以在自己关心的计算机上列出所需的内容。
|
||||
Ansible 是系统管理员和开发人员用来保持计算机系统处于最佳状态的一种流行的自动化工具。与可扩展框架一样,[Ansible][2] 本身功能有限,它真正的功能体现在许多模块中。在某种程度上,Ansible 模块就是 [Linux][3] 系统的命令。它们针对特定问题提供解决方案,而维护计算机时的一项常见任务是使所有计算机的更新和一致。
|
||||
|
||||
### 寻找正确的 Ansible
|
||||
我曾经使用软件包的文本列表来保持系统或多或少的同步:我会列出笔记本电脑上安装的软件包,然后将其与台式机或另一台服务器之间进行交叉参考,手动弥补差异。当然,在 Linux 机器上安装和维护应用程序是 Ansible 的一项基本功能,这意味着你可以在自己关心的计算机上列出所需的内容。
|
||||
|
||||
Ansible 模块的数量非常庞大,如何找到能完成你任务的模块?在 Linux 中,你可以在应用程序菜单或 `/usr/bin` 中查找要运行的应用程序。使用 Ansible 时,参考 [Ansible 模块索引][4]。
|
||||
### 寻找正确的 Ansible 模块
|
||||
|
||||
Ansible 模块的数量非常庞大,如何找到能完成你任务的模块?在 Linux 中,你可以在应用程序菜单或 `/usr/bin` 中查找要运行的应用程序。使用 Ansible 时,你可以参考 [Ansible 模块索引][4]。
|
||||
|
||||
这个索引按照类别列出。稍加搜索,你就很可能找到所需的模块。对于包管理,[Packaging 模块][5]几乎适用于所有带包管理器的系统。
|
||||
|
||||
### 动手写一个 Ansible 剧本
|
||||
|
||||
首先,选择本地计算机上的包管理器。例如,如果你打算在运行 Fedora 的笔记本电脑上编写 Ansible 指令(在 Ansible 中称为“剧本”),那么从 dnf 模块开始。如果你在 Elementary OS 上编写,使用 `apt` 模块,以此类推。这样你就可以开始进行测试和验证,并可以在以后扩展到其它计算机。
|
||||
首先,选择本地计算机上的包管理器。例如,如果你打算在运行 Fedora 的笔记本电脑上编写 Ansible 指令(在 Ansible 中称为“<ruby>剧本<rt>playbook</rt></ruby>”),那么从 `dnf` 模块开始。如果你在 Elementary OS 上编写,使用 `apt` 模块,以此类推。这样你就可以开始进行测试和验证,并可以在以后扩展到其它计算机。
|
||||
|
||||
第一步是创建一个代表你的剧本的目录。这不是绝对必要的,但这是一个好习惯。Ansible 只需要一个配置文件就可以运行在 YAML 中,但是如果你以后想要扩展剧本,你就可以通过改变目录和文件的方式来控制 Ansible。现在,只需创建一个名为 `install_packages` 或类似的目录:
|
||||
|
||||
第一步是创建一个代表你剧本的目录。这不是绝对必要的,但这是一个好习惯。Ansible 只需要一个配置文件就可以运行在 YAML 中,但是如果你以后想要扩展剧本,你就可以通过改变目录和文件的方式来控制 Ansible。现在,只需创建一个名为 `install_packages` 或类似的目录:
|
||||
```
|
||||
$ mkdir ~/install_packages
|
||||
```
|
||||
|
||||
你可以根据自己的喜好来命名 Ansible 的剧本,但通常将其命名为 `site.yml`:
|
||||
|
||||
```
|
||||
$ touch ~/install_packages/site.yml
|
||||
```
|
||||
|
||||
在你最喜欢的文本编辑器中打开 `site.yml`,添加以下内容:
|
||||
|
||||
```
|
||||
\---
|
||||
\- hosts: localhost
|
||||
tasks:
|
||||
- name: install packages
|
||||
become: true
|
||||
become_user: root
|
||||
dnf:
|
||||
state: present
|
||||
name:
|
||||
- tcsh
|
||||
- htop
|
||||
---
|
||||
- hosts: localhost
|
||||
tasks:
|
||||
- name: install packages
|
||||
become: true
|
||||
become_user: root
|
||||
dnf:
|
||||
state: present
|
||||
name:
|
||||
- tcsh
|
||||
- htop
|
||||
```
|
||||
|
||||
你必须调整使用的模块名称来匹配你使用的发行版。在此示例中,我使用 `dnf` 是因为我在 Fedora Linux 上编写剧本。
|
||||
你必须调整使用的模块名称以匹配你使用的发行版。在此示例中,我使用 `dnf` 是因为我在 Fedora Linux 上编写剧本。
|
||||
|
||||
就像 Linux 终端中的命令一样,知道 _如何_ 来调用 Ansible 模块就已经成功了一半。这个示例剧本遵循标准剧本格式:
|
||||
就像 Linux 终端中的命令一样,知道 **如何** 来调用 Ansible 模块就已经成功了一半。这个示例剧本遵循标准剧本格式:
|
||||
|
||||
* `hosts` 是一台或多台计算机。在本示例中,目标计算机是 `localhost`,即你当前正在使用的计算机(而不是你希望 Ansible 连接的远程系统)。
|
||||
* `tasks` 是你要在主机上执行的任务列表。
|
||||
@ -66,9 +70,8 @@ $ touch ~/install_packages/site.yml
|
||||
`dnf` 下的节点是 `dnf` 模块专用的。这是模块文档的关键所在。就像 Linux 命令的手册页一样,模块文档会告诉你可用的选项和所需的参数。
|
||||
|
||||
![Ansible 文档][6]
|
||||
Ansible module documentation (Seth Kenlon, [CC BY-SA 4.0][7])
|
||||
|
||||
安装软件包是一个相对简单的任务,仅需要两个元素。`state` 选项指示 Ansible 检查系统上是否存在 _软件包_,而 `name` 选项列出要查找的软件包。Ansible 会处理机器 _状态_,因此模块指令始终意味着更改。假如 Ansible 扫描了系统状态,发现剧本里描述的系统(在本例中,`tcsh` 和 `htop` 存在)与实际状态存在冲突,那么 Ansible 的任务是进行必要的更改来使系统与剧本匹配。Ansible 可以通过 dnf(或 apt 或者其它任何包管理器)模块进行更改。
|
||||
安装软件包是一个相对简单的任务,仅需要两个元素。`state` 选项指示 Ansible 检查系统上是否存在 **软件包**,而 `name` 选项列出要查找的软件包。Ansible 会针对机器的 **状态** 进行调整,因此模块指令始终意味着更改。假如 Ansible 扫描了系统状态,发现剧本里描述的系统(在本例中,`tcsh` 和 `htop` 存在)与实际状态存在冲突,那么 Ansible 的任务是进行必要的更改来使系统与剧本匹配。Ansible 可以通过 `dnf`(或 `apt` 或者其它任何包管理器)模块进行更改。
|
||||
|
||||
每个模块可能都有一组不同的选项,所以在编写剧本时,要经常参考模块文档。除非你对模块非常熟悉,否则这是期望模块完成工作的唯一合理方法。
|
||||
|
||||
@ -77,27 +80,31 @@ Ansible module documentation (Seth Kenlon, [CC BY-SA 4.0][7])
|
||||
剧本是用 YAML 编写的。因为 YAML 遵循严格的语法,所以安装 `yamllint` 来检查剧本是很有帮助的。更妙的是,有一个专门针对 Ansible 的检查工具称为 `ansible-lint`,它专门为剧本而生。在继续之前,安装它。
|
||||
|
||||
在 Fedora 或 CentOs 上:
|
||||
|
||||
```
|
||||
$ sudo dnf ins tall yamllint python3-ansible-lint
|
||||
```
|
||||
|
||||
在 Debian、Elementary 或 Ubuntu 上,同样的:
|
||||
|
||||
```
|
||||
$ sudo apt install yamllint ansible-lint
|
||||
```
|
||||
|
||||
使用 `ansible-link` 来验证你的剧本。如果你无法使用 `ansible-lint`,你可以使用 `yamllint`。
|
||||
|
||||
```
|
||||
$ ansible-lint ~/install_packages/site.yml
|
||||
```
|
||||
|
||||
成功则不返回任何内容,但如果文件中有错误,则必须先修复它们,然后再继续。复制和粘贴过程中的常见错误包括在最后一行的末尾省略换行符、使用制表符而不是空格来缩进。在文本编辑器中修复它们,重新运行 `ansible-llint`,重复这个过程,直到 `ansible-lint` 或 `yamllint` 没有返回为止。
|
||||
成功则不返回任何内容,但如果文件中有错误,则必须先修复它们,然后再继续。复制和粘贴过程中的常见错误包括在最后一行的末尾省略换行符、使用制表符而不是空格来缩进。在文本编辑器中修复它们,重新运行 `ansible-lint`,重复这个过程,直到 `ansible-lint` 或 `yamllint` 没有返回为止。
|
||||
|
||||
### 使用 Ansible 安装一个应用
|
||||
|
||||
现在你有了一个可验证的有效剧本,你终于可以在本地计算机上运行它了,因为你碰巧知道该剧本定义的任务需要 root 权限,所以在调用 Ansible 时必须使用 `--ask-become-pass` 选项,因此系统会提示你输入管理员密码。
|
||||
|
||||
开始安装:
|
||||
|
||||
```
|
||||
$ ansible-playbook --ask-become-pass ~/install_packages/site.yml
|
||||
BECOME password:
|
||||
@ -121,15 +128,15 @@ localhost: ok=0 changed=2 unreachable=0 failed=0 [...]
|
||||
|
||||
要连接到远程系统,你必须在 `/etc/ansible/hosts` 文件中定义远程系统,该文件与 Ansible 是一起安装的,所以它已经存在了,但它可能是空的,除了一些解释性注释之外。使用 `sudo` 在你喜欢的文本编辑器中打开它。
|
||||
|
||||
只要主机名可以解析,就可以通过其 IP 地址或主机名定义主机。例如,如果你已经在 `/etc/hosts` 中定义了 `liavara` 并可以成功 ping 通,那么你可以在 `/etc/ansible/hosts` 中将 `liavara` 设置为主机。或者,如果你正在运行一个域名服务器或 Avahi 服务器并且可以 ping 通 `liavara`,那么你就可以在 `/etc/ansible/hosts` 中定义它。否则,你必须使用它的 IP 地址。
|
||||
你可以通过其 IP 地址或主机名(只要主机名可以解析)定义主机。例如,如果你已经在 `/etc/hosts` 中定义了 `liavara` 并可以成功 `ping` 通,那么你可以在 `/etc/ansible/hosts` 中将 `liavara` 设置为主机。或者,如果你正在运行一个域名服务器或 Avahi 服务器并且可以 `ping` 通 `liavara`,那么你就可以在 `/etc/ansible/hosts` 中定义它。否则,你必须使用它的 IP 地址。
|
||||
|
||||
你还必须成功地建立与目标主机的安全 shell(SSH)连接。最简单的方法是使用 `ssh-copy-id` 命令,但是如果你以前从未与主机建立 SSH 连接,[阅读我关于如何创建自动 SSH 连接的文章][8]。
|
||||
|
||||
一旦你在 `/etc/ansible/hosts` 文件中输入了主机名或 IP 地址后,你就可以在剧本中更改 `hosts` 定义:
|
||||
|
||||
```
|
||||
\---
|
||||
\- hosts: all
|
||||
---
|
||||
- hosts: all
|
||||
tasks:
|
||||
- name: install packages
|
||||
become: true
|
||||
@ -141,7 +148,8 @@ localhost: ok=0 changed=2 unreachable=0 failed=0 [...]
|
||||
- htop
|
||||
```
|
||||
|
||||
再次运行 `ansible-playbook`:
|
||||
再次运行 `ansible-playbook`:
|
||||
|
||||
```
|
||||
$ ansible-playbook --ask-become-pass ~/install_packages/site.yml
|
||||
```
|
||||
@ -152,29 +160,31 @@ $ ansible-playbook --ask-become-pass ~/install_packages/site.yml
|
||||
|
||||
### 适用于混合环境的 Ansible
|
||||
|
||||
到目前为止,我们一直假定 Ansible 配置的所有主机都运行相同的操作系统(都是是使用 **dnf** 命令进行程序包管理的操作系统)。那么,如果你要管理不同发行版的主机,例如 Ubuntu(使用 **apt**)或 Arch(使用 **pacman**),或者其它的操作系统时,该怎么办?
|
||||
到目前为止,我们一直假定 Ansible 配置的所有主机都运行相同的操作系统(都是是使用 `dnf` 命令进行程序包管理的操作系统)。那么,如果你要管理不同发行版的主机,例如 Ubuntu(使用 `apt`)或 Arch(使用 `pacman`),或者其它的操作系统时,该怎么办?
|
||||
|
||||
只要目标操作系统具有程序包管理器([MacOs 有 Homebrew][9],[Windows 有 Chocolatey][10]),Ansible 就能派上用场。
|
||||
|
||||
这就是 Ansible 优势最明显的地方。在 shell 脚本中,你必须检查目标主机上有哪些可用的包管理器,即使使用纯 Python,也必须检查操作系统。Ansible 不仅内置了这些功能,而且还具有在剧本中使用命令结果的机制。你可以使用 **action** 关键字来执行由 Ansible 事实收集子系统提供的变量定义的任务,而不是使用 **dnf** 模块。
|
||||
这就是 Ansible 优势最明显的地方。在 shell 脚本中,你必须检查目标主机上有哪些可用的包管理器,即使使用纯 Python,也必须检查操作系统。Ansible 不仅内置了这些功能,而且还具有在剧本中使用命令结果的机制。你可以使用 `action` 关键字来执行由 Ansible 事实收集子系统提供的变量定义的任务,而不是使用 `dnf` 模块。
|
||||
|
||||
```
|
||||
\---
|
||||
\- hosts: all
|
||||
---
|
||||
- hosts: all
|
||||
tasks:
|
||||
- name: install packages
|
||||
become: true
|
||||
become_user: root
|
||||
action: >
|
||||
action: >
|
||||
{{ ansible_pkg_mgr }} name=htop,transmission state=present update_cache=yes
|
||||
```
|
||||
|
||||
**action** 关键字会加载目标插件。在本例中,它使用了 **ansible_pkg_mgr** 变量,该变量由 Ansible 在初始 **收集信息** 期间填充。你不需要告诉 Ansible 收集有关其运行操作系统的事实,所以很容易忽略这一点,但是当你运行一个剧本时,你会在默认输出中看到它:
|
||||
`action` 关键字会加载目标插件。在本例中,它使用了 `ansible_pkg_mgr` 变量,该变量由 Ansible 在初始 **收集信息** 期间填充。你不需要告诉 Ansible 收集有关其运行操作系统的事实,所以很容易忽略这一点,但是当你运行一个剧本时,你会在默认输出中看到它:
|
||||
|
||||
```
|
||||
TASK [Gathering Facts] *****************************************
|
||||
ok: [localhost]
|
||||
```
|
||||
|
||||
**action** 插件使用来自这个探针的信息,使用相关的包管理器命令填充 **ansible_pkg_mgr**,以安装在 **name** 参数之后列出的程序包。使用 8 行代码,你可以克服在其它脚本选项中很少允许的复杂跨平台难题。
|
||||
`action` 插件使用来自这个探针的信息,使用相关的包管理器命令填充 `ansible_pkg_mgr`,以安装在 `name` 参数之后列出的程序包。使用 8 行代码,你可以克服在其它脚本选项中很少允许的复杂跨平台难题。
|
||||
|
||||
### 使用 Ansible
|
||||
|
||||
@ -187,7 +197,7 @@ via: https://opensource.com/article/20/9/install-packages-ansible
|
||||
作者:[Seth Kenlon][a]
|
||||
选题:[lujun9972][b]
|
||||
译者:[MjSeven](https://github.com/MjSeven)
|
||||
校对:[校对者ID](https://github.com/校对者ID)
|
||||
校对:[wxy](https://github.com/wxy)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
@ -1,24 +1,28 @@
|
||||
[#]: collector: (lujun9972)
|
||||
[#]: translator: (geekpi)
|
||||
[#]: reviewer: ( )
|
||||
[#]: publisher: ( )
|
||||
[#]: url: ( )
|
||||
[#]: subject: (How to Use the Firefox Task Manager (to Find and Kill RAM and CPU Eating Tabs and Extensions))
|
||||
[#]: reviewer: (wxy)
|
||||
[#]: publisher: (wxy)
|
||||
[#]: url: (https://linux.cn/article-12708-1.html)
|
||||
[#]: subject: (How to Use the Firefox Task Manager \(to Find and Kill RAM and CPU Eating Tabs and Extensions\))
|
||||
[#]: via: (https://itsfoss.com/firefox-task-manager/)
|
||||
[#]: author: (Ankush Das https://itsfoss.com/author/ankush/)
|
||||
|
||||
如何使用 Firefox 任务管理器(查找并杀死占用内存和 CPU 的标签页和扩展程序)
|
||||
如何使用 Firefox 任务管理器
|
||||
======
|
||||
|
||||
Firefox 在 Linux 用户中很受欢迎。它是几个 Linux 发行版上的默认网络浏览器。
|
||||
![](https://img.linux.net.cn/data/attachment/album/202010/11/103209cc1l7ktc7asacjhe.jpg)
|
||||
|
||||
在许多其他功能中,Firefox 提供了一个自己的任务管理器。
|
||||
> 查找并杀死占用内存和 CPU 的标签页和扩展程序
|
||||
|
||||
现在,在 Linux 中既然你有[任务管理器][1]这种形式的[系统监控工具][2],为什么还要使用它呢?这是有一个很好的理由。
|
||||
Firefox 在 Linux 用户中很受欢迎。它是几个 Linux 发行版上的默认 Web 浏览器。
|
||||
|
||||
假设你的系统占用了太多的内存或 CPU。如果你使用 top 或其他一些系统[资源监控工具,如 Glances][3],你会发现这些工具无法区分打开的标签或扩展。
|
||||
在它所提供的许多功能之中,Firefox 也提供了一个自己的任务管理器。
|
||||
|
||||
通常情况下,每个 Firefox 标签页都显示为 **Web 内容**。你可以看到是某个 Firefox 进程导致了这个问题,但这无法准确判断是哪个标签页或扩展。
|
||||
不过,在 Linux 中既然你有[任务管理器][1]这种形式的[系统监控工具][2],为什么还要使用 Firefox 的呢?这里有个很好的理由。
|
||||
|
||||
假设你的系统占用了太多的内存或 CPU。如果你使用 `top` 或其他一些系统[资源监控工具,如 Glances][3],你会发现这些工具无法区分是哪个打开的标签或扩展占用了资源。
|
||||
|
||||
通常情况下,每个 Firefox 标签页都显示为 “<ruby>Web 内容<rt>Web Content</rt></ruby>”。你可以看到是某个 Firefox 进程导致了这个问题,但这无法准确判断是哪个标签页或扩展。
|
||||
|
||||
这时你可以使用 Firefox 任务管理器。让我来告诉你怎么做!
|
||||
|
||||
@ -30,21 +34,21 @@ Firefox 在 Linux 用户中很受欢迎。它是几个 Linux 发行版上的默
|
||||
|
||||
正如你在上面的截图中所看到的,你会看到标签页的名称、类型(标签或附加组件)、能源影响和消耗的内存。
|
||||
|
||||
虽然一切都明了,但**能源影响指的是 CPU 的使用**,如果你使用的是笔记本电脑,它是一个很好的指标,可以告诉你什么东西会更快耗尽电池。
|
||||
其它的都不言自明,但**“能源影响”指的是 CPU 的使用**,如果你使用的是笔记本电脑,它是一个很好的指标,可以告诉你什么东西会更快耗尽电池电量。
|
||||
|
||||
#### 在 Firefox 中访问任务管理器
|
||||
|
||||
令人意外的是,任务管理器没有 [Firefox 键盘快捷键][5]。
|
||||
|
||||
要快速启动 Firefox 任务管理器,可以在地址栏中输入“**about:performance**”,如下图所示。
|
||||
要快速启动 Firefox 任务管理器,可以在地址栏中输入 `about:performance`,如下图所示。
|
||||
|
||||
![Quickly access task manager in Firefox][6]
|
||||
|
||||
另外,你也可以点击**菜单**图标,然后进入“**更多**“选项,如下截图所示。
|
||||
另外,你也可以点击“菜单”图标,然后进入“更多”选项,如下截图所示。
|
||||
|
||||
![Accessing task manager in Firefox][7]
|
||||
|
||||
接下来,你会发现选择”**任务管理器**”的选项,只需点击它就行。
|
||||
接下来,你会发现选择“任务管理器”的选项,只需点击它就行。
|
||||
|
||||
![][8]
|
||||
|
||||
@ -56,14 +60,12 @@ Firefox 在 Linux 用户中很受欢迎。它是几个 Linux 发行版上的默
|
||||
|
||||
以下是你应该知道的:
|
||||
|
||||
* 能源影响指的是 CPU 消耗。
|
||||
* “能源影响”指的是 CPU 消耗。
|
||||
* 子框架或子任务通常是与需要在后台运行的标签相关联的跟踪器/脚本。
|
||||
|
||||
|
||||
|
||||
通过这个任务管理器,你可以发现网站上的流氓脚本,以及它是否导致你的浏览器变慢。
|
||||
|
||||
这并不是什么火箭科学,但并不是很多人都知道 Firefox 任务管理器。现在你知道了,它应该很方便,你觉得呢?
|
||||
这并不是什么 高科技,但并不是所有人都知道 Firefox 任务管理器。现在你知道了,它应该很方便,你觉得呢?
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
@ -72,7 +74,7 @@ via: https://itsfoss.com/firefox-task-manager/
|
||||
作者:[Ankush Das][a]
|
||||
选题:[lujun9972][b]
|
||||
译者:[geekpi](https://github.com/geekpi)
|
||||
校对:[校对者ID](https://github.com/校对者ID)
|
||||
校对:[wxy](https://github.com/wxy)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
@ -1,24 +1,24 @@
|
||||
[#]: collector: (lujun9972)
|
||||
[#]: translator: (rakino)
|
||||
[#]: reviewer: ( )
|
||||
[#]: publisher: ( )
|
||||
[#]: url: ( )
|
||||
[#]: reviewer: (wxy)
|
||||
[#]: publisher: (wxy)
|
||||
[#]: url: (https://linux.cn/article-12702-1.html)
|
||||
[#]: subject: (How to view information on your Linux devices with lshw)
|
||||
[#]: via: (https://www.networkworld.com/article/3583598/how-to-view-information-on-your-linux-devices-with-lshw.html)
|
||||
[#]: author: (Sandra Henry-Stocker https://www.networkworld.com/author/Sandra-Henry_Stocker/)
|
||||
|
||||
如何使用 lshw 查看 Linux 设备信息
|
||||
======
|
||||
Linux 系统上的 lshw(<ruby>列出硬件<rt>list hardware</rt></ruby>)命令提供的系统设备信息比我们大多数人想象的要多得多。
|
||||
|
||||
> Linux 系统上的 lshw 命令提供的系统设备信息比我们大多数人想象的要多得多。
|
||||
|
||||
![Kali Linux logo / gears / binary data][1]
|
||||
(Kali Linux / nevarpp / Getty Images)
|
||||
|
||||
虽然 **lshw** 命令(读作 “ls hardware”)远不是每个人最先学会的 50 个 Linux 命令之一,但它可以提供很多系统硬件的有用信息。
|
||||
虽然 `lshw` 命令(<ruby>列出硬件<rt>list hardware</rt></ruby>,读作 “ls hardware”)远不是每个人最先学会的 50 个 Linux 命令之一,但它可以提供很多系统硬件的有用信息。
|
||||
|
||||
它以一种相当易于理解的格式提取出可能比你知道的更多的信息。在看到描述、(设备)逻辑名称、大小等以后,你可能会理解到自己能获得多少信息。
|
||||
|
||||
这篇文章会研究 **lshw** 给出的信息,但侧重于磁盘及相关硬件。下面是 **lshw** 的输出示例:
|
||||
这篇文章会研究 `lshw` 给出的信息,但侧重于磁盘及相关硬件。下面是 `lshw` 的输出示例:
|
||||
|
||||
```
|
||||
$ sudo lshw -C disk
|
||||
@ -37,9 +37,9 @@ $ sudo lshw -C disk
|
||||
logical name: /dev/sdc
|
||||
```
|
||||
|
||||
请注意,你需要使用 **sudo** 运行 **lshw** 命令以确保能得到所有可用的信息。
|
||||
请注意,你需要使用 `sudo` 运行 `lshw` 命令以确保能得到所有可用的信息。
|
||||
|
||||
虽然我们在上面的命令中要求了“磁盘(disk)”(上面只包含了原始输出里五个条目中的一个),这里的输出却不是一个硬盘,而是读卡器——磁盘的一种。注意系统将这个设备命名为了 **/dev/sdc**。
|
||||
虽然我们在上面的命令中要求了输出“磁盘(`disk`)”(上面只包含了原始输出里五个条目中的一个),这里的输出却不是一个硬盘,而是读卡器——磁盘的一种。注意系统将这个设备命名为了 `/dev/sdc`。
|
||||
|
||||
系统的主磁盘上也有相似的信息:
|
||||
|
||||
@ -58,7 +58,7 @@ $ sudo lshw -C disk
|
||||
f63b5929
|
||||
```
|
||||
|
||||
这块硬盘是 **/dev/sda**。这个系统上的硬盘都显示为 **ATA** 磁盘,**ATA** 是一种把控制器与盘体集成在一起的磁盘驱动器实现。
|
||||
这块硬盘是 `/dev/sda`。这个系统上的硬盘都显示为 `ATA` 磁盘,`ATA` 是一种把控制器与盘体集成在一起的磁盘驱动器实现。
|
||||
|
||||
要获得“磁盘”类设备的简略列表,可以运行下面这条命令。注意其中有两个设备被列出了两次,所以我们看到的仍然是五个磁盘设备。
|
||||
|
||||
@ -75,7 +75,7 @@ H/W path Device Class Description
|
||||
/0/100/1f.5/0.0.0 /dev/sdb disk 500GB SAMSUNG HE502HJ
|
||||
```
|
||||
|
||||
如果你决定要查看系统上的 _**所有**_ 设备,请坐稳了;你会得到一个包含的东西比你通常认为的“设备”要多得多的列表,下面是一个例子,这是一个“简短(short)”(信息很少)的列表:
|
||||
如果你决定要查看系统上的 **所有** 设备,请坐稳了;你会得到一个包含的东西比你通常认为的“设备”要多得多的列表,下面是一个例子,这是一个“简短(`short`)”(信息很少)的列表:
|
||||
|
||||
```
|
||||
$ sudo lshw -short
|
||||
@ -174,18 +174,18 @@ $ sudo lshw -short | awk ‘{print substr($0,36,13)}’ | tail -n +3 | sort | un
|
||||
2 volume
|
||||
```
|
||||
|
||||
**注意:** 上面使用 **awk** 命令从 **lshw** 的输出中选择 Class(类别)栏是这样实现的:使用 $0(选取完整行),但只取从正确位置(第 36 个字符)开始的子串,而因为“类别”中并没有条目的长度超过 13 个字符,所以子串就在那里结束。命令中 **tail -n +3** 的部分移除了标题和下面的“=====”,所以最终的列表中只包含了那 14 种设备类型。
|
||||
**注意:** 上面使用 `awk` 命令从 `lshw` 的输出中选择 Class(类别)栏是这样实现的:使用 `$0`(选取完整行),但只取从正确位置(第 36 个字符)开始的子串,而因为“类别”中并没有条目的长度超过 13 个字符,所以子串就在那里结束。命令中 `tail -n +3` 的部分移除了标题和下面的`=====`,所以最终的列表中只包含了那 14 种设备类型。
|
||||
|
||||
(译注:上面的命令中 awk 的部分在选取子串时是从第 36 个字符开始的,这个数字基本上取决于最长的设备逻辑名称的长度,因而在不同的系统环境中可能有所不同,一个例子是,当你的系统上有 NVMe SSD 时,可能需要将其改为 41。)
|
||||
(LCTT 译注:上面的命令中 `awk` 的部分在选取子串时是从第 36 个字符开始的,这个数字基本上取决于最长的设备逻辑名称的长度,因而在不同的系统环境中可能有所不同,一个例子是,当你的系统上有 NVMe SSD 时,可能需要将其改为 41。)
|
||||
|
||||
你会发现在没有使用 **-short** 选项的时候,每一个磁盘类设备都会有大约 12 行的输出,包括像是 **/dev/sda** 这样的逻辑名称,磁盘大小和种类等等。
|
||||
你会发现在没有使用 `-short` 选项的时候,每一个磁盘类设备都会有大约 12 行的输出,包括像是 `/dev/sda` 这样的逻辑名称,磁盘大小和种类等等。
|
||||
|
||||
```
|
||||
$ sudo lshw -C disk
|
||||
[sudo] password for shs:
|
||||
*-disk:0
|
||||
description: SCSI Disk
|
||||
product: Card Reader-1 读卡器?
|
||||
product: Card Reader-1 <== 读卡器?
|
||||
vendor: JIE LI
|
||||
physical id: 0.0.0
|
||||
bus info: scsi@4:0.0.0
|
||||
@ -213,13 +213,13 @@ $ sudo lshw -C disk
|
||||
product: SSD2SC120G1CS175
|
||||
physical id: 0
|
||||
bus info: scsi@0:0.0.0
|
||||
logical name: /dev/sda 主要磁盘
|
||||
logical name: /dev/sda <== 主要磁盘
|
||||
version: 1101
|
||||
serial: PNY20150000778410606
|
||||
size: 111GiB (120GB)
|
||||
capabilities: partitioned partitioned:dos
|
||||
configuration: ansiversion=5 logicalsectorsize=512 sectorsize=512 signature=f63b5929
|
||||
*-cdrom 也叫 /dev/sr0
|
||||
*-cdrom <== 也叫 /dev/sr0
|
||||
description: DVD writer
|
||||
product: DVD+-RW GSA-H73N
|
||||
vendor: HL-DT-ST
|
||||
@ -239,7 +239,7 @@ $ sudo lshw -C disk
|
||||
product: SAMSUNG HE502HJ
|
||||
physical id: 0.0.0
|
||||
bus info: scsi@3:0.0.0
|
||||
logical name: /dev/sdb 次要磁盘
|
||||
logical name: /dev/sdb <== 次要磁盘
|
||||
version: 0002
|
||||
serial: S2B6J90B501053
|
||||
size: 465GiB (500GB)
|
||||
@ -249,9 +249,7 @@ $ sudo lshw -C disk
|
||||
|
||||
### 总结
|
||||
|
||||
**lshw** 命令提供了一些我们许多人通常不会处理的信息,不过即使你只用了其中的一部分,知道有多少信息可用还是很不错的。
|
||||
|
||||
加入 [Facebook][2] 和 [领英][3] 上的 Network World 社区,对最热门的话题发表评论。
|
||||
`lshw` 命令提供了一些我们许多人通常不会处理的信息,不过即使你只用了其中的一部分,知道有多少信息可用还是很不错的。
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
@ -260,7 +258,7 @@ via: https://www.networkworld.com/article/3583598/how-to-view-information-on-you
|
||||
作者:[Sandra Henry-Stocker][a]
|
||||
选题:[lujun9972][b]
|
||||
译者:[rakino](https://github.com/rakino)
|
||||
校对:[校对者ID](https://github.com/校对者ID)
|
||||
校对:[wxy](https://github.com/wxy)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
@ -1,22 +1,24 @@
|
||||
[#]: collector: (lujun9972)
|
||||
[#]: translator: (geekpi)
|
||||
[#]: reviewer: ( )
|
||||
[#]: publisher: ( )
|
||||
[#]: url: ( )
|
||||
[#]: reviewer: (wxy)
|
||||
[#]: publisher: (wxy)
|
||||
[#]: url: (https://linux.cn/article-12712-1.html)
|
||||
[#]: subject: (Drawing is an Open Source MS-Paint Type of App for Linux Desktop)
|
||||
[#]: via: (https://itsfoss.com/drawing-app/)
|
||||
[#]: author: (Abhishek Prakash https://itsfoss.com/author/abhishek/)
|
||||
|
||||
Drawing 是一款开源的类似微软画图的 Linux 桌面应用
|
||||
Drawing:一款开源的类似微软画图的 Linux 桌面应用
|
||||
======
|
||||
|
||||
_**简介:Drawing 是一个基本的图像编辑器,就像微软画图一样。有了这个开源的应用,你可以画箭头、线条、几何图形、添加颜色和其他你期望在普通绘图应用程序中做的事情。**_
|
||||
![](https://img.linux.net.cn/data/attachment/album/202010/12/231731q0pgrkzidsguosps.jpg)
|
||||
|
||||
> Drawing 是一个基本的图像编辑器,就像微软画图一样。有了这个开源的应用,你可以画箭头、线条、几何图形、添加颜色和其他你期望在普通绘图应用程序中做的事情。
|
||||
|
||||
### Drawing: 一个简单的 Linux 绘图应用
|
||||
|
||||
![][1]
|
||||
|
||||
对于从 Windows XP (或更早版本)开始使用电脑的人来说,微软画图是一个有趣的随机画一些草图的应用。在这个被 Photoshop 和 GIMP 主导的世界里,画图应用仍然具有一定的现实意义。
|
||||
对于从 Windows XP (或更早版本)开始使用电脑的人来说,微软<ruby>画图<rt>Paint</rt></ruby>是一个有趣的应用,是个可以随便画一些草图的应用。在这个被 Photoshop 和 GIMP 主导的世界里,画图应用仍然具有一定的现实意义。
|
||||
|
||||
有几个[可用于 Linux 的绘画应用][2],我打算在这个列表中再添加一个。
|
||||
|
||||
@ -29,34 +31,32 @@ _**简介:Drawing 是一个基本的图像编辑器,就像微软画图一样
|
||||
Drawing 拥有你所期待的绘图应用的所有功能。你可以:
|
||||
|
||||
* 从头开始创建新的绘图
|
||||
* 编辑现有的 PNG、JPEG 或 BMP 图像文件。
|
||||
* 添加几何图形、线条、箭头等。
|
||||
* 编辑现有的 PNG、JPEG 或 BMP 图像文件
|
||||
* 添加几何图形、线条、箭头等
|
||||
* 虚线
|
||||
* 使用铅笔工具进行自由手绘。
|
||||
* 使用铅笔工具进行自由手绘
|
||||
* 使用曲线和形状工具
|
||||
* 裁剪图像
|
||||
* 缩放图像到不同的像素大小
|
||||
* 添加文本
|
||||
* 选择图像的一部分(矩形、自由选择和颜色选择)。
|
||||
* 选择图像的一部分(矩形、自由选择和颜色选择)
|
||||
* 旋转图像
|
||||
* 添加复制到剪贴板的图像
|
||||
* 可在偏好中使用橡皮擦、荧光笔、油漆桶、颜色选择、颜色选择器工具
|
||||
* 无限撤销
|
||||
* 滤镜可以增加模糊、像素化、透明度等。
|
||||
|
||||
|
||||
* 滤镜可以增加模糊、像素化、透明度等
|
||||
|
||||
### 我使用 Drawing 的经验
|
||||
|
||||
![][5]
|
||||
|
||||
这个应用是新的,并且有不错的用户界面。它具有你期望在标准绘画应用中找到的所有基本功能。
|
||||
这个应用是新出现的,并且有不错的用户界面。它具有你期望在标准的绘画应用中找到的所有基本功能。
|
||||
|
||||
它有一些额外的工具,如颜色选择和颜色选择器,但在使用时可能会混淆。没有可用的文档来描述这些工具的使用,要全靠你自己。
|
||||
它有一些额外的工具,如颜色选择和拾色器,但在使用时可能会混淆。没有什么文档描述这些工具的使用,要全靠你自己摸索。
|
||||
|
||||
它的体验很流畅,我觉得这个工具很有潜力取代 Shutter 作为图像编辑工具(是的,我[用 Shutter 编辑截图][6])。
|
||||
它的体验很流畅,作为图像编辑工具,我觉得这个工具很有潜力取代 Shutter (是的,我[用 Shutter 编辑截图][6])。
|
||||
|
||||
我觉得最麻烦的是,添加元素后无法编辑/修改。你有撤消和重做选项,但如果你想修改一个你在 12 步前添加的文本,你就必须重做所有的步骤。这一点开发者可能会在未来的版本中进行研究。
|
||||
我觉得最麻烦的是,添加元素后无法编辑/修改。你有撤消和重做选项,但如果你想修改一个你在 12 步前添加的文本,你就必须重做所有的步骤。这是未来的版本中开发者可能要做的一些改进。
|
||||
|
||||
### 在 Linux 上安装 Drawing
|
||||
|
||||
@ -89,9 +89,9 @@ sudo add-apt-repository -r ppa:cartes/drawing
|
||||
|
||||
检查你的发行版的包管理器中是否有 Drawing,然后在那里安装。如果你想要最新的版本,你可以使用 Flatpak 版本的应用。
|
||||
|
||||
[Drawing Flatpak][9]
|
||||
- [Drawing Flatpak][9]
|
||||
|
||||
**总结**
|
||||
### 总结
|
||||
|
||||
你还在用画图应用么?你用的是哪一款?如果你已经尝试过 Drawing,你的体验如何?
|
||||
|
||||
@ -102,7 +102,7 @@ via: https://itsfoss.com/drawing-app/
|
||||
作者:[Abhishek Prakash][a]
|
||||
选题:[lujun9972][b]
|
||||
译者:[geekpi](https://github.com/geekpi)
|
||||
校对:[校对者ID](https://github.com/校对者ID)
|
||||
校对:[wxy](https://github.com/wxy)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
@ -0,0 +1,91 @@
|
||||
[#]: collector: (lujun9972)
|
||||
[#]: translator: ( )
|
||||
[#]: reviewer: ( )
|
||||
[#]: publisher: ( )
|
||||
[#]: url: ( )
|
||||
[#]: subject: (5 qualities of great open source developer advocates)
|
||||
[#]: via: (https://opensource.com/article/20/10/open-source-developer-advocates)
|
||||
[#]: author: (Jason Blais https://opensource.com/users/jasonblais)
|
||||
|
||||
5 qualities of great open source developer advocates
|
||||
======
|
||||
Whether you're looking to hire a developer advocate or become one, here
|
||||
are the qualities to aim for.
|
||||
![Women talking][1]
|
||||
|
||||
The developer relations job category is less than 10 years old, and [the developer advocate role is even newer][2]. In essence, developer advocates represent the voice of the user—in this case, that's usually the developer—internally to the company and the voice of the company externally to the community.
|
||||
|
||||
[Mattermost][3] depends on its developer advocates to be the bridge between the community and the organization. At Mattermost, a developer advocate's three primary areas of responsibility are:
|
||||
|
||||
* Raising awareness among developers about the open source project, including educating users and helping them get the most out of the platform
|
||||
* Building strong relationships with users across open source and developer communities
|
||||
* Advocating for users internally with the product team by sharing the community's feedback and challenges
|
||||
|
||||
|
||||
|
||||
Not everyone is cut out to succeed as an open source developer advocate. With that in mind, here are the top five qualities we've identified in outstanding developer advocates.
|
||||
|
||||
### 1\. A genuine passion for helping others
|
||||
|
||||
Developer advocates often start as developers or in other highly technical roles. They excel at their job but get less pleasure from creating solutions than they do by helping others do the same.
|
||||
|
||||
Over time, they turn this enthusiasm into enabling and empowering other developers to be successful. They naturally evolve into developer advocates by educating and helping users get the most out of the platforms they're working with.
|
||||
|
||||
Passion is one of the most important qualities of a developer advocate. To advocate for the user both internally and externally, they must put the user and the community first. This does not happen without genuine motivation to help others succeed.
|
||||
|
||||
### 2\. An authentic communication style
|
||||
|
||||
An outstanding developer advocate has a passion for writing and talking about technology—whether they're solving technical challenges, providing knowledge about specific frameworks (e.g., Kubernetes), or sharing solutions built on top of a platform.
|
||||
|
||||
But when they share their knowledge, they must be careful not to be perceived as promoting a specific platform or product. If any group is turned off by inauthentic marketing, [it is developers][4]. Developer relations is [not the same as developer marketing][5]. That is why authenticity is critical when connecting with developer communities—particularly in open source.
|
||||
|
||||
Combining these creates an authentic communicator who captures developers' attention and pulls people in. That engagement enables them to raise awareness, educate to help users get the most out of the platform, and build strong relationships with users—the three main areas that developer advocates own.
|
||||
|
||||
### 3\. A natural flair for building relationships
|
||||
|
||||
Developer advocates are typically extroverts. Through their authenticity and passion for helping others, they can build strong relationships with users and members of the community.
|
||||
|
||||
The truly outstanding advocates have a natural flair for creating connections through their superb networking skills. They also know where the communities exist, whether it's Reddit, Twitter, meetup groups, forums, or chat channels.
|
||||
|
||||
Why is having a natural flair for creating relationships so important?
|
||||
|
||||
First, developer advocates bring their existing connections to developers and open source communities when they join a team. Second, they come in knowing the right social media and developer channels to reach developers and open source users. Finally, they create new relationships with community influencers and open source leaders that can give your platform an opportunity to grow rapidly.
|
||||
|
||||
### 4\. A personal investment in the community
|
||||
|
||||
As I mentioned, developer advocates are not only the advocates of users internally; they are also the voice of the company externally to the community. This allows them to develop their own personal brand and "street cred" that they will carry with them after they move on to another company or community.
|
||||
|
||||
When a developer advocate cares about their personal brand and has a personal investment in what they do, they are typically more motivated. Not only are they responsible for cultivating the company's brand, but they are also putting their own reputation on the line. This can be frightening to some. But those who are brave and willing to personally commit have the edge needed to excel as a developer advocate.
|
||||
|
||||
As a side note, developer advocates who are employed by the company they're advocating for should work as part of the community and put the community ahead of themselves. They must be willing to continuously learn from and with the community, be a team player, and never put their own brand ahead of the company or community.
|
||||
|
||||
### 5\. Technical sharpness
|
||||
|
||||
The final key attribute of great developer advocates is their technical sharpness. Are they knowledgeable about cutting-edge technologies, languages, and frameworks? Do they learn technologies and tools easily? Are they self-taught, self-resourced learners?
|
||||
|
||||
Since other developers will look to them for guidance, it is important for developer advocates to be highly technical with several years of relevant experience in software development or DevOps. Without already being a developer (or otherwise highly technical), it would be difficult to really understand the developer mindset and know what tickles their curiosity.
|
||||
|
||||
### Other characteristics
|
||||
|
||||
These five qualities—a genuine passion for helping others, authentic communication capabilities, a natural flair for building relationships, a personal investment, and technical sharpness—are Mattermost's core characteristics in outstanding open source developer advocates. Are there any other must-have qualities I've missed? Let me know in the comments!
|
||||
|
||||
_Thank you to Justin Reynolds for the valuable edits on this article._
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
via: https://opensource.com/article/20/10/open-source-developer-advocates
|
||||
|
||||
作者:[Jason Blais][a]
|
||||
选题:[lujun9972][b]
|
||||
译者:[译者ID](https://github.com/译者ID)
|
||||
校对:[校对者ID](https://github.com/校对者ID)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]: https://opensource.com/users/jasonblais
|
||||
[b]: https://github.com/lujun9972
|
||||
[1]: https://opensource.com/sites/default/files/styles/image-full-size/public/lead-images/conversation-interview-mentor.png?itok=HjoOPcrB (Women talking)
|
||||
[2]: https://medium.com/@ashleymcnamara/what-is-developer-advocacy-3a92442b627c
|
||||
[3]: http://mattermost.com/
|
||||
[4]: https://hackernoon.com/developer-marketing-allergies-authenticity-622014fdebfb
|
||||
[5]: https://medium.com/@aspleenic/developer-relations-and-developer-marketing-they-arent-the-same-thing-35b896159825
|
@ -1,978 +0,0 @@
|
||||
[#]: collector: (oska874)
|
||||
[#]: translator: (gxlct008)
|
||||
[#]: reviewer: ( )
|
||||
[#]: publisher: ( )
|
||||
[#]: url: ( )
|
||||
[#]: subject: (Go on very small hardware (Part 2))
|
||||
[#]: via: (https://ziutek.github.io/2018/04/14/go_on_very_small_hardware2.html)
|
||||
[#]: author: (Michał Derkacz https://ziutek.github.io/)
|
||||
|
||||
Go on very small hardware (Part 2)
|
||||
============================================================
|
||||
|
||||
|
||||
[![STM32F030F4P6](https://ziutek.github.io/images/mcu/f030-demo-board/board.jpg)][1]
|
||||
|
||||
At the end of the [first part][2] of this article I promised to write something about _interfaces_ . I don’t want to write here a complete or even brief lecture about the interfaces. Instead, I’ll show a simple example how to define and use an interface, and then, how to take advantage of ubiquitous _io.Writer_ interface. There will also be a few words about _reflection_ and _semihosting_ .
|
||||
|
||||
Interfaces are a crucial part of Go language. If you want to learn more about them, I suggest to read [Effective Go][3] and [Russ Cox article][4].
|
||||
|
||||
### Concurrent Blinky – revisited
|
||||
|
||||
When you read the code of previous examples you probably noticed a counterintuitive way to turn the LED on or off. The _Set_ method was used to turn the LED off and the _Clear_ method was used to turn the LED on. This is due to driving the LEDs in open-drain configuration. What we can do to make the code less confusing? Let’s define the _LED_ type with _On_ and _Off_ methods:
|
||||
|
||||
```
|
||||
type LED struct {
|
||||
pin gpio.Pin
|
||||
}
|
||||
|
||||
func (led LED) On() {
|
||||
led.pin.Clear()
|
||||
}
|
||||
|
||||
func (led LED) Off() {
|
||||
led.pin.Set()
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
Now we can simply call `led.On()` and `led.Off()` which no longer raises any doubts.
|
||||
|
||||
In all previous examples I tried to use the same open-drain configuration to don’t complicate the code. But in the last example, it would be easier for me to connect the third LED between GND and PA3 pins and configure PA3 in push-pull mode. The next example will use a LED connected this way.
|
||||
|
||||
But our new _LED_ type doesn’t support the push-pull configuration. In fact, we should call it _OpenDrainLED_ and define another _PushPullLED_ type:
|
||||
|
||||
```
|
||||
type PushPullLED struct {
|
||||
pin gpio.Pin
|
||||
}
|
||||
|
||||
func (led PushPullLED) On() {
|
||||
led.pin.Set()
|
||||
}
|
||||
|
||||
func (led PushPullLED) Off() {
|
||||
led.pin.Clear()
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
Note, that both types have the same methods that work the same. It would be nice if the code that operates on LEDs could use both types, without paying attention to which one it uses at the moment. The _interface type_ comes to help:
|
||||
|
||||
```
|
||||
package main
|
||||
|
||||
import (
|
||||
"delay"
|
||||
|
||||
"stm32/hal/gpio"
|
||||
"stm32/hal/system"
|
||||
"stm32/hal/system/timer/systick"
|
||||
)
|
||||
|
||||
type LED interface {
|
||||
On()
|
||||
Off()
|
||||
}
|
||||
|
||||
type PushPullLED struct{ pin gpio.Pin }
|
||||
|
||||
func (led PushPullLED) On() {
|
||||
led.pin.Set()
|
||||
}
|
||||
|
||||
func (led PushPullLED) Off() {
|
||||
led.pin.Clear()
|
||||
}
|
||||
|
||||
func MakePushPullLED(pin gpio.Pin) PushPullLED {
|
||||
pin.Setup(&gpio.Config{Mode: gpio.Out, Driver: gpio.PushPull})
|
||||
return PushPullLED{pin}
|
||||
}
|
||||
|
||||
type OpenDrainLED struct{ pin gpio.Pin }
|
||||
|
||||
func (led OpenDrainLED) On() {
|
||||
led.pin.Clear()
|
||||
}
|
||||
|
||||
func (led OpenDrainLED) Off() {
|
||||
led.pin.Set()
|
||||
}
|
||||
|
||||
func MakeOpenDrainLED(pin gpio.Pin) OpenDrainLED {
|
||||
pin.Setup(&gpio.Config{Mode: gpio.Out, Driver: gpio.OpenDrain})
|
||||
return OpenDrainLED{pin}
|
||||
}
|
||||
|
||||
var led1, led2 LED
|
||||
|
||||
func init() {
|
||||
system.SetupPLL(8, 1, 48/8)
|
||||
systick.Setup(2e6)
|
||||
|
||||
gpio.A.EnableClock(false)
|
||||
led1 = MakeOpenDrainLED(gpio.A.Pin(4))
|
||||
led2 = MakePushPullLED(gpio.A.Pin(3))
|
||||
}
|
||||
|
||||
func blinky(led LED, period int) {
|
||||
for {
|
||||
led.On()
|
||||
delay.Millisec(100)
|
||||
led.Off()
|
||||
delay.Millisec(period - 100)
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
go blinky(led1, 500)
|
||||
blinky(led2, 1000)
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
We’ve defined _LED_ interface that has two methods: _On_ and _Off_ . The _PushPullLED_ and _OpenDrainLED_ types represent two ways of driving LEDs. We also defined two _Make_ _*LED_ functions which act as constructors. Both types implement the _LED_ interface, so the values of these types can be assigned to the variables of type _LED_ :
|
||||
|
||||
```
|
||||
led1 = MakeOpenDrainLED(gpio.A.Pin(4))
|
||||
led2 = MakePushPullLED(gpio.A.Pin(3))
|
||||
|
||||
```
|
||||
|
||||
In this case the assignability is checked at compile time. After the assignment the _led1_ variable contains `OpenDrainLED{gpio.A.Pin(4)}` and a pointer to the method set of the _OpenDrainLED_ type. The `led1.On()` call roughly corresponds to the following C code:
|
||||
|
||||
```
|
||||
led1.methods->On(led1.value)
|
||||
|
||||
```
|
||||
|
||||
As you can see, this is quite inexpensive abstraction if only consider the function call overhead.
|
||||
|
||||
But any assigment to an interface causes to include a lot of information about the assigned type. There can be a lot information in case of complex type which consists of many other types:
|
||||
|
||||
```
|
||||
$ egc
|
||||
$ arm-none-eabi-size cortexm0.elf
|
||||
text data bss dec hex filename
|
||||
10356 196 212 10764 2a0c cortexm0.elf
|
||||
|
||||
```
|
||||
|
||||
If we don’t use [reflection][5] we can save some bytes by avoid to include the names of types and struct fields:
|
||||
|
||||
```
|
||||
$ egc -nf -nt
|
||||
$ arm-none-eabi-size cortexm0.elf
|
||||
text data bss dec hex filename
|
||||
10312 196 212 10720 29e0 cortexm0.elf
|
||||
|
||||
```
|
||||
|
||||
The resulted binary still contains some necessary information about types and full information about all exported methods (with names). This information is need for checking assignability at runtime, mainly when you assign one value stored in the interface variable to any other variable.
|
||||
|
||||
We can also remove type and field names from imported packages by recompiling them all:
|
||||
|
||||
```
|
||||
$ cd $HOME/emgo
|
||||
$ ./clean.sh
|
||||
$ cd $HOME/firstemgo
|
||||
$ egc -nf -nt
|
||||
$ arm-none-eabi-size cortexm0.elf
|
||||
text data bss dec hex filename
|
||||
10272 196 212 10680 29b8 cortexm0.elf
|
||||
|
||||
```
|
||||
|
||||
Let’s load this program to see does it work as expected. This time we’ll use the [st-flash][6] command:
|
||||
|
||||
```
|
||||
$ arm-none-eabi-objcopy -O binary cortexm0.elf cortexm0.bin
|
||||
$ st-flash write cortexm0.bin 0x8000000
|
||||
st-flash 1.4.0-33-gd76e3c7
|
||||
2018-04-10T22:04:34 INFO usb.c: -- exit_dfu_mode
|
||||
2018-04-10T22:04:34 INFO common.c: Loading device parameters....
|
||||
2018-04-10T22:04:34 INFO common.c: Device connected is: F0 small device, id 0x10006444
|
||||
2018-04-10T22:04:34 INFO common.c: SRAM size: 0x1000 bytes (4 KiB), Flash: 0x4000 bytes (16 KiB) in pages of 1024 bytes
|
||||
2018-04-10T22:04:34 INFO common.c: Attempting to write 10468 (0x28e4) bytes to stm32 address: 134217728 (0x8000000)
|
||||
Flash page at addr: 0x08002800 erased
|
||||
2018-04-10T22:04:34 INFO common.c: Finished erasing 11 pages of 1024 (0x400) bytes
|
||||
2018-04-10T22:04:34 INFO common.c: Starting Flash write for VL/F0/F3/F1_XL core id
|
||||
2018-04-10T22:04:34 INFO flash_loader.c: Successfully loaded flash loader in sram
|
||||
11/11 pages written
|
||||
2018-04-10T22:04:35 INFO common.c: Starting verification of write complete
|
||||
2018-04-10T22:04:35 INFO common.c: Flash written and verified! jolly good!
|
||||
|
||||
```
|
||||
|
||||
I didn’t connected the NRST signal to the programmer so the _—reset_ option can’t be used and the reset button have to be pressed to run the program.
|
||||
|
||||
![Interfaces](https://ziutek.github.io/images/mcu/f030-demo-board/interfaces.png)
|
||||
|
||||
It seems that the _st-flash_ works a bit unreliably with this board (often requires reseting the ST-LINK dongle). Additionally, the current version doesn’t issue the reset command over SWD (uses only NRST signal). The software reset isn’t realiable however it usually works and lack of it introduces inconvenience. For this board-programmer pair the _OpenOCD_ works much better.
|
||||
|
||||
### UART
|
||||
|
||||
UART (Universal Aynchronous Receiver-Transmitter) is still one of the most important peripherals of today’s microcontrollers. Its advantage is unique combination of the following properties:
|
||||
|
||||
* relatively high speed,
|
||||
|
||||
* only two signal lines (even one in case of half-duplex communication),
|
||||
|
||||
* symmetry of roles,
|
||||
|
||||
* synchronous in-band signaling about new data (start bit),
|
||||
|
||||
* accurate timing inside transmitted word.
|
||||
|
||||
This causes that UART, originally intedned to transmit asynchronous messages consisting of 7-9 bit words, is also used to efficiently implement various other phisical protocols such as used by [WS28xx LEDs][7] or [1-wire][8] devices.
|
||||
|
||||
However, we will use the UART in its usual role: to printing text messages from our program.
|
||||
|
||||
```
|
||||
package main
|
||||
|
||||
import (
|
||||
"io"
|
||||
"rtos"
|
||||
|
||||
"stm32/hal/dma"
|
||||
"stm32/hal/gpio"
|
||||
"stm32/hal/irq"
|
||||
"stm32/hal/system"
|
||||
"stm32/hal/system/timer/systick"
|
||||
"stm32/hal/usart"
|
||||
)
|
||||
|
||||
var tts *usart.Driver
|
||||
|
||||
func init() {
|
||||
system.SetupPLL(8, 1, 48/8)
|
||||
systick.Setup(2e6)
|
||||
|
||||
gpio.A.EnableClock(true)
|
||||
tx := gpio.A.Pin(9)
|
||||
|
||||
tx.Setup(&gpio.Config{Mode: gpio.Alt})
|
||||
tx.SetAltFunc(gpio.USART1_AF1)
|
||||
d := dma.DMA1
|
||||
d.EnableClock(true)
|
||||
tts = usart.NewDriver(usart.USART1, d.Channel(2, 0), nil, nil)
|
||||
tts.Periph().EnableClock(true)
|
||||
tts.Periph().SetBaudRate(115200)
|
||||
tts.Periph().Enable()
|
||||
tts.EnableTx()
|
||||
|
||||
rtos.IRQ(irq.USART1).Enable()
|
||||
rtos.IRQ(irq.DMA1_Channel2_3).Enable()
|
||||
}
|
||||
|
||||
func main() {
|
||||
io.WriteString(tts, "Hello, World!\r\n")
|
||||
}
|
||||
|
||||
func ttsISR() {
|
||||
tts.ISR()
|
||||
}
|
||||
|
||||
func ttsDMAISR() {
|
||||
tts.TxDMAISR()
|
||||
}
|
||||
|
||||
//c:__attribute__((section(".ISRs")))
|
||||
var ISRs = [...]func(){
|
||||
irq.USART1: ttsISR,
|
||||
irq.DMA1_Channel2_3: ttsDMAISR,
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
You can find this code slightly complicated but for now there is no simpler UART driver in STM32 HAL (simple polling driver will be probably useful in some cases). The _usart.Driver_ is efficient driver that uses DMA and interrupts to ofload the CPU.
|
||||
|
||||
STM32 USART peripheral provides traditional UART and its synchronous version. To use it as output we have to connect its Tx signal to the right GPIO pin:
|
||||
|
||||
```
|
||||
tx.Setup(&gpio.Config{Mode: gpio.Alt})
|
||||
tx.SetAltFunc(gpio.USART1_AF1)
|
||||
|
||||
```
|
||||
|
||||
The _usart.Driver_ is configured in Tx-only mode (rxdma and rxbuf are set to nil):
|
||||
|
||||
```
|
||||
tts = usart.NewDriver(usart.USART1, d.Channel(2, 0), nil, nil)
|
||||
|
||||
```
|
||||
|
||||
We use its _WriteString_ method to print the famous sentence. Let’s clean everything and compile this program:
|
||||
|
||||
```
|
||||
$ cd $HOME/emgo
|
||||
$ ./clean.sh
|
||||
$ cd $HOME/firstemgo
|
||||
$ egc
|
||||
$ arm-none-eabi-size cortexm0.elf
|
||||
text data bss dec hex filename
|
||||
12728 236 176 13140 3354 cortexm0.elf
|
||||
|
||||
```
|
||||
|
||||
To see something you need an UART peripheral in your PC.
|
||||
|
||||
**Do not use RS232 port or USB to RS232 converter!**
|
||||
|
||||
The STM32 family uses 3.3 V logic but RS232 can produce from -15 V to +15 V which will probably demage your MCU. You need USB to UART converter that uses 3.3 V logic. Popular converters are based on FT232 or CP2102 chips.
|
||||
|
||||
![UART](https://ziutek.github.io/images/mcu/f030-demo-board/uart.jpg)
|
||||
|
||||
You also need some terminal emulator program (I prefer [picocom][9]). Flash the new image, run the terminal emulator and press the reset button a few times:
|
||||
|
||||
```
|
||||
$ openocd -d0 -f interface/stlink.cfg -f target/stm32f0x.cfg -c 'init; program cortexm0.elf; reset run; exit'
|
||||
Open On-Chip Debugger 0.10.0+dev-00319-g8f1f912a (2018-03-07-19:20)
|
||||
Licensed under GNU GPL v2
|
||||
For bug reports, read
|
||||
http://openocd.org/doc/doxygen/bugs.html
|
||||
debug_level: 0
|
||||
adapter speed: 1000 kHz
|
||||
adapter_nsrst_delay: 100
|
||||
none separate
|
||||
adapter speed: 950 kHz
|
||||
target halted due to debug-request, current mode: Thread
|
||||
xPSR: 0xc1000000 pc: 0x080016f4 msp: 0x20000a20
|
||||
adapter speed: 4000 kHz
|
||||
** Programming Started **
|
||||
auto erase enabled
|
||||
target halted due to breakpoint, current mode: Thread
|
||||
xPSR: 0x61000000 pc: 0x2000003a msp: 0x20000a20
|
||||
wrote 13312 bytes from file cortexm0.elf in 1.020185s (12.743 KiB/s)
|
||||
** Programming Finished **
|
||||
adapter speed: 950 kHz
|
||||
$
|
||||
$ picocom -b 115200 /dev/ttyUSB0
|
||||
picocom v3.1
|
||||
|
||||
port is : /dev/ttyUSB0
|
||||
flowcontrol : none
|
||||
baudrate is : 115200
|
||||
parity is : none
|
||||
databits are : 8
|
||||
stopbits are : 1
|
||||
escape is : C-a
|
||||
local echo is : no
|
||||
noinit is : no
|
||||
noreset is : no
|
||||
hangup is : no
|
||||
nolock is : no
|
||||
send_cmd is : sz -vv
|
||||
receive_cmd is : rz -vv -E
|
||||
imap is :
|
||||
omap is :
|
||||
emap is : crcrlf,delbs,
|
||||
logfile is : none
|
||||
initstring : none
|
||||
exit_after is : not set
|
||||
exit is : no
|
||||
|
||||
Type [C-a] [C-h] to see available commands
|
||||
Terminal ready
|
||||
Hello, World!
|
||||
Hello, World!
|
||||
Hello, World!
|
||||
|
||||
```
|
||||
|
||||
Every press of the reset button produces new “Hello, World!” line. Everything works as expected.
|
||||
|
||||
To see bi-directional UART code for this MCU check out [this example][10].
|
||||
|
||||
### io.Writer
|
||||
|
||||
The _io.Writer_ interface is probably the second most commonly used interface type in Go, right after the _error_ interface. Its definition looks like this:
|
||||
|
||||
```
|
||||
type Writer interface {
|
||||
Write(p []byte) (n int, err error)
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
_usart.Driver_ implements _io.Writer_ so we can replace:
|
||||
|
||||
```
|
||||
tts.WriteString("Hello, World!\r\n")
|
||||
|
||||
```
|
||||
|
||||
with
|
||||
|
||||
```
|
||||
io.WriteString(tts, "Hello, World!\r\n")
|
||||
|
||||
```
|
||||
|
||||
Additionally you need to add the _io_ package to the _import_ section.
|
||||
|
||||
The declaration of _io.WriteString_ function looks as follows:
|
||||
|
||||
```
|
||||
func WriteString(w Writer, s string) (n int, err error)
|
||||
|
||||
```
|
||||
|
||||
As you can see, the _io.WriteString_ allows to write strings using any type that implements _io.Writer_ interface. Internally it check does the underlying type has _WriteString_ method and uses it instead of _Write_ if available.
|
||||
|
||||
Let’s compile the modified program:
|
||||
|
||||
```
|
||||
$ egc
|
||||
$ arm-none-eabi-size cortexm0.elf
|
||||
text data bss dec hex filename
|
||||
15456 320 248 16024 3e98 cortexm0.elf
|
||||
|
||||
```
|
||||
|
||||
As you can see, _io.WriteString_ causes a significant increase in the size of the binary: 15776 - 12964 = 2812 bytes. There isn’t too much space left on the Flash. What caused such a drastic increase in size?
|
||||
|
||||
Using the command:
|
||||
|
||||
```
|
||||
arm-none-eabi-nm --print-size --size-sort --radix=d cortexm0.elf
|
||||
|
||||
```
|
||||
|
||||
we can print all symbols ordered by its size for both cases. By filtering and analyzing the obtained data (awk, diff) we can find about 80 new symbols. The ten largest are:
|
||||
|
||||
```
|
||||
> 00000062 T stm32$hal$usart$Driver$DisableRx
|
||||
> 00000072 T stm32$hal$usart$Driver$RxDMAISR
|
||||
> 00000076 T internal$Type$Implements
|
||||
> 00000080 T stm32$hal$usart$Driver$EnableRx
|
||||
> 00000084 t errors$New
|
||||
> 00000096 R $8$stm32$hal$usart$Driver$$
|
||||
> 00000100 T stm32$hal$usart$Error$Error
|
||||
> 00000360 T io$WriteString
|
||||
> 00000660 T stm32$hal$usart$Driver$Read
|
||||
|
||||
```
|
||||
|
||||
So, even though we don’t use the _usart.Driver.Read_ method it was compiled in, same as _DisableRx_ , _RxDMAISR_ , _EnableRx_ and other not mentioned above. Unfortunately, if you assign something to the interface, its full method set is required (with all dependences). This isn’t a problem for a large programs that use most of the methods anyway. But for our simple one it’s a huge burden.
|
||||
|
||||
We’re already close to the limits of our MCU but let’s try to print some numbers (you need to replace _io_ package with _strconv_ in _import_ section):
|
||||
|
||||
```
|
||||
func main() {
|
||||
a := 12
|
||||
b := -123
|
||||
|
||||
tts.WriteString("a = ")
|
||||
strconv.WriteInt(tts, a, 10, 0, 0)
|
||||
tts.WriteString("\r\n")
|
||||
tts.WriteString("b = ")
|
||||
strconv.WriteInt(tts, b, 10, 0, 0)
|
||||
tts.WriteString("\r\n")
|
||||
|
||||
tts.WriteString("hex(a) = ")
|
||||
strconv.WriteInt(tts, a, 16, 0, 0)
|
||||
tts.WriteString("\r\n")
|
||||
tts.WriteString("hex(b) = ")
|
||||
strconv.WriteInt(tts, b, 16, 0, 0)
|
||||
tts.WriteString("\r\n")
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
As in the case of _io.WriteString_ function, the first argument of the _strconv.WriteInt_ is of type _io.Writer_ .
|
||||
|
||||
```
|
||||
$ egc
|
||||
/usr/local/arm/bin/arm-none-eabi-ld: /home/michal/firstemgo/cortexm0.elf section `.rodata' will not fit in region `Flash'
|
||||
/usr/local/arm/bin/arm-none-eabi-ld: region `Flash' overflowed by 692 bytes
|
||||
exit status 1
|
||||
|
||||
```
|
||||
|
||||
This time we’ve run out of space. Let’s try to slim down the information about types:
|
||||
|
||||
```
|
||||
$ cd $HOME/emgo
|
||||
$ ./clean.sh
|
||||
$ cd $HOME/firstemgo
|
||||
$ egc -nf -nt
|
||||
$ arm-none-eabi-size cortexm0.elf
|
||||
text data bss dec hex filename
|
||||
15876 316 320 16512 4080 cortexm0.elf
|
||||
|
||||
```
|
||||
|
||||
It was close, but we fit. Let’s load and run this code:
|
||||
|
||||
```
|
||||
a = 12
|
||||
b = -123
|
||||
hex(a) = c
|
||||
hex(b) = -7b
|
||||
|
||||
```
|
||||
|
||||
The _strconv_ package in Emgo is quite different from its archetype in Go. It is intended for direct use to write formatted numbers and in many cases can replace heavy _fmt_ package. That’s why the function names start with _Write_ instead of _Format_ and have additional two parameters. Below is an example of their use:
|
||||
|
||||
```
|
||||
func main() {
|
||||
b := -123
|
||||
strconv.WriteInt(tts, b, 10, 0, 0)
|
||||
tts.WriteString("\r\n")
|
||||
strconv.WriteInt(tts, b, 10, 6, ' ')
|
||||
tts.WriteString("\r\n")
|
||||
strconv.WriteInt(tts, b, 10, 6, '0')
|
||||
tts.WriteString("\r\n")
|
||||
strconv.WriteInt(tts, b, 10, 6, '.')
|
||||
tts.WriteString("\r\n")
|
||||
strconv.WriteInt(tts, b, 10, -6, ' ')
|
||||
tts.WriteString("\r\n")
|
||||
strconv.WriteInt(tts, b, 10, -6, '0')
|
||||
tts.WriteString("\r\n")
|
||||
strconv.WriteInt(tts, b, 10, -6, '.')
|
||||
tts.WriteString("\r\n")
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
There is its output:
|
||||
|
||||
```
|
||||
-123
|
||||
-123
|
||||
-00123
|
||||
..-123
|
||||
-123
|
||||
-123
|
||||
-123..
|
||||
|
||||
```
|
||||
|
||||
### Unix streams and Morse code
|
||||
|
||||
Thanks to the fact that most of the functions that write something use _io.Writer_ instead of concrete type (eg. _FILE_ in C) we get a functionality similar to _Unix streams_ . In Unix we can easily combine simple commands to perform larger tasks. For example, we can write text to the file this way:
|
||||
|
||||
```
|
||||
echo "Hello, World!" > file.txt
|
||||
|
||||
```
|
||||
|
||||
The `>` operator writes the output stream of the preceding command to the file. There is also `|`operator that connects output and input streams of adjacent commands.
|
||||
|
||||
Thanks to the streams we can easily convert/filter output of any command. For example, to convert all letters to uppercase we can filter the echo’s output through _tr_ command:
|
||||
|
||||
```
|
||||
echo "Hello, World!" | tr a-z A-Z > file.txt
|
||||
|
||||
```
|
||||
|
||||
To show the analogy between _io.Writer_ and Unix streams let’s write our:
|
||||
|
||||
```
|
||||
io.WriteString(tts, "Hello, World!\r\n")
|
||||
|
||||
```
|
||||
|
||||
in the following pseudo-unix form:
|
||||
|
||||
```
|
||||
io.WriteString "Hello, World!" | usart.Driver usart.USART1
|
||||
|
||||
```
|
||||
|
||||
The next example will show how to do this:
|
||||
|
||||
```
|
||||
io.WriteString "Hello, World!" | MorseWriter | usart.Driver usart.USART1
|
||||
|
||||
```
|
||||
|
||||
Let’s create a simple encoder that encodes the text written to it using Morse coding:
|
||||
|
||||
```
|
||||
type MorseWriter struct {
|
||||
W io.Writer
|
||||
}
|
||||
|
||||
func (w *MorseWriter) Write(s []byte) (int, error) {
|
||||
var buf [8]byte
|
||||
for n, c := range s {
|
||||
switch {
|
||||
case c == '\n':
|
||||
c = ' ' // Replace new lines with spaces.
|
||||
case 'a' <= c && c <= 'z':
|
||||
c -= 'a' - 'A' // Convert to upper case.
|
||||
}
|
||||
if c < ' ' || 'Z' < c {
|
||||
continue // c is outside ASCII [' ', 'Z']
|
||||
}
|
||||
var symbol morseSymbol
|
||||
if c == ' ' {
|
||||
symbol.length = 1
|
||||
buf[0] = ' '
|
||||
} else {
|
||||
symbol = morseSymbols[c-'!']
|
||||
for i := uint(0); i < uint(symbol.length); i++ {
|
||||
if (symbol.code>>i)&1 != 0 {
|
||||
buf[i] = '-'
|
||||
} else {
|
||||
buf[i] = '.'
|
||||
}
|
||||
}
|
||||
}
|
||||
buf[symbol.length] = ' '
|
||||
if _, err := w.W.Write(buf[:symbol.length+1]); err != nil {
|
||||
return n, err
|
||||
}
|
||||
}
|
||||
return len(s), nil
|
||||
}
|
||||
|
||||
type morseSymbol struct {
|
||||
code, length byte
|
||||
}
|
||||
|
||||
//emgo:const
|
||||
var morseSymbols = [...]morseSymbol{
|
||||
{1<<0 | 1<<1 | 1<<2, 4}, // ! ---.
|
||||
{1<<1 | 1<<4, 6}, // " .-..-.
|
||||
{}, // #
|
||||
{1<<3 | 1<<6, 7}, // $ ...-..-
|
||||
|
||||
// Some code omitted...
|
||||
|
||||
{1<<0 | 1<<3, 4}, // X -..-
|
||||
{1<<0 | 1<<2 | 1<<3, 4}, // Y -.--
|
||||
{1<<0 | 1<<1, 4}, // Z --..
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
You can find the full _morseSymbols_ array [here][11]. The `//emgo:const` directive ensures that _morseSymbols_ array won’t be copied to the RAM.
|
||||
|
||||
Now we can print our sentence in two ways:
|
||||
|
||||
```
|
||||
func main() {
|
||||
s := "Hello, World!\r\n"
|
||||
mw := &MorseWriter{tts}
|
||||
|
||||
io.WriteString(tts, s)
|
||||
io.WriteString(mw, s)
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
We use the pointer to the _MorseWriter_ `&MorseWriter{tts}` instead os simple `MorseWriter{tts}` value beacuse the _MorseWriter_ is to big to fit into an interface variable.
|
||||
|
||||
Emgo, unlike Go, doesn’t dynamically allocate memory for value stored in interface variable. The interface type has limited size, equal to the size of three pointers (to fit _slice_ ) or two _float64_ (to fit _complex128_ ), what is bigger. It can directly store values of all basic types and small structs/arrays but for bigger values you must use pointers.
|
||||
|
||||
Let’s compile this code and see its output:
|
||||
|
||||
```
|
||||
$ egc
|
||||
$ arm-none-eabi-size cortexm0.elf
|
||||
text data bss dec hex filename
|
||||
15152 324 248 15724 3d6c cortexm0.elf
|
||||
|
||||
```
|
||||
|
||||
```
|
||||
Hello, World!
|
||||
.... . .-.. .-.. --- --..-- .-- --- .-. .-.. -.. ---.
|
||||
|
||||
```
|
||||
|
||||
### The Ultimate Blinky
|
||||
|
||||
The _Blinky_ is hardware equivalent of _Hello, World!_ program. Once we have a Morse encoder we can easly combine both to obtain the _Ultimate Blinky_ program:
|
||||
|
||||
```
|
||||
package main
|
||||
|
||||
import (
|
||||
"delay"
|
||||
"io"
|
||||
|
||||
"stm32/hal/gpio"
|
||||
"stm32/hal/system"
|
||||
"stm32/hal/system/timer/systick"
|
||||
)
|
||||
|
||||
var led gpio.Pin
|
||||
|
||||
func init() {
|
||||
system.SetupPLL(8, 1, 48/8)
|
||||
systick.Setup(2e6)
|
||||
|
||||
gpio.A.EnableClock(false)
|
||||
led = gpio.A.Pin(4)
|
||||
|
||||
cfg := gpio.Config{Mode: gpio.Out, Driver: gpio.OpenDrain, Speed: gpio.Low}
|
||||
led.Setup(&cfg)
|
||||
}
|
||||
|
||||
type Telegraph struct {
|
||||
Pin gpio.Pin
|
||||
Dotms int // Dot length [ms]
|
||||
}
|
||||
|
||||
func (t Telegraph) Write(s []byte) (int, error) {
|
||||
for _, c := range s {
|
||||
switch c {
|
||||
case '.':
|
||||
t.Pin.Clear()
|
||||
delay.Millisec(t.Dotms)
|
||||
t.Pin.Set()
|
||||
delay.Millisec(t.Dotms)
|
||||
case '-':
|
||||
t.Pin.Clear()
|
||||
delay.Millisec(3 * t.Dotms)
|
||||
t.Pin.Set()
|
||||
delay.Millisec(t.Dotms)
|
||||
case ' ':
|
||||
delay.Millisec(3 * t.Dotms)
|
||||
}
|
||||
}
|
||||
return len(s), nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
telegraph := &MorseWriter{Telegraph{led, 100}}
|
||||
for {
|
||||
io.WriteString(telegraph, "Hello, World! ")
|
||||
}
|
||||
}
|
||||
|
||||
// Some code omitted...
|
||||
|
||||
```
|
||||
|
||||
In the above example I omitted the definition of _MorseWriter_ type because it was shown earlier. The full version is available [here][12]. Let’s compile it and run:
|
||||
|
||||
```
|
||||
$ egc
|
||||
$ arm-none-eabi-size cortexm0.elf
|
||||
text data bss dec hex filename
|
||||
11772 244 244 12260 2fe4 cortexm0.elf
|
||||
|
||||
```
|
||||
|
||||
![Ultimate Blinky](https://ziutek.github.io/images/mcu/f030-demo-board/morse.png)
|
||||
|
||||
### Reflection
|
||||
|
||||
Yes, Emgo supports [reflection][13]. The _reflect_ package isn’t complete yet but that what is done is enough to implement _fmt.Print_ family of functions. Let’s see what can we do on our small MCU.
|
||||
|
||||
To reduce memory usage we will use [semihosting][14] as standard output. For convenience, we also write simple _println_ function which to some extent mimics _fmt.Println_ .
|
||||
|
||||
```
|
||||
package main
|
||||
|
||||
import (
|
||||
"debug/semihosting"
|
||||
"reflect"
|
||||
"strconv"
|
||||
|
||||
"stm32/hal/system"
|
||||
"stm32/hal/system/timer/systick"
|
||||
)
|
||||
|
||||
var stdout semihosting.File
|
||||
|
||||
func init() {
|
||||
system.SetupPLL(8, 1, 48/8)
|
||||
systick.Setup(2e6)
|
||||
|
||||
var err error
|
||||
stdout, err = semihosting.OpenFile(":tt", semihosting.W)
|
||||
for err != nil {
|
||||
}
|
||||
}
|
||||
|
||||
type stringer interface {
|
||||
String() string
|
||||
}
|
||||
|
||||
func println(args ...interface{}) {
|
||||
for i, a := range args {
|
||||
if i > 0 {
|
||||
stdout.WriteString(" ")
|
||||
}
|
||||
switch v := a.(type) {
|
||||
case string:
|
||||
stdout.WriteString(v)
|
||||
case int:
|
||||
strconv.WriteInt(stdout, v, 10, 0, 0)
|
||||
case bool:
|
||||
strconv.WriteBool(stdout, v, 't', 0, 0)
|
||||
case stringer:
|
||||
stdout.WriteString(v.String())
|
||||
default:
|
||||
stdout.WriteString("%unknown")
|
||||
}
|
||||
}
|
||||
stdout.WriteString("\r\n")
|
||||
}
|
||||
|
||||
type S struct {
|
||||
A int
|
||||
B bool
|
||||
}
|
||||
|
||||
func main() {
|
||||
p := &S{-123, true}
|
||||
|
||||
v := reflect.ValueOf(p)
|
||||
|
||||
println("kind(p) =", v.Kind())
|
||||
println("kind(*p) =", v.Elem().Kind())
|
||||
println("type(*p) =", v.Elem().Type())
|
||||
|
||||
v = v.Elem()
|
||||
|
||||
println("*p = {")
|
||||
for i := 0; i < v.NumField(); i++ {
|
||||
ft := v.Type().Field(i)
|
||||
fv := v.Field(i)
|
||||
println(" ", ft.Name(), ":", fv.Interface())
|
||||
}
|
||||
println("}")
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
The _semihosting.OpenFile_ function allows to open/create file on the host side. The special path _:tt_ corresponds to host’s standard output.
|
||||
|
||||
The _println_ function accepts arbitrary number of arguments, each of arbitrary type:
|
||||
|
||||
```
|
||||
func println(args ...interface{})
|
||||
|
||||
```
|
||||
|
||||
It’s possible because any type implements the empty interface _interface{}_ . The _println_ uses [type switch][15] to print strings, integers and booleans:
|
||||
|
||||
```
|
||||
switch v := a.(type) {
|
||||
case string:
|
||||
stdout.WriteString(v)
|
||||
case int:
|
||||
strconv.WriteInt(stdout, v, 10, 0, 0)
|
||||
case bool:
|
||||
strconv.WriteBool(stdout, v, 't', 0, 0)
|
||||
case stringer:
|
||||
stdout.WriteString(v.String())
|
||||
default:
|
||||
stdout.WriteString("%unknown")
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
Additionally it supports any type that implements _stringer_ interface, that is, any type that has _String()_ method. In any _case_ clause the _v_ variable has the right type, same as listed after _case_ keyword.
|
||||
|
||||
The `reflect.ValueOf(p)` returns _p_ in the form that allows to analyze its type and content programmatically. As you can see, we can even dereference pointers using `v.Elem()` and print all struct fields with their names.
|
||||
|
||||
Let’s try to compile this code. For now let’s see what will come out if compiled without type and field names:
|
||||
|
||||
```
|
||||
$ egc -nt -nf
|
||||
$ arm-none-eabi-size cortexm0.elf
|
||||
text data bss dec hex filename
|
||||
16028 216 312 16556 40ac cortexm0.elf
|
||||
|
||||
```
|
||||
|
||||
Only 140 free bytes left on the Flash. Let’s load it using OpenOCD with semihosting enabled:
|
||||
|
||||
```
|
||||
$ openocd -d0 -f interface/stlink.cfg -f target/stm32f0x.cfg -c 'init; program cortexm0.elf; arm semihosting enable; reset run'
|
||||
Open On-Chip Debugger 0.10.0+dev-00319-g8f1f912a (2018-03-07-19:20)
|
||||
Licensed under GNU GPL v2
|
||||
For bug reports, read
|
||||
http://openocd.org/doc/doxygen/bugs.html
|
||||
debug_level: 0
|
||||
adapter speed: 1000 kHz
|
||||
adapter_nsrst_delay: 100
|
||||
none separate
|
||||
adapter speed: 950 kHz
|
||||
target halted due to debug-request, current mode: Thread
|
||||
xPSR: 0xc1000000 pc: 0x08002338 msp: 0x20000a20
|
||||
adapter speed: 4000 kHz
|
||||
** Programming Started **
|
||||
auto erase enabled
|
||||
target halted due to breakpoint, current mode: Thread
|
||||
xPSR: 0x61000000 pc: 0x2000003a msp: 0x20000a20
|
||||
wrote 16384 bytes from file cortexm0.elf in 0.700133s (22.853 KiB/s)
|
||||
** Programming Finished **
|
||||
semihosting is enabled
|
||||
adapter speed: 950 kHz
|
||||
kind(p) = ptr
|
||||
kind(*p) = struct
|
||||
type(*p) =
|
||||
*p = {
|
||||
X. : -123
|
||||
X. : true
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
If you’ve actually run this code, you noticed that semihosting is slow, especially if you write a byte after byte (buffering helps).
|
||||
|
||||
As you can see, there is no type name for `*p` and all struct fields have the same _X._ name. Let’s compile this program again, this time without _-nt -nf_ options:
|
||||
|
||||
```
|
||||
$ egc
|
||||
$ arm-none-eabi-size cortexm0.elf
|
||||
text data bss dec hex filename
|
||||
16052 216 312 16580 40c4 cortexm0.elf
|
||||
|
||||
```
|
||||
|
||||
Now the type and field names have been included but only these defined in ~~_main.go_ file~~ _main_ package. The output of our program looks as follows:
|
||||
|
||||
```
|
||||
kind(p) = ptr
|
||||
kind(*p) = struct
|
||||
type(*p) = S
|
||||
*p = {
|
||||
A : -123
|
||||
B : true
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
Reflection is a crucial part of any easy to use serialization library and serialization ~~algorithms~~ like [JSON][16]gain in importance in the IOT era.
|
||||
|
||||
This is where I finish the second part of this article. I think there is a chance for the third part, more entertaining, where we connect to this board various interesting devices. If this board won’t carry them, we replace it with something a little bigger.
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
via: https://ziutek.github.io/2018/04/14/go_on_very_small_hardware2.html
|
||||
|
||||
作者:[Michał Derkacz ][a]
|
||||
译者:[译者ID](https://github.com/译者ID)
|
||||
校对:[校对者ID](https://github.com/校对者ID)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]:https://ziutek.github.io/
|
||||
[1]:https://ziutek.github.io/2018/04/14/go_on_very_small_hardware2.html
|
||||
[2]:https://ziutek.github.io/2018/03/30/go_on_very_small_hardware.html
|
||||
[3]:https://golang.org/doc/effective_go.html#interfaces
|
||||
[4]:https://research.swtch.com/interfaces
|
||||
[5]:https://blog.golang.org/laws-of-reflection
|
||||
[6]:https://github.com/texane/stlink
|
||||
[7]:http://www.world-semi.com/solution/list-4-1.html
|
||||
[8]:https://en.wikipedia.org/wiki/1-Wire
|
||||
[9]:https://github.com/npat-efault/picocom
|
||||
[10]:https://github.com/ziutek/emgo/blob/master/egpath/src/stm32/examples/f030-demo-board/usart/main.go
|
||||
[11]:https://github.com/ziutek/emgo/blob/master/egpath/src/stm32/examples/f030-demo-board/morseuart/main.go
|
||||
[12]:https://github.com/ziutek/emgo/blob/master/egpath/src/stm32/examples/f030-demo-board/morseled/main.go
|
||||
[13]:https://blog.golang.org/laws-of-reflection
|
||||
[14]:http://infocenter.arm.com/help/topic/com.arm.doc.dui0471g/Bgbjjgij.html
|
||||
[15]:https://golang.org/doc/effective_go.html#type_switch
|
||||
[16]:https://en.wikipedia.org/wiki/JSON
|
@ -1,284 +0,0 @@
|
||||
[#]: collector: (lujun9972)
|
||||
[#]: translator: (robsean)
|
||||
[#]: reviewer: ( )
|
||||
[#]: publisher: ( )
|
||||
[#]: url: ( )
|
||||
[#]: subject: (Learn the basics of programming with C)
|
||||
[#]: via: (https://opensource.com/article/20/8/c-programming-cheat-sheet)
|
||||
[#]: author: (Seth Kenlon https://opensource.com/users/seth)
|
||||
|
||||
Learn the basics of programming with C
|
||||
======
|
||||
Our new cheat sheet puts all the essentials of C syntax on an
|
||||
easy-to-read handout.
|
||||
![Cheat Sheet cover image][1]
|
||||
|
||||
In 1972, Dennis Ritchie was at Bell Labs, where a few years earlier, he and his fellow team members invented Unix. After creating an enduring OS (still in use today), he needed a good way to program those Unix computers so that they could perform new tasks. It seems strange now, but at the time, there were relatively few programming languages; Fortran, Lisp, [Algol][2], and B were popular but insufficient for what the Bell Labs researchers wanted to do. Demonstrating a trait that would become known as a primary characteristic of programmers, Dennis Ritchie created his own solution. He called it C, and nearly 50 years later, it's still in widespread use.
|
||||
|
||||
### Why you should learn C
|
||||
|
||||
Today, there are many languages that provide programmers more features than C. The most obvious one is C++, a rather blatantly named language that built upon C to create a nice object-oriented language. There are many others, though, and there's a good reason they exist. Computers are good at consistent repetition, so anything predictable enough to be built into a language means less work for programmers. Why spend two lines recasting an `int` to a `long` in C when one line of C++ (`long x = long(n);`) can do the same?
|
||||
|
||||
And yet C is still useful today.
|
||||
|
||||
First of all, C is a fairly minimal and straightforward language. There aren't very advanced concepts beyond the basics of programming, largely because C is literally one of the foundations of modern programming languages. For instance, C features arrays, but it doesn't offer a dictionary (unless you write it yourself). When you learn C, you learn the building blocks of programming that can help you recognize the improved and elaborate designs of recent languages.
|
||||
|
||||
Because C is a minimal language, your applications are likely to get a boost in performance that they wouldn't see with many other languages. It's easy to get caught up in the race to the bottom when you're thinking about how fast your code executes, so it's important to ask whether you _need_ more speed for a specific task. And with C, you have less to obsess over in each line of code, compared to, say, Python or Java. C is fast. There's a good reason the Linux kernel is written in C.
|
||||
|
||||
Finally, C is easy to get started with, especially if you're running Linux. You can already run C code because Linux systems include the GNU C library (`glibc`). To write and build it, all you need to do is install a compiler, open a text editor, and start coding.
|
||||
|
||||
### Getting started with C
|
||||
|
||||
If you're running Linux, you can install a C compiler using your package manager. On Fedora or RHEL:
|
||||
|
||||
|
||||
```
|
||||
`$ sudo dnf install gcc`
|
||||
```
|
||||
|
||||
On Debian and similar:
|
||||
|
||||
|
||||
```
|
||||
`$ sudo apt install build-essential`
|
||||
```
|
||||
|
||||
On macOS, you can [install Homebrew][3] and use it to install [GCC][4]:
|
||||
|
||||
|
||||
```
|
||||
`$ brew install gcc`
|
||||
```
|
||||
|
||||
On Windows, you can install a minimal set of GNU utilities, GCC included, with [MinGW][5].
|
||||
|
||||
Verify you've installed GCC on Linux or macOS:
|
||||
|
||||
|
||||
```
|
||||
$ gcc --version
|
||||
gcc (GCC) x.y.z
|
||||
Copyright (C) 20XX Free Software Foundation, Inc.
|
||||
```
|
||||
|
||||
On Windows, provide the full path to the EXE file:
|
||||
|
||||
|
||||
```
|
||||
PS> C:\MinGW\bin\gcc.exe --version
|
||||
gcc.exe (MinGW.org GCC Build-2) x.y.z
|
||||
Copyright (C) 20XX Free Software Foundation, Inc.
|
||||
```
|
||||
|
||||
### C syntax
|
||||
|
||||
C isn't a scripting language. It's compiled, meaning that it gets processed by a C compiler to produce a binary executable file. This is different from a scripting language like [Bash][6] or a hybrid language like [Python][7].
|
||||
|
||||
In C, you create _functions_ to carry out your desired task. A function named `main` is executed by default.
|
||||
|
||||
Here's a simple "hello world" program written in C:
|
||||
|
||||
|
||||
```
|
||||
#include <stdio.h>
|
||||
|
||||
int main() {
|
||||
[printf][8]("Hello world");
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
The first line includes a _header file_, essentially free and very low-level C code that you can reuse in your own programs, called `stdio.h` (standard input and output). A function called `main` is created and populated with a rudimentary print statement. Save this text to a file called `hello.c`, then compile it with GCC:
|
||||
|
||||
|
||||
```
|
||||
`$ gcc hello.c --output hello`
|
||||
```
|
||||
|
||||
Try running your C program:
|
||||
|
||||
|
||||
```
|
||||
$ ./hello
|
||||
Hello world$
|
||||
```
|
||||
|
||||
#### Return values
|
||||
|
||||
It's part of the Unix philosophy that a function "returns" something to you after it executes: nothing upon success and something else (an error message, for example) upon failure. These return codes are often represented with numbers (integers, to be precise): 0 represents nothing, and any number higher than 0 represents some non-successful state.
|
||||
|
||||
There's a good reason Unix and Linux are designed to expect silence upon success. It's so that you can always plan for success by assuming no errors nor warnings will get in your way when executing a series of commands. Similarly, functions in C expect no errors by design.
|
||||
|
||||
You can see this for yourself with one small modification to make your program appear to fail:
|
||||
|
||||
|
||||
```
|
||||
include <stdio.h>
|
||||
|
||||
int main() {
|
||||
[printf][8]("Hello world");
|
||||
return 1;
|
||||
}
|
||||
```
|
||||
|
||||
Compile it:
|
||||
|
||||
|
||||
```
|
||||
`$ gcc hello.c --output failer`
|
||||
```
|
||||
|
||||
Now run it using a built-in Linux test for success. The `&&` operator executes the second half of a command only upon success. For example:
|
||||
|
||||
|
||||
```
|
||||
$ echo "success" && echo "it worked"
|
||||
success
|
||||
it worked
|
||||
```
|
||||
|
||||
The `||` test executes the second half of a command upon _failure_.
|
||||
|
||||
|
||||
```
|
||||
$ ls blah || echo "it did not work"
|
||||
ls: cannot access 'blah': No such file or directory
|
||||
it did not work
|
||||
```
|
||||
|
||||
Now try your program, which does _not_ return 0 upon success; it returns 1 instead:
|
||||
|
||||
|
||||
```
|
||||
$ ./failer && echo "it worked"
|
||||
String is: hello
|
||||
```
|
||||
|
||||
The program executed successfully, yet did not trigger the second command.
|
||||
|
||||
#### Variables and types
|
||||
|
||||
In some languages, you can create variables without specifying what _type_ of data they contain. Those languages have been designed such that the interpreter runs some tests against a variable in an attempt to discover what kind of data it contains. For instance, Python knows that `var=1` defines an integer when you create an expression that adds `var` to something that is obviously an integer. It similarly knows that the word `world` is a string when you concatenate `hello` and `world`.
|
||||
|
||||
C doesn't do any of these investigations for you; you must define your variable type. There are several types of variables, including integers (int), characters (char), float, and Boolean.
|
||||
|
||||
You may also notice there's no string type. Unlike Python and Java and Lua and many others, C doesn't have a string type and instead sees strings as an array of characters.
|
||||
|
||||
Here's some simple code that establishes a `char` array variable, and then prints it to your screen using [printf][9] along with a short message:
|
||||
|
||||
|
||||
```
|
||||
#include <stdio.h>
|
||||
|
||||
int main() {
|
||||
char var[6] = "hello";
|
||||
[printf][8]("Your string is: %s\r\n",var);
|
||||
```
|
||||
|
||||
You may notice that this code sample allows six characters for a five-letter word. This is because there's a hidden terminator at the end of the string, which takes up one byte in the array. You can run the code by compiling and executing it:
|
||||
|
||||
|
||||
```
|
||||
$ gcc hello.c --output hello
|
||||
$ ./hello
|
||||
hello
|
||||
```
|
||||
|
||||
### Functions
|
||||
|
||||
As with other languages, C functions take optional parameters. You can pass parameters from one function to another by defining the type of data you want a function to accept:
|
||||
|
||||
|
||||
```
|
||||
#include <stdio.h>
|
||||
|
||||
int printmsg(char a[]) {
|
||||
[printf][8]("String is: %s\r\n",a);
|
||||
}
|
||||
|
||||
int main() {
|
||||
char a[6] = "hello";
|
||||
printmsg(a);
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
The way this code sample breaks one function into two isn't very useful, but it demonstrates that `main` runs by default and how to pass data between functions.
|
||||
|
||||
### Conditionals
|
||||
|
||||
In real-world programming, you usually want your code to make decisions based on data. This is done with _conditional_ statements, and the `if` statement is one of the most basic of them.
|
||||
|
||||
To make this example program more dynamic, you can include the `string.h` header file, which contains code to examine (as the name implies) strings. Try testing whether the string passed to the `printmsg` function is greater than 0 by using the `strlen` function from the `string.h` file:
|
||||
|
||||
|
||||
```
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
int printmsg(char a[]) {
|
||||
size_t len = [strlen][10](a);
|
||||
if ( len > 0) {
|
||||
[printf][8]("String is: %s\r\n",a);
|
||||
}
|
||||
}
|
||||
|
||||
int main() {
|
||||
char a[6] = "hello";
|
||||
printmsg(a);
|
||||
return 1;
|
||||
}
|
||||
```
|
||||
|
||||
As implemented in this example, the sample condition will never be untrue because the string provided is always "hello," the length of which is always greater than 0. The final touch to this humble re-implementation of the `echo` command is to accept input from the user.
|
||||
|
||||
### Command arguments
|
||||
|
||||
The `stdio.h` file contains code that provides two arguments each time a program is launched: a count of how many items are contained in the command (`argc`) and an array containing each item (`argv`). For example, suppose you issue this imaginary command:
|
||||
|
||||
|
||||
```
|
||||
`$ foo -i bar`
|
||||
```
|
||||
|
||||
The `argc` is three, and the contents of `argv` are:
|
||||
|
||||
* `argv[0] = foo`
|
||||
* `argv[1] = -i`
|
||||
* `argv[2] = bar`
|
||||
|
||||
|
||||
|
||||
Can you modify the example C program to accept `argv[2]` as the string instead of defaulting to `hello`?
|
||||
|
||||
### Imperative programming
|
||||
|
||||
C is an imperative programming language. It isn't object-oriented, and it has no class structure. Using C can teach you a lot about how data is processed and how to better manage the data you generate as your code runs. Use C enough, and you'll eventually be able to write libraries that other languages, such as Python and Lua, can use.
|
||||
|
||||
To learn more about C, you need to use it. Look in `/usr/include/` for useful C header files, and see what small tasks you can do to make C useful to you. As you learn, use our [C cheat sheet][11] by [Jim Hall][12] of FreeDOS. It's got all the basics on one double-sided sheet, so you can immediately access all the essentials of C syntax while you practice.
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
via: https://opensource.com/article/20/8/c-programming-cheat-sheet
|
||||
|
||||
作者:[Seth Kenlon][a]
|
||||
选题:[lujun9972][b]
|
||||
译者:[译者ID](https://github.com/译者ID)
|
||||
校对:[校对者ID](https://github.com/校对者ID)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]: https://opensource.com/users/seth
|
||||
[b]: https://github.com/lujun9972
|
||||
[1]: https://opensource.com/sites/default/files/styles/image-full-size/public/lead-images/coverimage_cheat_sheet.png?itok=lYkNKieP (Cheat Sheet cover image)
|
||||
[2]: https://opensource.com/article/20/6/algol68
|
||||
[3]: https://opensource.com/article/20/6/homebrew-mac
|
||||
[4]: https://gcc.gnu.org/
|
||||
[5]: https://opensource.com/article/20/8/gnu-windows-mingw
|
||||
[6]: https://opensource.com/resources/what-bash
|
||||
[7]: https://opensource.com/resources/python
|
||||
[8]: http://www.opengroup.org/onlinepubs/009695399/functions/printf.html
|
||||
[9]: https://opensource.com/article/20/8/printf
|
||||
[10]: http://www.opengroup.org/onlinepubs/009695399/functions/strlen.html
|
||||
[11]: https://opensource.com/downloads/c-programming-cheat-sheet
|
||||
[12]: https://opensource.com/users/jim-hall
|
@ -1,5 +1,5 @@
|
||||
[#]: collector: (lujun9972)
|
||||
[#]: translator: ( )
|
||||
[#]: translator: (HankChow)
|
||||
[#]: reviewer: ( )
|
||||
[#]: publisher: ( )
|
||||
[#]: url: ( )
|
||||
|
@ -1,110 +0,0 @@
|
||||
[#]: collector: (lujun9972)
|
||||
[#]: translator: ( )
|
||||
[#]: reviewer: ( )
|
||||
[#]: publisher: ( )
|
||||
[#]: url: ( )
|
||||
[#]: subject: (Linux Jargon Buster: What is a Package Manager in Linux? How Does it Work?)
|
||||
[#]: via: (https://itsfoss.com/package-manager/)
|
||||
[#]: author: (Abhishek Prakash https://itsfoss.com/author/abhishek/)
|
||||
|
||||
Linux Jargon Buster: What is a Package Manager in Linux? How Does it Work?
|
||||
======
|
||||
|
||||
One of the main points [how Linux distributions differ from each other][1] is the package management. In this part of the Linux jargon buster series, you’ll learn about packaging and package managers in Linux. You’ll learn what are packages, what are package managers and how do they work and what kind of package managers available.
|
||||
|
||||
### What is a package manager in Linux?
|
||||
|
||||
In simpler words, a package manager is a tool that allows users to install, remove, upgrade, configure and manage software packages on an operating system. The package manager can be a graphical application like a software center or a command line tool like [apt-get][2] or [pacman][3].
|
||||
|
||||
You’ll often find me using the term ‘package’ in tutorials and articles on It’s FOSS. To understand package manager, you must understand what a package is.
|
||||
|
||||
### What is a package?
|
||||
|
||||
A package is usually referred to an application but it could be a GUI application, command line tool or a software library (required by other software programs). A package is essentially an archive file containing the binary executable, configuration file and sometimes information about the dependencies.
|
||||
|
||||
In older days, [software used to installed from its source code][4]. You would refer to a file (usually named readme) and see what software components it needs, location of binaries. A configure script or makefile is often included. You will have to compile the software or on your own along with handling all the dependencies (some software require installation of other software) on your own.
|
||||
|
||||
To get rid of this complexity, Linux distributions created their own packaging format to provide the end users ready-to-use binary files (precompiled software) for installing software along with some [metadata][5] (version number, description) and dependencies.
|
||||
|
||||
It is like baking a cake versus buying a cake.
|
||||
|
||||
![][6]
|
||||
|
||||
Around mid 90s, Debian created .deb or DEB packaging format and Red Hat Linux created .rpm or RPM (short for Red Hat Package Manager) packaging system. Compiling source code still exists but it is optional now.
|
||||
|
||||
To interact with or use the packaging systems, you need a package manager.
|
||||
|
||||
### How does the package manager work?
|
||||
|
||||
Please keep in mind that package manager is a generic concept and it’s not exclusive to Linux. You’ll often find package manager for different software or programming languages. There is [PIP package manager just for Python packages][7]. Even [Atom editor has its own package manager][8].
|
||||
|
||||
Since the focus in this article is on Linux, I’ll take things from Linux’s perspective. However, most of the explanation here could be applied to package manager in general as well.
|
||||
|
||||
I have created this diagram (based on SUSE Wiki) so that you can easily understand how a package manager works.
|
||||
|
||||
![][9]
|
||||
|
||||
Almost all Linux distributions have software repositories which is basically collection of software packages. Yes, there could be more than one repository. The repositories contain software packages of different kind.
|
||||
|
||||
Repositories also have metadata files that contain information about the packages such as the name of the package, version number, description of package and the repository name etc. This is what you see if you use the [apt show command][10] in Ubuntu/Debian.
|
||||
|
||||
Your system’s package manager first interacts with the metadata. The package manager creates a local cache of metadata on your system. When you run the update option of the package manager (for example apt update), it updates this local cache of metadata by referring to metadata from the repository.
|
||||
|
||||
When you run the installation command of your package manager (for example apt install package_name), the package manager refers to this cache. If it finds the package information in the cache, it uses the internet connection to connect to the appropriate repository and downloads the package first before installing on your system.
|
||||
|
||||
A package may have dependencies. Meaning that it may require other packages to be installed. The package manager often takes care of the dependencies and installs it automatically along with the package you are installing.
|
||||
|
||||
![Package Manager Handling Dependencies In Linux][11]
|
||||
|
||||
Similarly, when you remove a package using the package manager, it either automatically removes or informs you that your system has unused packages that can be cleaned.
|
||||
|
||||
Apart from the obvious tasks of installing, removing, you can use the package manager to configure the packages and manage them as per your need. For example, you can [prevent the upgrade of a package version][12] from the regular system updates. There are many more things your package manager might be capable of.
|
||||
|
||||
### Different kinds of package managers
|
||||
|
||||
Package Managers differ based on packaging system but same packaging system may have more than one package manager.
|
||||
|
||||
For example, RPM has [Yum][13] and [DNF][14] package managers. For DEB, you have apt-get, [aptitude][15] command line based package managers.
|
||||
|
||||
![Synaptic package manager][16]
|
||||
|
||||
Package managers are not necessarily command line based. You have graphical package managing tools like [Synaptic][17]. Your distribution’s software center is also a package manager even if it runs apt-get or DNF underneath.
|
||||
|
||||
### Conclusion
|
||||
|
||||
I don’t want to go in further detail on this topic because I can go on and on. But it will deviate from the objective of the topic which is to give you a basic understanding of package manager in Linux.
|
||||
|
||||
I have omitted the new universal packaging formats like Snap and Flatpak for now.
|
||||
|
||||
I do hope that you have a bit better understanding of the package management system in Linux. If you are still confused or if you have some questions on this topic, please use the comment system. I’ll try to answer your questions and if required, update this article with new points.
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
via: https://itsfoss.com/package-manager/
|
||||
|
||||
作者:[Abhishek Prakash][a]
|
||||
选题:[lujun9972][b]
|
||||
译者:[译者ID](https://github.com/译者ID)
|
||||
校对:[校对者ID](https://github.com/校对者ID)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]: https://itsfoss.com/author/abhishek/
|
||||
[b]: https://github.com/lujun9972
|
||||
[1]: https://itsfoss.com/what-is-linux/
|
||||
[2]: https://itsfoss.com/apt-vs-apt-get-difference/
|
||||
[3]: https://itsfoss.com/pacman-command/
|
||||
[4]: https://itsfoss.com/install-software-from-source-code/
|
||||
[5]: https://www.computerhope.com/jargon/m/metadata.htm
|
||||
[6]: https://i1.wp.com/itsfoss.com/wp-content/uploads/2020/10/source-code-comilation-vs-packaging.png?resize=800%2C450&ssl=1
|
||||
[7]: https://itsfoss.com/install-pip-ubuntu/
|
||||
[8]: https://itsfoss.com/install-packages-in-atom/
|
||||
[9]: https://i2.wp.com/itsfoss.com/wp-content/uploads/2020/10/linux-package-manager-explanation.png?resize=800%2C450&ssl=1
|
||||
[10]: https://itsfoss.com/apt-search-command/
|
||||
[11]: https://i1.wp.com/itsfoss.com/wp-content/uploads/2020/10/package-manager-handling-dependencies-in-linux.png?resize=800%2C450&ssl=1
|
||||
[12]: https://itsfoss.com/prevent-package-update-ubuntu/
|
||||
[13]: https://fedoraproject.org/wiki/Yum
|
||||
[14]: https://fedoraproject.org/wiki/DNF
|
||||
[15]: https://wiki.debian.org/Aptitude
|
||||
[16]: https://i0.wp.com/itsfoss.com/wp-content/uploads/2020/06/see-packages-by-repositories-synaptic.png?resize=799%2C548&ssl=1
|
||||
[17]: https://itsfoss.com/synaptic-package-manager/
|
@ -1,5 +1,5 @@
|
||||
[#]: collector: (lujun9972)
|
||||
[#]: translator: ( )
|
||||
[#]: translator: (gxlct008)
|
||||
[#]: reviewer: ( )
|
||||
[#]: publisher: ( )
|
||||
[#]: url: ( )
|
||||
|
@ -1,134 +0,0 @@
|
||||
[#]: collector: (lujun9972)
|
||||
[#]: translator: ( )
|
||||
[#]: reviewer: ( )
|
||||
[#]: publisher: ( )
|
||||
[#]: url: ( )
|
||||
[#]: subject: (6 Essential Things To Do After Installing Manjaro Linux)
|
||||
[#]: via: (https://itsfoss.com/things-to-do-after-installing-manjaro/)
|
||||
[#]: author: (Dimitrios Savvopoulos https://itsfoss.com/author/dimitrios/)
|
||||
|
||||
6 Essential Things To Do After Installing Manjaro Linux
|
||||
======
|
||||
|
||||
So, you just did a [fresh installation of Manjaro Linux][1]. Now what?
|
||||
|
||||
Here are a few essential post installation steps I recommend you to follow.
|
||||
|
||||
Quite honestly, these are the things I prefer to do after installing Manjaro. Yours could differ depending on your need.
|
||||
|
||||
### Recommended Things To Do After Installing Manjaro Linux
|
||||
|
||||
![][2]
|
||||
|
||||
I am using Manjaro Xfce edition but the steps are applicable to other desktop variants of [Manjaro][3] as well.
|
||||
|
||||
#### 1\. Set the fastest mirror
|
||||
|
||||
Before even updating your system, I suggest to sort out your mirror list first. When refreshing the Manjaro system and downloading software from repositories, an optimized mirror list can have noticeable performance impact to the system.
|
||||
|
||||
Open the Terminal emulator and type the following command:
|
||||
|
||||
```
|
||||
sudo pacman-mirrors --fasttrack
|
||||
```
|
||||
|
||||
![][4]
|
||||
|
||||
#### 2\. Update your system
|
||||
|
||||
Keeping your system up-to-date reduces the chances of security vulnerabilities. Refreshing your system repository is also a recommended thing to do before installing new software.
|
||||
|
||||
You can [update your Manjaro system][5] by running the following command.
|
||||
|
||||
```
|
||||
sudo pacman -Syu
|
||||
```
|
||||
|
||||
![][6]
|
||||
|
||||
#### 3\. Enable AUR, Snap or Flatpak support
|
||||
|
||||
[Arch User Repository (AUR)][7] is one of the main reasons that a user chooses an [Arch-based system][8]. It gives you access to a huge number of additional software.
|
||||
|
||||
Optionally, you can also enable support for [Snaps][9] and [Flatpaks][10] directly from Pamac GUI package manager.
|
||||
|
||||
![][11]
|
||||
|
||||
#### 4\. Enable TRIM (SSD only)
|
||||
|
||||
If your root partition has been installed on SSD, enabling [TRIM][12] is one thing you need to do after installing Manjaro. TRIM helps to clean blocks in your SSD and extend the lifespan of your SSD.
|
||||
|
||||
To enable TRIM on Manjaro, run the following command in a terminal:
|
||||
|
||||
```
|
||||
sudo systemctl enable fstrim.timer
|
||||
```
|
||||
|
||||
![][13]
|
||||
|
||||
#### 5\. Installing a kernel of your choice (advanced users)
|
||||
|
||||
One of the topics that I covered in my [Manjaro Linux review][14], is how easily you can switch kernels through a graphical interface.
|
||||
|
||||
Do you prefer to use the command line? You can list the installed kernel(s) on your system and install a kernel using your terminal.
|
||||
|
||||
To list the installed kernels:
|
||||
|
||||
```
|
||||
mhwd-kernel -li
|
||||
```
|
||||
|
||||
**To install a new kernel** (the latest to date 5.8 kernel for example)**:**
|
||||
|
||||
```
|
||||
sudo mhwd-kernel -i linux58
|
||||
```
|
||||
|
||||
![][15]
|
||||
|
||||
#### 6\. Install Microsoft true type fonts (if you need it)
|
||||
|
||||
I have to often edit the work documents on my personal computer and hence I need the Microsoft fonts like Times New Roman or Arial.
|
||||
|
||||
If you also need to use Microsoft fonts, you can access the [package][16] from [AUR][7]. If you want to use the command line for AUR packages, you can install an [AUR helper][17].
|
||||
|
||||
![][18]
|
||||
|
||||
#### Conclusion
|
||||
|
||||
[Manjaro is a great distribution][19] if you want to use the benefits of Arch Linux on a pre-configured, desktop optimized distribution. Though it comes pre-configured with many essentials, there are a few steps that cannot be done in advance, as everyone has a different setup and different needs.
|
||||
|
||||
Please let us know in the comments below, which step apart from the already mentioned is the essential for you.
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
via: https://itsfoss.com/things-to-do-after-installing-manjaro/
|
||||
|
||||
作者:[Dimitrios Savvopoulos][a]
|
||||
选题:[lujun9972][b]
|
||||
译者:[译者ID](https://github.com/译者ID)
|
||||
校对:[校对者ID](https://github.com/校对者ID)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]: https://itsfoss.com/author/dimitrios/
|
||||
[b]: https://github.com/lujun9972
|
||||
[1]: https://itsfoss.com/install-manjaro-linux/
|
||||
[2]: https://i0.wp.com/itsfoss.com/wp-content/uploads/2020/10/things-to-do-after-installing-manjaro.jpg?resize=800%2C450&ssl=1
|
||||
[3]: https://manjaro.org
|
||||
[4]: https://i0.wp.com/itsfoss.com/wp-content/uploads/2020/08/manjaro-fasttrack.png?resize=800%2C600&ssl=1
|
||||
[5]: https://itsfoss.com/update-arch-linux/
|
||||
[6]: https://i1.wp.com/itsfoss.com/wp-content/uploads/2020/03/sudo-pacman-Syu.png?resize=800%2C504&ssl=1
|
||||
[7]: https://itsfoss.com/aur-arch-linux/
|
||||
[8]: https://itsfoss.com/arch-based-linux-distros/
|
||||
[9]: https://itsfoss.com/use-snap-packages-ubuntu-16-04/
|
||||
[10]: https://itsfoss.com/flatpak-guide/
|
||||
[11]: https://i2.wp.com/itsfoss.com/wp-content/uploads/2020/08/pamac-2.png?resize=800%2C600&ssl=1
|
||||
[12]: https://en.wikipedia.org/wiki/Trim_(computing)
|
||||
[13]: https://i0.wp.com/itsfoss.com/wp-content/uploads/2020/08/fstrim.timer_.png?resize=800%2C600&ssl=1
|
||||
[14]: https://itsfoss.com/manjaro-linux-review/
|
||||
[15]: https://i2.wp.com/itsfoss.com/wp-content/uploads/2020/08/manjaro-cli-kernels.png?resize=800%2C600&ssl=1
|
||||
[16]: https://aur.archlinux.org/packages/ttf-ms-fonts
|
||||
[17]: https://itsfoss.com/best-aur-helpers/
|
||||
[18]: https://i1.wp.com/itsfoss.com/wp-content/uploads/2020/08/ttf-ms-fonts.png?resize=800%2C600&ssl=1
|
||||
[19]: https://itsfoss.com/why-use-manjaro-linux/
|
@ -0,0 +1,114 @@
|
||||
[#]: collector: (lujun9972)
|
||||
[#]: translator: ( )
|
||||
[#]: reviewer: ( )
|
||||
[#]: publisher: ( )
|
||||
[#]: url: ( )
|
||||
[#]: subject: (Robust and Race-free Server Logging using Named Pipes)
|
||||
[#]: via: (https://theartofmachinery.com/2020/10/10/logging_with_named_pipes.html)
|
||||
[#]: author: (Simon Arneaud https://theartofmachinery.com)
|
||||
|
||||
Robust and Race-free Server Logging using Named Pipes
|
||||
======
|
||||
|
||||
If you do any server administration work, you’ll have worked with log files. And if your servers need to be reliable, you’ll know that log files are common source of problems, especially when you need to rotate or ship them (which is practically always). In particular, moving files around causes race conditions.
|
||||
|
||||
Thankfully, there are better ways. With named pipes, you can have a simple and robust logging framework, with no race conditions, and without patching your servers to support some network logging protocol.
|
||||
|
||||
### The problems with rotating log files
|
||||
|
||||
First, let’s talk about the problems. Race conditions are generally a problem with popular file-based logging setups, whether you’re rotating logs into archival storage, or shipping them to a remote log processing stack, or whatever. To keep things concrete, though, let me talk about [logrotate][1], just because it’s a popular tool.
|
||||
|
||||
Say you have a log file at `/var/log/foo`. It gets pretty big, and you want to process the logs periodically and start with a new, empty file. So you (or your distro maintainers) set up logrotate with various rules about when to rotate the file.
|
||||
|
||||
By default, logrotate will rename the file (to something like `/var/log/foo.1`) and create a new `/var/log/foo` to write to. That (mostly) works for software that runs intermittently (such as a package manager that does software updates). But it won’t do any good if the log file is generated by a long-running server. The server only uses the filename when it opens the file; after that it just keeps writing to its open file descriptor. That means it will keep writing to the old file (now named `/var/log/foo.1`), and the new `/var/log/foo` file will stay empty.
|
||||
|
||||
To handle this use-case, logrotate supports another mode: `copytruncate`. In this mode, instead of renaming, logrotate will copy the contents of `/var/log/foo` to an archival file, and then truncate the original file to zero length. As long as the server has the log file open in append mode, it will automatically write new logs to the start of the file, without needing to detect the truncation and do a file seek (the kernel handles that).
|
||||
|
||||
That `copytruncate` mode creates a race condition, though. Any log lines that are written after the copy but before the truncation will get destroyed. Actually, you tend to get the same race condition even with the default move-and-create mode. That’s because there’s not much point just splitting up the logs into multiple files. Most systems are configured to do something like compress the old log file, but ultimately you need to delete the old, uncompressed data, which creates the same race as truncating. (In practice, this race isn’t so likely for occasional log writers, like package managers, and the `delay` flag to logrotate makes it rarer, albeit by making the log handling a bit more complicated.)
|
||||
|
||||
Some servers, like [Nginx][2], support a modification of the default logrotate mode:
|
||||
|
||||
1. Rename the old file
|
||||
2. Create the new file
|
||||
3. (New step) notify the server that it needs to reopen its log file.
|
||||
|
||||
|
||||
|
||||
This works (as long as the logs processor doesn’t delete the old file before the server has finished reopening), but it requires special support from the server, and you’re out of luck with most software. There’s a lot of software out there, and log file handling just isn’t interesting enough to get high on the to-do list. This approach also only works for long-running servers.
|
||||
|
||||
I think this is a good point to stop and take a step back. Having multiple processes juggle log files around on disk without any synchronisation is just an inherently painful way to do things. It causes bugs and makes logging stacks complicated ([here’s just one of many examples][3]). One alternative is to use some network protocol like MQTT or networked syslog, but, realistically, most servers won’t support the one you want. And they shouldn’t have to — log files are a great interface for log writers.
|
||||
|
||||
That’s okay because *nix “everything is a file” lets us easily get a file interface on the writer side, with a streaming interface on the reader side.
|
||||
|
||||
### Named pipes 101
|
||||
|
||||
Maybe you’ve seen pipes in pipelines like this:
|
||||
|
||||
```
|
||||
$ sort user_log.txt | uniq
|
||||
```
|
||||
|
||||
The pipe connecting `sort` and `uniq` is a temporary, anonymous communication channel that `sort` writes to and `uniq` reads from. Named pipes are less common, but they’re also communication channels. The only difference is that they persist on the filesystem as if they were files.
|
||||
|
||||
Open up a terminal and `cd` into some temporary working directory. The following creates a named pipe and uses `cat` to open a writer:
|
||||
|
||||
```
|
||||
$ mkfifo p
|
||||
$ # This cat command will sit waiting for input
|
||||
$ cat > p
|
||||
```
|
||||
|
||||
Leave that `cat` command waiting, and open up another terminal in the same directory. In this terminal, start your reader:
|
||||
|
||||
```
|
||||
$ # This will sit waiting for data to come over the pipe
|
||||
$ cat p
|
||||
```
|
||||
|
||||
Now as you type things into the writer end, you’ll see them appear in the reader end. `cat` will use line buffering in interactive mode, so data will get transferred every time you start a new line.
|
||||
|
||||
`cat` doesn’t have to know anything about pipes for this to work — the pipe acts like a file as long as you just naïvely read or write to it. But if you check, you’ll see the data isn’t stored anywhere. You can pump gigabytes through a pipe without filling up any disk space. Once the data has been read once, it’s lost. (You can have multiple readers, but only one will receive any buffer-load of data.)
|
||||
|
||||
Another thing that makes pipes useful for communication is their buffering and blocking. You can start writing before any readers open the pipe, and data gets temporarily buffered inside the kernel until a reader comes along. If the reader starts first, its read will block, waiting for data from the writer. (The writer will also block if the pipe buffer gets full.) If you try the two-terminal experiment again with a regular file, you’ll see that the reader `cat` will eagerly read all the data it can and then exit.
|
||||
|
||||
### An annoying problem and a simple solution
|
||||
|
||||
Maybe you’re seeing how named pipes can help with logging: Servers can write to log “files” that are actually named pipes, and a logging stack can read log data directly from the named pipe without letting a single line fall onto the floor. You do whatever you want with the logs, without any racey juggling of files on disk.
|
||||
|
||||
There’s one annoying problem: the writer doesn’t need a reader to start writing, but if a reader opens the pipe and then closes it, the writer gets a `SIGPIPE` (“broken pipe”), which will kill it by default. (Try killing the reader `cat` while typing things into the writer to see what I mean.) Similarly, a reader can read without a writer, but if a writer opens the pipe and then closes it, that will be treated like an end of file. Although the named pipe persists on disk, it isn’t a stable communication channel if log writers and log readers can restart (as they will on a real server).
|
||||
|
||||
There’s a solution that’s a bit weird but very simple. Multiple processes can open the pipe for reading and writing, and the pipe will only close when _all_ readers or _all_ writers close it. All we need for a stable logging pipe is a daemon that holds the named pipe open for both reading and writing, without doing any actual reading or writing. I set this up on my personal server last year, and I wrote [a tiny, zero-config program to act as my pipe-holding daemon][4]. It just opens every file in its current working directory for both reading and writing. I run it from a directory that has symbolic links to every named pipe in my logging stack. The program runs in a loop that ends in a `wait()` for a `SIGHUP`. If I ever update the symlinks in the directory, I give the daemon a `kill -HUP` and it reopens them all. Sure, it could do its own directory watching, but the `SIGHUP` approach is simple and predictable, and the whole thing works reliably. Thanks to the pipe buffer, log writers and log readers can be shut down and restarted independently, any time, without breakage.
|
||||
|
||||
My server uses the [s6 supervision suite][5] to manage daemons, so I have s6-log reading from each logging pipe. The bottom part of the [s6-log documentation page][6] has some good insights into the problems with popular logging systems, and good ideas about better ways to do things.
|
||||
|
||||
### Imagine: a world without log rotation
|
||||
|
||||
Strictly speaking, named pipes aren’t necessary for race-free logs processing. The s6 suite encourages writing logs to some file descriptor (like standard error), and letting the supervision suite make sure those file descriptors point to something useful. However, the named pipe approach adds a few benefits:
|
||||
|
||||
* It doesn’t require any co-ordination between writer and reader
|
||||
* It integrates nicely with the software we have today
|
||||
* It gives things meaningful names (rather than `/dev/fd/4`)
|
||||
|
||||
|
||||
|
||||
I’ve worked with companies that spend about as much on their logging stacks as on their serving infrastructure, and, no, “we do logs processing” isn’t in their business models. Of course, log rotation and log shipping aren’t the only problems to blame, but it feels so wrong that we’ve made logs so complicated. If you work on any logging system, consider if you really need to juggle log files around. You could be helping to make the world a better place.
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
via: https://theartofmachinery.com/2020/10/10/logging_with_named_pipes.html
|
||||
|
||||
作者:[Simon Arneaud][a]
|
||||
选题:[lujun9972][b]
|
||||
译者:[译者ID](https://github.com/译者ID)
|
||||
校对:[校对者ID](https://github.com/校对者ID)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]: https://theartofmachinery.com
|
||||
[b]: https://github.com/lujun9972
|
||||
[1]: https://github.com/logrotate/logrotate
|
||||
[2]: https://www.nginx.com/resources/wiki/start/topics/examples/logrotation/
|
||||
[3]: https://community.splunk.com/t5/Getting-Data-In/Why-copytruncate-logrotate-does-not-play-well-with-splunk/td-p/196112
|
||||
[4]: https://gitlab.com/sarneaud/fileopenerd
|
||||
[5]: http://www.skarnet.org/software/s6/index.html
|
||||
[6]: http://www.skarnet.org/software/s6/s6-log.html
|
@ -0,0 +1,103 @@
|
||||
[#]: collector: (lujun9972)
|
||||
[#]: translator: ( )
|
||||
[#]: reviewer: ( )
|
||||
[#]: publisher: ( )
|
||||
[#]: url: ( )
|
||||
[#]: subject: (Linux Jargon Buster: What is Display Manager in Linux?)
|
||||
[#]: via: (https://itsfoss.com/display-manager/)
|
||||
[#]: author: (Abhishek Prakash https://itsfoss.com/author/abhishek/)
|
||||
|
||||
Linux Jargon Buster: What is Display Manager in Linux?
|
||||
======
|
||||
|
||||
_**In this chapter of the Linux Jargon Buster, you’ll learn about display manager in Linux. Is it part of the desktop environment? What does it do?**_
|
||||
|
||||
### What is display manager in Linux?
|
||||
|
||||
In simple words, a display manager is a program that provides graphical login capabilities for your Linux distribution. It controls the user sessions and manages user authentication. Display manager starts the [display server][1] and loads the [desktop environment][2] right after you enter your username and password.
|
||||
|
||||
The display manager is often synonymous to the login screen. It is the visible part of it after all. However, the visible login screen, also called greeter, is only a part of the display manager.
|
||||
|
||||
![Login screen is the visible part of a display manager][3]
|
||||
|
||||
Like [various desktop environments][4] and display servers, there are various display managers available as well. Let’s have a look at them.
|
||||
|
||||
### Different display managers
|
||||
|
||||
Some people think of the display manager as part of the desktop environment but that’s not true. It is a separate program.
|
||||
|
||||
A desktop environment may recommend a certain display manager but it doesn’t mean that it won’t work with some other display manager. If you ever installed more than one desktop environment in the same system, you would remember that a login screen (i.e. the display manager) allows you to switch the desktop environment.
|
||||
|
||||
![A display manager can be used with various desktop environments][5]
|
||||
|
||||
Though display manager is not part of the desktop environment itself, it is often developed by the same development team as the desktop environment. It also becomes identity of the desktop environment.
|
||||
|
||||
For example, the GNOME desktop environment develops GDM (GNOME Display Manager) and just by looking at the login screen, you would think of GNOME desktop environment.
|
||||
|
||||
![GNOME Login Screen with GDM][6]
|
||||
|
||||
Some popular display managers are:
|
||||
|
||||
* GDM ([GNOME Display Manager][7]): preferred by GNOME
|
||||
* [SDDM][8] (Simple Desktop Display Manager): preferred by KDE
|
||||
* LightDM: Developed by Ubuntu for Unity desktop
|
||||
|
||||
|
||||
|
||||
### Display managers can be customized
|
||||
|
||||
There are so many desktop environments available. Do they all have their own display managers? No. That’s not the case.
|
||||
|
||||
As I mentioned previously, the visible login screen is called greeter. This greeter can be customized to change the looks of the login screen.
|
||||
|
||||
In fact, many distributions and/or desktop environments have written their own greeter to give users a login screen that resembles their brand.
|
||||
|
||||
For example, Mint’s Cinnamon desktop uses LightDM but has its own greeter to give it more Minty (or should I say Cinnamon) looks.
|
||||
|
||||
![Linux Mint login screen based on LightDM][9]
|
||||
|
||||
Take a look at Kali Linux’s login screen:
|
||||
|
||||
![Kali Linux Login Screen][10]
|
||||
|
||||
If you are into coding and tweaking, you may modify or code your own greeter as per your liking.
|
||||
|
||||
### Changing display manager
|
||||
|
||||
You may [change the display manager][11] if you want. You need to install the display manager first. You’ll see the option to switch the display manager while installing.
|
||||
|
||||
![][12]
|
||||
|
||||
If you didn’t do it at that time, then you can change the display manager by manually configuring it later. The method to reconfigure the display manager is slightly different for different distributions and not in the scope of this article.
|
||||
|
||||
**Conclusion**
|
||||
|
||||
I hope you have a slight better understanding of the term display manager in Linux. The aim of this jargon buster series is to explain common Linux colloquial and technical terms in non-technical language without going into too much detail.
|
||||
|
||||
I welcome your comments and suggestion.
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
via: https://itsfoss.com/display-manager/
|
||||
|
||||
作者:[Abhishek Prakash][a]
|
||||
选题:[lujun9972][b]
|
||||
译者:[译者ID](https://github.com/译者ID)
|
||||
校对:[校对者ID](https://github.com/校对者ID)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]: https://itsfoss.com/author/abhishek/
|
||||
[b]: https://github.com/lujun9972
|
||||
[1]: https://itsfoss.com/display-server/
|
||||
[2]: https://itsfoss.com/what-is-desktop-environment/
|
||||
[3]: https://i0.wp.com/itsfoss.com/wp-content/uploads/2018/05/login-screen-opensuse.jpg?resize=800%2C474&ssl=1
|
||||
[4]: https://itsfoss.com/best-linux-desktop-environments/
|
||||
[5]: https://i1.wp.com/itsfoss.com/wp-content/uploads/2020/10/deepin-session-ubuntu.jpg?resize=800%2C414&ssl=1
|
||||
[6]: https://i2.wp.com/itsfoss.com/wp-content/uploads/2020/06/Login-screen-1.png?resize=800%2C450&ssl=1
|
||||
[7]: https://wiki.gnome.org/Projects/GDM
|
||||
[8]: https://github.com/sddm
|
||||
[9]: https://i2.wp.com/itsfoss.com/wp-content/uploads/2020/10/linux-mint-login-screen.jpg?resize=800%2C418&ssl=1
|
||||
[10]: https://i0.wp.com/itsfoss.com/wp-content/uploads/2020/10/kali-linux-login-screen.jpg?resize=799%2C450&ssl=1
|
||||
[11]: https://itsfoss.com/switch-gdm-and-lightdm-in-ubuntu-14-04/
|
||||
[12]: https://i2.wp.com/itsfoss.com/wp-content/uploads/2014/06/Switch_between_gdm_and_lightgdm_Ubuntu.jpeg?resize=700%2C448&ssl=1
|
947
translated/tech/20180414 Go on very small hardware Part 2.md
Normal file
947
translated/tech/20180414 Go on very small hardware Part 2.md
Normal file
@ -0,0 +1,947 @@
|
||||
[#]: collector: (oska874)
|
||||
[#]: translator: (gxlct008)
|
||||
[#]: reviewer: ( )
|
||||
[#]: publisher: ( )
|
||||
[#]: url: ( )
|
||||
[#]: subject: (Go on very small hardware Part 2)
|
||||
[#]: via: (https://ziutek.github.io/2018/04/14/go_on_very_small_hardware2.html)
|
||||
[#]: author: (Michał Derkacz https://ziutek.github.io/)
|
||||
|
||||
Go 语言在极小硬件上的运用(二)
|
||||
============================================================
|
||||
|
||||
|
||||
[![STM32F030F4P6](https://ziutek.github.io/images/mcu/f030-demo-board/board.jpg)][1]
|
||||
|
||||
|
||||
在本文的 [第一部分][2] 的结尾,我承诺要写关于 _interfaces_ 的内容。我不想在这里写有关接口的完整甚至简短的讲义。相反,我将展示一个简单的示例,来说明如何定义和使用接口,以及如何利用无处不在的 _io.Writer_ 接口。还有一些关于 _reflection_ 和 _semihosting_ 的内容。
|
||||
|
||||
接口是 Go 语言的重要组成部分。如果您想了解更多有关它们的信息,我建议您阅读 [Effective Go][3] 和 [Russ Cox 的文章][4]。
|
||||
|
||||
### 并发 Blinky – 回顾
|
||||
|
||||
当您阅读前面示例的代码时,您可能会注意到一个违反直觉的方式来打开或关闭 LED。 _Set_ 方法用于关闭 LED,_Clear_ 方法用于打开 LED。这是由于在 <ruby>漏极开路配置<rt>open-drain configuration</rt></ruby> 下驱动了 LED。我们可以做些什么来减少代码的混乱? 让我们用 _On_ 和 _Off_ 方法来定义 _LED_ 类型:
|
||||
|
||||
```
|
||||
type LED struct {
|
||||
pin gpio.Pin
|
||||
}
|
||||
|
||||
func (led LED) On() {
|
||||
led.pin.Clear()
|
||||
}
|
||||
|
||||
func (led LED) Off() {
|
||||
led.pin.Set()
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
现在我们可以简单地调用 `led.On()` 和 `led.Off()`,这不会再引起任何疑惑了。
|
||||
|
||||
|
||||
在前面的所有示例中,我都尝试使用相同的 <ruby>漏极开路配置<rt>open-drain configuration</rt></ruby>来 避免代码复杂化。但是在最后一个示例中,对于我来说,将第三个 LED 连接到 GND 和 PA3 引脚之间并将 PA3 配置为<ruby>推挽模式<rt>push-pull mode</rt></ruby>会更容易。下一个示例将使用以此方式连接的 LED。
|
||||
|
||||
但是我们的新 _LED_ 类型不支持推挽配置。实际上,我们应该将其称为 _OpenDrainLED_,并定义另一个类型 _PushPullLED_:
|
||||
|
||||
```
|
||||
type PushPullLED struct {
|
||||
pin gpio.Pin
|
||||
}
|
||||
|
||||
func (led PushPullLED) On() {
|
||||
led.pin.Set()
|
||||
}
|
||||
|
||||
func (led PushPullLED) Off() {
|
||||
led.pin.Clear()
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
请注意,这两种类型都具有相同的方法,它们的工作方式也相同。如果在 LED 上运行的代码可以同时使用这两种类型,而不必注意当前使用的是哪种类型,那就太好了。 _interface type_ 可以提供帮助:
|
||||
|
||||
```
|
||||
package main
|
||||
|
||||
import (
|
||||
"delay"
|
||||
|
||||
"stm32/hal/gpio"
|
||||
"stm32/hal/system"
|
||||
"stm32/hal/system/timer/systick"
|
||||
)
|
||||
|
||||
type LED interface {
|
||||
On()
|
||||
Off()
|
||||
}
|
||||
|
||||
type PushPullLED struct{ pin gpio.Pin }
|
||||
|
||||
func (led PushPullLED) On() {
|
||||
led.pin.Set()
|
||||
}
|
||||
|
||||
func (led PushPullLED) Off() {
|
||||
led.pin.Clear()
|
||||
}
|
||||
|
||||
func MakePushPullLED(pin gpio.Pin) PushPullLED {
|
||||
pin.Setup(&gpio.Config{Mode: gpio.Out, Driver: gpio.PushPull})
|
||||
return PushPullLED{pin}
|
||||
}
|
||||
|
||||
type OpenDrainLED struct{ pin gpio.Pin }
|
||||
|
||||
func (led OpenDrainLED) On() {
|
||||
led.pin.Clear()
|
||||
}
|
||||
|
||||
func (led OpenDrainLED) Off() {
|
||||
led.pin.Set()
|
||||
}
|
||||
|
||||
func MakeOpenDrainLED(pin gpio.Pin) OpenDrainLED {
|
||||
pin.Setup(&gpio.Config{Mode: gpio.Out, Driver: gpio.OpenDrain})
|
||||
return OpenDrainLED{pin}
|
||||
}
|
||||
|
||||
var led1, led2 LED
|
||||
|
||||
func init() {
|
||||
system.SetupPLL(8, 1, 48/8)
|
||||
systick.Setup(2e6)
|
||||
|
||||
gpio.A.EnableClock(false)
|
||||
led1 = MakeOpenDrainLED(gpio.A.Pin(4))
|
||||
led2 = MakePushPullLED(gpio.A.Pin(3))
|
||||
}
|
||||
|
||||
func blinky(led LED, period int) {
|
||||
for {
|
||||
led.On()
|
||||
delay.Millisec(100)
|
||||
led.Off()
|
||||
delay.Millisec(period - 100)
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
go blinky(led1, 500)
|
||||
blinky(led2, 1000)
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
我们定义了 _LED_ 接口,它有两个方法: _On_ 和 _Off_。 _PushPullLED_ 和 _OpenDrainLED_ 类型代表两种驱动 LED 的方式。我们还定义了两个用作构造函数的 _Make_ _*LED_ 函数。这两种类型都实现了 _LED_ 接口,因此可以将这些类型的值赋给 _LED_ 类型的变量:
|
||||
|
||||
```
|
||||
led1 = MakeOpenDrainLED(gpio.A.Pin(4))
|
||||
led2 = MakePushPullLED(gpio.A.Pin(3))
|
||||
```
|
||||
|
||||
在这种情况下,可赋值性在编译时检查。赋值后,_led1_ 变量包含一个 `OpenDrainLED{gpio.A.Pin(4)}`,以及一个指向 _OpenDainLED_ 类型的方法集的指针。 `led1.On()` 调用大致对应于以下 C 代码:
|
||||
|
||||
```
|
||||
led1.methods->On(led1.value)
|
||||
```
|
||||
|
||||
如您所见,如果仅考虑函数调用的开销,这是相当便宜的抽象。
|
||||
|
||||
|
||||
但是,对接口的任何赋值都会导致包含有关已赋值类型的大量信息。对于由许多其他类型组成的复杂类型,可能会有很多信息:
|
||||
|
||||
```
|
||||
$ egc
|
||||
$ arm-none-eabi-size cortexm0.elf
|
||||
text data bss dec hex filename
|
||||
10356 196 212 10764 2a0c cortexm0.elf
|
||||
```
|
||||
|
||||
如果我们不使用 [反射][5],可以通过避免包含类型和结构字段的名称来节省一些字节:
|
||||
|
||||
```
|
||||
$ egc -nf -nt
|
||||
$ arm-none-eabi-size cortexm0.elf
|
||||
text data bss dec hex filename
|
||||
10312 196 212 10720 29e0 cortexm0.elf
|
||||
```
|
||||
|
||||
生成的二进制文件仍然包含一些有关类型的必要信息和关于所有导出方法(带有名称)的完整信息。在运行时,主要是当您将存储在接口变量中的一个值赋值给任何其他变量时,需要此信息来检查可赋值性。
|
||||
|
||||
我们还可以通过重新编译所导入的包来删除它们的类型和字段名称:
|
||||
|
||||
```
|
||||
$ cd $HOME/emgo
|
||||
$ ./clean.sh
|
||||
$ cd $HOME/firstemgo
|
||||
$ egc -nf -nt
|
||||
$ arm-none-eabi-size cortexm0.elf
|
||||
text data bss dec hex filename
|
||||
10272 196 212 10680 29b8 cortexm0.elf
|
||||
```
|
||||
|
||||
让我们加载这个程序,看看它是否按预期工作。这一次我们将使用 [st-flash][6] 命令:
|
||||
|
||||
```
|
||||
$ arm-none-eabi-objcopy -O binary cortexm0.elf cortexm0.bin
|
||||
$ st-flash write cortexm0.bin 0x8000000
|
||||
st-flash 1.4.0-33-gd76e3c7
|
||||
2018-04-10T22:04:34 INFO usb.c: -- exit_dfu_mode
|
||||
2018-04-10T22:04:34 INFO common.c: Loading device parameters....
|
||||
2018-04-10T22:04:34 INFO common.c: Device connected is: F0 small device, id 0x10006444
|
||||
2018-04-10T22:04:34 INFO common.c: SRAM size: 0x1000 bytes (4 KiB), Flash: 0x4000 bytes (16 KiB) in pages of 1024 bytes
|
||||
2018-04-10T22:04:34 INFO common.c: Attempting to write 10468 (0x28e4) bytes to stm32 address: 134217728 (0x8000000)
|
||||
Flash page at addr: 0x08002800 erased
|
||||
2018-04-10T22:04:34 INFO common.c: Finished erasing 11 pages of 1024 (0x400) bytes
|
||||
2018-04-10T22:04:34 INFO common.c: Starting Flash write for VL/F0/F3/F1_XL core id
|
||||
2018-04-10T22:04:34 INFO flash_loader.c: Successfully loaded flash loader in sram
|
||||
11/11 pages written
|
||||
2018-04-10T22:04:35 INFO common.c: Starting verification of write complete
|
||||
2018-04-10T22:04:35 INFO common.c: Flash written and verified! jolly good!
|
||||
|
||||
```
|
||||
|
||||
我没有将 NRST 信号连接到编程器,因此无法使用 _-reset_ 选项,必须按下 reset 按钮才能运行程序。
|
||||
|
||||
![Interfaces](https://ziutek.github.io/images/mcu/f030-demo-board/interfaces.png)
|
||||
|
||||
看来,_st-flash_ 与此板配合使用有点不可靠 (通常需要重置 ST-LINK 加密狗)。此外,当前版本不会通过 SWD 发出 reset 命令 (仅使用 NRST 信号)。 软件重置是不现实的,但是它通常是有效的,缺少它会将会带来不便。对于<ruby>电路板-程序员<rt>board-programmer</rt></ruby> 组合 _OpenOCD_ 工作得更好。
|
||||
|
||||
### UART
|
||||
|
||||
UART(<ruby>通用异步收发传输器<rt>Universal Aynchronous Receiver-Transmitter</rt></ruby>)仍然是当今微控制器最重要的外设之一。它的优点是以下属性的独特组合:
|
||||
|
||||
* 相对较高的速度,
|
||||
|
||||
* 仅两条信号线(在 <ruby>半双工<rt>half-duplex</rt></ruby> 通信的情况下甚至一条),
|
||||
|
||||
* 角色对称,
|
||||
|
||||
* 关于新数据的 <ruby>同步带内信令<rt>synchronous in-band signaling</rt></ruby>(起始位),
|
||||
|
||||
* 在传输 <ruby>字<rt>words</rt></ruby> 内的精确计时。
|
||||
|
||||
|
||||
这使得最初用于传输由 7-9 位 words 组成的异步消息的 UART,也被用于有效地实现各种其他物理协议,例如被 [WS28xx LEDs][7] 或 [1-wire][8] 设备使用的协议。
|
||||
|
||||
但是,我们将以其通常的角色使用 UART:从程序中打印文本消息。
|
||||
|
||||
```
|
||||
package main
|
||||
|
||||
import (
|
||||
"io"
|
||||
"rtos"
|
||||
|
||||
"stm32/hal/dma"
|
||||
"stm32/hal/gpio"
|
||||
"stm32/hal/irq"
|
||||
"stm32/hal/system"
|
||||
"stm32/hal/system/timer/systick"
|
||||
"stm32/hal/usart"
|
||||
)
|
||||
|
||||
var tts *usart.Driver
|
||||
|
||||
func init() {
|
||||
system.SetupPLL(8, 1, 48/8)
|
||||
systick.Setup(2e6)
|
||||
|
||||
gpio.A.EnableClock(true)
|
||||
tx := gpio.A.Pin(9)
|
||||
|
||||
tx.Setup(&gpio.Config{Mode: gpio.Alt})
|
||||
tx.SetAltFunc(gpio.USART1_AF1)
|
||||
d := dma.DMA1
|
||||
d.EnableClock(true)
|
||||
tts = usart.NewDriver(usart.USART1, d.Channel(2, 0), nil, nil)
|
||||
tts.Periph().EnableClock(true)
|
||||
tts.Periph().SetBaudRate(115200)
|
||||
tts.Periph().Enable()
|
||||
tts.EnableTx()
|
||||
|
||||
rtos.IRQ(irq.USART1).Enable()
|
||||
rtos.IRQ(irq.DMA1_Channel2_3).Enable()
|
||||
}
|
||||
|
||||
func main() {
|
||||
io.WriteString(tts, "Hello, World!\r\n")
|
||||
}
|
||||
|
||||
func ttsISR() {
|
||||
tts.ISR()
|
||||
}
|
||||
|
||||
func ttsDMAISR() {
|
||||
tts.TxDMAISR()
|
||||
}
|
||||
|
||||
//c:__attribute__((section(".ISRs")))
|
||||
var ISRs = [...]func(){
|
||||
irq.USART1: ttsISR,
|
||||
irq.DMA1_Channel2_3: ttsDMAISR,
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
您会发现此代码可能有些复杂,但目前 STM32 HAL 中没有更简单的 UART 驱动程序(在某些情况下,简单的轮询驱动程序可能会很有用)。 _usart.Driver_ 是使用 DMA 和中断来卸载 CPU 的高效驱动程序。
|
||||
|
||||
STM32 USART 外设提供传统的 UART 及其同步版本。要将其用作输出,我们必须将其 Tx 信号连接到正确的 GPIO 引脚:
|
||||
|
||||
```
|
||||
tx.Setup(&gpio.Config{Mode: gpio.Alt})
|
||||
tx.SetAltFunc(gpio.USART1_AF1)
|
||||
```
|
||||
|
||||
在 Tx-only 模式下配置 _usart.Driver_ (rxdma 和 rxbuf 设置为 nil):
|
||||
|
||||
```
|
||||
tts = usart.NewDriver(usart.USART1, d.Channel(2, 0), nil, nil)
|
||||
```
|
||||
|
||||
我们使用它的 _WriteString_ 方法来打印这句名句。让我们清理所有内容并编译该程序:
|
||||
|
||||
```
|
||||
$ cd $HOME/emgo
|
||||
$ ./clean.sh
|
||||
$ cd $HOME/firstemgo
|
||||
$ egc
|
||||
$ arm-none-eabi-size cortexm0.elf
|
||||
text data bss dec hex filename
|
||||
12728 236 176 13140 3354 cortexm0.elf
|
||||
```
|
||||
|
||||
要查看某些内容,您需要在 PC 中使用 UART 外设。
|
||||
|
||||
**请勿使用 RS232 端口或 USB 转 RS232 转换器!**
|
||||
|
||||
STM32 系列使用 3.3V 逻辑,但是 RS232 可以产生 -15 V ~ +15 V 的电压,这可能会损坏您的 MCU。您需要使用 3.3 V 逻辑的 USB 转 UART 转换器。流行的转换器基于 FT232 或 CP2102 芯片。
|
||||
|
||||
![UART](https://ziutek.github.io/images/mcu/f030-demo-board/uart.jpg)
|
||||
|
||||
您还需要一些终端仿真程序 (我更喜欢 [picocom][9])。刷新新图像,运行终端仿真器,然后按几次 reset 按钮:
|
||||
|
||||
```
|
||||
$ openocd -d0 -f interface/stlink.cfg -f target/stm32f0x.cfg -c 'init; program cortexm0.elf; reset run; exit'
|
||||
Open On-Chip Debugger 0.10.0+dev-00319-g8f1f912a (2018-03-07-19:20)
|
||||
Licensed under GNU GPL v2
|
||||
For bug reports, read
|
||||
http://openocd.org/doc/doxygen/bugs.html
|
||||
debug_level: 0
|
||||
adapter speed: 1000 kHz
|
||||
adapter_nsrst_delay: 100
|
||||
none separate
|
||||
adapter speed: 950 kHz
|
||||
target halted due to debug-request, current mode: Thread
|
||||
xPSR: 0xc1000000 pc: 0x080016f4 msp: 0x20000a20
|
||||
adapter speed: 4000 kHz
|
||||
** Programming Started **
|
||||
auto erase enabled
|
||||
target halted due to breakpoint, current mode: Thread
|
||||
xPSR: 0x61000000 pc: 0x2000003a msp: 0x20000a20
|
||||
wrote 13312 bytes from file cortexm0.elf in 1.020185s (12.743 KiB/s)
|
||||
** Programming Finished **
|
||||
adapter speed: 950 kHz
|
||||
$
|
||||
$ picocom -b 115200 /dev/ttyUSB0
|
||||
picocom v3.1
|
||||
|
||||
port is : /dev/ttyUSB0
|
||||
flowcontrol : none
|
||||
baudrate is : 115200
|
||||
parity is : none
|
||||
databits are : 8
|
||||
stopbits are : 1
|
||||
escape is : C-a
|
||||
local echo is : no
|
||||
noinit is : no
|
||||
noreset is : no
|
||||
hangup is : no
|
||||
nolock is : no
|
||||
send_cmd is : sz -vv
|
||||
receive_cmd is : rz -vv -E
|
||||
imap is :
|
||||
omap is :
|
||||
emap is : crcrlf,delbs,
|
||||
logfile is : none
|
||||
initstring : none
|
||||
exit_after is : not set
|
||||
exit is : no
|
||||
|
||||
Type [C-a] [C-h] to see available commands
|
||||
Terminal ready
|
||||
Hello, World!
|
||||
Hello, World!
|
||||
Hello, World!
|
||||
```
|
||||
|
||||
每次按下 reset 按钮都会产生新的 “Hello,World!”行。一切都在按预期进行。
|
||||
|
||||
要查看此 MCU 的 <ruby>双向<rt>bi-directional</rt></ruby> UART 代码,请查看 [此示例][10]。
|
||||
|
||||
### io.Writer 接口
|
||||
|
||||
_io.Writer_ 接口可能是 Go 中第二种最常用的接口类型,紧接在 _error_ 接口之后。其定义如下所示:
|
||||
|
||||
```
|
||||
type Writer interface {
|
||||
Write(p []byte) (n int, err error)
|
||||
}
|
||||
```
|
||||
|
||||
_usart.Driver_ 实现了 _io.Writer_ ,因此我们可以替换:
|
||||
|
||||
```
|
||||
tts.WriteString("Hello, World!\r\n")
|
||||
```
|
||||
|
||||
为
|
||||
|
||||
```
|
||||
io.WriteString(tts, "Hello, World!\r\n")
|
||||
```
|
||||
|
||||
此外,您需要将 _io_ 包添加到 _import_ 部分。
|
||||
|
||||
_io.WriteString_ 函数的声明如下所示:
|
||||
|
||||
```
|
||||
func WriteString(w Writer, s string) (n int, err error)
|
||||
```
|
||||
|
||||
如您所见,_io.WriteString_ 允许使用实现了 _io.Writer_ 接口的任何类型来编写字符串。在内部,它检查基础类型是否具有 _WriteString_ 方法,并使用该方法代替 _Write_ (如果可用)。
|
||||
|
||||
让我们编译修改后的程序:
|
||||
|
||||
```
|
||||
$ egc
|
||||
$ arm-none-eabi-size cortexm0.elf
|
||||
text data bss dec hex filename
|
||||
15456 320 248 16024 3e98 cortexm0.elf
|
||||
```
|
||||
|
||||
如您所见,_io.WriteString_ 导致二进制文件的大小显着增加:15776-12964 = 2812字节。 Flash 上没有太多空间了。是什么引起了这么大规模的增长?
|
||||
|
||||
使用这个命令:
|
||||
|
||||
```
|
||||
arm-none-eabi-nm --print-size --size-sort --radix=d cortexm0.elf
|
||||
```
|
||||
|
||||
我们可以打印两种情况下按其大小排序的所有符号。通过过滤和分析获得的数据(awk,diff),我们可以找到大约 80 个新符号。最大的十个如下所示:
|
||||
|
||||
```
|
||||
> 00000062 T stm32$hal$usart$Driver$DisableRx
|
||||
> 00000072 T stm32$hal$usart$Driver$RxDMAISR
|
||||
> 00000076 T internal$Type$Implements
|
||||
> 00000080 T stm32$hal$usart$Driver$EnableRx
|
||||
> 00000084 t errors$New
|
||||
> 00000096 R $8$stm32$hal$usart$Driver$$
|
||||
> 00000100 T stm32$hal$usart$Error$Error
|
||||
> 00000360 T io$WriteString
|
||||
> 00000660 T stm32$hal$usart$Driver$Read
|
||||
```
|
||||
|
||||
因此,即使我们不使用 _usart.Driver.Read_ 方法进行编译,也与 _DisableRx_、_RxDMAISR_、_EnableRx_ 以及上面未提及的其他方法相同。不幸的是,如果您为接口赋值了一些内容,那么它的完整方法集是必需的(包含所有依赖项)。对于使用大多数方法的大型程序来说,这不是问题。但是对于我们这种极简的情况而言,这是一个巨大的负担。
|
||||
|
||||
我们已经接近 MCU 的极限,但让我们尝试打印一些数字(您需要在 _import_ 部分中用 _strconv_ 替换 _io_ 包):
|
||||
|
||||
```
|
||||
func main() {
|
||||
a := 12
|
||||
b := -123
|
||||
|
||||
tts.WriteString("a = ")
|
||||
strconv.WriteInt(tts, a, 10, 0, 0)
|
||||
tts.WriteString("\r\n")
|
||||
tts.WriteString("b = ")
|
||||
strconv.WriteInt(tts, b, 10, 0, 0)
|
||||
tts.WriteString("\r\n")
|
||||
|
||||
tts.WriteString("hex(a) = ")
|
||||
strconv.WriteInt(tts, a, 16, 0, 0)
|
||||
tts.WriteString("\r\n")
|
||||
tts.WriteString("hex(b) = ")
|
||||
strconv.WriteInt(tts, b, 16, 0, 0)
|
||||
tts.WriteString("\r\n")
|
||||
}
|
||||
```
|
||||
|
||||
与使用 _io.WriteString_ 函数的情况一样,_strconv.WriteInt_ 的第一个参数的类型为 _io.Writer_ 。
|
||||
|
||||
|
||||
```
|
||||
$ egc
|
||||
/usr/local/arm/bin/arm-none-eabi-ld: /home/michal/firstemgo/cortexm0.elf section `.rodata' will not fit in region `Flash'
|
||||
/usr/local/arm/bin/arm-none-eabi-ld: region `Flash' overflowed by 692 bytes
|
||||
exit status 1
|
||||
```
|
||||
|
||||
这一次我们的空间用完了。让我们试着精简一下有关类型的信息:
|
||||
|
||||
```
|
||||
$ cd $HOME/emgo
|
||||
$ ./clean.sh
|
||||
$ cd $HOME/firstemgo
|
||||
$ egc -nf -nt
|
||||
$ arm-none-eabi-size cortexm0.elf
|
||||
text data bss dec hex filename
|
||||
15876 316 320 16512 4080 cortexm0.elf
|
||||
```
|
||||
|
||||
很接近,但很合适。让我们加载并运行此代码:
|
||||
|
||||
```
|
||||
a = 12
|
||||
b = -123
|
||||
hex(a) = c
|
||||
hex(b) = -7b
|
||||
```
|
||||
|
||||
Emgo 中的 _strconv_ 包与 Go 中的原型有很大的不同。 它旨在直接用于写入格式化的数字,并且在许多情况下可以替换繁重的 _fmt_ 包。 这就是为什么函数名称以 _Write_ 而不是 _Format_ 开头,并具有额外的两个参数的原因。 以下是其用法示例:
|
||||
|
||||
```
|
||||
func main() {
|
||||
b := -123
|
||||
strconv.WriteInt(tts, b, 10, 0, 0)
|
||||
tts.WriteString("\r\n")
|
||||
strconv.WriteInt(tts, b, 10, 6, ' ')
|
||||
tts.WriteString("\r\n")
|
||||
strconv.WriteInt(tts, b, 10, 6, '0')
|
||||
tts.WriteString("\r\n")
|
||||
strconv.WriteInt(tts, b, 10, 6, '.')
|
||||
tts.WriteString("\r\n")
|
||||
strconv.WriteInt(tts, b, 10, -6, ' ')
|
||||
tts.WriteString("\r\n")
|
||||
strconv.WriteInt(tts, b, 10, -6, '0')
|
||||
tts.WriteString("\r\n")
|
||||
strconv.WriteInt(tts, b, 10, -6, '.')
|
||||
tts.WriteString("\r\n")
|
||||
}
|
||||
```
|
||||
|
||||
下面是它的输出:
|
||||
|
||||
```
|
||||
-123
|
||||
-123
|
||||
-00123
|
||||
..-123
|
||||
-123
|
||||
-123
|
||||
-123..
|
||||
```
|
||||
|
||||
### Unix 流 和 <ruby>莫尔斯电码<rt>Morse code</rt></ruby>
|
||||
|
||||
得益于事实上大多数写入功能的函数都使用 _io.Writer_ 而不是具体类型(例如 C 中的 _FILE_ ),因此我们获得了类似于 _Unix stream_ 的功能。在 Unix 中,我们可以轻松地组合简单的命令来执行更大的任务。例如,我们可以通过以下方式将文本写入文件:
|
||||
|
||||
```
|
||||
echo "Hello, World!" > file.txt
|
||||
```
|
||||
|
||||
`>` 操作符将前面命令的输出流写入文件。还有 `|` 操作符,用于连接相邻命令的输出流和输入流。
|
||||
|
||||
|
||||
多亏了流,我们可以轻松地转换/过滤任何命令的输出。例如,要将所有字母转换为大写,我们可以通过 _tr_ 命令过滤 echo 的输出:
|
||||
```
|
||||
echo "Hello, World!" | tr a-z A-Z > file.txt
|
||||
```
|
||||
|
||||
为了显示 _io.Writer_ 和 Unix 流之间的类比,让我们编写以下代码:
|
||||
|
||||
```
|
||||
io.WriteString(tts, "Hello, World!\r\n")
|
||||
```
|
||||
|
||||
采用以下伪 unix 形式:
|
||||
|
||||
```
|
||||
io.WriteString "Hello, World!" | usart.Driver usart.USART1
|
||||
```
|
||||
|
||||
下一个示例将显示如何执行此操作:
|
||||
|
||||
```
|
||||
io.WriteString "Hello, World!" | MorseWriter | usart.Driver usart.USART1
|
||||
```
|
||||
|
||||
让我们来创建一个简单的编码器,它使用莫尔斯电码对写入的文本进行编码:
|
||||
|
||||
```
|
||||
type MorseWriter struct {
|
||||
W io.Writer
|
||||
}
|
||||
|
||||
func (w *MorseWriter) Write(s []byte) (int, error) {
|
||||
var buf [8]byte
|
||||
for n, c := range s {
|
||||
switch {
|
||||
case c == '\n':
|
||||
c = ' ' // Replace new lines with spaces.
|
||||
case 'a' <= c && c <= 'z':
|
||||
c -= 'a' - 'A' // Convert to upper case.
|
||||
}
|
||||
if c < ' ' || 'Z' < c {
|
||||
continue // c is outside ASCII [' ', 'Z']
|
||||
}
|
||||
var symbol morseSymbol
|
||||
if c == ' ' {
|
||||
symbol.length = 1
|
||||
buf[0] = ' '
|
||||
} else {
|
||||
symbol = morseSymbols[c-'!']
|
||||
for i := uint(0); i < uint(symbol.length); i++ {
|
||||
if (symbol.code>>i)&1 != 0 {
|
||||
buf[i] = '-'
|
||||
} else {
|
||||
buf[i] = '.'
|
||||
}
|
||||
}
|
||||
}
|
||||
buf[symbol.length] = ' '
|
||||
if _, err := w.W.Write(buf[:symbol.length+1]); err != nil {
|
||||
return n, err
|
||||
}
|
||||
}
|
||||
return len(s), nil
|
||||
}
|
||||
|
||||
type morseSymbol struct {
|
||||
code, length byte
|
||||
}
|
||||
|
||||
//emgo:const
|
||||
var morseSymbols = [...]morseSymbol{
|
||||
{1<<0 | 1<<1 | 1<<2, 4}, // ! ---.
|
||||
{1<<1 | 1<<4, 6}, // " .-..-.
|
||||
{}, // #
|
||||
{1<<3 | 1<<6, 7}, // $ ...-..-
|
||||
|
||||
// Some code omitted...
|
||||
|
||||
{1<<0 | 1<<3, 4}, // X -..-
|
||||
{1<<0 | 1<<2 | 1<<3, 4}, // Y -.--
|
||||
{1<<0 | 1<<1, 4}, // Z --..
|
||||
}
|
||||
```
|
||||
|
||||
您可以在 [这里][11] 找到完整的 _morseSymbols_ 数组。 `//emgo:const` 指令确保 _morseSymbols_ 数组不会被复制到 RAM 中。
|
||||
|
||||
现在我们可以通过两种方式打印句子:
|
||||
|
||||
```
|
||||
func main() {
|
||||
s := "Hello, World!\r\n"
|
||||
mw := &MorseWriter{tts}
|
||||
|
||||
io.WriteString(tts, s)
|
||||
io.WriteString(mw, s)
|
||||
}
|
||||
```
|
||||
|
||||
我们使用指向 _MorseWriter_ `&MorseWriter{tts}` 的指针而不是简单的 `MorseWriter{tts}` 值,因为 _MorseWriter_ 太大,不适合接口变量。
|
||||
|
||||
|
||||
与 Go 不同,Emgo 不会为存储在接口变量中的值动态分配内存。接口类型的大小受限制,等于三个指针(适合 _slice_ )或两个 _float64_(适合 _complex128_ )的大小,以较大者为准。它可以直接存储所有基本类型和小型 “结构体/数组” 的值,但是对于较大的值,您必须使用指针。
|
||||
|
||||
让我们编译此代码并查看其输出:
|
||||
|
||||
```
|
||||
$ egc
|
||||
$ arm-none-eabi-size cortexm0.elf
|
||||
text data bss dec hex filename
|
||||
15152 324 248 15724 3d6c cortexm0.elf
|
||||
```
|
||||
|
||||
```
|
||||
Hello, World!
|
||||
.... . .-.. .-.. --- --..-- .-- --- .-. .-.. -.. ---.
|
||||
```
|
||||
|
||||
### 终极 Blinky
|
||||
|
||||
_Blinky_ 等效于 _Hello,World!_ 程序的硬件。一旦有了 Morse 编码器,我们就可以轻松地将两者结合起来以获得 _Ultimate Blinky_ 程序:
|
||||
|
||||
```
|
||||
package main
|
||||
|
||||
import (
|
||||
"delay"
|
||||
"io"
|
||||
|
||||
"stm32/hal/gpio"
|
||||
"stm32/hal/system"
|
||||
"stm32/hal/system/timer/systick"
|
||||
)
|
||||
|
||||
var led gpio.Pin
|
||||
|
||||
func init() {
|
||||
system.SetupPLL(8, 1, 48/8)
|
||||
systick.Setup(2e6)
|
||||
|
||||
gpio.A.EnableClock(false)
|
||||
led = gpio.A.Pin(4)
|
||||
|
||||
cfg := gpio.Config{Mode: gpio.Out, Driver: gpio.OpenDrain, Speed: gpio.Low}
|
||||
led.Setup(&cfg)
|
||||
}
|
||||
|
||||
type Telegraph struct {
|
||||
Pin gpio.Pin
|
||||
Dotms int // Dot length [ms]
|
||||
}
|
||||
|
||||
func (t Telegraph) Write(s []byte) (int, error) {
|
||||
for _, c := range s {
|
||||
switch c {
|
||||
case '.':
|
||||
t.Pin.Clear()
|
||||
delay.Millisec(t.Dotms)
|
||||
t.Pin.Set()
|
||||
delay.Millisec(t.Dotms)
|
||||
case '-':
|
||||
t.Pin.Clear()
|
||||
delay.Millisec(3 * t.Dotms)
|
||||
t.Pin.Set()
|
||||
delay.Millisec(t.Dotms)
|
||||
case ' ':
|
||||
delay.Millisec(3 * t.Dotms)
|
||||
}
|
||||
}
|
||||
return len(s), nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
telegraph := &MorseWriter{Telegraph{led, 100}}
|
||||
for {
|
||||
io.WriteString(telegraph, "Hello, World! ")
|
||||
}
|
||||
}
|
||||
|
||||
// Some code omitted...
|
||||
|
||||
```
|
||||
|
||||
在上面的示例中,我省略了 _MorseWriter_ 类型的定义,因为它已在前面展示过。完整版可通过 [这里][12] 获取。让我们编译它并运行:
|
||||
|
||||
```
|
||||
$ egc
|
||||
$ arm-none-eabi-size cortexm0.elf
|
||||
text data bss dec hex filename
|
||||
11772 244 244 12260 2fe4 cortexm0.elf
|
||||
```
|
||||
|
||||
![Ultimate Blinky](https://ziutek.github.io/images/mcu/f030-demo-board/morse.png)
|
||||
|
||||
### 反射
|
||||
|
||||
是的,Emgo 支持 [反射][13]。 _reflect_ 包尚未完成,但是已完成的部分足以实现 _fmt.Print_ 函数族了。来看看我们可以在小型 MCU 上做什么。
|
||||
|
||||
为了减少内存使用,我们将使用 [semihosting][14] 作为标准输出。为了方便起见,我们还编写了简单的 _println_ 函数,它在某种程度上类似于 _fmt.Println_。
|
||||
|
||||
```
|
||||
package main
|
||||
|
||||
import (
|
||||
"debug/semihosting"
|
||||
"reflect"
|
||||
"strconv"
|
||||
|
||||
"stm32/hal/system"
|
||||
"stm32/hal/system/timer/systick"
|
||||
)
|
||||
|
||||
var stdout semihosting.File
|
||||
|
||||
func init() {
|
||||
system.SetupPLL(8, 1, 48/8)
|
||||
systick.Setup(2e6)
|
||||
|
||||
var err error
|
||||
stdout, err = semihosting.OpenFile(":tt", semihosting.W)
|
||||
for err != nil {
|
||||
}
|
||||
}
|
||||
|
||||
type stringer interface {
|
||||
String() string
|
||||
}
|
||||
|
||||
func println(args ...interface{}) {
|
||||
for i, a := range args {
|
||||
if i > 0 {
|
||||
stdout.WriteString(" ")
|
||||
}
|
||||
switch v := a.(type) {
|
||||
case string:
|
||||
stdout.WriteString(v)
|
||||
case int:
|
||||
strconv.WriteInt(stdout, v, 10, 0, 0)
|
||||
case bool:
|
||||
strconv.WriteBool(stdout, v, 't', 0, 0)
|
||||
case stringer:
|
||||
stdout.WriteString(v.String())
|
||||
default:
|
||||
stdout.WriteString("%unknown")
|
||||
}
|
||||
}
|
||||
stdout.WriteString("\r\n")
|
||||
}
|
||||
|
||||
type S struct {
|
||||
A int
|
||||
B bool
|
||||
}
|
||||
|
||||
func main() {
|
||||
p := &S{-123, true}
|
||||
|
||||
v := reflect.ValueOf(p)
|
||||
|
||||
println("kind(p) =", v.Kind())
|
||||
println("kind(*p) =", v.Elem().Kind())
|
||||
println("type(*p) =", v.Elem().Type())
|
||||
|
||||
v = v.Elem()
|
||||
|
||||
println("*p = {")
|
||||
for i := 0; i < v.NumField(); i++ {
|
||||
ft := v.Type().Field(i)
|
||||
fv := v.Field(i)
|
||||
println(" ", ft.Name(), ":", fv.Interface())
|
||||
}
|
||||
println("}")
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
_semihosting.OpenFile_ 函数允许在主机端 打开/创建 文件。特殊路径 _:tt_ 对应于主机的标准输出。
|
||||
|
||||
_println_ 函数接受任意数量的参数,每个参数的类型都是任意的:
|
||||
|
||||
```
|
||||
func println(args ...interface{})
|
||||
```
|
||||
|
||||
可能是因为任何类型都实现了空接口 _interface{}_。 _println_ 使用 [类型开关][15] 打印字符串,整数和布尔值:
|
||||
|
||||
```
|
||||
switch v := a.(type) {
|
||||
case string:
|
||||
stdout.WriteString(v)
|
||||
case int:
|
||||
strconv.WriteInt(stdout, v, 10, 0, 0)
|
||||
case bool:
|
||||
strconv.WriteBool(stdout, v, 't', 0, 0)
|
||||
case stringer:
|
||||
stdout.WriteString(v.String())
|
||||
default:
|
||||
stdout.WriteString("%unknown")
|
||||
}
|
||||
```
|
||||
|
||||
此外,它还支持任何实现了 _stringer_ 接口的类型,即任何具有 _String()_ 方法的类型。在任何 _case_ 子句中,_v_ 变量具有正确的类型,与 _case_ 关键字后列出的类型相同。
|
||||
|
||||
|
||||
reflect.ValueOf(p) 函数以允许以编程方式分析其类型和内容的形式返回 _p_。如您所见,我们甚至可以使用 `v.Elem()` 取消引用指针,并打印所有结构体及其名称。
|
||||
|
||||
让我们尝试编译这段代码。现在,让我们看看如果不使用类型和字段名进行编译会产生什么结果:
|
||||
|
||||
```
|
||||
$ egc -nt -nf
|
||||
$ arm-none-eabi-size cortexm0.elf
|
||||
text data bss dec hex filename
|
||||
16028 216 312 16556 40ac cortexm0.elf
|
||||
```
|
||||
|
||||
闪存上只剩下 140 个可用字节。让我们使用启用了 semihosting 的 OpenOCD 加载它:
|
||||
|
||||
```
|
||||
$ openocd -d0 -f interface/stlink.cfg -f target/stm32f0x.cfg -c 'init; program cortexm0.elf; arm semihosting enable; reset run'
|
||||
Open On-Chip Debugger 0.10.0+dev-00319-g8f1f912a (2018-03-07-19:20)
|
||||
Licensed under GNU GPL v2
|
||||
For bug reports, read
|
||||
http://openocd.org/doc/doxygen/bugs.html
|
||||
debug_level: 0
|
||||
adapter speed: 1000 kHz
|
||||
adapter_nsrst_delay: 100
|
||||
none separate
|
||||
adapter speed: 950 kHz
|
||||
target halted due to debug-request, current mode: Thread
|
||||
xPSR: 0xc1000000 pc: 0x08002338 msp: 0x20000a20
|
||||
adapter speed: 4000 kHz
|
||||
** Programming Started **
|
||||
auto erase enabled
|
||||
target halted due to breakpoint, current mode: Thread
|
||||
xPSR: 0x61000000 pc: 0x2000003a msp: 0x20000a20
|
||||
wrote 16384 bytes from file cortexm0.elf in 0.700133s (22.853 KiB/s)
|
||||
** Programming Finished **
|
||||
semihosting is enabled
|
||||
adapter speed: 950 kHz
|
||||
kind(p) = ptr
|
||||
kind(*p) = struct
|
||||
type(*p) =
|
||||
*p = {
|
||||
X. : -123
|
||||
X. : true
|
||||
}
|
||||
```
|
||||
|
||||
如果您实际运行过此代码,则会注意到 semihosting 运行缓慢,尤其是在逐字节写入时(缓冲很有用)。
|
||||
|
||||
如您所见,`*p` 没有类型名称,并且所有结构字段都具有相同的 _X._ 名称。让我们再次编译该程序,这次不带 _-nt -nf_ 选项:
|
||||
|
||||
```
|
||||
$ egc
|
||||
$ arm-none-eabi-size cortexm0.elf
|
||||
text data bss dec hex filename
|
||||
16052 216 312 16580 40c4 cortexm0.elf
|
||||
```
|
||||
|
||||
现在已经包括了类型和字段名称,但仅在 ~~_main.go_ 文件中~~ _main_ 包中定义了它们。该程序的输出如下所示:
|
||||
|
||||
```
|
||||
kind(p) = ptr
|
||||
kind(*p) = struct
|
||||
type(*p) = S
|
||||
*p = {
|
||||
A : -123
|
||||
B : true
|
||||
}
|
||||
```
|
||||
|
||||
反射是任何易于使用的序列化库的关键部分,而像 [JSON][16] 这样的序列化 ~~算法~~ 在<ruby>物联网<rt>IoT</rt></ruby>时代也越来越重要。
|
||||
|
||||
这些就是我完成的本文的第二部分。我认为有机会进行第三部分,更具娱乐性的部分,在那里我们将各种有趣的设备连接到这块板上。如果这块板装不下,我们就换一块大一点的。
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
via: https://ziutek.github.io/2018/04/14/go_on_very_small_hardware2.html
|
||||
|
||||
作者:[Michał Derkacz ][a]
|
||||
译者:[gxlct008](https://github.com/gxlct008)
|
||||
校对:[校对者ID](https://github.com/校对者ID)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]:https://ziutek.github.io/
|
||||
[1]:https://ziutek.github.io/2018/04/14/go_on_very_small_hardware2.html
|
||||
[2]:https://ziutek.github.io/2018/03/30/go_on_very_small_hardware.html
|
||||
[3]:https://golang.org/doc/effective_go.html#interfaces
|
||||
[4]:https://research.swtch.com/interfaces
|
||||
[5]:https://blog.golang.org/laws-of-reflection
|
||||
[6]:https://github.com/texane/stlink
|
||||
[7]:http://www.world-semi.com/solution/list-4-1.html
|
||||
[8]:https://en.wikipedia.org/wiki/1-Wire
|
||||
[9]:https://github.com/npat-efault/picocom
|
||||
[10]:https://github.com/ziutek/emgo/blob/master/egpath/src/stm32/examples/f030-demo-board/usart/main.go
|
||||
[11]:https://github.com/ziutek/emgo/blob/master/egpath/src/stm32/examples/f030-demo-board/morseuart/main.go
|
||||
[12]:https://github.com/ziutek/emgo/blob/master/egpath/src/stm32/examples/f030-demo-board/morseled/main.go
|
||||
[13]:https://blog.golang.org/laws-of-reflection
|
||||
[14]:http://infocenter.arm.com/help/topic/com.arm.doc.dui0471g/Bgbjjgij.html
|
||||
[15]:https://golang.org/doc/effective_go.html#type_switch
|
||||
[16]:https://en.wikipedia.org/wiki/JSON
|
@ -1,303 +0,0 @@
|
||||
[#]: collector: (lujun9972)
|
||||
[#]: translator: (gxlct008)
|
||||
[#]: reviewer: ( )
|
||||
[#]: publisher: ( )
|
||||
[#]: url: ( )
|
||||
[#]: subject: (TCP window scaling, timestamps and SACK)
|
||||
[#]: via: (https://fedoramagazine.org/tcp-window-scaling-timestamps-and-sack/)
|
||||
[#]: author: (Florian Westphal https://fedoramagazine.org/author/strlen/)
|
||||
|
||||
TCP 窗口缩放,时间戳和 SACK
|
||||
======
|
||||
|
||||
![][1]
|
||||
|
||||
Linux TCP 协议栈具有无数个 _sysctl_ 旋钮,允许更改其行为。 这包括可用于接收或发送操作的内存量,套接字的最大数量、可选特性和协议扩展。
|
||||
|
||||
有很多文章出于各种“性能调优”或“安全性”原因,建议禁用 TCP 扩展,比如时间戳或<ruby>选择性确认<rt>selective acknowledgments</rt></ruby> (SACK)。
|
||||
|
||||
本文提供了这些扩展的功能背景,默认情况下处于启用状态的原因,它们之间是如何关联的,以及为什么通常情况下将它们关闭是个坏主意。
|
||||
|
||||
### TCP 窗口缩放
|
||||
|
||||
TCP 可以维持的数据传输速率受到几个因素的限制。其中包括:
|
||||
|
||||
* 往返时间(RTT)。这是数据包到达目的地并返回回复所花费的时间。越低越好。
|
||||
* 所涉及的网络路径的最低链路速度
|
||||
* 丢包频率
|
||||
* 新数据可用于传输的速度。 例如,CPU 需要能够以足够快的速度将数据传递到网络适配器。如果 CPU 需要首先加密数据,则适配器可能必须等待新数据。同样地,如果磁盘存储不能足够快地读取数据,则磁盘存储可能会成为瓶颈。
|
||||
* TCP 接收窗口的最大可能大小。接收窗口决定 TCP 在必须等待接收方报告接收到该数据之前可以传输多少数据 (以字节为单位)。这是由接收方宣布的。接收方将在读取并确认接收到传入数据时不断更新此值。接收窗口当前值包含在 [TCP 报头][2] 中,它是 TCP 发送的每个数据段的一部分。因此,只要发送方接收到来自对等方的确认,它就知道当前的接收窗口。这意味着往返时间(RTT)越长,发送方获得接收窗口更新所需的时间就越长。
|
||||
|
||||
|
||||
TCP 被限制为最多 64KB 的未确认(正在传输)数据。在大多数网络场景中,这甚至还不足以维持一个像样的数据速率。让我们看看一些例子。
|
||||
|
||||
##### 理论数据速率
|
||||
|
||||
由于往返时间 (RTT) 为 100 毫秒,TCP 每秒最多可以传输 640KB。在延迟 1 秒的情况下,最大理论数据速率降至 64KB/s。
|
||||
|
||||
这是因为接收窗口的原因。一旦发送了 64KB 的数据,接收窗口就已经满了。发送方必须等待,直到对等方通知它应用程序已经读取了至少一部分数据。
|
||||
|
||||
发送的第一个段会把 TCP 窗口缩减一个自身的大小。在接收窗口值的更新可用之前,需要往返一次。当更新以 1 秒的延迟到达时,即使链路有足够的可用带宽,也会导致 64KB 的限制。
|
||||
|
||||
为了充分利用一个具有几毫秒延迟的快速网络,必须有一个比传统 TCP 支持的窗口大的窗口。“64KB 限制”是协议规范的产物:TCP 头只为接收窗口大小保留 16 位。这允许接收窗口高达 64KB。在 TCP 协议最初设计时,这个大小并没有被视为一个限制。
|
||||
|
||||
不幸的是,想通过仅仅更改 TCP 头来支持更大的最大窗口值是不可能的。如果这样做就意味着 TCP 的所有实现都必须同时更新,否则它们将无法相互理解。为了解决这个问题,需要改变接收窗口值的解释。
|
||||
|
||||
“窗口缩放选项”允许这样做,同时保持与现有实现的兼容性。
|
||||
|
||||
#### TCP 选项:向后兼容的协议扩展
|
||||
|
||||
TCP 支持可选扩展。 这允许使用新特性增强协议,而无需立即更新所有实现。 当 TCP 启动器连接到对等方时,它还会发送一个支持的扩展列表。 所有扩展名都遵循相同的格式:一个唯一的选项号,后跟选项的长度以及选项数据本身。
|
||||
|
||||
TCP 响应程序检查连接请求中包含的所有选项号。 如果它遇到一个不能理解的选项号,则会跳过
|
||||
该选项号附带的“长度”字节的数据,并检查下一个选项号。 响应者忽略了从答复中无法理解的内容。 这使发送方和接收方都够了解所支持的通用选项集。
|
||||
|
||||
使用窗口缩放时,选项数据总是由单个数字组成。
|
||||
|
||||
### 窗口缩放选项
|
||||
|
||||
```
|
||||
窗口缩放选项 (WSopt): Kind: 3, Length: 3
|
||||
+---------+---------+---------+
|
||||
| Kind=3 |Length=3 |shift.cnt|
|
||||
+---------+---------+---------+
|
||||
1 1 1
|
||||
```
|
||||
|
||||
[窗口缩放][3] 选项告诉对等点,应该使用给定的数字缩放 TCP 标头中的接收窗口值,以获取实际大小。
|
||||
|
||||
例如,一个宣告窗口缩放比例因子为 7 的 TCP 启动器试图指示响应程序,任何将来携带接收窗口值为 512 的数据包实际上都会宣告 65536 字节的窗口。 增加了 128 倍。这将允许最大为 8MB 的 TCP 窗口。
|
||||
|
||||
不能理解此选项的 TCP 响应程序将会忽略它。 为响应连接请求而发送的 TCP 数据包(SYN-ACK)不包含窗口缩放选项。在这种情况下,双方只能使用 64k 的窗口大小。幸运的是,默认情况下,几乎每个 TCP 堆栈都支持并启用此选项,包括 Linux。
|
||||
|
||||
响应程序包括它自己所需的比例因子。两个对等点可以使用不同的号码。宣布比例因子为 0 也是合法的。这意味着对等点应该逐字处理它接收到的接收窗口值,但它允许应答方向上的缩放值,然后接收方可以使用更大的接收窗口。
|
||||
|
||||
与 SACK 或 TCP 时间戳不同,窗口缩放选项仅出现在 TCP 连接的前两个数据包中,之后无法更改。也不可能通过查看不包含初始连接三次握手的连接的数据包捕获来确定比例因子。
|
||||
|
||||
支持的最大比例因子为 14。这将允许 TCP 窗口的大小高达 1GB。
|
||||
|
||||
##### 窗口缩放的缺点
|
||||
|
||||
在非常特殊的情况下,它可能导致数据损坏。 在禁用该选项之前——通常情况下是不可能的。 还有一种解决方案可以防止这种情况。不幸的是,有些人在没有意识到与窗口缩放的关系的情况下禁用了该解决方案。 首先,让我们看一下需要解决的实际问题。 想象以下事件序列:
|
||||
|
||||
1. 发送方发送段:s_1,s_2,s_3,... s_n
|
||||
2. 接收方看到:s_1,s_3,.. s_n,并发送对 s_1 的确认。
|
||||
3. 发送方认为 s_2 丢失,然后再次发送。 它还发送段 s_n+1 中包含的新数据。
|
||||
4. 接收方然后看到:s_2,s_n+1,s_2:数据包 s_2 被接收两次。
|
||||
|
||||
例如,当发送方过早触发重新传输时,可能会发生这种情况。 在正常情况下,即使使用窗口缩放,这种错误的重传也绝不会成为问题。 接收方将只丢弃重复项。
|
||||
|
||||
#### 从旧数据到新数据
|
||||
|
||||
TCP 序列号最多可以为 4GB。如果它变得大于此值,则序列会回绕到 0,然后再次增加。这本身不是问题,但是如果这种问题发生得足够快,则上述情况可能会造成歧义。
|
||||
|
||||
如果在正确的时刻发生回绕,则序列号 s_2(重新发送的数据包)可能已经大于 s_n+1。 因此,在最后的步骤(4)中,接收器可以将其解释为:s_2,s_n+1,s_n+m,即它可以将 **“旧”** 数据包 s_2 视为包含新数据。
|
||||
|
||||
通常,这不会发生,因为即使在高带宽链接上,“回绕”也只会每隔几秒钟或几分钟发生一次。原始和不需要的重传之间的间隔将小得多。
|
||||
|
||||
例如,对于 50MB/s 的传输速度,副本要延迟到一分钟以上才会成为问题。序列号的包装速度不够快,小的延迟才会导致这个问题。
|
||||
|
||||
一旦 TCP 达到 “GB/s” 的吞吐率,序列号的包装速度就会非常快,以至于即使只有几毫秒的延迟也可能会造成 TCP 无法再检测到的重复项。通过解决接收窗口太小的问题,TCP 现在可以用于以前无法实现的网络速度,这会产生一个新的,尽管很少见的问题。为了在 RTT 非常低的环境中安全使用 GB/s 的速度,接收方必须能够检测到这些旧副本,而不必仅依赖序列号。
|
||||
|
||||
### TCP 时间戳
|
||||
|
||||
#### 最佳使用日期。
|
||||
|
||||
用最简单的术语来说,[TCP 时间戳][3]只是在数据包上添加时间戳,以解决由非常快速的序列号回绕引起的歧义。 如果一个段看起来包含新数据,但其时间戳早于最后一个在窗口内的数据包,则该序列号已被重新包装,而“新”数据包实际上是一个较旧的副本。 这解决了即使在极端情况下重传的歧义。
|
||||
|
||||
但是,该扩展不仅仅是检测旧数据包。 TCP 时间戳的另一个主要功能是更精确的往返时间测量(RTTm)。
|
||||
|
||||
#### 需要准确的 RTT 估算
|
||||
|
||||
当两个对等方都支持时间戳时,每个 TCP 段都携带两个附加数字:时间戳值和时间戳回显。
|
||||
|
||||
```
|
||||
TCP 时间戳选项 (TSopt): Kind: 8, Length: 10
|
||||
+-------+----+----------------+-----------------+
|
||||
|Kind=8 | 10 |TS Value (TSval)|EchoReply (TSecr)|
|
||||
+-------+----+----------------+-----------------+
|
||||
1 1 4 4
|
||||
```
|
||||
|
||||
准确的 RTT 估算对于 TCP 性能至关重要。 TCP 自动重新发送未确认的数据。 重传由计时器触发:如果超时,则 TCP 会将尚未收到确认的一个或多个数据包视为丢失。 然后再发送一次。
|
||||
|
||||
但是,“尚未得到确认” 并不意味着该段已丢失。 也有可能是接收方到目前为止没有发送确认,或者确认仍在传输中。 这就造成了一个两难的困境:TCP 必须等待足够长的时间,才能让这种轻微的延迟变得无关紧要,但它也不能等待太久。
|
||||
|
||||
##### 低网络延迟 VS 高网络延迟
|
||||
|
||||
在延迟较高的网络中,如果计时器触发过快,TCP 经常会将时间和带宽浪费在不必要的重发上。
|
||||
|
||||
然而,在延迟较低的网络中,等待太长时间会导致真正发生数据包丢失时吞吐量降低。因此,在低延迟网络中,计时器应该比高延迟网络中更早到期。 所以,TCP 重传超时不能使用固定常量值作为超时。它需要根据其在网络中所经历的延迟来调整该值。
|
||||
|
||||
##### RTT(往返时间)的测量
|
||||
|
||||
TCP 选择基于预期往返时间(RTT)的重传超时。 RTT 事先是未知的。它是通过测量发送段与 TCP 接收到该段所承载数据的确认之间的增量来估算的。
|
||||
|
||||
由于多种因素使其而变得复杂。
|
||||
|
||||
* 出于性能原因,TCP 不会为收到的每个数据包生成新的确认。它等待的时间非常短:如果有更多的数据段到达,则可以通过单个 ACK 数据包确认其接收。这称为<ruby>“累积确认”<rt>cumulative ACK</rt></ruby>。
|
||||
* 往返时间并不恒定。 这是有多种因素造成的。例如,客户端可能是一部移动电话,随其移动而切换到不同的基站。也可能是当链路或 CPU 利用率提高时,数据包交换花费了更长的时间。
|
||||
* 必须重新发送的数据包在计算过程中必须被忽略。
|
||||
这是因为发送方无法判断重传数据段的 ACK 是在确认原始传输 (毕竟已到达) 还是在确认重传。
|
||||
|
||||
最后一点很重要:当 TCP 忙于从丢失中恢复时,它可能仅接收到重传段的 ACK。这样,它就无法在此恢复阶段测量(更新)RTT。所以,它无法调整重传超时,然后超时将以指数级增长。那是一种非常具体的情况(它假设其他机制,如快速重传或 SACK 不起作用)。但是,使用 TCP 时间戳,即使在这种情况下也会进行 RTT 评估。
|
||||
|
||||
如果使用了扩展,则对等方将从 TCP 段扩展空间中读取时间戳值并将其存储在本地。然后,它将该值放入作为 “时间戳回显” 发回的所有数据段中。
|
||||
|
||||
因此,该选项带有两个时间戳:它的发送方自己的时间戳和它从对等方收到的最新时间戳。原始发送方使用“回显时间戳”来计算 RTT。它是当前时间戳时钟与“时间戳回显”中所反映的值之间的增量。
|
||||
|
||||
##### 时间戳的其他用用途
|
||||
|
||||
TCP 时间戳甚至还有除 PAWS 和 RTT 测量以外的其他用途。例如,可以检测是否不需要重发。如果该确认携带较旧的时间戳回显,则该确认针对的是初始数据包,而不是重新发送的数据包。
|
||||
|
||||
TCP 时间戳的另一个更晦涩的用例与 TCP [syn cookie][4] 功能有关。
|
||||
|
||||
##### 在服务器端建立 TCP 连接
|
||||
|
||||
当连接请求到达的速度快于服务器应用程序可以接受新的传入连接的速度时,连接积压最终将达到其极限。这可能是由于系统配置错误或应用程序中的错误引起的。当一个或多个客户端发送连接请求而不对 “SYN ACK” 响应做出反应时,也会发生这种情况。这将用不完整的连接填充连接队列。这些条目需要几秒钟才会超时。这被称为<ruby>“同步洪水攻击”<rt>syn flood attack</rt></ruby>。
|
||||
|
||||
##### TCP 时间戳和 TCP Syn Cookie
|
||||
|
||||
即使队列已满,某些 TCP 协议栈也允许继续接受新连接。发生这种情况时,Linux 内核将在系统日志中打印一条突出的消息:
|
||||
|
||||
> P 端口上可能发生 SYN 泛洪。正在发送 Cookie。检查 SNMP 计数器。
|
||||
|
||||
此机制将完全绕过连接队列。通常存储在连接队列中的信息被编码到 SYN/ACK 响应 TCP 序列号中。当 ACK 返回时,可以根据序列号重建队列条目。
|
||||
|
||||
序列号只有有限的空间来存储信息。 因此,使用 “TCP Syn Cookie” 机制建立的连接不能支持 TCP 选项。
|
||||
|
||||
但是,对两个对等点都通用的 TCP 选项可以存储在时间戳中。 ACK 数据包在时间戳回显字段中反映了该值,这也允许恢复已达成共识的 TCP 选项。否则,cookie 连接受标准的 64KB 接收窗口限制。
|
||||
|
||||
##### 常见误区 —— 时间戳不利于性能
|
||||
|
||||
不幸的是,一些指南建议禁用 TCP 时间戳,以减少内核访问时间戳时钟来获取当前时间所需的次数。这是不正确的。如前所述,RTT 估算是 TCP 的必要部分。因此,内核在接收/发送数据包时总是采用微秒级的时间戳。
|
||||
|
||||
在包处理步骤的其余部分中,Linux 会重用 RTT 估算所需的时钟时间戳。这还避免了将时间戳添加到传出 TCP 数据包的额外时钟访问。
|
||||
|
||||
整个时间戳选项在每个数据包中仅需要 10 个字节的 TCP 选项空间,这并没有显著减少可用于数据包有效负载的空间。
|
||||
|
||||
##### 常见误区 —— 时间戳是个安全问题
|
||||
|
||||
一些安全审计工具和 (较旧的) 博客文章建议禁用 TCP 时间戳,因为据称它们泄露了系统正常运行时间:这样一来,便可以估算系统/内核的补丁级别。这在过去是正确的:时间戳时钟基于不断增加的值,该值在每次系统引导时都以固定值开始。时间戳值可以估计机器已经运行了多长时间 (正常运行时间)。
|
||||
|
||||
从 Linux 4.12 开始,TCP 时间戳不再显示正常运行时间。发送的所有时间戳值都使用对等设备特定的偏移量。时间戳值也每 49 天换行一次。
|
||||
|
||||
换句话说,从地址 “A” 出发,或者终到地址 “A” 的连接看到的时间戳与到远程地址 “B” 的连接看到的时间戳不同。
|
||||
|
||||
运行 _sysctl net.ipv4.tcp_timeamp=2_ 以禁用随机化偏移。这使得分析由诸如 _Wireshark_ 或 _tcpdump_ 之类的工具记录的数据包跟踪变得更容易 —— 从主机发送的数据包在其 TCP 选项时间戳中都具有相同的时钟基准。因此,对于正常操作,默认设置应保持不变。
|
||||
|
||||
### 选择性确认
|
||||
|
||||
如果丢失同一数据窗口中的多个数据包,TCP 将会出现问题。 这是因为 TCP 确认是累积的,但仅适用于按顺序到达的数据包。例如:
|
||||
|
||||
* 发送方发送段 s_1,s_2,s_3,... s_n
|
||||
* 发送方收到 s_2 的 ACK
|
||||
* 这意味着 s_1 和 s_2 都已收到,并且发送方不再需要保留这些段。
|
||||
* s_3 是否应该重新发送? s_4呢? s_n?
|
||||
|
||||
发送方等待 “重传超时” 或 “重复 ACK” 以使 s_2 到达。如果发生重传超时或到达 s_2 的多个重复 ACK,则发送方再次发送 s_3。
|
||||
|
||||
如果发送方收到对 s_n 的确认,则 s_3 是唯一丢失的数据包。这是理想的情况。仅发送单个丢失的数据包。
|
||||
|
||||
如果发送方收到的确认段小于 s_n,例如 s_4,则意味着丢失了多个数据包。
|
||||
发送方也需要重传下一个数据段。
|
||||
|
||||
##### 重传策略
|
||||
|
||||
可能只是重复相同的序列:重新发送下一个数据包,直到接收方指示它已处理了直至 s_n 的所有数据包为止。这种方法的问题在于,它需要一个 RTT,直到发送方知道接下来必须重新发送的数据包为止。尽管这种策略可以避免不必要的重传,但要等到 TCP 重新发送整个数据窗口后,它可能要花几秒钟甚至更长的时间。
|
||||
|
||||
另一种方法是一次重新发送几个数据包。当丢失了几个数据包时,此方法可使 TCP 恢复更快。在上面的示例中,TCP 重新发送了 s_3,s_4,s_5,...,但是只能确保已丢失 s_3。
|
||||
|
||||
从延迟的角度来看,这两种策略都不是最佳的。如果只有一个数据包需要重新发送,第一种策略是快速的,但是当多个数据包丢失时,它花费的时间太长。
|
||||
|
||||
即使必须重新发送多个数据包,第二个也是快速的,但是以浪费带宽为代价。此外,这样的 TCP 发送方在进行不必要的重传时可能已经发送了新数据。
|
||||
|
||||
通过可用信息,TCP 无法知道丢失了哪些数据包。这就是 TCP [选择性确认][5](SACK)的用武之地了。就像窗口缩放和时间戳一样,它是另一个可选的但非常有用的 TCP 特性。
|
||||
|
||||
##### SACK 选项
|
||||
|
||||
```
|
||||
TCP Sack-Permitted Option: Kind: 4, Length 2
|
||||
+---------+---------+
|
||||
| Kind=4 | Length=2|
|
||||
+---------+---------+
|
||||
```
|
||||
|
||||
支持此扩展的发送方在连接请求中包括 “允许 SACK” 选项。如果两个端点都支持扩展,则检测到数据流中丢失数据包的对等点可以将此信息通知发送方。
|
||||
|
||||
```
|
||||
TCP SACK Option: Kind: 5, Length: Variable
|
||||
+--------+--------+
|
||||
| Kind=5 | Length |
|
||||
+--------+--------+--------+--------+
|
||||
| Left Edge of 1st Block |
|
||||
+--------+--------+--------+--------+
|
||||
| Right Edge of 1st Block |
|
||||
+--------+--------+--------+--------+
|
||||
| |
|
||||
/ . . . /
|
||||
| |
|
||||
+--------+--------+--------+--------+
|
||||
| Left Edge of nth Block |
|
||||
+--------+--------+--------+--------+
|
||||
| Right Edge of nth Block |
|
||||
+--------+--------+--------+--------+
|
||||
```
|
||||
|
||||
接收方遇到 segment_s2 后跟 s_5 ... s_n,则在发送对 s_2 的确认时将包括一个 SACK 块:
|
||||
|
||||
```
|
||||
|
||||
+--------+-------+
|
||||
| Kind=5 | 10 |
|
||||
+--------+------+--------+-------+
|
||||
| Left edge: s_5 |
|
||||
+--------+--------+-------+------+
|
||||
| Right edge: s_n |
|
||||
+--------+-------+-------+-------+
|
||||
```
|
||||
|
||||
这告诉发送方到 s_2 的段都是按顺序到达的,但也让发送方知道段 s_5 至 s_n 也已收到。 然后,发送方可以重新发送这两个数据包,并继续发送新数据。
|
||||
|
||||
##### 神话般的无损网络
|
||||
|
||||
从理论上讲,如果连接不会丢包,那么 SACK 就没有任何优势。或者连接具有如此低的延迟,甚至等待一个完整的 RTT 都无关紧要。
|
||||
|
||||
在实践中,无损行为几乎是不可能保证的。
|
||||
即使网络及其所有交换机和路由器具有足够的带宽和缓冲区空间,数据包仍然可能丢失:
|
||||
|
||||
* 主机操作系统可能面临内存压力并丢弃数据包。请记住,一台主机可能同时处理数万个数据包流。
|
||||
* CPU 可能无法足够快地消耗掉来自网络接口的传入数据包。这会导致网络适配器本身中的数据包丢失。
|
||||
* 如果 TCP 时间戳不可用,即使一个非常小的 RTT 的连接也可能在丢失恢复期间暂时停止。
|
||||
|
||||
使用 SACK 不会增加 TCP 数据包的大小,除非连接遇到数据包丢失。因此,几乎没有理由禁用此功能。几乎所有的 TCP 协议栈都支持 SACK —— 它通常只在不进行 TCP 批量数据传输的低功耗 IOT 类似设备上才不存在。
|
||||
|
||||
当 Linux 系统接受来自此类设备的连接时,TCP 会自动为受影响的连接禁用 SACK。
|
||||
|
||||
### 总结
|
||||
|
||||
本文中研究的三个 TCP 扩展都与 TCP 性能有关,最好都保留其默认设置:enabled。
|
||||
|
||||
TCP 握手可确保仅使用双方都可以理解的扩展,因此,永远不需因为对等方可能不支持而全局禁用扩展。
|
||||
|
||||
关闭这些扩展会导致严重的性能损失,尤其是在 TCP 窗口缩放和 SACK 的情况下。 可以禁用 TCP 时间戳而不会立即造成不利影响,但是现在没有令人信服的理由这样做了。
|
||||
启用它们还可以支持 TCP 选项,即使在 SYN cookie 生效时也是如此。
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
via: https://fedoramagazine.org/tcp-window-scaling-timestamps-and-sack/
|
||||
|
||||
作者:[Florian Westphal][a]
|
||||
选题:[lujun9972][b]
|
||||
译者:[译者ID](https://github.com/gxlct008)
|
||||
校对:[校对者ID](https://github.com/校对者ID)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]: https://fedoramagazine.org/author/strlen/
|
||||
[b]: https://github.com/lujun9972
|
||||
[1]: https://fedoramagazine.org/wp-content/uploads/2020/08/tcp-window-scaling-816x346.png
|
||||
[2]: https://en.wikipedia.org/wiki/Transmission_Control_Protocol#TCP_segment_structure
|
||||
[3]: https://www.rfc-editor.org/info/rfc7323
|
||||
[4]: https://en.wikipedia.org/wiki/SYN_cookies
|
||||
[5]: https://www.rfc-editor.org/info/rfc2018
|
@ -0,0 +1,284 @@
|
||||
[#]: collector: (lujun9972)
|
||||
[#]: translator: (robsean)
|
||||
[#]: reviewer: ( )
|
||||
[#]: publisher: ( )
|
||||
[#]: url: ( )
|
||||
[#]: subject: (Learn the basics of programming with C)
|
||||
[#]: via: (https://opensource.com/article/20/8/c-programming-cheat-sheet)
|
||||
[#]: author: (Seth Kenlon https://opensource.com/users/seth)
|
||||
|
||||
使用 C 语言学习基本的编程
|
||||
======
|
||||
我们将所有的 C 语言要素放置到一份易读的备忘录上。
|
||||
![备忘录封面图片][1]
|
||||
|
||||
在 1972 年,丹尼斯·里奇在贝尔实验室,在几年前,他和他的团队成员发明了 Unix 。在创建了一个经久不衰的操作系统(至今仍在使用)之后,他需要一种好的方法来编程这些 Unix 计算机,以便它们可用执行新的任务。在现在看来,这很奇怪,但在当时,编程语言相对较少;Fortran,Lisp,[Algol][2] 以及 B 语言都很流行,但是,对于贝尔实验室的研究员们想要做的事情来说,它们还是远远不够的。丹尼斯·里奇创造了他自己的解决方案,表现出一种以程序员的主要特征而闻名的特质,他称之为 C 语言,并且在近 50 年后,它仍在广泛的使用。
|
||||
|
||||
### 为什么你应该学习 C 语言
|
||||
|
||||
今天,这里有很多语言为程序员提供比 C 语言更多是特性。最明显的一种语言是 C++ 语言,一种相当明显的命名方式的语言,它在 C 语言之上构建,创建了一种很好的面向对象的语言。不过,这里有很多其它的语言,它们都很好的存在理由。计算机擅长始终如一的重复,因此任何可预见的东西都足以构建到一种语言中,对程序员来说这意味着更少的工作量。为什么要在 C++ 语言中一行 (`long x = long(n);`) 可以做到的相同的事时,而要花费两行来改写 C 语言中的一个 `int` 为一个 `long` ?
|
||||
|
||||
然而,C 语言在今天仍然有用。
|
||||
|
||||
首先,C 语言是一种最小和简单的的语言。这里没有超出编程基础的非常高级的概念,很大程度上是因为 C 语言简直是现代编程语言的基础之一。例如,C 语言的特性数组,但是它不提供字典(除非你自己写)。当你学习 C 语言时,你将学习编程的构建语句块,它可以帮助你辨别出当前语言的改善和精心制作的设计构思。
|
||||
|
||||
因为 C 语言是一种最小的编程语言,你的应用程序很可能会获得性能上的提升,这在很多其它编程语言中是看不到的。当你考虑你的代码可以执行多快的时候,它很容易被卷入到下面的速度竞赛,因此,询问你是否 _需要_ 更快的速度来完成一项特定的任务是很重要的。与 Python 或 Java 相比,使用 C 语言,你没有必要为每一行的代码所困扰。C 语言程序运行很快。这是 Linux 内核使用 C 语言编写的一个很好的理由。
|
||||
|
||||
最后,C 语言很容易入门,特别是,如果你正在运行 Linux ,你可能已经在运行 C 语言代码,因为 Linux 系统包含 GNU C 库(`glibc`)。为了编写和构建 C 语言程序, 你需要做的全部工作就是安装一个编译器,打开一个文本编辑器,开始编码。
|
||||
|
||||
### 开始学习 C 语言
|
||||
|
||||
如果你正在运行 Linux ,你可以使用你的软件包管理器安装一个 C 编译器。在 Fedora 或 RHEL 上:
|
||||
|
||||
|
||||
```
|
||||
$ sudo dnf install gcc
|
||||
```
|
||||
|
||||
在 Debian 及其衍生系统上:
|
||||
|
||||
|
||||
```
|
||||
$ sudo apt install build-essential
|
||||
```
|
||||
|
||||
在 macOS 上,你可以 [安装 Homebrew][3] ,并使用它来安装 [GCC][4]:
|
||||
|
||||
|
||||
```
|
||||
`$ brew install gcc`
|
||||
```
|
||||
|
||||
在 Windows 上, 你可以使用 [MinGW][5] 安装一套最小的包含 GCC的 GNU 实用程序集。
|
||||
|
||||
在 Linux 或 macOS 上验证你已经安装的 GCC:
|
||||
|
||||
|
||||
```
|
||||
$ gcc --version
|
||||
gcc (GCC) x.y.z
|
||||
Copyright (C) 20XX Free Software Foundation, Inc.
|
||||
```
|
||||
|
||||
在 Windows 上, 提供 EXE 文件的完整路径:
|
||||
|
||||
|
||||
```
|
||||
PS> C:\MinGW\bin\gcc.exe --version
|
||||
gcc.exe (MinGW.org GCC Build-2) x.y.z
|
||||
Copyright (C) 20XX Free Software Foundation, Inc.
|
||||
```
|
||||
|
||||
### C 语法
|
||||
|
||||
C 语言不是一种脚本型的语言。它是一种编译型的语言,这意味着它由 C 编译器处理来产生一个二进制可执行文件。这不同于一种脚本型语言(像:[Bash][6] )或一种混合型语言(像:[Python][7] )。
|
||||
|
||||
在 C 语言中,你创建 _函数_ 来实施你渴望做到的任务。默认情况下,一个名称为 `main` 的函数将被执行。
|
||||
|
||||
这里是一个使用 C 语言写的简单的 "hello world" 程序:
|
||||
|
||||
|
||||
```
|
||||
#include <stdio.h>
|
||||
|
||||
int main() {
|
||||
printf("Hello world");
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
第一行包含一个被称为 `stdio.h` (标准输入和输出) 的 _头文件_ ,它基本上是自由的、非常初级的 C 语言代码,你可以在你自己的程序中重复使用。创建一个由一个基本的输出语句构成的名称为 `main` 的函数。保存这些文本到一个被称为 `hello.c` 的文件中,然后使用 GCC 编译它:
|
||||
|
||||
|
||||
```
|
||||
`$ gcc hello.c --output hello`
|
||||
```
|
||||
|
||||
尝试运行你的 C 语言程序:
|
||||
|
||||
|
||||
```
|
||||
$ ./hello
|
||||
Hello world$
|
||||
```
|
||||
|
||||
#### 返回值
|
||||
|
||||
一个函数在执行后“返回”一些东西是 Unix 哲学的一部分:在成功时不返回任何东西,在失败使返回其它的一些东西(例如,一个错误信息)。这些返回代码通常使用数字(确切地说是整数)表示: 0 表示没有东西,任何大于 0 的数字都表示一些不成功的状态。
|
||||
|
||||
Unix 和 Linux 被设计成在运行成功时要求沉默的是很明智的。这样,你可以总是通过假设在执行一系列命令后没有获得任何错误或警告来期待成功。类似地,在 C 语言中的函数在设计上也不希望出现错误。
|
||||
|
||||
你可以使用一小处使你的出现出现错误的修改来自己看到这些设计:
|
||||
|
||||
|
||||
```
|
||||
include <stdio.h>
|
||||
|
||||
int main() {
|
||||
printf("Hello world");
|
||||
return 1;
|
||||
}
|
||||
```
|
||||
|
||||
编译它:
|
||||
|
||||
|
||||
```
|
||||
$ gcc hello.c --output failer
|
||||
```
|
||||
|
||||
现在使用一个内置的 Linux 测试来运行它。仅在成功时,`&&` 操作符执行一个命令的第二部分。例如:
|
||||
|
||||
|
||||
```
|
||||
$ echo "success" && echo "it worked"
|
||||
success
|
||||
it worked
|
||||
```
|
||||
|
||||
在 _失败_ 时,`||`测试执行一个命令的第二部分。
|
||||
|
||||
|
||||
```
|
||||
$ ls blah || echo "it did not work"
|
||||
ls: cannot access 'blah': No such file or directory
|
||||
it did not work
|
||||
```
|
||||
|
||||
现在,尝试你的程序,在成功时,它 _不_ 返回 0 ;而是返回 1 :
|
||||
|
||||
|
||||
```
|
||||
$ ./failer &&; echo "it worked"
|
||||
String is: hello
|
||||
```
|
||||
|
||||
这个程序成功地执行,但是没有触发第二个命令。
|
||||
|
||||
#### 变量和类型
|
||||
|
||||
在一些语言中,你可以在不具体指定变量所包含的数据的 _类型_ 的情况下创建变量。这些语言如此设计使得解释器对一个变量运行一些测试来企图发现变量什么样的数据类型。例如,Python 知道当你创建一个表达式时,`var=1` 定义了一个整型数,将 `var` 添加一些东西上,这显然是一个整型数。它同样知道当你连接 `hello` 和 `world` 时,单词 `world` 是一个字符串。
|
||||
|
||||
C 语言不会为你做任何这些识别和调查;你必需定义你自己的变量类型。这里有几种变量类型,包括整型(int),字符型(char),浮点型(float),布尔型(Boolean)。
|
||||
|
||||
你可能也注意到这里没有字符串类型。与 Python 和 Java 和 Lua 以及其它的编程语言不同,C 语言没有字符串类型,而是将字符串看作一个字符数组。
|
||||
|
||||
这里是一些简单的代码,它建立了一个 `char` 数组变量,然后使用 [printf][9] 将数组变量和一段简单的信息打印到你的屏幕上:
|
||||
|
||||
|
||||
```
|
||||
#include <stdio.h>
|
||||
|
||||
int main() {
|
||||
char var[6] = "hello";
|
||||
printf("Your string is: %s\r\n",var);
|
||||
}
|
||||
```
|
||||
|
||||
你可能会注意到,这个代码示例向一个由五个字母组成的单词提供六个字符的空间。这是因为在字符串的结尾有处一个隐藏的终止符,终止符占用数组中的一个字节。你可以通过编译和执行代码来运行它:
|
||||
|
||||
|
||||
```
|
||||
$ gcc hello.c --output hello
|
||||
$ ./hello
|
||||
hello
|
||||
```
|
||||
|
||||
### 函数
|
||||
|
||||
和其它的编程语言一样,C 函数采取可选函数。你可以通过定义你希望一个函数接受的数据类型来将参数从一个函数传递到另一个函数:
|
||||
|
||||
|
||||
```
|
||||
#include <stdio.h>
|
||||
|
||||
int printmsg(char a[]) {
|
||||
printf("String is: %s\r\n",a);
|
||||
}
|
||||
|
||||
int main() {
|
||||
char a[6] = "hello";
|
||||
printmsg(a);
|
||||
return 0;
|
||||
}
|
||||
```
|
||||
|
||||
使用这种方法简单将一个函数分解为两个函数的方法并不是非常有用,但是它证明 `main` 默认运行以及如何在函数之间传递数据。
|
||||
|
||||
### 条件语句
|
||||
|
||||
在真实的编程中,你通常希望你的代码根据数据做出判断。这是使用This is done with _条件_ 语句完成的,并且 `if` 语句是它们中最基础的一个语句。
|
||||
|
||||
为了使这个示例程序更具动态性,你可以包含 `string.h` 头文件,它包含用于检查(顾名思义)字符串的代码。尝试使用来自 `string.h` 文件中的 `strlen` 函数测试传递给 `printmsg` 函数的字符串是否大于 0 :
|
||||
|
||||
|
||||
```
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
int printmsg(char a[]) {
|
||||
size_t len = strlen(a);
|
||||
if ( len > 0) {
|
||||
printf("String is: %s\r\n",a);
|
||||
}
|
||||
}
|
||||
|
||||
int main() {
|
||||
char a[6] = "hello";
|
||||
printmsg(a);
|
||||
return 1;
|
||||
}
|
||||
```
|
||||
|
||||
正如在这个示例中所实现的,示例条件永远都不会是真实的,因为所提供的字符串总是 "hello" ,它的长度总是大于 0 。`echo` 命令的这个不够认真的重新实施的最后接触是接受来自用户的输入。
|
||||
|
||||
### 命令参数
|
||||
|
||||
`stdio.h` 文件包含的代码在每次程序启动时提供两个参数: 一个是包含在命令 (`argc`) 中的项目总数,一个是包含每个项目 (`argv`) 的数组。例如, 假设你发出这个虚构的命令:
|
||||
|
||||
|
||||
```
|
||||
$ foo -i bar
|
||||
```
|
||||
|
||||
`argc` 是 3 个,`argv` 的内容是:
|
||||
|
||||
* `argv[0] = foo`
|
||||
* `argv[1] = -i`
|
||||
* `argv[2] = bar`
|
||||
|
||||
|
||||
|
||||
你可以修改示例 C 语言程序来接受 `argv[2]` 作为字符串,而不是默认来接受`hello` 吗?
|
||||
|
||||
### 命令式编程语言
|
||||
|
||||
C 语言是一种命令式编程语言。它不是面向对象的,它没有类结构。使用 C 语言的经验可以教你很多关于如何处理数据和如何更好地管理你的代码运行时生成的数据。充分地使用 C 语言,你最后能够编写其它语言 (例如 Python 和 Lua) 能够使用的库。
|
||||
|
||||
为了学习更多关于 C 的知识,你需要使用它。在 `/usr/include/` 中查找有用的 C 语言头文件,并且看看你可以做什么小任务来使其对你学习 C 语言有用。 正如你学习时,使用 [Jim Hall][12] 的 FreeDOS 的 [C 语言忘备录][11]。它在一张双面纸忘备录上放置所有的基本要素,因此在你实施练习时,你可以立即访问 C 语言语法的所有要素。
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
via: https://opensource.com/article/20/8/c-programming-cheat-sheet
|
||||
|
||||
作者:[Seth Kenlon][a]
|
||||
选题:[lujun9972][b]
|
||||
译者:[robsean](https://github.com/robsean)
|
||||
校对:[校对者ID](https://github.com/校对者ID)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]: https://opensource.com/users/seth
|
||||
[b]: https://github.com/lujun9972
|
||||
[1]: https://opensource.com/sites/default/files/styles/image-full-size/public/lead-images/coverimage_cheat_sheet.png?itok=lYkNKieP (Cheat Sheet cover image)
|
||||
[2]: https://opensource.com/article/20/6/algol68
|
||||
[3]: https://opensource.com/article/20/6/homebrew-mac
|
||||
[4]: https://gcc.gnu.org/
|
||||
[5]: https://opensource.com/article/20/8/gnu-windows-mingw
|
||||
[6]: https://opensource.com/resources/what-bash
|
||||
[7]: https://opensource.com/resources/python
|
||||
[8]: http://www.opengroup.org/onlinepubs/009695399/functions/printf.html
|
||||
[9]: https://opensource.com/article/20/8/printf
|
||||
[10]: http://www.opengroup.org/onlinepubs/009695399/functions/strlen.html
|
||||
[11]: https://opensource.com/downloads/c-programming-cheat-sheet
|
||||
[12]: https://opensource.com/users/jim-hall
|
@ -0,0 +1,110 @@
|
||||
[#]: collector: (lujun9972)
|
||||
[#]: translator: (wxy)
|
||||
[#]: reviewer: (wxy)
|
||||
[#]: publisher: ( )
|
||||
[#]: url: ( )
|
||||
[#]: subject: (Linux Jargon Buster: What is a Package Manager in Linux? How Does it Work?)
|
||||
[#]: via: (https://itsfoss.com/package-manager/)
|
||||
[#]: author: (Abhishek Prakash https://itsfoss.com/author/abhishek/)
|
||||
|
||||
Linux 黑话解释:什么是包管理器?它是如何工作的?
|
||||
======
|
||||
|
||||
[Linux 发行版之间有什么不同][1]的要点之一是包管理。在这篇 Linux 黑话解释中,你将了解 Linux 中的打包和包管理器。你将了解什么是包,什么是包管理器,它们是如何工作的,以及有什么样的包管理器可用。
|
||||
|
||||
### 什么是包管理器?
|
||||
|
||||
简单来说,“<ruby>包管理器<rt>package manager</rt></ruby>”(或“软件包管理器”)是一种工具,它允许用户在操作系统上安装、删除、升级、配置和管理软件包。软件包管理器可以是像“软件中心”这样的图形化应用,也可以是像 [apt-get][2] 或 [pacman][3] 这样的命令行工具。
|
||||
|
||||
你会发现我经常在教程和文章中使用“包”这个词。要了解包管理器,你必须了解什么是包。
|
||||
|
||||
### 什么是包?
|
||||
|
||||
一个“<ruby>包<rt>package</rt></ruby>”(或“软件包”)通常指的是一个应用程序,它可以是一个 GUI 应用程序、命令行工具或(其他软件程序需要的)软件库。包本质上是一个存档文件,包含二进制可执行文件、配置文件,有时还包含依赖关系的信息。
|
||||
|
||||
在旧时代,[软件曾经是从它的源代码安装的][4]。你会参考一个文件(通常命名为 `README`),看看它需要什么软件组件、二进制文件的位置。它通常包括一个配置脚本或 `Makefile`。你必须自己编译该软件或自己处理所有的依赖关系(有些软件需要安装其他软件)。
|
||||
|
||||
为了摆脱这种复杂性,Linux 发行版创建了自己的打包格式,为终端用户提供随时可用的二进制文件(预编译软件),以便安装软件,同时提供一些[元数据][5](版本号、描述)和依赖关系。
|
||||
|
||||
这就像烤蛋糕与买蛋糕一样。
|
||||
|
||||
![][6]
|
||||
|
||||
大约在上世纪 90 年代中期,Debian 创建了 DEB 打包格式(`.deb`),Red Hat Linux 创建了 RPM(Red Hat Package Manager 的缩写)打包系统(`.rpm`)。编译源代码的方式仍然存在,但现在是可选的。
|
||||
|
||||
要与打包系统交互或使用打包系统,你需要一个包管理器。
|
||||
|
||||
### 包管理器是如何工作的?
|
||||
|
||||
请记住,包管理器是一个通用的概念,它并不是 Linux 独有的。你会经常发现各种软件或编程语言的包管理器。有[只是针对 Python 包的 PIP 包管理器][7]。甚至 [Atom 编辑器也有自己的包管理器][8]。
|
||||
|
||||
由于本文的重点是 Linux,所以我会从 Linux 的角度出发。不过,这里的大部分解释也可以应用于一般的包管理器。
|
||||
|
||||
我创建了这个图(基于 SUSE Wiki),这样你就可以很容易理解包管理器是如何工作的。
|
||||
|
||||
![][9]
|
||||
|
||||
几乎所有的 Linux 发行版都有“<ruby>软件仓库<rt>software repository</rt></ruby>”,它基本上是软件包的集合。是的,可以有不止一个软件库。软件库包含不同种类的软件包。
|
||||
|
||||
软件仓库也有元数据文件,其中包含了软件包的信息,如软件包的名称、版本号、软件包的描述和软件仓库名称等。这就是你在 Ubuntu/Debian 中使用 [apt show 命令][10]所看到的。
|
||||
|
||||
你的系统上的包管理器首先会与元数据进行交互。包管理器在你的系统上创建了一个元数据的本地缓存。当你运行包管理器的更新选项(例如 `apt update`)时,它会通过引用仓库中的元数据来更新本地元数据缓存。
|
||||
|
||||
当你运行软件包管理器的安装命令(例如 `apt install package_name`)时,软件包管理器会引用这个缓存。如果它在缓存中找到了包的信息,它就会使用互联网连接到相应的仓库,并在你的系统上安装之前先下载包。
|
||||
|
||||
一个包可能有依赖关系。意思是说,它可能需要安装其他软件包。软件包管理器通常会处理这些依赖关系,并将其与你正在安装的软件包一起自动安装。
|
||||
|
||||
![Linux 中包管理器会处理依赖关系][11]
|
||||
|
||||
同样,当你使用包管理器删除一个包时,它要么自动删除,要么通知你系统有未使用的包可以清理。
|
||||
|
||||
除了安装、删除这些显而易见的任务外,你还可以使用包管理器对包进行配置,并根据自己的需要进行管理。例如,你可以在常规的系统更新中[防止升级某个包的版本][12]。你的包管理器可能还能做很多事情。
|
||||
|
||||
### 不同种类的包管理器
|
||||
|
||||
包管理器因打包系统而异,但同一打包系统却可能有多个包管理器。
|
||||
|
||||
例如,RPM 有 [Yum][13] 和 [DNF][14] 包管理器。对于 DEB,你有 `apt-get`、[aptitude][15] 等基于命令行的包管理器。
|
||||
|
||||
![Synaptic 包管理器][16]
|
||||
|
||||
软件包管理器不一定是基于命令行的,也有图形化的软件包管理工具,比如 [Synaptic][17]。你的发行版的“软件中心”也是一个软件包管理器,即使它在底层运行的是 `apt-get` 或 DNF。
|
||||
|
||||
### 结论
|
||||
|
||||
我不想进一步详细介绍这个话题,虽然我可以继续说下去,但这将偏离本主题的目标 —— 即让你对 Linux 中的包管理器有一个基本的了解。
|
||||
|
||||
我暂时忽略了新的通用打包格式,比如 Snap 和 Flatpak。
|
||||
|
||||
我希望你对 Linux 中的包管理系统有更好的理解。如果你还有困惑,或者你对这个主题有一些问题,请发表评论。我会尽量回答你的问题,如果需要的话,我会在本文中更新新的内容。
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
via: https://itsfoss.com/package-manager/
|
||||
|
||||
作者:[Abhishek Prakash][a]
|
||||
选题:[lujun9972][b]
|
||||
译者:[wxy](https://github.com/wxy)
|
||||
校对:[wxy](https://github.com/wxy)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]: https://itsfoss.com/author/abhishek/
|
||||
[b]: https://github.com/lujun9972
|
||||
[1]: https://itsfoss.com/what-is-linux/
|
||||
[2]: https://itsfoss.com/apt-vs-apt-get-difference/
|
||||
[3]: https://itsfoss.com/pacman-command/
|
||||
[4]: https://itsfoss.com/install-software-from-source-code/
|
||||
[5]: https://www.computerhope.com/jargon/m/metadata.htm
|
||||
[6]: https://i1.wp.com/itsfoss.com/wp-content/uploads/2020/10/source-code-comilation-vs-packaging.png?resize=800%2C450&ssl=1
|
||||
[7]: https://itsfoss.com/install-pip-ubuntu/
|
||||
[8]: https://itsfoss.com/install-packages-in-atom/
|
||||
[9]: https://i2.wp.com/itsfoss.com/wp-content/uploads/2020/10/linux-package-manager-explanation.png?resize=800%2C450&ssl=1
|
||||
[10]: https://itsfoss.com/apt-search-command/
|
||||
[11]: https://i1.wp.com/itsfoss.com/wp-content/uploads/2020/10/package-manager-handling-dependencies-in-linux.png?resize=800%2C450&ssl=1
|
||||
[12]: https://itsfoss.com/prevent-package-update-ubuntu/
|
||||
[13]: https://fedoraproject.org/wiki/Yum
|
||||
[14]: https://fedoraproject.org/wiki/DNF
|
||||
[15]: https://wiki.debian.org/Aptitude
|
||||
[16]: https://i0.wp.com/itsfoss.com/wp-content/uploads/2020/06/see-packages-by-repositories-synaptic.png?resize=799%2C548&ssl=1
|
||||
[17]: https://itsfoss.com/synaptic-package-manager/
|
@ -1,5 +1,5 @@
|
||||
[#]: collector: (lujun9972)
|
||||
[#]: translator: ( )
|
||||
[#]: translator: (geekpi)
|
||||
[#]: reviewer: ( )
|
||||
[#]: publisher: ( )
|
||||
[#]: url: ( )
|
||||
@ -7,32 +7,32 @@
|
||||
[#]: via: (https://www.2daygeek.com/linux-remove-delete-physical-volume-pv-from-volume-group-vg-in-lvm/)
|
||||
[#]: author: (Magesh Maruthamuthu https://www.2daygeek.com/author/magesh/)
|
||||
|
||||
How to Remove Physical Volume from a Volume Group in LVM
|
||||
如何从 LVM 的卷组中删除物理卷?
|
||||
======
|
||||
|
||||
If a device is no longer need for use by LVM, you can use the vgreduce command to remove physical volumes from a volume group.
|
||||
如果 LVM 不再需要使用某个设备,你可以使用 vgreduce 命令从卷组中删除物理卷。
|
||||
|
||||
The Vgreduce command shrinks the capacity of a volume group by removing a physical volume.
|
||||
vgreduce 命令通过删除物理卷来缩小卷组的容量。
|
||||
|
||||
But make sure that the physical volume is not used by any logical volumes using the pvdisplay command.
|
||||
但要确保物理卷没有被任何逻辑卷使用,请使用 pvdisplay 命令。
|
||||
|
||||
If the physical volume is still being used, you must transfer the data to another physical volume using the pvmove command.
|
||||
如果物理卷仍在使用,你必须使用 pvmove 命令将数据转移到另一个物理卷。
|
||||
|
||||
Once the data is moved, it can be removed from the volume group.
|
||||
数据转移后,它就可以从卷组中删除。
|
||||
|
||||
Finally use the pvremove command to remove the LVM label and LVM metadata on the empty physical volume.
|
||||
最后使用 pvremove 命令删除空物理卷上的 LVM 标签和 LVM 元数据。
|
||||
|
||||
* **Part-1: [How to Create/Configure LVM (Logical Volume Management) in Linux][1]**
|
||||
* **Part-2: [How to Extend/Increase LVM’s (Logical Volume Resize) in Linux][2]**
|
||||
* **Part-3: [How to Reduce/Shrink LVM’s (Logical Volume Resize) in Linux][3]**
|
||||
* **第一部分:[如何在 Linux 中创建/配置 LVM(逻辑卷管理)][1]**。
|
||||
* **第二部分:[如何在 Linux 中扩展/增加 LVM 大小(逻辑卷调整)][2]**。
|
||||
* **第三部分:[如何在 Linux 中减少/缩小 LVM 大小(逻辑卷调整)][3]**。
|
||||
|
||||
|
||||
|
||||
![][4]
|
||||
|
||||
### 1) Moving Extents to Existing Physical Volumes
|
||||
### 1) 将扩展移动到现有物理卷上
|
||||
|
||||
Use the pvs command to check if the desired physical volume (we plan to remove the **“/dev/sdb1”** disk in LVM) is used or not.
|
||||
使用 pvs 命令检查是否使用了所需的物理卷(我们计划删除 LVM 中的 **“/dev/sdb1”** 磁盘)。
|
||||
|
||||
```
|
||||
# pvs -o+pv_used
|
||||
@ -43,9 +43,9 @@ PV VG Fmt Attr PSize PFree Used
|
||||
/dev/sdc1 myvg lvm2 a- 17.15G 12.15G 5.00G
|
||||
```
|
||||
|
||||
If this is used, check to see if there are enough free extents on the other physics volumes in the volume group.
|
||||
如果使用了,请检查卷组中的其他物理卷是否有足够的空闲空间。
|
||||
|
||||
If so, you can run the pvmove command on the device you want to remove. Extents will be distributed to other devices.
|
||||
如果有的话,你可以在需要删除的设备上运行 pvmove 命令。扩展将被分配到其他设备上。
|
||||
|
||||
```
|
||||
# pvmove /dev/sdb1
|
||||
@ -57,7 +57,7 @@ If so, you can run the pvmove command on the device you want to remove. Extents
|
||||
/dev/sdb1: Moved: 100.0%
|
||||
```
|
||||
|
||||
When the pvmove command is complete. Re-use the pvs command to check whether the physics volume is free or not.
|
||||
当 pvmove 命令完成后。再次使用 pvs 命令检查物理卷是否有空闲。
|
||||
|
||||
```
|
||||
# pvs -o+pv_used
|
||||
@ -68,25 +68,25 @@ PV VG Fmt Attr PSize PFree Used
|
||||
/dev/sdc1 myvg lvm2 a- 17.15G 12.15G 5.00G
|
||||
```
|
||||
|
||||
If it’s free, use the vgreduce command to remove the physical volume /dev/sdb1 from the volume group.
|
||||
如果它是空闲的,使用 vgreduce 命令从卷组中删除物理卷 /dev/sdb1。
|
||||
|
||||
```
|
||||
# vgreduce myvg /dev/sdb1
|
||||
Removed "/dev/sdb1" from volume group "myvg"
|
||||
```
|
||||
|
||||
Finally, run the pvremove command to remove the disk from the LVM configuration. Now, the disk is completely removed from the LVM and can be used for other purposes.
|
||||
最后,运行 pvremove 命令从 LVM 配置中删除磁盘。现在,磁盘已经完全从 LVM 中移除,可以用于其他用途。
|
||||
|
||||
```
|
||||
# pvremove /dev/sdb1
|
||||
Labels on physical volume "/dev/sdb1" successfully wiped.
|
||||
```
|
||||
|
||||
### 2) Moving Extents to a New Disk
|
||||
### 2) 移动扩展到新磁盘
|
||||
|
||||
If you don’t have enough free extents on the other physics volumes in the volume group. Add new physical volume using the steps below.
|
||||
如果你在卷组中的其他物理卷上没有足够的可用扩展。使用以下步骤添加新的物理卷。
|
||||
|
||||
Request new LUNs from the storage team. Once this is allocated, run the following commands to **[discover newly added LUNs or disks in Linux][5]**.
|
||||
向存储组申请新的 LUN。分配完毕后,运行以下命令来**[在 Linux 中发现新添加的 LUN 或磁盘][5]**。
|
||||
|
||||
```
|
||||
# ls /sys/class/scsi_host
|
||||
@ -101,21 +101,21 @@ host0
|
||||
# fdisk -l
|
||||
```
|
||||
|
||||
Once the disk is detected in the OS, use the pvcreate command to create the physical volume.
|
||||
操作系统中检测到磁盘后,使用 pvcreate 命令创建物理卷。
|
||||
|
||||
```
|
||||
# pvcreate /dev/sdd1
|
||||
Physical volume "/dev/sdd1" successfully created
|
||||
```
|
||||
|
||||
Use the following command to add new physical volume /dev/sdd1 to the existing volume group vg01.
|
||||
使用以下命令将新的物理卷 /dev/sdd1 添加到现有卷组 vg01 中。
|
||||
|
||||
```
|
||||
# vgextend vg01 /dev/sdd1
|
||||
Volume group "vg01" successfully extended
|
||||
```
|
||||
|
||||
Now, use the pvs command to see the new disk **“/dev/sdd1”** that you have added.
|
||||
现在,使用 pvs 命令查看你添加的新磁盘 **“/dev/sdd1”**。
|
||||
|
||||
```
|
||||
# pvs -o+pv_used
|
||||
@ -127,7 +127,7 @@ PV VG Fmt Attr PSize PFree Used
|
||||
/dev/sdd1 myvg lvm2 a- 60.00G 60.00G 0
|
||||
```
|
||||
|
||||
Use the pvmove command to move the data from /dev/sdb1 to /dev/sdd1.
|
||||
使用 pvmove 命令将数据从 /dev/sdb1 移动到 /dev/sdd1。
|
||||
|
||||
```
|
||||
# pvmove /dev/sdb1 /dev/sdd1
|
||||
@ -139,7 +139,7 @@ Use the pvmove command to move the data from /dev/sdb1 to /dev/sdd1.
|
||||
/dev/sdb1: Moved: 100.0%
|
||||
```
|
||||
|
||||
After the data is moved to the new disk. Re-use the pvs command to check whether the physics volume is free.
|
||||
数据移动到新磁盘后。再次使用 pvs 命令检查物理卷是否空闲。
|
||||
|
||||
```
|
||||
# pvs -o+pv_used
|
||||
@ -151,14 +151,14 @@ PV VG Fmt Attr PSize PFree Used
|
||||
/dev/sdd1 myvg lvm2 a- 60.00G 10.00G 50.00G
|
||||
```
|
||||
|
||||
If it’s free, use the vgreduce command to remove the physical volume /dev/sdb1 from the volume group.
|
||||
如果空闲,使用 vgreduce 命令从卷组中删除物理卷 /dev/sdb1。
|
||||
|
||||
```
|
||||
# vgreduce myvg /dev/sdb1
|
||||
Removed "/dev/sdb1" from volume group "myvg"
|
||||
```
|
||||
|
||||
Finally, run the pvremove command to remove the disk from the LVM configuration. Now, the disk is completely removed from the LVM and can be used for other purposes.
|
||||
最后,运行 pvremove 命令从 LVM 配置中删除磁盘。现在,磁盘已经完全从 LVM 中移除,可以用于其他用途。
|
||||
|
||||
```
|
||||
# pvremove /dev/sdb1
|
||||
@ -171,7 +171,7 @@ via: https://www.2daygeek.com/linux-remove-delete-physical-volume-pv-from-volume
|
||||
|
||||
作者:[Magesh Maruthamuthu][a]
|
||||
选题:[lujun9972][b]
|
||||
译者:[译者ID](https://github.com/译者ID)
|
||||
译者:[geekpi](https://github.com/geekpi)
|
||||
校对:[校对者ID](https://github.com/校对者ID)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
@ -0,0 +1,136 @@
|
||||
[#]: collector: (lujun9972)
|
||||
[#]: translator: (rakino)
|
||||
[#]: reviewer: ( )
|
||||
[#]: publisher: ( )
|
||||
[#]: url: ( )
|
||||
[#]: subject: (6 Essential Things To Do After Installing Manjaro Linux)
|
||||
[#]: via: (https://itsfoss.com/things-to-do-after-installing-manjaro/)
|
||||
[#]: author: (Dimitrios Savvopoulos https://itsfoss.com/author/dimitrios/)
|
||||
|
||||
安装 Manjaro Linux 后要做的 6 件事
|
||||
======
|
||||
|
||||
所以,你刚刚[全新安装了 Manjaro Linux][1],那么现在该做什么呢?
|
||||
|
||||
下面是我推荐你在安装后进行的一些步骤。
|
||||
|
||||
不过说实话,这些都是我在安装 Manjaro 后喜欢做的事,根据你的需求,步骤可能会有所不同。
|
||||
|
||||
### 推荐在安装完 Manjaro Linux 后去做的事
|
||||
|
||||
![][2]
|
||||
|
||||
我使用的是 Xfce 版的 Manjaro,但这些步骤也适用于 [Manjaro][3] 的其它桌面环境版本。
|
||||
|
||||
#### 1、设置最快的镜像
|
||||
|
||||
在更新系统之前,我建议先整理一下镜像列表。在刷新 Manjaro 系统和从软件仓库下载软件包的时候,优化后的镜像列表会对系统的性能产生明显的影响。
|
||||
|
||||
打开终端模拟器并输入以下命令:
|
||||
|
||||
```
|
||||
sudo pacman-mirrors --fasttrack
|
||||
```
|
||||
|
||||
![][4]
|
||||
|
||||
#### 2、更新系统
|
||||
|
||||
保持系统更新可以降低安全漏洞的发生机率,在安装新的软件之前也建议刷新系统的软件仓库。
|
||||
|
||||
你可以用下面的命令来[更新 Manjaro 系统][5]:
|
||||
|
||||
```
|
||||
sudo pacman -Syu
|
||||
```
|
||||
|
||||
![][6]
|
||||
|
||||
#### 3、启用 AUR,Snap 以及 Flatpak 支持
|
||||
|
||||
[<ruby>Arch 用户仓库<rt>Arch User Repository</rt></ruby>(AUR)][7]是用户选择[基于 Arch Linux 的系统][8]的一个主要理由。你可以在 AUR 中访问到大量的附加软件。
|
||||
|
||||
(译注:AUR 中的 PKGBUILD 均为用户上传且未经审核,使用者需要自负责任,在构建软件包前请注意检查其中内容是否合理。)
|
||||
|
||||
作为可选项,你可以直接在 Pamac 图形化软件包管理器中启用对 [Snap][9] 以及 [Flatpak][10] 的支持。
|
||||
|
||||
![][11]
|
||||
|
||||
#### 启用 TRIM(仅 SSD)
|
||||
|
||||
如果你的根分区已经安装在了 SSD 上,启用 [TRIM][12] 会是你在安装 Manjaro 后需要做的一件事。TRIM 会帮助清理 SSD 中的块,从而延长 SSD 的使用寿命。
|
||||
|
||||
要在 Manjaro 中启用 TRIM,请在终端中输入以下命令:
|
||||
|
||||
```
|
||||
sudo systemctl enable fstrim.timer
|
||||
```
|
||||
|
||||
![][13]
|
||||
|
||||
#### 5、安装内核(高级用户)
|
||||
|
||||
我在 [Manjaro 评测][14]中提到的一个话题就是,你可以在图形界面中轻易地更换内核。
|
||||
|
||||
喜欢使用命令行?你也可以在终端中列出系统中已安装的内核以及安装新的内核。
|
||||
|
||||
列出已安装的内核:
|
||||
|
||||
```
|
||||
mhwd-kernel -li
|
||||
```
|
||||
|
||||
**安装新内核**(以最新的 5.8 版本内核为例)**:**
|
||||
|
||||
```
|
||||
sudo mhwd-kernel -i linux58
|
||||
```
|
||||
|
||||
![][15]
|
||||
|
||||
#### 6、安装微软 TrueType 字体(如果需要)
|
||||
|
||||
我经常在个人电脑上编辑工作文件,因此我需要 Times New Roman 或 Arial 等微软字体。
|
||||
|
||||
如果你也需要使用微软字体,可以从 [AUR][7] 中取得这个[软件包][16]。如果你想要在命令行中管理 AUR 软件包,可以选择安装一个 [AUR 助手][17]。
|
||||
|
||||
![][18]
|
||||
|
||||
#### 结论
|
||||
|
||||
如果你想在一个预配置、为桌面优化的发行版上享受 Arch Linux 的优点,[Manjaro是一个很好的发行版][19]。虽然它预置了很多东西,但由于每个人设置和需求的不同,有几个步骤是不能提前完成的。
|
||||
|
||||
除开已经提到的步骤,还有哪一步对你来说是必不可少的?请在下面的评论中告诉我们。
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
via: https://itsfoss.com/things-to-do-after-installing-manjaro/
|
||||
|
||||
作者:[Dimitrios Savvopoulos][a]
|
||||
选题:[lujun9972][b]
|
||||
译者:[rakino](https://github.com/rakino)
|
||||
校对:[校对者ID](https://github.com/校对者ID)
|
||||
|
||||
本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译,[Linux中国](https://linux.cn/) 荣誉推出
|
||||
|
||||
[a]: https://itsfoss.com/author/dimitrios/
|
||||
[b]: https://github.com/lujun9972
|
||||
[1]: https://itsfoss.com/install-manjaro-linux/
|
||||
[2]: https://i0.wp.com/itsfoss.com/wp-content/uploads/2020/10/things-to-do-after-installing-manjaro.jpg?resize=800%2C450&ssl=1
|
||||
[3]: https://manjaro.org
|
||||
[4]: https://i0.wp.com/itsfoss.com/wp-content/uploads/2020/08/manjaro-fasttrack.png?resize=800%2C600&ssl=1
|
||||
[5]: https://itsfoss.com/update-arch-linux/
|
||||
[6]: https://i1.wp.com/itsfoss.com/wp-content/uploads/2020/03/sudo-pacman-Syu.png?resize=800%2C504&ssl=1
|
||||
[7]: https://itsfoss.com/aur-arch-linux/
|
||||
[8]: https://itsfoss.com/arch-based-linux-distros/
|
||||
[9]: https://itsfoss.com/use-snap-packages-ubuntu-16-04/
|
||||
[10]: https://itsfoss.com/flatpak-guide/
|
||||
[11]: https://i2.wp.com/itsfoss.com/wp-content/uploads/2020/08/pamac-2.png?resize=800%2C600&ssl=1
|
||||
[12]: https://en.wikipedia.org/wiki/Trim_(computing)
|
||||
[13]: https://i0.wp.com/itsfoss.com/wp-content/uploads/2020/08/fstrim.timer_.png?resize=800%2C600&ssl=1
|
||||
[14]: https://itsfoss.com/manjaro-linux-review/
|
||||
[15]: https://i2.wp.com/itsfoss.com/wp-content/uploads/2020/08/manjaro-cli-kernels.png?resize=800%2C600&ssl=1
|
||||
[16]: https://aur.archlinux.org/packages/ttf-ms-fonts
|
||||
[17]: https://itsfoss.com/best-aur-helpers/
|
||||
[18]: https://i1.wp.com/itsfoss.com/wp-content/uploads/2020/08/ttf-ms-fonts.png?resize=800%2C600&ssl=1
|
||||
[19]: https://itsfoss.com/why-use-manjaro-linux/
|
Loading…
Reference in New Issue
Block a user