在 Linux 命令行中自定义文本颜色

>
> 在 Linux 命令行当中使用不同颜色以期提供一种根据文件类型来识别文件的简单方式。你可以修改这些颜色，但是在做之前应该对你做的事情有充分的理由。
>
>
>

![](/data/attachment/album/201807/12/105947xztc1kpcvv8o9izn.jpg)

如果你在 Linux 命令行上花费了大量的时间（如果没有，那么你可能不会读这篇文章），你无疑注意到了 `ls` 以多种不同的颜色显示文件。你可能也注意到了一些区别 —— 目录是一种颜色，可执行文件是另一种颜色等等。

这一切是如何发生的呢？以及，你可以选择哪些选项来改变颜色分配可能就不是很多人都知道的。

一种方法是运行 `dircolors` 命令得到一大堆展示了如何指定这些颜色的数据。它会显示以下这些东西：

```
$ dircolors
LS_COLORS='rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do
=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:mi=00:su=37;41:sg
=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01
;31:*.tgz=01;31:*.arc=01;31:*.arj=01;31:*.taz=01;31:*.lha=01
;31:*.lz4=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=0
1;31:*.tzo=01;31:*.t7z=01;31:*.zip=01;31:*.z=01;31:*.Z=01;31
:*.dz=01;31:*.gz=01;31:*.lrz=01;31:*.lz=01;31:*.lzo=01;31:*.
xz=01;31:*.zst=01;31:*.tzst=01;31:*.bz2=01;31:*.bz=01;31:*.t
bz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.j
ar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.a
lz=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.r
z=01;31:*.cab=01;31:*.jpg=01;35:*.jpeg=01;35:*.mjpg=01;35:*.
mjpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:
*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:
*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;3
5:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;
35:*.mkv=01;35:*.webm=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01
;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01
;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01
;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;3
5:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.ogv=01;3
5:*.ogx=01;35:*.aac=00;36:*.au=00;36:*.flac=00;36:*.m4a=00;3
6:*.mid=00;36:*.midi=00;36:*.mka=00;36:*.mp3=00;36:*.mpc=00;
36:*.ogg=00;36:*.ra=00;36:*.wav=00;36:*.oga=00;36:*.opus=00;
36:*.spx=00;36:*.xspf=00;36:';
export LS_COLORS

```

如果你擅长解析文件，那么你可能会注意到这个列表有一种<ruby> 模式 <rt>  patten </rt></ruby>。用冒号分隔开，你会看到这样的东西：

```
$ dircolors | tr ":" "\n" | head -10
LS_COLORS='rs=0
di=01;34
ln=01;36
mh=00
pi=40;33
so=01;35
do=01;35
bd=40;33;01
cd=40;33;01
or=40;31;01

```

OK，这里有一个模式 —— 一系列定义，有一到三个数字组件。我们来看看其中的一个定义。

```
pi=40;33

```

有些人可能会问的第一个问题是“pi 是什么？”在这里，我们研究的是颜色和文件类型，所以这显然不是以 3.14 开头的那个有趣的数字。当然不是，这个 “pi” 代表 “pipe（管道）” —— Linux 系统上的一种特殊类型的文件，它可以将数据从一个程序传递给另一个程序。所以，让我们建立一个管道。

```
$ mknod /tmp/mypipe p
$ ls -l /tmp/mypipe
prw-rw-r-- 1 shs shs 0 May 1 14:00 /tmp/mypipe

```

当我们在终端窗口中查看我们的管道和其他几个文件时，颜色差异非常明显。

![font colors](/data/attachment/album/201807/12/105959yu66w4wwq4e2t6tt.jpg)

在 `pi` 的定义中（如上所示），“40” 使文件在终端（或 PuTTY）窗口中使用黑色背景显示，31 使字体颜色变红。管道是特殊的文件，这种特殊的处理使它们在目录列表中突出显示。

`bd` 和 `cd` 定义是相同的 —— `40;33;01`，它有一个额外的设置。这个设置会导致 <ruby> 块设备 <rt>  block device </rt></ruby>（bd）和 <ruby> 字符设备 <rt>  character device </rt></ruby>（cd）以黑色背景，橙色字体和另一种效果显示 —— 字符将以粗体显示。

以下列表显示由<ruby> 文件类型 <rt>  file type </rt></ruby>所指定的颜色和字体分配：

```
setting         file type
=======         =========
rs=0            reset to no color
di=01;34        directory
ln=01;36        link
mh=00           multi-hard link
pi=40;33        pipe
so=01;35        socket
do=01;35        door
bd=40;33;01     block device
cd=40;33;01     character device
or=40;31;01     orphan
mi=00           missing?
su=37;41        setuid
sg=30;43        setgid
ca=30;41        file with capability
tw=30;42        directory with sticky bit and world writable
ow=34;42        directory that is world writable
st=37;44        directory with sticky bit
ex=01;93        executable

```

你可能已经注意到，在 `dircolors` 命令输出中，我们的大多数定义都以星号开头（例如，`*.wav=00;36`）。这些按<ruby> 文件扩展名 <rt>  file extension </rt></ruby>而不是文件类型定义显示属性。这有一个示例：

```
$ dircolors | tr ":" "\n" | tail -10
*.mpc=00;36
*.ogg=00;36
*.ra=00;36
*.wav=00;36
*.oga=00;36
*.opus=00;36
*.spx=00;36
*.xspf=00;36
';
export LS_COLORS

```

这些设置（上面列表中所有的 `00;36`）将使这些文件名以青色显示。可用的颜色如下所示。

![all colors](/data/attachment/album/201807/12/110003t752g2b5obxfqx2g.jpg)

### 如何改变设置

你要使用 `ls` 的别名来打开颜色显示功能。这通常是 Linux 系统上的默认设置，看起来是这样的：

```
alias ls='ls --color=auto'

```

如果要关闭字体颜色，可以运行 `unalias ls` 命令，然后文件列表将仅以默认字体颜色显示。

你可以通过修改 `$LS_COLORS` 设置和导出修改后的设置来更改文本颜色。

```
$ export LS_COLORS='rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;...

```

注意：上面的命令由于太长被截断了。

如果希望文本颜色的修改是永久性的，则需要将修改后的 `$LS_COLORS` 定义添加到一个启动文件中，例如 `.bashrc`。

### 更多关于命令行文本

你可以在 NetworkWorld 的 [2016 年 11 月](https://www.networkworld.com/article/3138909/linux/coloring-your-world-with-ls-colors.html)的帖子中找到有关文本颜色的其他信息。

---

via: <https://www.networkworld.com/article/3269587/linux/customizing-your-text-colors-on-the-linux-command-line.html>

作者：[Sandra Henry-Stocker](https://www.networkworld.com/author/Sandra-Henry_Stocker/) 选题：[lujun9972](https://github.com/lujun9972) 译者：[MjSeven](https://github.com/MjSeven) 校对：[pityonline](https://github.com/pityonline)

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


无密码验证：客户端

![](/data/attachment/album/201807/12/114131efzt9yrb5ggd93yr.jpg)

我们继续 [无密码验证](/article-9748-1.html) 的文章。上一篇文章中，我们用 Go 写了一个 HTTP 服务，用这个服务来做无密码验证 API。今天，我们为它再写一个 JavaScript 客户端。

我们将使用 [这里的](/article-9815-1.html) 这个单页面应用程序（SPA）来展示使用的技术。如果你还没有读过它，请先读它。

记住流程：

* 用户输入其 email。
* 用户收到一个带有魔法链接的邮件。
* 用户点击该链接、
* 用户验证成功。

对于根 URL（`/`），我们将根据验证的状态分别使用两个不同的页面：一个是带有访问表单的页面，或者是已验证通过的用户的欢迎页面。另一个页面是验证回调的重定向页面。

### 伺服

我们将使用相同的 Go 服务器来为客户端提供服务，因此，在我们前面的 `main.go` 中添加一些路由：

```
router.Handle("GET", "/...", http.FileServer(SPAFileSystem{http.Dir("static")}))

```

```
type SPAFileSystem struct {
    fs http.FileSystem
}

func (spa SPAFileSystem) Open(name string) (http.File, error) {
    f, err := spa.fs.Open(name)
    if err != nil {
        return spa.fs.Open("index.html")
    }
    return f, nil
}

```

这个伺服文件放在 `static` 下，配合 `static/index.html` 作为回调。

你可以使用你自己的服务器，但是你得在服务器上启用 [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS)。

### HTML

我们来看一下那个 `static/index.html` 文件。

```
<!DOCTYPE html>
<html lang="en">
<head>
 <meta charset="utf-8">
 <meta name="viewport" content="width=device-width, initial-scale=1.0">
 <title>Passwordless Demo</title>
 <link rel="shortcut icon" href="data:,">
 <script src="/js/main.js" type="module"></script>
</head>
<body></body>
</html>

```

单页面应用程序的所有渲染由 JavaScript 来完成，因此，我们使用了一个空的 body 部分和一个 `main.js` 文件。

我们将使用 [上篇文章](/article-9815-1.html) 中的 Router。

### 渲染

现在，我们使用下面的内容来创建一个 `static/js/main.js` 文件：

```
import Router from 'https://unpkg.com/@nicolasparada/router'
import { isAuthenticated } from './auth.js'

const router = new Router()

router.handle('/', guard(view('home')))
router.handle('/callback', view('callback'))
router.handle(/^\//, view('not-found'))

router.install(async resultPromise => {
    document.body.innerHTML = ''
    document.body.appendChild(await resultPromise)
})

function view(name) {
    return (...args) => import(`/js/pages/${name}-page.js`)
        .then(m => m.default(...args))
}

function guard(fn1, fn2 = view('welcome')) {
    return (...args) => isAuthenticated()
        ? fn1(...args)
        : fn2(...args)
}

```

与上篇文章不同的是，我们实现了一个 `isAuthenticated()` 函数和一个 `guard()` 函数，使用它去渲染两种验证状态的页面。因此，当用户访问 `/` 时，它将根据用户是否通过了验证来展示主页或者是欢迎页面。

### 验证

现在，我们来编写 `isAuthenticated()` 函数。使用下面的内容来创建一个 `static/js/auth.js` 文件：

```
export function getAuthUser() {
    const authUserItem = localStorage.getItem('auth_user')
    const expiresAtItem = localStorage.getItem('expires_at')

    if (authUserItem !== null && expiresAtItem !== null) {
        const expiresAt = new Date(expiresAtItem)

        if (!isNaN(expiresAt.valueOf()) && expiresAt > new Date()) {
            try {
                return JSON.parse(authUserItem)
            } catch (_) { }
        }
    }

    return null
}

export function isAuthenticated() {
    return localStorage.getItem('jwt') !== null && getAuthUser() !== null
}

```

当有人登入时，我们将保存 JSON 格式的 web 令牌、它的过期日期，以及在 `localStorage` 上的当前已验证用户。这个模块就是这个用处。

* `getAuthUser()` 用于从 `localStorage` 获取已认证的用户，以确认 JSON 格式的 Web 令牌没有过期。
* `isAuthenticated()` 在前面的函数中用于去检查它是否没有返回 `null`。

### 获取

在继续这个页面之前，我将写一些与服务器 API 一起使用的 HTTP 工具。

我们使用以下的内容去创建一个 `static/js/http.js` 文件：

```
import { isAuthenticated } from './auth.js'

function get(url, headers) {
    return fetch(url, {
        headers: Object.assign(getAuthHeader(), headers),
    }).then(handleResponse)
}

function post(url, body, headers) {
    return fetch(url, {
        method: 'POST',
        headers: Object.assign(getAuthHeader(), { 'content-type': 'application/json' }, headers),
        body: JSON.stringify(body),
    }).then(handleResponse)
}

function getAuthHeader() {
    return isAuthenticated()
        ? { authorization: `Bearer ${localStorage.getItem('jwt')}` }
        : {}
}

export async function handleResponse(res) {
    const body = await res.clone().json().catch(() => res.text())
    const response = {
        statusCode: res.status,
        statusText: res.statusText,
        headers: res.headers,
        body,
    }
    if (!res.ok) {
        const message = typeof body === 'object' && body !== null && 'message' in body
            ? body.message
            : typeof body === 'string' && body !== ''
                ? body
                : res.statusText
        const err = new Error(message)
        throw Object.assign(err, response)
    }
    return response
}

export default {
    get,
    post,
}

```

这个模块导出了 `get()` 和 `post()` 函数。它们是 `fetch` API 的封装。当用户是已验证的，这二个函数注入一个 `Authorization: Bearer <token_here>` 头到请求中；这样服务器就能对我们进行身份验证。

### 欢迎页

我们现在来到欢迎页面。用如下的内容创建一个 `static/js/pages/welcome-page.js` 文件：

```
const template = document.createElement('template')
template.innerHTML = `
    <h1>Passwordless Demo</h1>
    <h2>Access</h2>
    <form id="access-form">
        <input type="email" placeholder="Email" autofocus required>
        <button type="submit">Send Magic Link</button>
    </form>
`

export default function welcomePage() {
    const page = template.content.cloneNode(true)

    page.getElementById('access-form')
        .addEventListener('submit', onAccessFormSubmit)

    return page
}

```

这个页面使用一个 `HTMLTemplateElement` 作为视图。这只是一个输入用户 email 的简单表单。

为了避免干扰，我将跳过错误处理部分，只是将它们输出到控制台上。

现在，我们来写 `onAccessFormSubmit()` 函数。

```
import http from '../http.js'

function onAccessFormSubmit(ev) {
    ev.preventDefault()

    const form = ev.currentTarget
    const input = form.querySelector('input')
    const email = input.value

    sendMagicLink(email).catch(err => {
        console.error(err)
        if (err.statusCode === 404 && wantToCreateAccount()) {
            runCreateUserProgram(email)
        }
    })
}

function sendMagicLink(email) {
    return http.post('/api/passwordless/start', {
        email,
        redirectUri: location.origin + '/callback',
    }).then(() => {
        alert('Magic link sent. Go check your email inbox.')
    })
}

function wantToCreateAccount() {
    return prompt('No user found. Do you want to create an account?')
}

```

它对 `/api/passwordless/start` 发起了 POST 请求，请求体中包含 `email` 和 `redirectUri`。在本例中它返回 `404 Not Found` 状态码时，我们将创建一个用户。

```
function runCreateUserProgram(email) {
    const username = prompt("Enter username")
    if (username === null) return

    http.post('/api/users', { email, username })
        .then(res => res.body)
        .then(user => sendMagicLink(user.email))
        .catch(console.error)
}

```

这个用户创建程序，首先询问用户名，然后使用 email 和用户名做一个 `POST` 请求到 `/api/users`。成功之后，给创建的用户发送一个魔法链接。

### 回调页

这是访问表单的全部功能，现在我们来做回调页面。使用如下的内容来创建一个 `static/js/pages/callback-page.js` 文件：

```
import http from '../http.js'

const template = document.createElement('template')
template.innerHTML = `
    <h1>Authenticating you</h1>
`

export default function callbackPage() {
    const page = template.content.cloneNode(true)

    const hash = location.hash.substr(1)
    const fragment = new URLSearchParams(hash)
    for (const [k, v] of fragment.entries()) {
        fragment.set(decodeURIComponent(k), decodeURIComponent(v))
    }
    const jwt = fragment.get('jwt')
    const expiresAt = fragment.get('expires_at')

    http.get('/api/auth_user', { authorization: `Bearer ${jwt}` })
        .then(res => res.body)
        .then(authUser => {
            localStorage.setItem('jwt', jwt)
            localStorage.setItem('auth_user', JSON.stringify(authUser))
            localStorage.setItem('expires_at', expiresAt)

            location.replace('/')
        })
        .catch(console.error)

    return page
}

```

请记住……当点击魔法链接时，我们会来到 `/api/passwordless/verify_redirect`，它将把我们重定向到重定向 URI，我们将放在哈希中的 JWT 和过期日期传递给 `/callback`。

回调页面解码 URL 中的哈希，提取这些参数去做一个 `GET` 请求到 `/api/auth_user`，用 JWT 保存所有数据到 `localStorage` 中。最后，重定向到主页面。

### 主页

创建如下内容的 `static/pages/home-page.js` 文件：

```
import { getAuthUser } from '../auth.js'

export default function homePage() {
    const authUser = getAuthUser()

    const template = document.createElement('template')
    template.innerHTML = `
        <h1>Passwordless Demo</h1>
        <p>Welcome back, ${authUser.username}
```


Mesos 和 Kubernetes：不是竞争者

>
> 人们经常用 x 相对于 y 这样的术语来考虑问题，但是它并不是一个技术对另一个技术的问题。Ben Hindman 在这里解释了 Mesos 是如何对另外一种技术进行补充的。
>
>
>

![](/data/attachment/album/201807/13/121145qs151ol13s2f4os8.jpg)

Mesos 的起源可以追溯到 2009 年，当时，Ben Hindman 还是加州大学伯克利分校研究并行编程的博士生。他们在 128 核的芯片上做大规模的并行计算，以尝试去解决多个问题，比如怎么让软件和库在这些芯片上运行更高效。他与同学们讨论能否借鉴并行处理和多线程的思想，并将它们应用到集群管理上。

Hindman 说 “最初，我们专注于大数据” 。那时，大数据非常热门，而 Hadoop 就是其中的一个热门技术。“我们发现，人们在集群上运行像 Hadoop 这样的程序与运行多线程应用及并行应用很相似。”Hindman 说。

但是，它们的效率并不高，因此，他们开始去思考，如何通过集群管理和资源管理让它们运行的更好。“我们查看了那个时期很多的各种技术” Hindman 回忆道。

然后，Hindman 和他的同事们决定去采用一种全新的方法。“我们决定对资源管理创建一个低级的抽象，然后在此之上运行调度服务和做其它的事情。” Hindman 说，“基本上，这就是 Mesos 的本质 —— 将资源管理部分从调度部分中分离出来。”

他成功了，并且 Mesos 从那时开始强大了起来。

### 将项目呈献给 Apache

这个项目发起于 2009 年。在 2010 年时，团队决定将这个项目捐献给 Apache 软件基金会（ASF）。它在 Apache 孵化，并于 2013 年成为顶级项目（TLP）。

为什么 Mesos 社区选择 Apache 软件基金会有很多的原因，比如，Apache 许可证，以及基金会已经拥有了一个充满活力的其它此类项目的社区。

与影响力也有关系。许多在 Mesos 上工作的人也参与了 Apache，并且许多人也致力于像 Hadoop 这样的项目。同时，来自 Mesos 社区的许多人也致力于其它大数据项目，比如 Spark。这种交叉工作使得这三个项目 —— Hadoop、Mesos，以及 Spark —— 成为 ASF 的项目。

与商业也有关系。许多公司对 Mesos 很感兴趣，并且开发者希望它能由一个中立的机构来维护它，而不是让它成为一个私有项目。

### 谁在用 Mesos？

更好的问题应该是，谁不在用 Mesos？从 Apple 到 Netflix 每个都在用 Mesos。但是，Mesos 也面临任何技术在早期所面对的挑战。“最初，我要说服人们，这是一个很有趣的新技术。它叫做‘容器’，因为它不需要使用虚拟机” Hindman 说。

从那以后，这个行业发生了许多变化，现在，只要与别人聊到基础设施，必然是从”容器“开始的 —— 感谢 Docker 所做出的工作。今天再也不需要做说服工作了，而在 Mesos 出现的早期，前面提到的像 Apple、Netflix，以及 PayPal 这样的公司。他们已经知道了容器替代虚拟机给他们带来的技术优势。“这些公司在容器成为一种现象之前，已经明白了容器的价值所在”， Hindman 说。

可以在这些公司中看到，他们有大量的容器而不是虚拟机。他们所做的全部工作只是去管理和运行这些容器，并且他们欣然接受了 Mesos。在 Mesos 早期就使用它的公司有 Apple、Netflix、PayPal、Yelp、OpenTable 和 Groupon。

“大多数组织使用 Mesos 来运行各种服务” Hindman 说，“但也有些公司用它做一些非常有趣的事情，比如，数据处理、数据流、分析任务和应用程序。“

这些公司采用 Mesos 的其中一个原因是，资源管理层之间有一个明晰的界线。当公司运营容器的时候，Mesos 为他们提供了很好的灵活性。

“我们尝试使用 Mesos 去做的一件事情是去创建一个层，以让使用者享受到我们的层带来的好处，当然也可以在它之上创建任何他们想要的东西，” Hindman 说。 “我认为这对一些像 Netflix 和 Apple 这样的大公司非常有用。”

但是，并不是每个公司都是技术型的公司；不是每个公司都有或者应该有这种专长。为帮助这样的组织，Hindman 联合创建了 Mesosphere 去围绕 Mesos 提供服务和解决方案。“我们最终决定，为这样的组织去构建 DC/OS，它不需要技术专长或者不想把时间花费在像构建这样的事情上。”

### Mesos vs. Kubernetes?

人们经常用 x 相对于 y 这样的术语来考虑问题，但是它并不是一个技术对另一个技术的问题。大多数的技术在一些领域总是重叠的，并且它们可以是互补的。“我不喜欢将所有的这些东西都看做是竞争者。我认为它们中的一些与另一个在工作中是互补的，” Hindman 说。

“事实上，名字 Mesos 表示它处于 ‘中间’；它是一种中间的操作系统”， Hindman 说，“我们有一个容器调度器的概念，它能够运行在像 Mesos 这样的东西之上。当 Kubernetes 刚出现的时候，我们实际上在 Mesos 的生态系统中接受了它，并将它看做是在 Mesos 上的 DC/OS 中运行容器的另一种方式。”

Mesos 也复活了一个名为 [Marathon](https://mesosphere.github.io/marathon/)（一个用于 Mesos 和 DC/OS 的容器编排器）的项目，它成为了 Mesos 生态系统中最重要的成员。但是，Marathon 确实无法与 Kubernetes 相比较。“Kubernetes 比 Marathon 做的更多，因此，你不能将它们简单地相互交换，” Hindman 说，“与此同时，我们在 Mesos 中做了许多 Kubernetes 中没有的东西。因此，这些技术之间是互补的。”

不要将这些技术视为相互之间是敌对的关系，它们应该被看做是对行业有益的技术。它们不是技术上的重复；它们是多样化的。据 Hindman 说，“对于开源领域的终端用户来说，这可能会让他们很困惑，因为他们很难去知道哪个技术适用于哪种任务，但这是这个被称之为开源的本质所在。“

这只是意味着有更多的选择，并且每个都是赢家。

---

via: <https://www.linux.com/blog/2018/6/mesos-and-kubernetes-its-not-competition>

作者：[Swapnil Bhartiya](https://www.linux.com/users/arnieswap) 选题：[lujun9972](https://github.com/lujun9972) 译者：[qhwdw](https://github.com/qhwdw) 校对：[wxy](https://github.com/wxy)

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


使用 Ledger 记录（财务）情况

Summary: 自 2005 年搬到加拿大以来，我使用 Ledger CLI 来跟踪我的财务状况。

![](/data/attachment/album/201807/13/124053qclqspp9ps2tp3q4.jpg)

自 2005 年搬到加拿大以来，我使用 [Ledger CLI](http://www.ledger-cli.org/) 来跟踪我的财务状况。我喜欢纯文本的方式，它支持虚拟信封意味着我可以同时将我的银行帐户余额和我的虚拟分配到不同的目录下。以下是我们如何使用这些虚拟信封分别管理我们的财务状况。

每个月，我都有一个条目将我生活开支分配到不同的目录中，包括家庭开支的分配。W- 不要求太多， 所以我要谨慎地处理这两者之间的差别和我自己的生活费用。我们处理它的方式是我支付固定金额，这是贷记我支付的杂货。由于我们的杂货总额通常低于我预算的家庭开支，因此任何差异都会留在标签上。我过去常常给他写支票，但最近我只是支付偶尔额外的大笔费用。

这是个示例信封分配：

```
2014.10.01 * Budget
 [Envelopes:Living]
 [Envelopes:Household] $500
 ;; More lines go here

```

这是设置的信封规则之一。它鼓励我正确地分类支出。所有支出都从我的 “Play” 信封中取出。

```
= /^Expenses/
 (Envelopes:Play) -1.0

```

这个为家庭支出报销 “Play” 信封，将金额从 “Household” 信封转移到 “Play” 信封。

```
= /^Expenses:House$/
 (Envelopes:Play) 1.0
 (Envelopes:Household) -1.0

```

我有一套定期的支出来模拟我的预算中的家庭开支。例如，这是 10 月份的。

```
2014.10.1 * House
 Expenses:House
 Assets:Household $-500

```

这是杂货交易的形式：

```
2014.09.28 * No Frills
 Assets:Household:Groceries $70.45
 Liabilities:MBNA:September $-70.45

```

接着 `ledger bal Assets:Household` 就会告诉我是否欠他钱（负余额）。如果我支付大笔费用（例如：机票、通管道），那么正常家庭开支预算会逐渐减少余额。

我从 W- 那找到了一个为我的信用卡交易添加一个月标签的技巧，他还使用 Ledger 跟踪他的交易。它允许我再次检查条目的余额，看看前一个条目是否已被正确清除。

这个资产分类使用有点奇怪，但它在精神上对我有用。

使用 Ledger 以这种方式跟踪它可以让我跟踪我们的杂货费用以及我实际支付费用和我预算费用之间的差额。如果我最终支出超出预期，我可以从更多可自由支配的信封中移动虚拟货币，因此我的预算始终保持平衡。

Ledger 是一个强大的工具。相当极客，但也许更多的工作流描述可能会帮助那些正在搞清楚它的人！

---

via: <http://sachachua.com/blog/2014/11/keeping-financial-score-ledger/>

作者：[Sacha Chua](http://sachachua.com) 选题：[lujun9972](https://github.com/lujun9972) 译者：[geekpi](https://github.com/geekpi) 校对：[wxy](https://github.com/wxy)

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


使用 Open edX 托管课程

>
> Open edX 为各种规模和类型的组织提供了一个强大而多功能的开源课程管理的解决方案。要不要了解一下。
>
>
>

![](/data/attachment/album/201807/13/130427e5auhxz8iwiw7a5a.png)

[Open edX 平台](https://open.edx.org/about-open-edx) 是一个自由开源的课程管理系统，它是 [全世界](https://www.edx.org/schools-partners) 都在使用的大规模网络公开课（MOOC）以及小型课程和培训模块的托管平台。在 Open edX 的 [第七个主要发行版](https://openedx.atlassian.net/wiki/spaces/DOC/pages/11108700/Open+edX+Releases) 中，到现在为止，它已经提供了超过 8,000 个原创课程和 5000 万个课程注册数。你可以使用你自己的本地设备或者任何行业领先的云基础设施服务提供商来安装这个平台，而且，随着项目的[服务提供商](https://openedx.atlassian.net/wiki/spaces/COMM/pages/65667081/Open+edX+Service+Providers)名单越来越长，来自它们中的软件即服务（SaaS）的可用模型也越来越多了。

Open edX 平台被来自世界各地的顶尖教育机构、私人公司、公共机构、非政府组织、非营利机构，以及教育技术初创企业广泛地使用，并且该项目的服务提供商全球社区不断地让甚至更小的组织也可以访问这个平台。如果你打算向广大的读者设计和提供教育内容，你应该考虑去使用 Open edX 平台。

### 安装

安装这个软件有多种方式，这可能有点让你难以选择，至少刚开始是这样。但是不管你是以何种方式 [安装 Open edX](https://openedx.atlassian.net/wiki/spaces/OpenOPS/pages/60227779/Open+edX+Installation+Options)，最终你都得到的是有相同功能的应用程序。默认安装包含一个为在线学习者提供的、全功能的学习管理系统（LMS），和一个全功能的课程管理工作室（CMS），CMS 可以让你的讲师团队用它来编写原创课程内容。你可以把 CMS 当做是课程内容设计和管理的 “[Wordpress](https://wordpress.com/)”，把 LMS 当做是课程销售、分发、和消费的 “[Magento](https://magento.com/)”。

Open edX 是设备无关的、完全响应式的应用软件，并且不用花费很多的努力就可发布一个原生的 iOS 和 Android 应用，它可以无缝地集成到你的实例后端。Open edX 平台的代码库、原生移动应用、以及安装脚本都发布在 [GitHub](https://github.com/edx) 上。

#### 有何期望

Open edX 平台的 [GitHub 仓库](https://github.com/edx/edx-platform) 包含适用于各种类型的组织的、性能很好的、产品级的代码。来自数百个机构的数千名程序员经常为 edX 仓库做贡献，并且这个平台是一个名副其实的、研究如何去构建和管理一个复杂的企业级应用的好案例。因此，尽管你可能会遇到大量的类似“如何将平台迁移到生产环境中”的问题，但是你无需对 Open edX 平台代码库本身的质量和健状性担忧。

通过少量的培训，你的讲师就可以去设计不错的在线课程。但是请记住，Open edX 是通过它的 [XBlock](https://open.edx.org/xblocks) 组件架构进行扩展的，因此，通过他们和你的努力，你的讲师将有可能将不错的课程变成精品课程。

这个平台在单服务器环境下也运行的很好，并且它是高度模块化的，几乎可以进行无限地水平扩展。它也是主题化的和本地化的，平台的功能和外观可以根据你的需要进行几乎无限制地调整。平台在你的设备上可以按需安装并可靠地运行。

#### 需要一些封装

请记住，有大量的 edX 软件模块是不包含在默认安装中的，并且这些模块提供的经常都是各种组织所需要的功能。比如，分析模块、电商模块，以及课程的通知/公告模块都是不包含在默认安装中的，并且这些单独的模块都是值得安装的。另外，在数据备份/恢复和系统管理方面要完全依赖你自己去处理。幸运的是，有关这方面的内容，社区有越来越多的文档和如何去做的文章。你可以通过 Google 和 Bing 去搜索，以帮助你在生产环境中安装它们。

虽然有很多文档良好的程序，但是根据你的技能水平，配置 [oAuth](https://oauth.net/) 和 [SSL/TLS](https://en.wikipedia.org/wiki/Transport_Layer_Security)，以及使用平台的 [REST API](https://en.wikipedia.org/wiki/Representational_state_transfer) 可能对你是一个挑战。另外，一些组织要求将 MySQL 和/或 MongoDB 数据库在中心化环境中管理，如果你正好是这种情况，你还需要将这些服务从默认平台安装中分离出来。edX 设计团队已经尽可能地为你做了简化，但是由于它是一个非常重大的更改，因此可能需要一些时间去实现。

如果你面临资源和/或技术上的困难 —— 不要气馁，Open edX 社区 SaaS 提供商，像 [appsembler](https://www.appsembler.com/) 和 [eduNEXT](https://www.edunext.co/)，提供了引人入胜的替代方案去进行 DIY 安装，尤其是如果你只想简单购买就行。

### 技术栈

在 Open edX 平台的安装上探索是件令人兴奋的事情，从架构的角度来说，这个项目是一个典范。应用程序模块是 [Django](https://www.djangoproject.com/) 应用，它利用了大量的开源社区的顶尖项目，包括 [Ubuntu](https://www.ubuntu.com/)、[MySQL](https://www.mysql.com/)、[MongoDB](https://www.mongodb.com/)、[RabbitMQ](https://www.rabbitmq.com/)、[Elasticsearch](https://www.elastic.co/)、[Hadoop](http://hadoop.apache.org/)、等等。

![edx-architecture.png](/data/attachment/album/201807/13/130517vvpxvakuxltqtvvn.png "edx-architecture.png")

*Open edX 技术栈（CC BY，来自 edX）*

将这些组件安装并配置好本身就是一件非常不容易的事情，但是以这样的一种方式将所有的组件去打包，并适合于任意规模和复杂性的组织，并且能够按他们的需要进行任意调整搭配而无需在代码上做重大改动，看起来似乎是不可能的事情 —— 它就是这种情况，直到你看到主要的平台配置参数安排和命名是多少的巧妙和直观。请注意，平台的组织结构有一个学习曲线，但是，你所学习的一切都是值的去学习的，不仅是对这个项目，对一般意义上的大型 IT 项目都是如此。

提醒一点：这个平台的 UI 是在不断变动的，最终的目标是在 [React](%E2%80%9Chttps://reactjs.org/%E2%80%9C) 和 [Bootstrap](%E2%80%9Chttps://getbootstrap.com/%E2%80%9C) 上实现标准化。与此同时，你将会发现基本主题有多个实现的样式，这可能会让你感到困惑。

### 采用

edX 项目能够迅速得到世界范围内的采纳，很大程度上取决于该软件的运行情况。这一点也不奇怪，这个项目成功地吸引了大量才华卓越的人参与其中，他们作为程序员、项目顾问、翻译者、技术作者、以及博客作者参与了项目的贡献。一年一次的 [Open edX 会议](https://con.openedx.org/)、[官方的 edX Google Group](https://groups.google.com/forum/#!forum/openedx-ops)、以及 [Open edX 服务提供商名单](https://openedx.atlassian.net/wiki/spaces/COMM/pages/65667081/Open+edX+Service+Providers) 是了解这个多样化的、不断成长的生态系统的非常好的起点。我作为相对而言的新人，我发现参与和直接从事这个项目的各个方面是非常容易的。

祝你学习之旅一切顺利，并且当你构思你的项目时，你可以随时联系我。

---

via: <https://opensource.com/article/18/6/getting-started-open-edx>

作者：[Lawrence Mc Daniel](https://opensource.com/users/mcdaniel0073) 选题：[lujun9972](https://github.com/lujun9972) 译者：[qhwdw](https://github.com/qhwdw) 校对：[wxy](https://github.com/wxy)

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


在 Linux 上如何得到一个段错误的核心转储

![](/data/attachment/album/201807/13/221835y0g3m1gmbj7zg781.jpg)

本周工作中，我花了整整一周的时间来尝试调试一个段错误。我以前从来没有这样做过，我花了很长时间才弄清楚其中涉及的一些基本事情（获得核心转储、找到导致段错误的行号）。于是便有了这篇博客来解释如何做那些事情！

在看完这篇博客后，你应该知道如何从“哦，我的程序出现段错误，但我不知道正在发生什么”到“我知道它出现段错误时的堆栈、行号了！ ”。

### 什么是段错误？

“<ruby> 段错误 <rt>  segmentation fault </rt></ruby>”是指你的程序尝试访问不允许访问的内存地址的情况。这可能是由于：

* 试图解引用空指针（你不被允许访问内存地址 `0`）；
* 试图解引用其他一些不在你内存（LCTT 译注：指不在合法的内存地址区间内）中的指针；
* 一个已被破坏并且指向错误的地方的 <ruby> C++ 虚表指针 <rt>  C++ vtable pointer </rt></ruby>，这导致程序尝试执行没有执行权限的内存中的指令；
* 其他一些我不明白的事情，比如我认为访问未对齐的内存地址也可能会导致段错误（LCTT 译注：在要求自然边界对齐的体系结构，如 MIPS、ARM 中更容易因非对齐访问产生段错误）。

这个“C++ 虚表指针”是我的程序发生段错误的情况。我可能会在未来的博客中解释这个，因为我最初并不知道任何关于 C++ 的知识，并且这种虚表查找导致程序段错误的情况也是我所不了解的。

但是！这篇博客后不是关于 C++ 问题的。让我们谈论的基本的东西，比如，我们如何得到一个核心转储？

### 步骤1：运行 valgrind

我发现找出为什么我的程序出现段错误的最简单的方式是使用 `valgrind`：我运行

```
valgrind -v your-program

```

这给了我一个故障时的堆栈调用序列。 简洁！

但我想也希望做一个更深入调查，并找出些 `valgrind` 没告诉我的信息！ 所以我想获得一个核心转储并探索它。

### 如何获得一个核心转储

<ruby> 核心转储 <rt>  core dump </rt></ruby>是您的程序内存的一个副本，并且当您试图调试您的有问题的程序哪里出错的时候它非常有用。

当您的程序出现段错误，Linux 的内核有时会把一个核心转储写到磁盘。 当我最初试图获得一个核心转储时，我很长一段时间非常沮丧，因为 - Linux 没有生成核心转储！我的核心转储在哪里？

这就是我最终做的事情：

1. 在启动我的程序之前运行 `ulimit -c unlimited`
2. 运行 `sudo sysctl -w kernel.core_pattern=/tmp/core-%e.%p.%h.%t`

### ulimit：设置核心转储的最大尺寸

`ulimit -c` 设置核心转储的最大尺寸。 它往往设置为 0，这意味着内核根本不会写核心转储。 它以千字节为单位。 `ulimit` 是按每个进程分别设置的 —— 你可以通过运行 `cat /proc/PID/limit` 看到一个进程的各种资源限制。

例如这些是我的系统上一个随便一个 Firefox 进程的资源限制：

```
$ cat /proc/6309/limits
Limit                     Soft Limit           Hard Limit           Units
Max cpu time              unlimited            unlimited            seconds
Max file size             unlimited            unlimited            bytes
Max data size             unlimited            unlimited            bytes
Max stack size            8388608              unlimited            bytes
Max core file size        0                    unlimited            bytes
Max resident set          unlimited            unlimited            bytes
Max processes             30571                30571                processes
Max open files            1024                 1048576              files
Max locked memory         65536                65536                bytes
Max address space         unlimited            unlimited            bytes
Max file locks            unlimited            unlimited            locks
Max pending signals       30571                30571                signals
Max msgqueue size         819200               819200               bytes
Max nice priority         0                    0
Max realtime priority     0                    0
Max realtime timeout      unlimited            unlimited            us

```

内核在决定写入多大的核心转储文件时使用<ruby> 软限制 <rt>  soft limit </rt></ruby>（在这种情况下，`max core file size = 0`）。 您可以使用 shell 内置命令 `ulimit`（`ulimit -c unlimited`） 将软限制增加到<ruby> 硬限制 <rt>  hard limit </rt></ruby>。

### kernel.core\_pattern：核心转储保存在哪里

`kernel.core_pattern` 是一个内核参数，或者叫 “sysctl 设置”，它控制 Linux 内核将核心转储文件写到磁盘的哪里。

内核参数是一种设定您的系统全局设置的方法。您可以通过运行 `sysctl -a` 得到一个包含每个内核参数的列表，或使用 `sysctl kernel.core_pattern` 来专门查看 `kernel.core_pattern` 设置。

所以 `sysctl -w kernel.core_pattern=/tmp/core-%e.%p.%h.%t` 将核心转储保存到目录 `/tmp` 下，并以 `core` 加上一系列能够标识（出故障的）进程的参数构成的后缀为文件名。

如果你想知道这些形如 `%e`、`%p` 的参数都表示什么，请参考 [man core](http://man7.org/linux/man-pages/man5/core.5.html)。

有一点很重要，`kernel.core_pattern` 是一个全局设置 —— 修改它的时候最好小心一点，因为有可能其它系统功能依赖于把它被设置为一个特定的方式（才能正常工作）。

### kernel.core\_pattern 和 Ubuntu

默认情况下在 ubuntu 系统中，`kernel.core_pattern` 被设置为下面的值：

```
$ sysctl kernel.core_pattern
kernel.core_pattern = |/usr/share/apport/apport %p %s %c %d %P

```

这引起了我的迷惑（这 apport 是干什么的，它对我的核心转储做了什么？）。以下关于这个我了解到的：

* Ubuntu 使用一种叫做 apport 的系统来报告 apt 包有关的崩溃信息。
* 设定 `kernel.core_pattern=|/usr/share/apport/apport %p %s %c %d %P` 意味着核心转储将被通过管道送给 `apport` 程序。
* apport 的日志保存在文件 `/var/log/apport.log` 中。
* apport 默认会忽略来自不属于 Ubuntu 软件包一部分的二进制文件的崩溃信息

我最终只是跳过了 apport，并把 `kernel.core_pattern` 重新设置为 `sysctl -w kernel.core_pattern=/tmp/core-%e.%p.%h.%t`，因为我在一台开发机上，我不在乎 apport 是否工作，我也不想尝试让 apport 把我的核心转储留在磁盘上。

### 现在你有了核心转储，接下来干什么？

好的，现在我们了解了 `ulimit` 和 `kernel.core_pattern` ，并且实际上在磁盘的 `/tmp` 目录中有了一个核心转储文件。太好了！接下来干什么？我们仍然不知道该程序为什么会出现段错误！

下一步将使用 `gdb` 打开核心转储文件并获取堆栈调用序列。

### 从 gdb 中得到堆栈调用序列

你可以像这样用 `gdb` 打开一个核心转储文件：

```
$ gdb -c my_core_file

```

接下来，我们想知道程序崩溃时的堆栈是什么样的。在 `gdb` 提示符下运行 `bt` 会给你一个<ruby> 调用序列 <rt>  backtrace </rt></ruby>。在我的例子里，`gdb` 没有为二进制文件加载符号信息，所以这些函数名就像 “??????”。幸运的是，（我们通过）加载符号修复了它。

下面是如何加载调试符号。

```
symbol-file /path/to/my/binary
sharedlibrary

```

这从二进制文件及其引用的任何共享库中加载符号。一旦我这样做了，当我执行 `bt` 时，gdb 给了我一个带有行号的漂亮的堆栈跟踪！

如果你想它能工作，二进制文件应该以带有调试符号信息的方式被编译。在试图找出程序崩溃的原因时，堆栈跟踪中的行号非常有帮助。:)

### 查看每个线程的堆栈

通过以下方式在 `gdb` 中获取每个线程的调用栈！

```
thread apply all bt full

```

### gdb + 核心转储 = 惊喜

如果你有一个带调试符号的核心转储以及 `gdb`，那太棒了！您可以上下查看调用堆栈（LCTT 译注：指跳进调用序列不同的函数中以便于查看局部变量），打印变量，并查看内存来得知发生了什么。这是最好的。

如果您仍然正在基于 gdb 向导来工作上，只打印出栈跟踪与bt也可以。 :)

### ASAN

另一种搞清楚您的段错误的方法是使用 AddressSanitizer 选项编译程序（“ASAN”，即 `$CC -fsanitize=address`）然后运行它。 本文中我不准备讨论那个，因为本文已经相当长了，并且在我的例子中打开 ASAN 后段错误消失了，可能是因为 ASAN 使用了一个不同的内存分配器（系统内存分配器，而不是 tcmalloc）。

在未来如果我能让 ASAN 工作，我可能会多写点有关它的东西。（LCTT 译注：这里指使用 ASAN 也能复现段错误）

### 从一个核心转储得到一个堆栈跟踪真的很亲切！

这个博客听起来很多，当我做这些的时候很困惑，但说真的，从一个段错误的程序中获得一个堆栈调用序列不需要那么多步骤：

1. 试试用 `valgrind`

如果那没用，或者你想要拿到一个核心转储来调查：

1. 确保二进制文件编译时带有调试符号信息；
2. 正确的设置 `ulimit` 和 `kernel.core_pattern`；
3. 运行程序；
4. 一旦你用 `gdb` 调试核心转储了，加载符号并运行 `bt`；
5. 尝试找出发生了什么！

我可以使用 `gdb` 弄清楚有个 C++ 的虚表条目指向一些被破坏的内存，这有点帮助，并且使我感觉好像更懂了 C++ 一点。也许有一天我们会更多地讨论如何使用 `gdb` 来查找问题！

---

via: <https://jvns.ca/blog/2018/04/28/debugging-a-segfault-on-linux/>

作者：[Julia Evans](https://jvns.ca/about/) 译者：[stephenxs](https://github.com/stephenxs) 校对：[wxy](https://github.com/wxy)

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


TrueOS 不再想要成为“桌面 BSD”了

Summary: TrueOS 很快会有一些非常重大的变化。今天，我们将了解桌面 BSD 领域将会发生什么。

[TrueOS](https://www.trueos.org/) 很快会有一些非常重大的变化。今天，我们将了解桌面 BSD 领域将会发生什么。

### 通告

![TrueOS: Core Operating System BSD](/data/attachment/album/201807/13/225349lgbvc4gnx0szecwa.jpg)

[TrueOS](https://www.trueos.org/) 背后的团队[宣布](https://www.trueos.org/blog/trueosdownstream/)，他们将改变项目的重点。到目前为止，TrueOS 使用开箱即用的图形用户界面来轻松安装 BSD。然而，它现在将成为“一个先进的操作系统，保留你所知道和喜欢的 ZFS（[OpenZFS](http://open-zfs.org/wiki/Main_Page)）和 [FreeBSD](https://www.freebsd.org/)的所有稳定性，并添加额外的功能来创造一个全新的、创新的操作系统。我们的目标是创建一个核心操作系统，该系统具有模块化、实用性，非常适合自己动手和高级用户。“

从本质上讲，TrueOs 将成为 FreeBSD 的下游分支。他们将集成更新一些的软件到系统中，例如 [OpenRC](https://en.wikipedia.org/wiki/OpenRC) 和 [LibreSSL](http://www.libressl.org/)。他们希望能坚持 6 个月的发布周期。

其目标是使 TrueOS 成为可以作为其他项目构建的基础。缺少图形部分以使其更加地与发行版无关。

### 桌面用户如何？

如果你读过我的[TrueOS 评论](https://itsfoss.com/trueos-bsd-review/)并且有兴趣尝试使用桌面 BSD 或已经使用 TrueOS，请不要担心（这对于生活来说也是一个很好的建议）。TrueOS 的所有桌面元素都将剥离到 [Project Trident](http://www.project-trident.org/)。目前，Project Trident 网站的细节不多。他们仿佛还在进行剥离的幕后工作。

如果你目前拥有 TrueOS，则无需担心迁移。TrueOS 团队表示，“对于那些希望迁移到其他基于 FreeBSD 的发行版，如 Project Trident 或 [GhostBSD](https://www.ghostbsd.org/) 的人而言将会有迁移方式。”

### 想法

当我第一次阅读该公告时，坦率地说有点担心。改变名字可能是一个坏主意。客户将习惯使用一个名称，但如果产品名称发生变化，他们可能很容易失去对项目的跟踪。TrueOS 经历过名称更改。该项目于 2006 年启动时，它被命名为 PC-BSD，但在 2016 年，名称更改为 TrueOS。它让我想起了[ArchMerge 和 Arcolinux 传奇](https://itsfoss.com/archlabs-vs-archmerge/)。

话虽这么说，我认为这对 BSD 的桌面用户来说是一件好事。我常听见对 PC-BSD 和 TrueOS 的一个批评是它不是很精致。剥离项目的两个部分将有助于提高相关开发人员的关注度。TrueOS 团队将能够为缓慢进展的 FreeBSD 添加更新的功能，Project Trident 团队将能够改善用户的桌面体验。

我希望两个团队都好。请记住，当有人为开源而努力时，即使是我们不会使用的部分，我们也都会受益。

你对 TrueOS 和 Project Trident 的未来有何看法？请在下面的评论中告诉我们。

---

关于作者：

我叫 John Paul Wohlscheid。我是一个有抱负的神秘作家，喜欢玩技术，尤其是 Linux。你可以在[我的个人网站](http://johnpaulwohlscheid.work/)关注我。

---

via: <https://itsfoss.com/trueos-plan-change/>

作者：[John Paul Wohlscheid](https://itsfoss.com/author/john/) 译者：[geekpi](https://github.com/geekpi) 校对：[wxy](https://github.com/wxy)

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


Buildah 入门

>
> Buildah 提供一种灵活、可脚本编程的方式，来使用你熟悉的工具创建精简、高效的容器镜像。
>
>
>

![](/data/attachment/album/201807/13/233303ah3m26y2bsnb2rk2.jpg)

[Buildah](https://github.com/projectatomic/buildah) 是一个命令行工具，可以方便、快捷的构建与<ruby> <a href="https://www.opencontainers.org/">  开放容器标准 </a> <rt>  Open Container Initiative </rt></ruby>（OCI）兼容的容器镜像，这意味着其构建的镜像与 Docker 和 Kubernetes 兼容。该工具可作为 Docker 守护进程 `docker build` 命令（即使用传统的 Dockerfile 构建镜像）的一种<ruby> 简单 <rt>  drop-in </rt></ruby>替换，而且更加灵活，允许构建镜像时使用你擅长的工具。Buildah 可以轻松与脚本集成并生成<ruby> 流水线 <rt>  pipeline </rt></ruby>，最好之处在于构建镜像不再需要运行容器守护进程（LCTT 译注：这里主要是指 Docker 守护进程）。

### docker build 的简单替换

目前你可能使用 Dockerfile 和 `docker build` 命令构建镜像，那么你可以马上使用 Buildah 进行替代。Buildah 的 `build-using-dockerfile` （或 `bud`）子命令与 `docker build` 基本等价，因此可以轻松的与已有脚本结合或构建流水线。

类似我的上一篇关于 Buildah 的[文章](http://chris.collins.is/2017/08/17/buildah-a-new-way-to-build-container-images/)，我也将以使用源码安装 “GNU Hello” 为例进行说明，对应的 Dockerfile 文件如下：

```
FROM fedora:28
LABEL maintainer Chris Collins <collins.christopher@gmail.com>

RUN dnf install -y tar gzip gcc make \
        && dnf clean all

ADD http://ftpmirror.gnu.org/hello/hello-2.10.tar.gz /tmp/hello-2.10.tar.gz

RUN tar xvzf /tmp/hello-2.10.tar.gz -C /opt

WORKDIR /opt/hello-2.10

RUN ./configure
RUN make
RUN make install
RUN hello -v
ENTRYPOINT "/usr/local/bin/hello"

```

使用 Buildah 从 Dockerfile 构建镜像也很简单，使用 `buildah bud -t hello .` 替换 `docker build -t hello .` 即可：

```
[chris@krang] $ sudo buildah bud -t hello .
STEP 1: FROM fedora:28
Getting image source signatures
Copying blob sha256:e06fd16225608e5b92ebe226185edb7422c3f581755deadf1312c6b14041fe73
 81.48 MiB / 81.48 MiB [====================================================] 8s
Copying config sha256:30190780b56e33521971b0213810005a69051d720b73154c6e473c1a07ebd609
 2.29 KiB / 2.29 KiB [======================================================] 0s
Writing manifest to image destination
Storing signatures
STEP 2: LABEL maintainer Chris Collins <collins.christopher@gmail.com>
STEP 3: RUN dnf install -y tar gzip gcc make    && dnf clean all

<考虑篇幅，略去后续输出>

```

镜像构建完毕后，可以使用 `buildah images` 命令查看这个新镜像：

```
[chris@krang] $ sudo buildah images
IMAGE ID        IMAGE NAME                              CREATED AT              SIZE
30190780b56e    docker.io/library/fedora:28             Mar 7, 2018 16:53       247 MB
6d54bef73e63    docker.io/library/hello:latest    May 3, 2018 15:24     391.8 MB

```

新镜像的标签为 `hello:latest`，我们可以将其推送至远程镜像仓库，可以使用 [CRI-O](http://cri-o.io/) 或其它 Kubernetes CRI 兼容的运行时来运行该镜像，也可以推送到远程仓库。如果你要测试对 Docker build 命令的替代性，你可以将镜像拷贝至 docker 守护进程的本地镜像存储中，这样 Docker 也可以使用该镜像。使用 `buildah push` 可以很容易的完成推送操作：

```
[chris@krang] $ sudo buildah push hello:latest docker-daemon:hello:latest
Getting image source signatures
Copying blob sha256:72fcdba8cff9f105a61370d930d7f184702eeea634ac986da0105d8422a17028
 247.02 MiB / 247.02 MiB [==================================================] 2s
Copying blob sha256:e567905cf805891b514af250400cc75db3cb47d61219750e0db047c5308bd916
 144.75 MiB / 144.75 MiB [==================================================] 1s
Copying config sha256:6d54bef73e638f2e2dd8b7bf1c4dfa26e7ed1188f1113ee787893e23151ff3ff
 1.59 KiB / 1.59 KiB [======================================================] 0s
Writing manifest to image destination
Storing signatures

[chris@krang] $ sudo docker images | head -n2
REPOSITORY              TAG             IMAGE ID        CREATED                 SIZE
docker.io/hello      latest       6d54bef73e63  2 minutes ago   398 MB

[chris@krang] $ sudo docker run -t hello:latest
Hello, world!

```

### 若干差异

与 Docker build 不同，Buildah 不会自动的将 Dockerfile 中的每条指令产生的变更提到新的<ruby> 分层 <rt>  layer </rt></ruby>中，只是简单的每次从头到尾执行构建。类似于<ruby> 自动化 <rt>  automation </rt></ruby>和<ruby> 流水线构建 <rt>  build pipeline </rt></ruby>，这种<ruby> 无缓存构建 <rt>  non-cached </rt></ruby>方式的好处是可以提高构建速度，在指令较多时尤为明显。从<ruby> 自动部署 <rt>  automated deployment </rt></ruby>或<ruby> 持续交付 <rt>  continuous delivery </rt></ruby>的视角来看，使用这种方式可以快速的将新变更落实到生产环境中。

但从实际角度出发，缓存机制的缺乏对镜像开发不利，毕竟缓存层可以避免一遍遍的执行构建，从而显著的节省时间。自动分层只在 `build-using-dockerfile` 命令中生效。但我们在下面会看到，Buildah 原生命令允许我们选择将变更提交到硬盘的时间，提高了开发的灵活性。

### Buildah 原生命令

Buildah *真正* 有趣之处在于它的原生命令，你可以在容器构建过程中使用这些命令进行交互。相比与使用 `build-using-dockerfile/bud` 命令执行每次构建，Buildah 提供命令让你可以与构建过程中的临时容器进行交互。（Docker 也使用临时或<ruby> <em>  中间 </em> <rt>  intermediate </rt></ruby>容器，但你无法在镜像构建过程中与其交互。）

还是使用 “GNU Hello” 为例，考虑使用如下 Buildah 命令构建的镜像：

```
#!/usr/bin/env bash

set -o errexit

# Create a container
container=$(buildah from fedora:28)

# Labels are part of the "buildah config" command
buildah config --label maintainer="Chris Collins <collins.christopher@gmail.com>" $container

# Grab the source code outside of the container
curl -sSL http://ftpmirror.gnu.org/hello/hello-2.10.tar.gz -o hello-2.10.tar.gz

buildah copy $container hello-2.10.tar.gz /tmp/hello-2.10.tar.gz

buildah run $container dnf install -y tar gzip gcc make
buildah run $container dnf clean all
buildah run $container tar xvzf /tmp/hello-2.10.tar.gz -C /opt

# Workingdir is also a "buildah config" command
buildah config --workingdir /opt/hello-2.10 $container

buildah run $container ./configure
buildah run $container make
buildah run $container make install
buildah run $container hello -v

# Entrypoint, too, is a “buildah config” command
buildah config --entrypoint /usr/local/bin/hello $container

# Finally saves the running container to an image
buildah commit --format docker $container hello:latest

```

我们可以一眼看出这是一个 Bash 脚本而不是 Dockerfile。基于 Buildah 的原生命令，可以轻易的使用任何脚本语言或你擅长的自动化工具编写脚本。形式可以是 makefile、Python 脚本或其它你擅长的类型。

这个脚本做了哪些工作呢？首先，Buildah 命令 `container=$(buildah from fedora:28)` 基于 fedora:28 镜像创建了一个正在运行的容器，将容器名（`buildah from` 命令的返回值）保存到变量中，便于后续使用。后续所有命令都是有 `$container` 变量指明需要操作的容器。这些命令的功能大多可以从名称看出：`buildah copy` 将文件拷贝至容器，`buildah run` 会在容器中执行命令。可以很容易的将上述命令与 Dockerfile 中的指令对应起来。

最后一条命令 `buildah commit` 将容器提交到硬盘上的镜像中。当不使用 Dockerfile 而是使用 Buildah 命令构建镜像时，你可以使用 `commit` 命令决定何时保存变更。在上例中，所有的变更是一起提交的；但也可以增加中间提交，让你可以选择作为起点的<ruby> 缓存点 <rt>  cache point </rt></ruby>。（例如，执行完 `dnf install` 命令后将变更缓存到硬盘是特别有意义的，一方面因为该操作耗时较长，另一方面每次执行的结果也确实相同。）

### 挂载点，安装目录以及 chroot

另一个可以大大增加构建镜像灵活性的 Buildah 命令是 `buildah mount`，可以将容器的根目录挂载到你主机的一个挂载点上。例如：

```
[chris@krang] $ container=$(sudo buildah from fedora:28)
[chris@krang] $ mountpoint=$(sudo buildah mount ${container})
[chris@krang] $ echo $mountpoint
/var/lib/containers/storage/overlay2/463eda71ec74713d8cebbe41ee07da5f6df41c636f65139a7bd17b24a0e845e3/merged
[chris@krang] $ cat ${mountpoint}/etc/redhat-release
Fedora release 28 (Twenty Eight)
[chris@krang] $ ls ${mountpoint}
bin   dev  home  lib64          media  opt   root  sbin  sys  usr
boot  etc  lib   lost+found  mnt        proc  run   srv   tmp  var

```

这太棒了，你可以通过与挂载点交互对容器镜像进行修改。这允许你使用主机上的工具进行构建和安装软件，不用将这些构建工具打包到容器镜像本身中。例如，在我们上面的 Bash 脚本中，我们需要安装 tar、Gzip、GCC 和 make，在容器内编译 “GNU Hello”。如果使用挂载点，我仍使用同样的工具进行构建，但下载的压缩包和 tar、Gzip 等 RPM 包都在主机而不是容器和生成的镜像内：

```
#!/usr/bin/env bash

set -o errexit

# Create a container
container=$(buildah from fedora:28)
mountpoint=$(buildah mount $container)

buildah config --label maintainer="Chris Collins <collins.christopher@gmail.com>" $container

curl -sSL http://ftpmirror.gnu.org/hello/hello-2.10.tar.gz \
     -o /tmp/hello-2.10.tar.gz
tar xvzf src/hello-2.10.tar.gz -C ${mountpoint}/opt

pushd ${mountpoint}/opt/hello-2.10
./configure
make
make install DESTDIR=${mountpoint}
popd

chroot $mountpoint bash -c "/usr/local/bin/hello -v"

buildah config --entrypoint "/usr/local/bin/hello" $container
buildah commit --format docker $container hello
buildah unmount $container

```

在上述脚本中，需要提到如下几点：

1. `curl` 命令将压缩包下载到主机中，而不是镜像中；
2. （主机中的） `tar` 命令将压缩包中的源代码解压到容器的 `/opt` 目录；
3. `configure`，`make` 和 `make install` 命令都在主机的挂载点目录中执行，而不是在容器内；
4. 这里的 `chroot` 命令用于将挂载点本身当作根路径并测试 "hello" 是否正常工作；类似于前面例子中用到的 `buildah run` 命令。

这个脚本更加短小，使用大多数 Linux 爱好者都很熟悉的工具，最后生成的镜像也更小（没有 tar 包，没有额外的软件包等）。你甚至可以使用主机系统上的包管理器为容器安装软件。例如，（出于某种原因）你希望安装 GNU Hello 的同时在容器中安装 [NGINX](https://www.nginx.com/)：

```
[chris@krang] $ mountpoint=$(sudo buildah mount ${container})
[chris@krang] $ sudo dnf install nginx --installroot $mountpoint
[chris@krang] $ sudo chroot $mountpoint nginx -v
nginx version: nginx/1.12.1

```

在上面的例子中，DNF 使用 `--installroot` 参数将 NGINX 安装到容器中，可以通过 chroot 进行校验。

### 快来试试吧！

Buildah 是一种轻量级、灵活的容器镜像构建方法，不需要在主机上运行完整的 Docker 守护进程。除了提供基于 Dockerfile 构建容器的开箱即用支持，Buildah 还可以很容易的与脚本或你喜欢的构建工具相结合，特别是可以使用主机上已有的工具构建容器镜像。Buildah 生成的容器体积更小，更便于网络传输，占用更小的存储空间，而且潜在的受攻击面更小。快来试试吧！

**[阅读相关的故事，[使用 Buildah 创建小体积的容器](/article-9719-1.html)]**

---

via: <https://opensource.com/article/18/6/getting-started-buildah>

作者：[Chris Collins](https://opensource.com/users/clcollins) 选题：[lujun9972](https://github.com/lujun9972) 译者：[pinewall](https://github.com/pinewall) 校对：[wxy](https://github.com/wxy)

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


你所不了解的 Bash：关于 Bash 数组的介绍

Summary: 进入这个古怪而神奇的 Bash 数组的世界。

非常有用，谢谢

非常有用，谢谢

非常有用，谢谢

非常有用，谢谢

很好，感谢了！

进入这个古怪而神奇的 Bash 数组的世界。

>
> 进入这个古怪而神奇的 Bash 数组的世界。
>
>
>

![](/data/attachment/album/201807/15/112215n3r55lbqpdb4tbg9.jpg)

尽管软件工程师常常使用命令行来进行各种开发，但命令行中的数组似乎总是一个模糊的东西（虽然不像正则操作符 `=~` 那么复杂隐晦）。除开隐晦和有疑问的语法，[Bash](https://opensource.com/article/17/7/bash-prompt-tips-and-tricks) 数组其实是非常有用的。

### 稍等，这是为什么？

写 Bash 相关的东西很难，但如果是写一篇像手册那样注重怪异语法的文章，就会非常简单。不过请放心，这篇文章的目的就是让你不用去读该死的使用手册。

#### 真实（通常是有用的）示例

为了这个目的，想象一下真实世界的场景以及 Bash 是怎么帮忙的：你正在公司里面主导一个新工作，评估并优化内部数据管线的运行时间。首先，你要做个参数扫描分析来评估管线使用线程的状况。简单起见，我们把这个管道当作一个编译好的 C++ 黑盒子，这里面我们能够调整的唯一的参数是用于处理数据的线程数量：`./pipeline --threads 4`。

### 基础

我们首先要做的事是定义一个数组，用来容纳我们想要测试的 `--threads` 参数：

```
allThreads=(1 2 4 8 16 32 64 128)

```

本例中，所有元素都是数字，但参数并不一定是数字，Bash 中的数组可以容纳数字和字符串，比如 `myArray=(1 2 "three" 4 "five")` 就是个有效的表达式。就像 Bash 中其它的变量一样，确保赋值符号两边没有空格。否则 Bash 将会把变量名当作程序来执行，把 `=` 当作程序的第一个参数。

现在我们初始化了数组，让我们解析它其中的一些元素。仅仅输入 `echo $allThreads` ，你能发现，它只会输出第一个元素。

要理解这个产生的原因，需要回到上一步，回顾我们一般是怎么在 Bash 中输出变量。考虑以下场景：

```
type="article"
echo "Found 42 $type"

```

假如我们得到的变量 `$type` 是一个单词，我们想要添加在句子结尾一个 `s`。我们无法直接把 `s` 加到 `$type` 里面，因为这会把它变成另一个变量，`$types`。尽管我们可以利用像 `echo "Found 42 "$type"s"` 这样的代码形变，但解决这个问题的最好方法是用一个花括号：`echo "Found 42 ${type}s"`，这让我们能够告诉 Bash 变量名的起止位置（有趣的是，JavaScript/ES6 在 [template literals](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals) 中注入变量和表达式的语法和这里是一样的）

事实上，尽管 Bash 变量一般不用花括号，但在数组中需要用到花括号。这反而允许我们指定要访问的索引，例如 `echo ${allThreads[1]}` 返回的是数组中的第二个元素。如果不写花括号，比如 `echo $allThreads[1]`，会导致 Bash 把 `[1]` 当作字符串然后输出。

是的，Bash 数组的语法很怪，但是至少他们是从 0 开始索引的，不像有些语言（说的就是你，`R` 语言）。

### 遍历数组

上面的例子中我们直接用整数作为数组的索引，我们现在考虑两种其他情况：第一，如果想要数组中的第 `$i` 个元素，这里 `$i` 是一个代表索引的变量，我们可以这样 `echo ${allThreads[$i]}` 解析这个元素。第二，要输出一个数组的所有元素，我们把数字索引换成 `@` 符号（你可以把 `@` 当作表示 `all` 的符号）：`echo ${allThreads[@]}`。

#### 遍历数组元素

记住上面讲过的，我们遍历 `$allThreads` 数组，把每个值当作 `--threads` 参数启动管线：

```
for t in ${allThreads[@]}; do
  ./pipeline --threads $t
done

```

#### 遍历数组索引

接下来，考虑一个稍稍不同的方法。不遍历所有的数组元素，我们可以遍历所有的索引：

```
for i in ${!allThreads[@]}; do
  ./pipeline --threads ${allThreads[$i]}
done

```

一步一步看：如之前所见，`${allThreads[@]}` 表示数组中的所有元素。前面加了个感叹号，变成 `${!allThreads[@]}`，这会返回数组索引列表（这里是 0 到 7）。换句话说。`for` 循环就遍历所有的索引 `$i` 并从 `$allThreads` 中读取第 `$i` 个元素，当作 `--threads` 选项的参数。

这看上去很辣眼睛，你可能奇怪为什么我要一开始就讲这个。这是因为有时候在循环中需要同时获得索引和对应的值，例如，如果你想要忽视数组中的第一个元素，使用索引可以避免额外创建在循环中累加的变量。

### 填充数组

到目前为止，我们已经能够用给定的 `--threads` 选项启动管线了。现在假设按秒计时的运行时间输出到管线。我们想要捕捉每个迭代的输出，然后把它保存在另一个数组中，因此我们最终可以随心所欲的操作它。

#### 一些有用的语法

在深入代码前，我们要多介绍一些语法。首先，我们要能解析 Bash 命令的输出。用这个语法可以做到：`output=$( ./my_script.sh )`，这会把命令的输出存储到变量 `$output` 中。

我们需要的第二个语法是如何把我们刚刚解析的值添加到数组中。完成这个任务的语法看起来很熟悉：

```
myArray+=( "newElement1" "newElement2" )

```

#### 参数扫描

万事具备，执行参数扫描的脚步如下：

```
allThreads=(1 2 4 8 16 32 64 128)
allRuntimes=()
for t in ${allThreads[@]}; do
  runtime=$(./pipeline --threads $t)
  allRuntimes+=( $runtime )
done

```

就是这个了！

### 还有什么能做的？

这篇文章中，我们讲过使用数组进行参数扫描的场景。我敢保证有很多理由要使用 Bash 数组，这里就有两个例子：

#### 日志警告

本场景中，把应用分成几个模块，每一个都有它自己的日志文件。我们可以编写一个 cron 任务脚本，当某个模块中出现问题标志时向特定的人发送邮件：

```
# 日志列表，发生问题时应该通知的人
logPaths=("api.log" "auth.log" "jenkins.log" "data.log")
logEmails=("jay@email" "emma@email" "jon@email" "sophia@email")

# 在每个日志中查找问题标志
for i in ${!logPaths[@]};
do
  log=${logPaths[$i]}
  stakeholder=${logEmails[$i]}
  numErrors=$( tail -n 100 "$log" | grep "ERROR" | wc -l )

  # 如果近期发现超过 5 个错误，就警告负责人
  if [[ "$numErrors" -gt 5 ]];
  then
    emailRecipient="$stakeholder"
    emailSubject="WARNING: ${log} showing unusual levels of errors"
    emailBody="${numErrors} errors found in log ${log}"
    echo "$emailBody" | mailx -s "$emailSubject" "$emailRecipient"
  fi
done

```

#### API 查询

如果你想要生成一些分析数据，分析你的 Medium 帖子中用户评论最多的。由于我们无法直接访问数据库，SQL 不在我们考虑范围，但我们可以用 API！

为了避免陷入关于 API 授权和令牌的冗长讨论，我们将会使用 [JSONPlaceholder](https://github.com/typicode/jsonplaceholder)，这是一个面向公众的测试服务 API。一旦我们查询每个帖子，解析出每个评论者的邮箱，我们就可以把这些邮箱添加到我们的结果数组里：

```
endpoint="https://jsonplaceholder.typicode.com/comments"
allEmails=()

# 查询前 10 个帖子
for postId in {1..10};
do
  # 执行 API 调用，获取该帖子评论者的邮箱
  response=$(curl "${endpoint}?postId=${postId}")

  # 使用 jq 把 JSON 响应解析成数组
  allEmails+=( $( jq '.[].email' <<< "$response" ) )
done

```

注意这里我是用 [jq 工具](https://stedolan.github.io/jq/) 从命令行里解析 JSON 数据。关于 `jq` 的语法超出了本文的范围，但我强烈建议你了解它。

你可能已经想到，使用 Bash 数组在数不胜数的场景中很有帮助，我希望这篇文章中的示例可以给你思维的启发。如果你从自己的工作中找到其它的例子想要分享出来，请在帖子下方评论。

### 请等等，还有很多东西！

由于我们在本文讲了很多数组语法，这里是关于我们讲到内容的总结，包含一些还没讲到的高级技巧：

| 语法 | 效果 |
| --- | --- |
| `arr=()` | 创建一个空数组 |
| `arr=(1 2 3)` | 初始化数组 |
| `${arr[2]}` | 取得第三个元素 |
| `${arr[@]}` | 取得所有元素 |
| `${!arr[@]}` | 取得数组索引 |
| `${#arr[@]}` | 计算数组长度 |
| `arr[0]=3` | 覆盖第 1 个元素 |
| `arr+=(4)` | 添加值 |
| `str=$(ls)` | 把 `ls` 输出保存到字符串 |
| `arr=( $(ls) )` | 把 `ls` 输出的文件保存到数组里 |
| `${arr[@]:s:n}` | 取得从索引 `s` 开始的 `n` 个元素 |

### 最后一点思考

正如我们所见，Bash 数组的语法很奇怪，但我希望这篇文章让你相信它们很有用。只要你理解了这些语法，你会发现以后会经常使用 Bash 数组。

#### Bash 还是 Python？

问题来了：什么时候该用 Bash 数组而不是其他的脚本语法，比如 Python？

对我而言，完全取决于需求——如果你可以只需要调用命令行工具就能立马解决问题，你也可以用 Bash。但有些时候，当你的脚本属于一个更大的 Python 项目时，你也可以用 Python。

比如，我们可以用 Python 来实现参数扫描，但我们只用编写一个 Bash 的包装：

```
import subprocess

all_threads = [1, 2, 4, 8, 16, 32, 64, 128]
all_runtimes = []

# 用不同的线程数字启动管线
for t in all_threads:
  cmd = './pipeline --threads {}'.format(t)

  # 使用子线程模块获得返回的输出
  p = subprocess.Popen(cmd, stdout=subprocess.PIPE, shell=True)
  output = p.communicate()[0]
  all_runtimes.append(output)

```

由于本例中没法避免使用命令行，所以可以优先使用 Bash。

#### 羞耻的宣传时间

如果你喜欢这篇文章，这里还有很多类似的文章！ [在此注册，加入 OSCON](https://conferences.oreilly.com/oscon/oscon-or)，2018 年 7 月 17 号我会在这做一个主题为 [你所不了解的 Bash](https://conferences.oreilly.com/oscon/oscon-or/public/schedule/detail/67166) 的在线编码研讨会。没有幻灯片，不需要门票，只有你和我在命令行里面敲代码，探索 Bash 中的奇妙世界。

本文章由 [Medium] 首发，再发布时已获得授权。

---

via: <https://opensource.com/article/18/5/you-dont-know-bash-intro-bash-arrays>

作者：[Robert Aboukhalil](https://opensource.com/users/robertaboukhalil) 选题：[lujun9972](https://github.com/lujun9972) 译者：[BriFuture](https://github.com/BriFuture) 校对：[wxy](https://github.com/wxy)

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


使用 ftrace 跟踪内核

Summary: ftrace，它是添加到内核中的第一款跟踪工具，今天我们来看一下它都能做什么，让我们从它的一些重要术语开始吧。

错误太多了

![](/data/attachment/album/201807/15/121725wjwvkpp8epnrmnug.png)

在内核层面上分析事件有很多的工具：[SystemTap](https://sourceware.org/systemtap/)、[ktap](https://github.com/ktap/ktap)、[Sysdig](http://www.sysdig.org/)、[LTTNG](http://lttng.org/) 等等，你也可以在网络上找到关于这些工具的大量介绍文章和资料。

而对于使用 Linux 原生机制去跟踪系统事件以及检索/分析故障信息的方面的资料却很少找的到。这就是 [ftrace](https://www.kernel.org/doc/Documentation/trace/ftrace.txt)，它是添加到内核中的第一款跟踪工具，今天我们来看一下它都能做什么，让我们从它的一些重要术语开始吧。

### 内核跟踪和分析

<ruby> 内核分析 <rt>  Kernel profiling </rt></ruby>可以发现性能“瓶颈”。分析能够帮我们发现在一个程序中性能损失的准确位置。特定的程序生成一个<ruby> 概述 <rt>  profile </rt></ruby> — 这是一个事件总结 — 它能够用于帮我们找出哪个函数占用了大量的运行时间。尽管这些程序并不能识别出为什么会损失性能。

瓶颈经常发生在无法通过分析来识别的情况下。要推断出为什么会发生事件，就必须保存发生事件时的相关上下文，这就需要去<ruby> 跟踪 <rt>  tracing </rt></ruby>。

跟踪可以理解为在一个正常工作的系统上活动的信息收集过程。它使用特定的工具来完成这项工作，就像录音机来记录声音一样，用它来记录各种系统事件。

跟踪程序能够同时跟踪应用级和操作系统级的事件。它们收集的信息能够用于诊断多种系统问题。

有时候会将跟踪与日志比较。它们两者确时很相似，但是也有不同的地方。

对于跟踪，记录的信息都是些低级别事件。它们的数量是成百上千的，甚至是成千上万的。对于日志，记录的信息都是些高级别事件，数量上通常少多了。这些包含用户登录系统、应用程序错误、数据库事务等等。

就像日志一样，跟踪数据可以被原样读取，但是用特定的应用程序提取的信息更有用。所有的跟踪程序都能这样做。

在内核跟踪和分析方面，Linux 内核有三个主要的机制：

* <ruby> 跟踪点 <rt>  tracepoint </rt></ruby>：一种基于静态测试代码的工作机制
* <ruby> 探针 <rt>  kprobe </rt></ruby>：一种动态跟踪机制，用于在任意时刻中断内核代码的运行，调用它自己的处理程序，在完成需要的操作之后再返回
* perf\_events —— 一个访问 PMU（<ruby> 性能监视单元 <rt>  Performance Monitoring Unit </rt></ruby>）的接口

我并不想在这里写关于这些机制方面的内容，任何对它们感兴趣的人可以去访问 [Brendan Gregg 的博客](http://www.brendangregg.com/blog/index.html)。

使用 ftrace，我们可以与这些机制进行交互，并可以从用户空间直接得到调试信息。下面我们将讨论这方面的详细内容。示例中的所有命令行都是在内核版本为 3.13.0-24 的 Ubuntu 14.04 中运行的。

### ftrace：常用信息

ftrace 是 Function Trace 的简写，但它能做的远不止这些：它可以跟踪上下文切换、测量进程阻塞时间、计算高优先级任务的活动时间等等。

ftrace 是由 Steven Rostedt 开发的，从 2008 年发布的内核 2.6.27 中开始就内置了。这是为记录数据提供的一个调试 Ring 缓冲区的框架。这些数据由集成到内核中的跟踪程序来采集。

ftrace 工作在 debugfs 文件系统上，在大多数现代 Linux 发行版中都默认挂载了。要开始使用 ftrace，你将进入到 `sys/kernel/debug/tracing` 目录（仅对 root 用户可用）：

```
# cd /sys/kernel/debug/tracing

```

这个目录的内容看起来应该像这样：

```
аvailable_filter_functions  options            stack_trace_filter
available_tracers           per_cpu             trace
buffer_size_kb              printk_formats      trace_clock
buffer_total_size_kb        README              trace_marker
current_tracer              saved_cmdlines      trace_options
dyn_ftrace_total_info       set_event           trace_pipe
enabled_functions           set_ftrace_filter   trace_stat
events                      set_ftrace_notrace  tracing_cpumask
free_buffer                 set_ftrace_pid      tracing_max_latency
function_profile_enabled    set_graph_function  tracing_on
instances                   set_graph_notrace   tracing_thresh
kprobe_events               snapshot            uprobe_events
kprobe_profile              stack_max_size      uprobe_profile

```

我不想去描述这些文件和子目录；它们的描述在 [官方文档](https://www.kernel.org/doc/Documentation/trace/ftrace.txt) 中已经写的很详细了。我只想去详细介绍与我们这篇文章相关的这几个文件：

* available\_tracers —— 可用的跟踪程序
* current\_tracer —— 正在运行的跟踪程序
* tracing\_on —— 负责启用或禁用数据写入到 Ring 缓冲区的系统文件（如果启用它，数字 1 被添加到文件中，禁用它，数字 0 被添加）
* trace —— 以人类友好格式保存跟踪数据的文件

### 可用的跟踪程序

我们可以使用如下的命令去查看可用的跟踪程序的一个列表：

```
root@andrei:/sys/kernel/debug/tracing#: cat available_tracers
blk mmiotrace function_graph wakeup_rt wakeup function nop

```

我们来快速浏览一下每个跟踪程序的特性：

* function —— 一个无需参数的函数调用跟踪程序
* function\_graph —— 一个使用子调用的函数调用跟踪程序
* blk —— 一个与块 I/O 跟踪相关的调用和事件跟踪程序（它是 blktrace 使用的）
* mmiotrace —— 一个内存映射 I/O 操作跟踪程序
* nop —— 最简单的跟踪程序，就像它的名字所暗示的那样，它不做任何事情（尽管在某些情况下可能会派上用场，我们将在后文中详细解释）

### 函数跟踪程序

在开始介绍函数跟踪程序 ftrace 之前，我们先看一个测试脚本：

```
#!/bin/sh

dir=/sys/kernel/debug/tracing

sysctl kernel.ftrace_enabled=1
echo function > ${dir}/current_tracer
echo 1 > ${dir}/tracing_on
sleep 1
echo 0 > ${dir}/tracing_on
less ${dir}/trace

```

这个脚本是非常简单的，但是还有几个需要注意的地方。命令 `sysctl ftrace.enabled=1` 启用了函数跟踪程序。然后我们通过写它的名字到 `current_tracer` 文件来启用 `current tracer`。

接下来，我们写入一个 `1` 到 `tracing_on`，它启用了 Ring 缓冲区。这些语法都要求在 `1` 和 `>` 符号前后有一个空格；写成像 `echo 1> tracing_on` 这样将不能工作。一行之后我们禁用它（如果 `0` 写入到 `tracing_on`， 缓冲区不会被清除并且 ftrace 并不会被禁用）。

我们为什么这样做呢？在两个 `echo` 命令之间，我们看到了命令 `sleep 1`。我们启用了缓冲区，运行了这个命令，然后禁用它。这将使跟踪程序采集了这个命令运行期间发生的所有系统调用的信息。

在脚本的最后一行，我们写了一个在控制台上显示跟踪数据的命令。

一旦脚本运行完成后，我们将看到下列的输出（这里只列出了一个小片断）：

```
# tracer: function
#
# entries-in-buffer/entries-written: 29571/29571   #P:2
#
#                           _-----=> irqs-off
#                           / _----=> need-resched
#                           | / _---=> hardirq/softirq
#                           || / _--=> preempt-depth
#                           ||| /   delay
#           TASK-PID   CPU#  ||||   TIMESTAMP  FUNCTION
#           | |     |   ||||    |       |
        trace.sh-1295  [000] ....   90.502874: mutex_unlock <-rb_simple_write
        trace.sh-1295  [000] ....   90.502875: __fsnotify_parent <-vfs_write
        trace.sh-1295  [000] ....   90.502876: fsnotify <-vfs_write
        trace.sh-1295  [000] ....   90.502876: __srcu_read_lock <-fsnotify
        trace.sh-1295  [000] ....   90.502876: __srcu_read_unlock <-fsnotify
        trace.sh-1295  [000] ....   90.502877: __sb_end_write <-vfs_write
        trace.sh-1295  [000] ....   90.502877: syscall_trace_leave <-int_check_syscall_exit_work
        trace.sh-1295  [000] ....   90.502878: context_tracking_user_exit <-syscall_trace_leave
        trace.sh-1295  [000] ....   90.502878: context_tracking_user_enter <-syscall_trace_leave
        trace.sh-1295  [000] d...   90.502878: vtime_user_enter <-context_tracking_user_enter
        trace.sh-1295  [000] d...   90.502878: _raw_spin_lock <-vtime_user_enter
        trace.sh-1295  [000] d...   90.502878: __vtime_account_system <-vtime_user_enter
        trace.sh-1295  [000] d...   90.502878: get_vtime_delta <-__vtime_account_system
        trace.sh-1295  [000] d...   90.502879: account_system_time <-__vtime_account_system
        trace.sh-1295  [000] d...   90.502879: cpuacct_account_field <-account_system_time
        trace.sh-1295  [000] d...   90.502879: acct_account_cputime <-account_system_time
        trace.sh-1295  [000] d...   90.502879: __acct_update_integrals <-acct_account_cputime

```

这个输出以“缓冲区中的信息条目数量”和“写入的全部条目数量”开始。这两者的数据差异是缓冲区中事件的丢失数量（在我们的示例中没有发生丢失）。

在这里有一个包含下列信息的函数列表：

* 进程标识符（PID）
* 运行这个进程的 CPU（CPU#）
* 进程开始时间（TIMESTAMP）
* 被跟踪函数的名字以及调用它的父级函数；例如，在我们输出的第一行，`rb_simple_write` 调用了 `mutex-unlock` 函数。

### function\_graph 跟踪程序

function\_graph 跟踪程序的工作和函数跟踪程序一样，但是它更详细：它显示了每个函数的进入和退出点。使用这个跟踪程序，我们可以跟踪函数的子调用并且测量每个函数的运行时间。

我们来编辑一下最后一个示例的脚本：

```
#!/bin/sh

dir=/sys/kernel/debug/tracing

sysctl kernel.ftrace_enabled=1
echo function_graph > ${dir}/current_tracer
echo 1 > ${dir}/tracing_on
sleep 1
echo 0 > ${dir}/tracing_on
less ${dir}/trace

```

运行这个脚本之后，我们将得到如下的输出：

```
# tracer: function_graph
#
# CPU  DURATION                 FUNCTION CALLS
# |     |   |                   |   |   |   |
 0)   0.120 us  |                               } /* resched_task */
 0)   1.877 us  |                               } /* check_preempt_curr */
 0)   4.264 us  |                           } /* ttwu_do_wakeup */
 0) + 29.053 us   |                         } /* ttwu_do_activate.constprop.74 */
 0)   0.091 us  |                           _raw_spin_unlock();
 0)   0.260 us  |                           ttwu_stat();
 0)   0.133 us  |                           _raw_spin_unlock_irqrestore();
 0) + 37.785 us   |                         } /* try_to_wake_up */
 0) + 38.478 us   |                     } /* default_wake_function */
 0) + 39.203 us   |                     } /* pollwake */
 0) + 40.793 us   |                 } /* __wake_up_common */
 0)   0.104 us  |                   _raw_spin_unlock_irqrestore();
 0) + 42.920 us   |                 } /* __wake_up_sync_key */
 0) + 44.160 us   |             } /* sock_def_readable */
 0) ! 192.850 us  |             } /* tcp_rcv_established */
 0) ! 197.445 us  |         } /* tcp_v4_do_rcv */
 0)   0.113 us  |           _raw_spin_unlock();
 0) ! 205.655 us  |         } /* tcp_v4_rcv */
 0) ! 208.154 us  |     } /* ip_local_deliver_finish */

```

在这个图中，`DURATION` 展示了花费在每个运行的函数上的时间。注意使用 `+` 和 `!` 符号标记的地方。加号（`+`）意思是这个函数花费的时间超过 10 毫秒；而感叹号（`!`）意思是这个函数花费的时间超过了 100 毫秒。

在 `FUNCTION_CALLS` 下面，我们可以看到每个函数调用的信息。

和 C 语言一样使用了花括号（`{`）标记每个函数的边界，它展示了每个函数的开始和结束，一个用于开始，一个用于结束；不能调用其它任何函数的叶子函数用一个分号（`;`）标记。

### 函数过滤器

ftrace 输出可能会很大，精确找出你所需要的内容可能会非常困难。我们可以使用过滤器去简化我们的搜索：输出中将只显示与我们感兴趣的函数相关的信息。为实现过滤，我们只需要在 `set_ftrace_filter` 文件中写入我们需要过滤的函数的名字即可。例如：

```
root@andrei:/sys/kernel/debug/tracing# echo kfree > set_ftrace_filter

```

如果禁用过滤器，我们只需要在这个文件中添加一个空白行即可：

```
root@andrei:/sys/kernel/debug/tracing# echo  > set_ftrace_filter

```

通过运行这个命令：

```
root@andrei:/sys/kernel/debug/tracing# echo kfree > set_ftrace_notrace

```

我们将得到相反的结果：输出将包含除了 `kfree()` 以外的任何函数的信息。

另一个有用的选项是 `set_ftrace_pid`。它是为在一个特定的进程运行期间调用跟踪函数准备的。

ftrace 还有很多过滤选项。对于它们更详细的介绍，你可以去查看 Steven Rostedt 在 [LWN.net](https://lwn.net/Articles/370423/) 上的文章。

### 跟踪事件

我们在上面提到到跟踪点机制。跟踪点是插入的触发系统事件的特定代码。跟踪点可以是动态的（意味着可能会在它们上面附加几个检查），也可以是静态的（意味着不会附加任何检查）。

静态跟踪点不会对系统有任何影响；它们只是在测试的函数末尾增加几个字节的函数调用以及在一个独立的节上增加一个数据结构。

当相关代码片断运行时，动态跟踪点调用一个跟踪函数。跟踪数据是写入到 Ring 缓冲区。

跟踪点可以设置在代码的任何位置；事实上，它们确实可以在许多的内核函数中找到。我们来看一下 `kmem_cache_alloc` 函数（取自 [这里](http://lxr.free-electrons.com/source/mm/slab.c)）：

```
{
    void *ret = slab_alloc(cachep, flags, _RET_IP_);

    trace_kmem_cache_alloc(_RET_IP_, ret,
                            cachep->object_size, cachep->size, flags);
         return ret;
         }

```

`trace_kmem_cache_alloc` 它本身就是一个跟踪点。我们可以通过查看其它内核函数的源代码找到这样无数的例子。

在 Linux 内核中为了从用户空间使用跟踪点，它有一个专门的 API。在 `/sys/kernel/debug/tracing` 目录中，这里有一个事件目录，它是为了保存系统事件。这些只是为了跟踪系统事件。在这个上下文中系统事件可以理解为包含在内核中的跟踪点。

可以通过运行如下的命令来查看这个事件列表：

```
root@andrei:/sys/kernel/debug/tracing# cat available_events

```

这个命令将在控制台中输出一个很长的列表。这样看起来很不方便。我们可以使用如下的命令来列出一个结构化的列表：

```
root@andrei:/sys/kernel/debug/tracing# ls events

block           gpio        mce     random      skb     vsyscall
btrfs           header_event  migrate  ras          sock    workqueue
compaction      header_page   module   raw_syscalls  spi    writeback
context_tracking  iommu         napi    rcu         swiotlb   xen
enable          irq         net     regmap      syscalls  xfs
exceptions      irq_vectors   nmi   regulator   task    xhci-hcd
ext4            jbd2        oom     rpm         timer
filemap         kmem        pagemap  sched      udp
fs              kvm         power   scsi        vfs
ftrace          kvmmmu      printk   signal     vmscan

```

所有可能的事件都按子系统分组到子目录中。在我们开始跟踪事件之前，我们要先确保启用了 Ring 缓冲区写入：

```
root@andrei:/sys/kernel/debug/tracing# cat tracing_on

```

如果在控制台中显示的是数字 0，那么，我们可以运行如下的命令来启用它：

```
root@andrei:/sys/kernel/debug/tracing# echo 1 > tracing_on

```

在我们上一篇的文章中，我们写了关于 `chroot()` 系统调用的内容；我们来跟踪访问一下这个系统调用。对于我们的跟踪程序，我们使用 `nop` 因为函数跟踪程序和 `function_graph` 跟踪程序记录的信息太多，它包含了我们不感兴趣的事件信息。

```
root@andrei:/sys/kernel/debug/tracing# echo nop > current_tracer

```

所有事件相关的系统调用都保存在系统调用目录下。在这里我们将找到一个进入和退出各种系统调用的目录。我们需要在相关的文件中通过写入数字 `1` 来激活跟踪点：

```
root@andrei:/sys/kernel/debug/tracing# echo 1 > events/syscalls/sys_enter_chroot/enable

```

然后我们使用 `chroot` 来创建一个独立的文件系统（更多内容，请查看 [之前这篇文章](https://blog.selectel.com/containerization-mechanisms-namespaces/)）。在我们执行完我们需要的命令之后，我们将禁用跟踪程序，以便于不需要的信息或者过量信息不会出现在输出中：

```
root@andrei:/sys/kernel/debug/tracing# echo 0 > tracing_on

```

然后，我们去查看 Ring 缓冲区的内容。在输出的结束部分，我们找到了有关的系统调用信息（这里只是一个节选）。

```
root@andrei:/sys/kernel/debug/tracing# сat trace

......
          chroot-11321 [000] ....  4606.265208: sys_chroot(filename: 7fff785ae8c2)
          chroot-11325 [000] ....  4691.677767: sys_chroot(filename: 7fff242308cc)
            bash-11338 [000] ....  4746.971300: sys_chroot(filename: 7fff1efca8cc)
            bash-11351 [000] ....  5379.020609: sys_chroot(filename: 7fffbf9918cc)

```

关于配置事件跟踪的更的信息可以在 [这里](https://www.kernel.org/doc/Documentation/trace/events.txt) 找到。

### 结束语

在这篇文篇中，我们做了一个 ftrace 的功能概述。我们非常感谢你的任何意见或者补充。如果你想深入研究这个主题，我们为你推荐下列的资源：

* <https://www.kernel.org/doc/Documentation/trace/tracepoints.txt> — 一个跟踪点机制的详细描述
* <https://www.kernel.org/doc/Documentation/trace/events.txt> — 在 Linux 中跟踪系统事件的指南
* <https://www.kernel.org/doc/Documentation/trace/ftrace.txt> — ftrace 的官方文档
* <https://lttng.org/files/thesis/desnoyers-dissertation-2009-12-v27.pdf> — Mathieu Desnoyers（作者是跟踪点和 LTTNG 的创建者）的关于内核跟踪和分析的学术论文。
* <https://lwn.net/Articles/370423/> — Steven Rostedt 的关于 ftrace 功能的文章
* <http://alex.dzyoba.com/linux/profiling-ftrace.html> — 用 ftrace 分析实际案例的一个概述

---

via:<https://blog.selectel.com/kernel-tracing-ftrace/>

作者：[Andrej Yemelianov](https://blog.selectel.com/author/yemelianov/) 译者：[qhwdw](https://github.com/qhwdw) 校对：[wxy](https://github.com/wxy)

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


2018 年 6 月 COPR 中值得尝试的 4 个很酷的新项目

Summary: COPR 是个人软件仓库集合，它不在 Fedora 中。这是因为某些软件不符合轻松打包的标准。

![](/data/attachment/album/201807/15/124434vfv2bojtobboovwg.jpg)

COPR 是个人软件仓库[集合](https://copr.fedorainfracloud.org/)，它不在 Fedora 中。这是因为某些软件不符合轻松打包的标准。或者它可能不符合其他 Fedora 标准，尽管它是免费和开源的。COPR 可以在 Fedora 套件之外提供这些项目。COPR 中的软件不被 Fedora 基础设施不支持或没有被该项目所签名。但是，这是一种尝试新的或实验性的软件的一种巧妙的方式。

这是 COPR 中一组新的有趣项目。

### Ghostwriter

[Ghostwriter](http://wereturtle.github.io/ghostwriter/) 是 [Markdown](https://daringfireball.net/) 格式的文本编辑器，它有一个最小的界面。它以 HTML 格式提供文档预览，并为 Markdown 提供语法高亮显示。它提供了仅高亮显示当前正在编写的段落或句子的选项。此外，Ghostwriter 可以将文档导出为多种格式，包括 PDF 和 HTML。最后，它有所谓的“海明威”模式，其中删除被禁用，迫使用户现在智能编写，而在稍后编辑。

![](/data/attachment/album/201807/15/124435l7v4p99mp8l4kz76.png)

#### 安装说明

仓库目前为 Fedora 26、27、28 和 Rawhide 以及 EPEL 7 提供 Ghostwriter。要安装 Ghostwriter，请使用以下命令：

```
sudo dnf copr enable scx/ghostwriter
sudo dnf install ghostwriter

```

### Lector

[Lector](https://github.com/BasioMeusPuga/Lector) 是一个简单的电子书阅读器程序。Lector 支持最常见的电子书格式，如 EPUB、MOBI 和 AZW，以及漫画书格式 CBZ 和 CBR。它很容易设置 —— 只需指定包含电子书的目录即可。你可以使用表格或书籍封面浏览 Lector 库内的书籍。Lector 的功能包括书签、用户自定义标签和内置字典。![](/data/attachment/album/201807/15/124435uaxxhbqu3o2iuoiu.png)

#### 安装说明

该仓库目前为 Fedora 26、27、28 和 Rawhide 提供Lector。要安装 Lector，请使用以下命令：

```
sudo dnf copr enable bugzy/lector
sudo dnf install lector

```

### Ranger

Ranerger 是一个基于文本的文件管理器，它带有 Vim 键绑定。它以三列显示目录结构。左边显示父目录，中间显示当前目录的内容，右边显示所选文件或目录的预览。对于文本文件，Ranger 将文件的实际内容作为预览。![](/data/attachment/album/201807/15/124435gx2f1qiivil8liig.png)

#### 安装说明

该仓库目前为 Fedora 27、28 和 Rawhide 提供 Ranger。要安装 Ranger，请使用以下命令：

```
sudo dnf copr enable fszymanski/ranger
sudo dnf install ranger

```

### PrestoPalette

PrestoPeralette 是一款帮助创建平衡调色板的工具。PrestoPalette 的一个很好的功能是能够使用光照来影响调色板的亮度和饱和度。你可以将创建的调色板导出为 PNG 或 JSON。

![](/data/attachment/album/201807/15/124436myb910aupvppjsty.png)

#### 安装说明

仓库目前为 Fedora 26、27、28 和 Rawhide 以及 EPEL 7 提供 PrestoPalette。要安装 PrestoPalette，请使用以下命令：

```
sudo dnf copr enable dagostinelli/prestopalette
sudo dnf install prestopalette

```

---

via: <https://fedoramagazine.org/4-try-copr-june-2018/>

作者：[Dominik Turecek](https://fedoramagazine.org) 选题：[lujun9972](https://github.com/lujun9972) 译者：[geekpi](https://github.com/geekpi) 校对：[wxy](https://github.com/wxy)

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


区块链进化简史：为什么开源是其核心所在

Summary: 从比特币到下一代区块链。

从比特币到下一代区块链。

>
> 从比特币到下一代区块链。
>
>
>

![](/data/attachment/album/201807/16/163706ej7j7rrtww1ggrgy.png)

当开源项目开发下一个新版本时，用后缀 “-ng” 表示 “下一代”的情况并不鲜见。幸运的是，到目前为止，快速演进的区块链成功地避开了这个命名陷阱。但是在这个开源生态系统的演进过程中，改变是不断发生的，而好的创意以典型的开源方式在许多不同的项目中被采用、交融和演进。

在本文中，我将审视不同代次的区块链，并且看一看在处理这个生态系统遇到的问题时出现什么创意。当然，任何对生态系统进行分类的尝试都有其局限性的 —— 和不同意见者的 —— 但是这也将为混乱的区块链项目提供了一个粗略的指南。

### 始作俑者：比特币

第一代的区块链起源于 <ruby>   比特币  <rt>  Bitcoin </rt></ruby> 区块链，这是以去中心化、点对点加密货币为基础的<ruby> 总帐 <rt>  ledger </rt></ruby>，它从 [Slashdot](https://slashdot.org/) 网站上的杂谈变成了一个主流话题。

这个区块链是一个分布式总帐，它对所有用户的<ruby> 交易 <rt>  transaction </rt></ruby>保持跟踪，以避免他们<ruby> 双重支付 <rt>  double-spending </rt></ruby>（双花）货币（在历史上，这个任务是委托给第三方—— 银行 ——来做的）。为防范攻击者在系统上捣乱，总帐被复制到每个参与到比特币网络的计算机上，并且每次只允许一台计算机去更新总帐。为决定哪台计算机能够获得更新总帐的权力，系统安排在比特币网络上的计算机之间每 10 分钟进行一场竞赛，这将消耗它们的（许多）能源才能参与竞赛。赢家将获得将前 10 分钟发生的交易写入到总帐（区块链中的“区块”）的权力，并且为赢家写入区块链的工作给予一些比特币奖励。这种方式被称为<ruby> 工作量证明 <rt>  proof of work </rt></ruby>（PoW）共识机制。

这就是区块链最有趣的地方。比特币以[开源项目](https://github.com/bitcoin/bitcoin)的方式发布于 2009 年 1 月 。在 2010 年，由于意识到这些元素中的许多是可以调整的，围绕比特币聚集起了一个社区 —— [bitcointalk 论坛](https://bitcointalk.org/)，来开始各种实验。

起初，看到的比特币区块链是一个分布式数据库的形式， [Namecoin](https://www.namecoin.org/) 项目出现后，建议去保存任意数据到它的事务数据库中。如果区块链能够记录金钱的转移，那么它也应该能够记录其它资产的转移，比如域名。这正是 Namecoin 的主要使用场景，它上线于 2011 年 4 月 —— 也就是比特币出现两年后。

Namecoin 调整的地方是区块链的内容，<ruby>   莱特币  <rt>  Litecoin </rt></ruby> 调整的是两个技术部分：一是将两个区块的时间间隔从 10 分钟减少到 2.5 分钟，二是改变了竞赛方式（用 [scrypt](https://en.wikipedia.org/wiki/Scrypt) 来替换了 SHA-256 安全哈希算法）。这是能够做到的，因为比特币是以开源软件的方式来发布的，而莱特币本质上与比特币在其它部分是完全相同的。莱特币是修改了比特币共识机制的第一个分叉，这也为其它的更多“币”铺平了道路。

沿着这条道路，基于比特币代码库的各种变种越来越多。其中一些扩展了比特币的用途，比如 [Zerocash](http://zerocash-project.org/index) 协议，它专注于提供交易的匿名性和可替换性，但它最终分拆为它自己的货币 —— [Zcash](https://z.cash)。

虽然 Zcash 带来了它自己的创新，使用了最近被称为“<ruby> 零知识证明 <rt>  zero-knowledge proof </rt></ruby>”的加密技术，但它维持着与大多数主要的比特币代码库的兼容性，这意味着它能够从上游的比特币创新中获益。

另外的项目 —— [CryptoNote](https://cryptonote.org/)，它萌芽于相同的社区，但是并没有使用相同的代码，它以比特币为背景来构建的，但又与之不同。它发布于 2012 年 12 月，由于它的出现，导致了几种加密货币的诞生，最著名的 <ruby>   门罗币  <rt>  Monero </rt></ruby> （2014）就是其中之一。门罗币与 Zcash 使用了不同的方法，但解决了相同的问题：隐私性和可替换性。

就像在开源世界中经常出现的案例一样，做同样的工作有不止一个的工具可用。

### 下一代：“Blockchain-ng”

但是，到目前为止，所有的这些变体只是改进加密货币或者扩展它们去支持其它类型的事务。因此，这就引出了第二代区块链。

一旦社区开始去修改区块链的用法和调整技术部分时，对于一些想去扩展和重新思考它们未来的人来说，这种调整花费不了多长时间的。比特币的长期追随者 —— [Vitalik Buterin](https://en.wikipedia.org/wiki/Vitalik_Buterin) 在 2013 年底建议，区域链的事务应该能够表示一个状态机的状态变化，将区域链视为能够运行应用程序（“<ruby> 智能合约 <rt>  smart contract </rt></ruby>”）的分布式计算机。这个项目 —— <ruby>   以太坊  <rt>  Ethereum </rt></ruby>，上线于 2015 年 4 月。它在运行分布式应用程序方面取得了巨大的成功，它的一些非常流行的分布式应用程序（<ruby>   加密猫  <rt>  CryptoKitties </rt></ruby>）甚至导致以太坊区块链变慢。

这证明了目前的区块链存在一个很大的局限性：速度和容量。（速度通常用每秒事务数来测量，简称 TPS）有几个提议都建议去解决这个速度问题，从<ruby> 分片 <rt>  sharding </rt></ruby>到<ruby> 侧链 <rt>  sidechain </rt></ruby>，以及一个被称为“<ruby> 第二层 <rt>  second-layer </rt></ruby>”的解决方案。这里需要更多的创新。

随着“智能合约”这个词开始流行起来，并且用已经被证实仍然很慢的技术去运行它们，那么就需要实现其它的思路：<ruby> 许可区块链 <rt>  Permissioned blockchain </rt></ruby>。到目前为止，我们所介绍的所有区块链网络有两个没有明说的特征：一是它们是公开的（任何人都可以看到它们的功能），二是它们不需要许可（任何人都可以加入它们）。这两个部分是运行一个分布式的、非基于第三方的货币应该具有的和必需具有的条件。

随着区块链被认为出现与加密货币越来越明显的分离趋势，开始去考虑一些隐私、许可场景是很有意义的。一个有业务关系但不需要彼此完全信任的财团类型的参与者，能够从这些区块链类型中获益 —— 比如，物流链上的参与者，定期进行双边结算或者使用一个清算中心的金融、保险、或医疗保健机构。

一旦你将设置从“任何人都可以加入”变为“仅邀请者方可加入”，进一步对区块链构建区块的方式进行改变和调整将变得可能，那么对一些人来说，结果将变得非常有趣。

首先，设计用来保护网络不受恶意或者垃圾参与者的影响的工作量证明（PoW）可以被替换为更简单的和更少资源消耗的一些东西，比如，基于 [Raft](https://en.wikipedia.org/wiki/Raft_(computer_science)) 的共识协议。在更高级别的安全性和更快的速度之间进行权衡，采用更简单的共识算法。对于更多群体来说这样更理想，因为他们可以用基于加密技术的担保来取代其它的基于法律关系的担保，例如为避免由于竞争而产生的大量能源消耗，而工作量证明就是这种情况。另外一个创新的地方是，使用 <ruby>   股权证明  <rt>  Proof of Stake </rt></ruby>（PoS），它是公共网络共识机制的一个重量级的竞争者。它将可能像许可链网络一样找到它自己的实现方式。

有几个项目可以让创建许可区块链变得更简单，包括 [Quorum](https://www.jpmorgan.com/global/Quorum) （以太坊的一个分叉）和 [Hyperledger](https://hyperledger.org/) 的 [Fabric](https://www.hyperledger.org/projects/fabric) 和 [Sawtooth](https://www.hyperledger.org/projects/sawtooth)，这是基于新代码的两个开源项目。

许可区块链可以避免公共的、非许可方式的区块链中某些错综复杂的问题，但是它自己也存在一些问题。正确地管理参与者是其中的一个问题：谁可以加入？如何辨别他们？如何将他们从网络上移除？网络上的一个实体是否去管理一个中央公共密钥基础设施（PKI）?

### 区块链的开放本质

到目前为止的所有案例中，有一件事情是很明确的：使用一个区块链的目标是去提升网络中的参与者和它产生的数据的信任水平，理想情况下，不需要做进一步的工作即可足以使用它。

只有为这个网络提供动力的软件是自由和开源的，才能达到这种信任水平。即便是一个正确的、专用的、分布式区块链，它的本质仍然是运行着相同的第三方代码的私有代理的集合。从本质上来说，区块链的源代码必须是开源的，但仅是开源还不够。随着生态系统持续成长，这既是最低限度的担保也是进一步创新的源头。

最后，值得一提的是，虽然区块链的开放本质被认为是创新和变化的源头，它也被认为是一种治理形式：代码治理，用户期望运行的任何一个特定版本，都应该包含他们认为的整个网络应该包含的功能和方法。在这方面，需要说明的一点是，一些区块链的开放本质正在“变味”。但是这一问题正在解决。

### 第三和第四代：治理

接下来，我正在考虑第三代和第四代区块链：区块链将内置治理工具，并且项目将去解决棘手的大量不同区块链之间互连互通的问题，以便于它们之间可以交换信息和价值。

---

关于作者

axel simon： 长期的自由及开源软件爱好者，就职于 Red Hat ，关注安全和区块链技术，以及分布式系统和协议。致力于保护互联网及其成就（知识分享、信息访问、去中心化和网络中立）。

---

via: <https://opensource.com/article/18/6/blockchain-guide-next-generation>

作者：[Axel Simon](https://opensource.com/users/axel) 选题：[lujun9972](https://github.com/lujun9972) 译者：[qhwdw](https://github.com/qhwdw) 校对：[wxy](https://github.com/wxy)

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


如何在绝大部分类型的机器上安装 NVIDIA 显卡驱动

Summary: 这个流程适用于任何支持 UEFI 的计算机和任意新的 NVIDIA 显卡。

这个流程适用于任何支持 UEFI 的计算机和任意新的 NVIDIA 显卡。

![](/data/attachment/album/201807/16/183011kvccauc7ufddagag.jpg)

无论是研究还是娱乐，安装一个最新的显卡驱动都能提升你的计算机性能，并且使你能全方位地实现新功能。本安装指南使用 Fedora 28 的新的第三方仓库来安装 NVIDIA 驱动。它将引导您完成硬件和软件两方面的安装，并且涵盖需要让你的 NVIDIA 显卡启动和运行起来的一切知识。这个流程适用于任何支持 UEFI 的计算机和任意新的 NVIDIA 显卡。

### 准备

本指南依赖于下面这些材料：

* 一台使用 [UEFI](https://whatis.techtarget.com/definition/Unified-Extensible-Firmware-Interface-UEFI) 的计算机，如果你不确定你的电脑是否有这种固件，请运行 `sudo dmidecode -t 0`。如果输出中出现了 “UEFI is supported”，你的安装过程就可以继续了。不然的话，虽然可以在技术上更新某些电脑来支持 UEFI，但是这个过程的要求很苛刻，我们通常不建议你这么使用。
* 一个现代的、支持 UEFI 的 NVIDIA 的显卡
* 一个满足你的 NVIDIA 显卡的功率和接线要求的电源（有关详细信息，请参考“硬件和修改”的章节）
* 网络连接
* Fedora 28 系统

### 安装实例

这个安装示例使用的是：

* 一台 Optiplex 9010 的主机（一台相当老的机器）
* [NVIDIA GeForce GTX 1050 Ti XLR8 游戏超频版 4 GB GDDR5 PCI Express 3.0 显卡](https://www.cnet.com/products/pny-geforce-gtx-xlr8-gaming-1050-ti-overclocked-edition-graphics-card-gf-gtx-1050-ti-4-gb/specs/)
* 为了满足新显卡的电源要求，电源升级为 [EVGA – 80 PLUS 600 W ATX 12V/EPS 12V](https://www.evga.com/products/product.aspx?pn=100-B1-0600-KR)，这个最新的电源（PSU）比推荐的最低要求高了 300 W，但在大部分情况下，满足推荐的最低要求就足够了。
* 然后，当然的，Fedora 28 也别忘了.

### 硬件和修改

#### 电源（PSU）

打开你的台式机的机箱，检查印刷在电源上的最大输出功率。然后，查看你的 NVIDIA 显卡的文档，确定推荐的最小电源功率要求（以瓦特为单位）。除此之外，检查你的显卡，看它是否需要额外的接线，例如 6 针连接器，大多数的入门级显卡只从主板获取电力，但是有一些显卡需要额外的电力，如果出现以下情况，你需要升级你的电源：

1. 你的电源的最大输出功率低于显卡建议的最小电源功率。注意：根据一些显卡厂家的说法，比起推荐的功率，预先构建的系统可能会需要更多或更少的功率，而这取决于系统的配置。如果你使用的是一个特别耗电或者特别节能的配置，请灵活决定你的电源需求。
2. 你的电源没有提供必须的接线口来为你的显卡供电。

电源的更换很容易，但是在你拆除你当前正在使用的电源之前，请务必注意你的接线布局。除此之外，请确保你选择的电源适合你的机箱。

#### CPU

虽然在大多数老机器上安装高性能的 NVIDIA 显卡是可能的，但是一个缓慢或受损的 CPU 会阻碍显卡性能的发挥，如果要计算在你的机器上瓶颈效果的影响，请点击[这里](http://thebottlenecker.com "Home: The Bottle Necker")。了解你的 CPU 性能来避免高性能的显卡和 CPU 无法保持匹配是很重要的。升级你的 CPU 是一个潜在的考虑因素。

#### 主板

在继续进行之前，请确认你的主板和你选择的显卡是兼容的。你的显卡应该插在最靠近散热器的 PCI-E x16 插槽中。确保你的设置为显卡预留了足够的空间。此外，请注意，现在大部分的显卡使用的都是 PCI-E 3.0 技术。虽然这些显卡如果插在 PCI-E 3.0 插槽上会运行地最好，但如果插在一个旧版的插槽上的话，性能也不会受到太大的影响。

### 安装

1、 首先，打开终端更新你的包管理器（如果没有更新的话）：

```
sudo dnf update

```

2、 然后，使用这条简单的命令进行重启：

```
reboot

```

3、 在重启之后，安装 Fedora 28 的工作站的仓库：

```
sudo dnf install fedora-workstation-repositories

```

4、 接着，设置 NVIDIA 驱动的仓库：

```
sudo dnf config-manager --set-enabled rpmfusion-nonfree-nvidia-driver

```

5、 然后，再次重启。

6、 在这次重启之后，通过下面这条命令验证是否添加了仓库：

```
sudo dnf repository-packages rpmfusion-nonfree-nvidia-driver info

```

如果加载了多个 NVIDIA 工具和它们各自的 spec 文件，请继续进行下一步。如果没有，你可能在添加新仓库的时候遇到了一个错误。你应该再试一次。

7、 登录，连接到互联网，然后打开“软件”应用程序。点击“加载项>硬件驱动> NVIDIA Linux 图形驱动>安装”。

如果你使用更老的显卡或者想使用多个显卡，请进一步查看 [RPMFusion 指南](https://rpmfusion.org/Howto/NVIDIA?highlight=%28CategoryHowto%29)。最后，要确保启动成功，设置 `/etc/gdm/custom.conf` 中的 `WaylandEnable=false`，确认避免使用安全启动。 接着，再一次重启。

8、这个过程完成后，关闭所有的应用并**关机**。拔下电源插头，然后按下电源按钮以释放余电，避免你被电击。如果你对电源有开关，关闭它。

9、 最后，安装显卡，拔掉老的显卡并将新的显卡插入到正确的 PCI-E x16 插槽中。成功安装新的显卡之后，关闭你的机箱，插入电源 ，然后打开计算机，它应该会成功启动。

**注意：** 要禁用此安装中使用的 NVIDIA 驱动仓库，或者要禁用所有的 Fedora 工作站仓库，请参考这个 [Fedora Wiki 页面](https://fedoraproject.org/wiki/Workstation/Third_Party_Software_Repositories)。

### 验证

1、 如果你新安装的 NVIDIA 显卡已连接到你的显示器并显示正确，则表明你的 NVIDIA 驱动程序已成功和显卡建立连接。

如果你想去查看你的设置，或者验证驱动是否在正常工作（这里，主板上安装了两块显卡），再次打开 “NVIDIA X 服务器设置应用程序”。这次，你应该不会得到错误信息提示，并且系统会给出有关 X 的设置文件和你的 NVIDIA 显卡的信息。（请参考下面的屏幕截图）

![NVIDIA X Server Settings](/data/attachment/album/201807/16/183014p8kwwai28886u2k8.png "NVIDIA X Server Settings")

通过这个应用程序，你可以根据你的需要需改 X 配置文件，并可以监控显卡的性能，时钟速度和温度信息。

2、 为确保新显卡以满功率运行，显卡性能测试是非常必要的。GL Mark 2，是一个提供后台处理、构建、照明、纹理等等有关信息的标准工具。它提供了一个优秀的解决方案。GL Mark 2 记录了各种各样的图形测试的帧速率，然后输出一个总体的性能评分（这被称为 glmark2 分数）。

**注意:** glxgears 只会测试你的屏幕或显示器的性能，不会测试显卡本身，请使用 GL Mark 2。

要运行 GLMark2：

1. 打开终端并关闭其他所有的应用程序
2. 运行 `sudo dnf install glmark2` 命令
3. 运行 `glmark2` 命令
4. 允许运行完整的测试来得到最好的结果。检查帧速率是否符合你对这块显卡的预期。如果你想要额外的验证，你可以查阅网站来确认是否已有你这块显卡的 glmark2 测试评分被公布到网上，你可以比较这个分数来评估你这块显卡的性能。
5. 如果你的帧速率或者 glmark2 评分低于预期，请思考潜在的因素。CPU 造成的瓶颈？其他问题导致？

如果诊断的结果很好，就开始享受你的新显卡吧。

### 参考链接

* [How to benchmark your GPU on Linux](https://www.howtoforge.com/tutorial/linux-gpu-benchmark/)
* [How to install a graphics card](https://www.pcworld.com/article/2913370/components-graphics/how-to-install-a-graphics-card.html)
* [The Fedora Wiki Page](https://fedoraproject.org/wiki/Workstation/Third_Party_Software_Repositories)
* [The Bottlenecker](http://thebottlenecker.com "Home: The Bottle Necker")
* [What Is Unified Extensible Firmware Interface (UEFI)](https://whatis.techtarget.com/definition/Unified-Extensible-Firmware-Interface-UEFI)

---

via: <https://fedoramagazine.org/install-nvidia-gpu/>

作者：[Justice del Castillo](https://fedoramagazine.org/author/justice/) 选题：[lujun9972](https://github.com/lujun9972) 译者：[hopefully2333](https://github.com/hopefully2333) 校对：[wxy](https://github.com/wxy)

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


我的第一个系统管理员错误

Summary: 如何在崩溃的局面中集中精力寻找解决方案。

如何在崩溃的局面中集中精力寻找解决方案。

>
> 如何在崩溃的局面中集中精力寻找解决方案。
>
>
>

![](/data/attachment/album/201807/17/151748xgant68566rtr56z.jpg)

如果你在 IT 领域工作，你知道事情永远不会像你想象的那样完好。在某些时候，你会遇到错误或出现问题，你最终必须解决问题。这就是系统管理员的工作。

作为人类，我们都会犯错误。我们不是已经犯错，就是即将犯错。结果，我们最终还必须解决自己的错误。总是这样。我们都会失误、敲错字母或犯错。

作为一名年轻的系统管理员，我艰难地学到了这一课。我犯了一个大错。但是多亏了上级的指导，我学会了不去纠缠于我的错误，而是制定一个“错误策略”来做正确的事情。从错误中吸取教训。克服它，继续前进。

我的第一份工作是一家小公司的 Unix 系统管理员。真的，我是一名生嫩的系统管理员，但我大部分时间都独自工作。我们是一个小型 IT 团队，只有我们三个人。我是 20 或 30 台 Unix 工作站和服务器的唯一系统管理员。另外两个支持 Windows 服务器和桌面。

任何阅读这篇文章的系统管理员都不会对此感到意外，作为一个不成熟的初级系统管理员，我最终在错误的目录中运行了 `rm` 命令——作为 root 用户。我以为我正在为我们的某个程序删除一些陈旧的缓存文件。相反，我错误地清除了 `/etc` 目录中的所有文件。糟糕。

我意识到犯了错误是看到了一条错误消息，“`rm` 无法删除某些子目录”。但缓存目录应该只包含文件！我立即停止了 `rm` 命令，看看我做了什么。然后我惊慌失措。一下子，无数个想法涌入了我的脑中。我刚刚销毁了一台重要的服务器吗？系统会怎么样？我会被解雇吗？

幸运的是，我运行的是 `rm *` 而不是 `rm -rf *`，因此我只删除了文件。子目录仍在那里。但这并没有让我感觉更好。

我立刻去找我的主管告诉她我做了什么。她看到我对自己的错误感到愚蠢，但这是我犯的。尽管紧迫，她花了几分钟时间跟我做了一些指导。 她说：“你不是第一个这样做的人，在你这种情况下，别人会怎么做？”这帮助我平静下来并专注。我开始更少考虑我刚刚做的愚蠢事情，而更多地考虑我接下来要做的事情。

我做了一个简单的策略：不要重启服务器。使用相同的系统作为模板，并重建 `/etc` 目录。

制定了行动计划后，剩下的就很容易了。只需运行正确的命令即可从另一台服务器复制 `/etc` 文件并编辑配置，使其与系统匹配。多亏了我对所有东西都做记录的习惯，我使用已有的文档进行最后的调整。我避免了完全恢复服务器，这意味着一个巨大的宕机事件。

可以肯定的是，我从这个错误中吸取了教训。在接下来作为系统管理员的日子中，我总是在运行任何命令之前确认我所在的目录。

我还学习了构建“错误策略”的价值。当事情出错时，恐慌并思考接下来可能发生的所有坏事是很自然的。这是人性。但是制定一个“错误策略”可以帮助我不再担心出了什么问题，而是专注于让事情变得更好。我仍然会想一下，但是知道我接下来的步骤可以让我“克服它”。

---

via: <https://opensource.com/article/18/7/my-first-sysadmin-mistake>

作者：[Jim Hall](https://opensource.com/users/jim-hall) 选题：[lujun9972](https://github.com/lujun9972) 译者：[geekpi](https://github.com/geekpi) 校对：[wxy](https://github.com/wxy)

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


6 个可以帮你理解互联网工作原理的 RFC

Summary: 以及 3 个有趣的 RFC。

以及 3 个有趣的 RFC。

>
> 以及 3 个有趣的 RFC。
>
>
>

![](/data/attachment/album/201807/17/154848hfshhs8vhfnu5his.jpg)

阅读源码是开源软件的重要组成部分。这意味着用户可以查看代码并了解做了什么。

但“阅读源码”并不仅适用于代码。理解代码实现的标准同样重要。这些标准编写在由<ruby> <a href="https://www.ietf.org">  互联网工程任务组 </a> <rt>  Internet Engineering Task Force </rt></ruby>（IETF）发布的称为“<ruby> 意见征集 <rt>  Requests for Comment </rt></ruby>”（RFC）的文档中。多年来已经发布了数以千计的 RFC，因此我们收集了一些我们的贡献者认为必读的内容。

### 6 个必读的 RFC

#### RFC 2119 - 在 RFC 中用于指示需求级别的关键字

这是一个快速阅读，但它对了解其它 RFC 非常重要。 [RFC 2119](https://www.rfc-editor.org/rfc/rfc2119.txt) 定义了后续 RFC 中使用的需求级别。 “MAY” 究竟意味着什么？如果标准说 “SHOULD”，你*真的*必须这样做吗？通过为需求提供明确定义的分类，RFC 2119 有助于避免歧义。

#### RFC 3339 - 互联网上的日期和时间：时间戳

时间是全世界程序员的祸根。 [RFC 3339](https://www.rfc-editor.org/rfc/rfc3339.txt) 定义了如何格式化时间戳。基于 [ISO 8601](https://www.iso.org/iso-8601-date-and-time-format.html) 标准，3339 为我们提供了一种表达时间的常用方法。例如，像星期几这样的冗余信息不应该包含在存储的时间戳中，因为它很容易计算。

#### RFC 1918 - 私有互联网的地址分配

有属于每个人的互联网，也有只属于你的互联网。私有网络一直在使用，[RFC 1918](https://www.rfc-editor.org/rfc/rfc1918.txt) 定义了这些网络。当然，你可以在路由器上设置在内部使用公网地址，但这是一个坏主意。或者，你可以将未使用的公共 IP 地址视为内部网络。在任何一种情况下都表明你从未阅读过 RFC 1918。

#### RFC 1912 - 常见的 DNS 操作和配置错误

一切都是 #@%@ 的 DNS 问题，对吧？ [RFC 1912](https://www.rfc-editor.org/rfc/rfc1912.txt) 列出了管理员在试图保持互联网运行时所犯的错误。虽然它是在 1996 年发布的，但 DNS（以及人们犯的错误）并没有真正改变这么多。为了理解我们为什么首先需要 DNS，如今我们再来看看 [RFC 289 - 我们希望正式的主机列表是什么样子的](https://www.rfc-editor.org/rfc/rfc289.txt) 就知道了。

#### RFC 2822 — 互联网邮件格式

想想你知道什么是有效的电子邮件地址么？如果你知道有多少个站点不接受我邮件地址中 “+” 的话，你就知道你知道不知道了。 [RFC 2822](https://www.rfc-editor.org/rfc/rfc2822.txt) 定义了有效的电子邮件地址。它还详细介绍了电子邮件的其余部分。

#### RFC 7231 - 超文本传输协议（HTTP/1.1）：语义和内容

想想看，几乎我们在网上做的一切都依赖于 HTTP。 [RFC 7231](https://www.rfc-editor.org/rfc/rfc7231.txt) 是该协议的最新更新。它有超过 100 页，定义了方法、请求头和状态代码。

### 3 个应该阅读的 RFC

好吧，并非每个 RFC 都是严肃的。

#### RFC 1149 - 在禽类载体上传输 IP 数据报的标准

网络以多种不同方式传递数据包。 [RFC 1149](https://www.rfc-editor.org/rfc/rfc1149.txt) 描述了鸽子载体的使用。当我距离州际高速公路一英里以外时，它们的可靠性不会低于我的移动提供商。

#### RFC 2324 — 超文本咖啡壶控制协议（HTCPCP/1.0）

咖啡对于完成工作非常重要，当然，我们需要一个用于管理咖啡壶的程序化界面。 [RFC 2324](https://www.rfc-editor.org/rfc/rfc2324.txt) 定义了一个用于与咖啡壶交互的协议，并添加了 HTTP 418（“我是一个茶壶”）。

#### RFC 69 — M.I.T.的分发列表更改

[RFC 69](https://www.rfc-editor.org/rfc/rfc69.txt) 是否是第一个误导取消订阅请求的发布示例？

你必须阅读的 RFC 是什么（无论它们是否严肃）？在评论中分享你的列表。

---

via: <https://opensource.com/article/18/7/requests-for-comments-to-know>

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

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


供应链管理方面的 5 个开源软件工具

Summary: 跟踪您的库存和需要的材料，用这些供应链管理工具制造产品。

跟踪您的库存和需要的材料，用这些供应链管理工具制造产品。

>
> 跟踪您的库存和需要的材料，用这些供应链管理工具制造产品。
>
>
>

![](/data/attachment/album/201807/17/161700iansswwghskka1ww.jpg)

本文最初发表于 2016 年 1 月 14 日，最后的更新日期为 2018 年 3 月 2 日。

如果你正在管理着处理实体货物的业务，[供应链管理](https://en.wikipedia.org/wiki/Supply_chain_management) 是你的业务流程中非常重要的一部分。不论你是经营着一个只有几个客户的小商店，还是在世界各地拥有数以百万计客户和成千上万产品的世界财富 500 强的制造商或零售商，很清楚地知道你的库存和制造产品所需要的零部件，对你来说都是非常重要的事情。

保持对货品、供应商、客户的持续跟踪，而且所有与它们相关的变动部分都会受益于这些用来帮助管理工作流的专门软件，而在某些情况下需要完全依赖这些软件。在本文中，我们将去了解一些自由及开源的供应链管理方面的软件，以及它们的其中一些功能。

供应链管理比单纯的库存管理更为强大。它能帮你去跟踪货物流以降低成本，以及为可能发生的各种糟糕的变化来制定应对计划。它能够帮你对出口合规性进行跟踪，不论是否是出于法律要求、最低品质要求、还是社会和环境责任。它能够帮你计划最低供应量，让你能够在订单数量和交付时间之间做出明智的决策。

由于其本质决定了许多供应链管理软件是与类似的软件捆绑在一起的，比如，[客户关系管理](https://opensource.com/business/14/7/top-5-open-source-crm-tools)（CRM）和 [企业资源计划管理](/article-9785-1.html) （ERP）。因此，当你选择哪个工具更适合你的组织时，你可能会考虑与其它工具集成作为你的决策依据之一。

### Apache OFBiz

[Apache OFBiz](http://ofbiz.apache.org/) 是一套帮你管理多种业务流程的相关工具。虽然它能管理多种相关问题，比如，分类、电子商务网站、会计和 POS，它在供应链管理方面的主要功能关注于仓库管理、履行、订单和生产管理。它的可定制性很强，但是，对应的它需要大量的规划去设置和集成到你现有的流程中。这就是它适用于中大型业务的原因之一。项目的功能构建于三个层面：展示层、业务层和数据层，它是一个弹性很好的解决方案，但是，再强调一遍，它也很复杂。

Apache OFBiz 的源代码在其 [项目仓库](http://ofbiz.apache.org/source-repositories.html) 中可以找到。Apache OFBiz 是用 Java 写的，并且它是按 [Apache 2.0 许可证](http://www.apache.org/licenses/LICENSE-2.0) 授权的。

如果你对它感兴趣，你也可以去查看 [opentaps](http://www.opentaps.org/)，它是在 OFBiz 之上构建的。Opentaps 强化了 OFBiz 的用户界面，并且添加了 ERP 和 CRM 的核心功能，包括仓库管理、采购和计划。它是按 [AGPL 3.0](http://www.fsf.org/licensing/licenses/agpl-3.0.html) 授权使用的，对于不接受开源授权的组织，它也提供了商业授权。

### OpenBoxes

[OpenBoxes](http://openboxes.com/) 是一个供应链管理和存货管理项目，最初的主要设计目标是为了医疗行业中的药品跟踪管理，但是，它可以通过修改去跟踪任何类型的货品和相关的业务流。它有一个需求预测工具，可以基于历史订单数量、存储跟踪、支持多种场所、过期日期跟踪、销售点支持等进行预测，并且它还有许多其它功能，这使它成为医疗行业的理想选择，但是，它也可以用于其它行业。

它在 [Eclipse 公开许可证](http://opensource.org/licenses/eclipse-1.0.php) 下可用，OpenBoxes 主要是由 Groovy 写的，它的源代码可以在 [GitHub](https://github.com/openboxes/openboxes) 上看到。

### OpenLMIS

与 OpenBoxes 类似，[OpenLMIS](http://openlmis.org/) 也是一个医疗行业的供应链管理工具，但是，它专用设计用于在非洲的资源缺乏地区使用，以确保有限的药物和医疗用品能够用到需要的病人上。它是 API 驱动的，这样用户可以去定制和扩展 OpenLMIS，同时还能维护一个与通用基准代码的连接。它是由洛克菲勒基金会开发的，其它的贡献者包括联合国、美国国际开发署、和比尔 & 梅林达·盖茨基金会。

OpenLMIS 是用 Java 和 JavaScript 的 AngularJS 写的。它在 [AGPL 3.0 许可证](https://github.com/OpenLMIS/openlmis-ref-distro/blob/master/LICENSE) 下使用，它的源代码在 [GitHub](https://github.com/OpenLMIS/openlmis-ref-distro/blob/master/LICENSE) 上可以找到。

### Odoo

你可能在我们以前的 [ERP 项目](/article-9785-1.html) 榜的文章上见到过 [Odoo](https://www.odoo.com/)。事实上，根据你的需要，一个全功能的 ERP 对你来说是最适合的。Odoo 的供应链管理工具主要围绕存货和采购管理，同时还与电子商务网站和 POS 连接，但是，它也可以与其它的工具连接，比如，与 [frePPLe](https://frepple.com/) 连接，它是一个开源的生产计划工具。

Odoo 既有软件即服务（SaaS）的解决方案，也有开源的社区版本。开源的版本是以 [LGPL](https://github.com/odoo/odoo/blob/9.0/LICENSE) 版本 3 下发行的，源代码在 [GitHub](https://github.com/odoo/odoo) 上可以找到。Odoo 主要是用 Python 来写的。

### xTuple

[xTuple](https://xtuple.com/) 标称自己是“为成长中的企业提供供应链管理软件”，它专注于已经超越了其传统的小型企业 ERP 和 CRM 解决方案的企业。它的开源版本称为 Postbooks，添加了一些存货、分销、采购、以及供应商报告的功能，它提供的核心功能是会计、CRM、以及 ERP 功能，而它的商业版本扩展了制造和分销的 [功能](https://xtuple.com/comparison-chart)。

xTuple 在 [CPAL](https://xtuple.com/products/license-options#cpal) 下使用，这个项目欢迎开发者去复刻它，为基于存货的制造商去创建其它的业务软件。它的 Web 应用核心是用 JavaScript 写的，它的源代码在 [GitHub](http://xtuple.github.io/) 上可以找到。

就这些，当然了，还有其它的可以帮你处理供应链管理的开源软件。如果你知道还有更好的软件，请在下面的评论区告诉我们。

---

via: <https://opensource.com/tools/supply-chain-management>

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

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


在 Kubernetes 上运行一个 Python 应用程序

>
> 这个分步指导教程教你通过在 Kubernetes 上部署一个简单的 Python 应用程序来学习部署的流程。
>
>
>

![](/data/attachment/album/201807/17/164214h888zbu9iqk926g9.jpg)

Kubernetes 是一个具备部署、维护和可伸缩特性的开源平台。它在提供可移植性、可扩展性以及自我修复能力的同时，简化了容器化 Python 应用程序的管理。

不论你的 Python 应用程序是简单还是复杂，Kubernetes 都可以帮你高效地部署和伸缩它们，在有限的资源范围内滚动升级新特性。

在本文中，我将描述在 Kubernetes 上部署一个简单的 Python 应用程序的过程，它包括：

* 创建 Python 容器镜像
* 发布容器镜像到镜像注册中心
* 使用持久卷
* 在 Kubernetes 上部署 Python 应用程序

### 必需条件

你需要 Docker、`kubectl` 以及这个 [源代码](https://github.com/jnanjekye/k8s_python_sample_code/tree/master)。

Docker 是一个构建和承载已发布的应用程序的开源平台。可以参照 [官方文档](https://docs.docker.com/engine/installation/) 去安装 Docker。运行如下的命令去验证你的系统上运行的 Docker：

```
$ docker info
Containers: 0
Images: 289
Storage Driver: aufs
 Root Dir: /var/lib/docker/aufs
 Dirs: 289
Execution Driver: native-0.2
Kernel Version: 3.16.0-4-amd64
Operating System: Debian GNU/Linux 8 (jessie)
WARNING: No memory limit support
WARNING: No swap limit support

```

`kubectl` 是在 Kubernetes 集群上运行命令的一个命令行界面。运行下面的 shell 脚本去安装 `kubectl`：

```
curl -LO https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl

```

部署到 Kubernetes 的应用要求必须是一个容器化的应用程序。我们来回顾一下 Python 应用程序的容器化过程。

### 一句话了解容器化

容器化是指将一个应用程序所需要的东西打包进一个自带操作系统的容器中。这种完整机器虚拟化的好处是，一个应用程序能够在任何机器上运行而无需考虑它的依赖项。

我们以 Roman Gaponov 的 [文章](https://hackernoon.com/docker-tutorial-getting-started-with-python-redis-and-nginx-81a9d740d091) 为参考，来为我们的 Python 代码创建一个容器。

### 创建一个 Python 容器镜像

为创建这些镜像，我们将使用 Docker，它可以让我们在一个隔离的 Linux 软件容器中部署应用程序。Docker 可以使用来自一个 Dockerfile 中的指令来自动化构建镜像。

这是我们的 Python 应用程序的 Dockerfile：

```
FROM python:3.6
MAINTAINER XenonStack

# Creating Application Source Code Directory
RUN mkdir -p /k8s_python_sample_code/src

# Setting Home Directory for containers
WORKDIR /k8s_python_sample_code/src

# Installing python dependencies
COPY requirements.txt /k8s_python_sample_code/src
RUN pip install --no-cache-dir -r requirements.txt

# Copying src code to Container
COPY . /k8s_python_sample_code/src/app

# Application Environment variables
ENV APP_ENV development

# Exposing Ports
EXPOSE 5035

# Setting Persistent data
VOLUME ["/app-data"]

# Running Python Application
CMD ["python", "app.py"]

```

这个 Dockerfile 包含运行我们的示例 Python 代码的指令。它使用的开发环境是 Python 3.5。

### 构建一个 Python Docker 镜像

现在，我们可以使用下面的这个命令按照那些指令来构建 Docker 镜像：

```
docker build -t k8s_python_sample_code .

```

这个命令为我们的 Python 应用程序创建了一个 Docker 镜像。

### 发布容器镜像

我们可以将我们的 Python 容器镜像发布到不同的私有/公共云仓库中，像 Docker Hub、AWS ECR、Google Container Registry 等等。本教程中我们将发布到 Docker Hub。

在发布镜像之前，我们需要给它标记一个版本号：

```
docker tag k8s_python_sample_code:latest k8s_python_sample_code:0.1

```

### 推送镜像到一个云仓库

如果使用一个 Docker 注册中心而不是 Docker Hub 去保存镜像，那么你需要在你本地的 Docker 守护程序和 Kubernetes Docker 守护程序上添加一个容器注册中心。对于不同的云注册中心，你可以在它上面找到相关信息。我们在示例中使用的是 Docker Hub。

运行下面的 Docker 命令去推送镜像：

```
docker push k8s_python_sample_code

```

### 使用 CephFS 持久卷

Kubernetes 支持许多的持久存储提供商，包括 AWS EBS、CephFS、GlusterFS、Azure Disk、NFS 等等。我在示例中使用 CephFS 做为 Kubernetes 的持久卷。

为使用 CephFS 存储 Kubernetes 的容器数据，我们将创建两个文件：

`persistent-volume.yml` ：

```
apiVersion: v1
kind: PersistentVolume
metadata:
  name: app-disk1
  namespace: k8s_python_sample_code
spec:
  capacity:
  storage: 50Gi
  accessModes:
  - ReadWriteMany
  cephfs:
  monitors:
    - "172.17.0.1:6789"
  user: admin
  secretRef:
    name: ceph-secret
  readOnly: false

```

`persistent_volume_claim.yaml`：

```
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: appclaim1
  namespace: k8s_python_sample_code
spec:
  accessModes:
  - ReadWriteMany
  resources:
  requests:
    storage: 10Gi

```

现在，我们将使用 `kubectl` 去添加持久卷并声明到 Kubernetes 集群中：

```
$ kubectl create -f persistent-volume.yml
$ kubectl create -f persistent-volume-claim.yml

```

现在，我们准备去部署 Kubernetes。

### 在 Kubernetes 上部署应用程序

为管理部署应用程序到 Kubernetes 上的最后一步，我们将创建两个重要文件：一个服务文件和一个部署文件。

使用下列的内容创建服务文件，并将它命名为 `k8s_python_sample_code.service.yml`：

```
apiVersion: v1
kind: Service
metadata:
  labels:
  k8s-app: k8s_python_sample_code
  name: k8s_python_sample_code
  namespace: k8s_python_sample_code
spec:
  type: NodePort
  ports:
  - port: 5035
  selector:
  k8s-app: k8s_python_sample_code

```

使用下列的内容创建部署文件并将它命名为 `k8s_python_sample_code.deployment.yml`：

```
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: k8s_python_sample_code
  namespace: k8s_python_sample_code
spec:
  replicas: 1
  template:
  metadata:
    labels:
    k8s-app: k8s_python_sample_code
  spec:
    containers:
    - name: k8s_python_sample_code
      image: k8s_python_sample_code:0.1
      imagePullPolicy: "IfNotPresent"
      ports:
      - containerPort: 5035
      volumeMounts:
        - mountPath: /app-data
          name: k8s_python_sample_code
     volumes:
         - name: <name of application>
           persistentVolumeClaim:
             claimName: appclaim1

```

最后，我们使用 `kubectl` 将应用程序部署到 Kubernetes：

```
$ kubectl create -f k8s_python_sample_code.deployment.yml $ kubectl create -f k8s_python_sample_code.service.yml

```

现在，你的应用程序已经成功部署到 Kubernetes。

你可以通过检查运行的服务来验证你的应用程序是否在运行：

```
kubectl get services

```

或许 Kubernetes 可以解决未来你部署应用程序的各种麻烦！

*想学习更多关于 Python 的知识？Nanjekye 的书，[和平共处的 Python 2 和 3](https://www.apress.com/gp/book/9781484229545) 提供了完整的方法，让你写的代码在 Python 2 和 3 上完美运行，包括如何转换已有的 Python 2 代码为能够可靠运行在 Python 2 和 3 上的代码的详细示例。*

### 关于作者

Joannah Nanjekye - Straight Outta 256，只要结果不问原因，充满激情的飞行员，喜欢用代码说话。[关于我的更多信息](https://opensource.com/users/nanjekyejoannah)

---

via: <https://opensource.com/article/18/1/running-python-application-kubernetes>

作者：[Joannah Nanjekye](https://opensource.com/users/nanjekyejoannah)  译者：[qhwdw](https://github.com/qhwdw) 校对：[wxy](https://github.com/wxy)

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


我们能否建立一个服务于用户而非广告商的社交网络？

Summary: 找出 Human Connection 是如何将透明度和社区放在首位的。

找出 Human Connection 是如何将透明度和社区放在首位的。

>
> 找出 Human Connection 是如何将透明度和社区放在首位的。
>
>
>

![](/data/attachment/album/201807/18/142649cmi6symi3w3j33sb.png)

如今，开源软件具有深远的意义，在推动数字经济创新方面发挥着关键作用。世界正在快速彻底地改变。世界各地的人们需要一个专门的、中立的、透明的在线平台来迎接我们这个时代的挑战。

开放的原则也许是让我们达成这一目标的方法。如果我们用开放的思维方式将数字创新与社会创新结合在一起，会发生什么？

这个问题是我们在 [Human Connection](https://human-connection.org/en/) 工作的核心，这是一个具有前瞻性的，以德国为基础的知识和行动网络，其使命是创建一个服务于全球的真正的社交网络。我们受到这样一种观念为指引，即人类天生慷慨而富有同情心，并且他们在慈善行为上茁壮成长。但我们还没有看到一个完全支持我们的自然趋势，与乐于助人和合作以促进共同利益的社交网络。Human Connection 渴望成为让每个人都成为积极变革者的平台。

为了实现一个以解决方案为导向的平台的梦想，让人们通过与慈善机构、社区团体和社会变革活动人士的接触，围绕社会公益事业采取行动，Human Connection 将开放的价值观作为社会创新的载体。

以下是有关它如何工作的。

### 首先是透明

透明是 Human Connection 的指导原则之一。Human Connection 邀请世界各地的程序员通过[在 Github 上提交他们的源代码](https://github.com/human-connection/)共同开发平台的源代码（JavaScript、Vue、nuxt），并通过贡献代码或编程附加功能来支持真正的社交网络。

但我们对透明的承诺超出了我们的发展实践。事实上，当涉及到建立一种新的社交网络，促进那些让世界变得更好的人之间的真正联系和互动，分享源代码只是迈向透明的一步。

为促进公开对话，Human Connection 团队举行[定期在线公开会议](https://youtu.be/tPcYRQcepYE)。我们在这里回答问题，鼓励建议并对潜在的问题作出回应。我们的 Meet The Team 活动也会记录下来，并在事后向公众开放。通过对我们的流程，源代码和财务状况完全透明，我们可以保护自己免受批评或其他潜在的不利影响。

对透明的承诺意味着，所有在 Human Connection 上公开分享的用户贡献者将在 Creative Commons 许可下发布，最终作为数据包下载。通过让大众知识变得可用，特别是以一种分散的方式，我们创造了一个多元化社会的机会。

有一个问题指导我们所有的组织决策：“它是否服务于人民和更大的利益？”我们用<ruby> <a href="http://www.un.org/en/charter-united-nations/index.html">  联合国宪章 </a> <rt>  UN Charter </rt></ruby>和“<ruby> 世界人权宣言 <rt>  Universal Declaration of Human Rights </rt></ruby>”作为我们价值体系的基础。随着我们的规模越来越大，尤其是即将推出的公测版，我们必须对此任务负责。我甚至愿意邀请 Chaos Computer Club （LCTT 译注：这是欧洲最大的黑客联盟）或其他黑客俱乐部通过随机检查我们的平台来验证我们的代码和行为的完整性。

### 一个合作的社会

以一种[以社区为中心的协作方法](https://youtu.be/BQHBno-efRI)来编写 Human Connection 平台是超越社交网络实际应用理念的基础。我们的团队是通过找到问题的答案来驱动：“是什么让一个社交网络真正地社会化？”

一个抛弃了以利润为导向的算法、为最终用户而不是广告商服务的网络，只能通过转向对等生产和协作的过程而繁荣起来。例如，像 [Code Alliance](http://codealliance.org/) 和 [Code for America](https://www.codeforamerica.org/) 这样的组织已经证明了如何在一个开源环境中创造技术，造福人类并变革现状。社区驱动的项目，如基于地图的报告平台 [FixMyStreet](http://fixmystreet.org/)，或者为 Humanitarian OpenStreetMap 而建立的 [Tasking Manager](https://tasks.hotosm.org/)，已经将众包作为推动其使用的一种方式。

我们建立 Human Connection 的方法从一开始就是合作。为了收集关于必要功能和真正社交网络的目的的初步数据，我们与巴黎<ruby> 索邦大学 <rt>  University Sorbonne </rt></ruby>的<ruby> 国家东方语言与文明研究所 <rt>  National Institute for Oriental Languages and Civilizations </rt></ruby>（INALCO）和德国<ruby> 斯图加特媒体大学 <rt>  Stuttgart Media University </rt></ruby>合作。这两个项目的研究结果都被纳入了 Human Connection 的早期开发。多亏了这项研究，[用户将拥有一套全新的功能](https://youtu.be/AwSx06DK2oU)，让他们可以控制自己看到的内容以及他们如何与他人的互动。由于早期的支持者[被邀请到网络的 alpha 版本](https://youtu.be/AwSx06DK2oU)，他们可以体验到第一个可用的值得注意的功能。这里有一些：

* 将信息与行动联系起来是我们研究会议的一个重要主题。当前的社交网络让用户处于信息阶段。这两所大学的学生团体都认为，需要一个以行动为导向的组件，以满足人类共同解决问题的本能。所以我们在平台上构建了一个[“Can Do”功能](https://youtu.be/g2gYLNx686I)。这是一个人在阅读了某个话题后可以采取行动的一种方式。“Can Do” 是用户建议的活动，在“<ruby> 采取行动 <rt>  Take Action </rt></ruby>”领域，每个人都可以实现。
* “Versus” 功能是另一个成果。在传统社交网络仅限于评论功能的地方，我们的学生团体认为需要采用更加结构化且有用的方式进行讨论和争论。“Versus” 是对公共帖子的反驳，它是单独显示的，并提供了一个机会来突出围绕某个问题的不同意见。
* 今天的社交网络并没有提供很多过滤内容的选项。研究表明，情绪过滤选项可以帮助我们根据日常情绪驾驭社交空间，并可能通过在我们希望仅看到令人振奋的内容的那一天时，不显示悲伤或难过的帖子来潜在地保护我们的情绪健康。

Human Connection 邀请改革者合作开发一个网络，有可能动员世界各地的个人和团体将负面新闻变成 “Can Do”，并与慈善机构和非营利组织一起参与社会创新项目。

[订阅我们的每周时事通讯](https://opensource.com/open-organization/resources/newsletter)以了解有关开放组织的更多信息。

---

via: <https://opensource.com/open-organization/18/3/open-social-human-connection>

作者：[Dennis Hack](https://opensource.com/users/dhack) 译者：[MjSeven](https://github.com/MjSeven) 校对：[wxy](https://github.com/wxy)

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


调试器到底怎样工作

Summary: 你也许用过调速器检查过你的代码，但你知道它们是如何做到的吗？

你也许用过调速器检查过你的代码，但你知道它们是如何做到的吗？

>
> 你也许用过调速器检查过你的代码，但你知道它们是如何做到的吗？
>
>
>

![](/data/attachment/album/201807/18/230247sopnnhcu2uepgnae.jpg)

调试器是大多数（即使不是每个）开发人员在软件工程职业生涯中至少使用过一次的那些软件之一，但是你们中有多少人知道它们到底是如何工作的？我在悉尼 [linux.conf.au 2018](https://linux.conf.au/index.html) 的演讲中，将讨论从头开始编写调试器……使用 [Rust](https://www.rust-lang.org)！

在本文中，术语<ruby> 调试器 <rt>  debugger </rt></ruby>和<ruby> 跟踪器 <rt>  tracer </rt></ruby>可以互换。 “<ruby> 被跟踪者 <rt>  Tracee </rt></ruby>”是指正在被跟踪器跟踪的进程。

### ptrace 系统调用

大多数调试器严重依赖称为 `ptrace(2)` 的系统调用，其原型如下：

```
long ptrace(enum __ptrace_request request, pid_t pid, void *addr, void *data);

```

这是一个可以操纵进程几乎所有方面的系统调用；但是，在调试器可以连接到一个进程之前，“被跟踪者”必须以请求 `PTRACE_TRACEME` 调用 `ptrace`。这告诉 Linux，父进程通过 `ptrace` 连接到这个进程是合法的。但是……我们如何强制一个进程调用 `ptrace`？很简单！`fork/execve` 提供了在 `fork` 之后但在被跟踪者真正开始使用 `execve` 之前调用 `ptrace` 的简单方法。很方便地，`fork` 还会返回被跟踪者的 `pid`，这是后面使用 `ptrace` 所必需的。

现在被跟踪者可以被调试器追踪，重要的变化发生了：

* 每当一个信号被传送到被跟踪者时，它就会停止，并且一个可以被 `wait` 系列的系统调用捕获的等待事件被传送给跟踪器。
* 每个 `execve` 系统调用都会导致 `SIGTRAP` 被传递给被跟踪者。（与之前的项目相结合，这意味着被跟踪者在一个 `execve` 完全发生之前停止。）

这意味着，一旦我们发出 `PTRACE_TRACEME` 请求并调用 `execve` 系统调用来实际在被跟踪者（进程上下文）中启动程序时，被跟踪者将立即停止，因为 `execve` 会传递一个 `SIGTRAP`，并且会被跟踪器中的等待事件捕获。我们如何继续？正如人们所期望的那样，`ptrace` 有大量的请求可以用来告诉被跟踪者可以继续：

* `PTRACE_CONT`：这是最简单的。 被跟踪者运行，直到它接收到一个信号，此时等待事件被传递给跟踪器。这是最常见的实现真实世界调试器的“继续直至断点”和“永远继续”选项的方式。断点将在下面介绍。
* `PTRACE_SYSCALL`：与 `PTRACE_CONT` 非常相似，但在进入系统调用之前以及在系统调用返回到用户空间之前停止。它可以与其他请求（我们将在本文后面介绍）结合使用来监视和修改系统调用的参数或返回值。系统调用追踪程序 `strace` 很大程度上使用这个请求来获知进程发起了哪些系统调用。
* `PTRACE_SINGLESTEP`：这个很好理解。如果您之前使用过调试器（你会知道），此请求会执行下一条指令，然后立即停止。

我们可以通过各种各样的请求停止进程，但我们如何获得被调试者的状态？进程的状态大多是通过其寄存器捕获的，所以当然 `ptrace` 有一个请求来获得（或修改）寄存器：

* `PTRACE_GETREGS`：这个请求将给出被跟踪者刚刚被停止时的寄存器的状态。
* `PTRACE_SETREGS`：如果跟踪器之前通过调用 `PTRACE_GETREGS` 得到了寄存器的值，它可以在参数结构中修改相应寄存器的值，并使用 `PTRACE_SETREGS` 将寄存器设为新值。
* `PTRACE_PEEKUSER` 和 `PTRACE_POKEUSER`：这些允许从被跟踪者的 `USER` 区读取信息，这里保存了寄存器和其他有用的信息。 这可以用来修改单一寄存器，而避免使用更重的 `PTRACE_{GET,SET}REGS` 请求。

在调试器仅仅修改寄存器是不够的。调试器有时需要读取一部分内存，甚至对其进行修改。GDB 可以使用 `print` 得到一个内存位置或变量的值。`ptrace` 通过下面的方法实现这个功能：

* `PTRACE_PEEKTEXT` 和 `PTRACE_POKETEXT`：这些允许读取和写入被跟踪者地址空间中的一个字。当然，使用这个功能时被跟踪者要被暂停。

真实世界的调试器也有类似断点和观察点的功能。 在接下来的部分中，我将深入体系结构对调试器支持的细节。为了清晰和简洁，本文将只考虑 x86。

### 体系结构的支持

`ptrace` 很酷，但它是如何工作？ 在前面的部分中，我们已经看到 `ptrace` 跟信号有很大关系：`SIGTRAP` 可以在单步跟踪、`execve` 之前以及系统调用前后被传送。信号可以通过一些方式产生，但我们将研究两个具体的例子，以展示信号可以被调试器用来在给定的位置停止程序（有效地创建一个断点！）：

* **未定义的指令**：当一个进程尝试执行一个未定义的指令，CPU 将产生一个异常。此异常通过 CPU 中断处理，内核中相应的中断处理程序被调用。这将导致一个 `SIGILL` 信号被发送给进程。 这依次导致进程被停止，跟踪器通过一个等待事件被通知，然后它可以决定后面做什么。在 x86 上，指令 `ud2` 被确保始终是未定义的。
* **调试中断**：前面的方法的问题是，`ud2` 指令需要占用两个字节的机器码。存在一条特殊的单字节指令能够触发一个中断，它是 `int $3`，机器码是 `0xCC`。 当该中断发出时，内核向进程发送一个 `SIGTRAP`，如前所述，跟踪器被通知。

这很好，但如何我们才能胁迫被跟踪者执行这些指令？ 这很简单：利用 `ptrace` 的 `PTRACE_POKETEXT` 请求，它可以覆盖内存中的一个字。 调试器将使用 `PTRACE_PEEKTEXT` 读取该位置原来的值并替换为 `0xCC` ，然后在其内部状态中记录该处原来的值，以及它是一个断点的事实。 下次被跟踪者执行到该位置时，它将被通过 `SIGTRAP` 信号自动停止。 然后调试器的最终用户可以决定如何继续（例如，检查寄存器）。

好吧，我们已经讲过了断点，那观察点呢？ 当一个特定的内存位置被读或写，调试器如何停止程序？ 当然你不可能为了能够读或写内存而去把每一个指令都覆盖为 `int $3`。有一组调试寄存器为了更有效的满足这个目的而被设计出来：

* `DR0` 到 `DR3`：这些寄存器中的每个都包含一个地址（内存位置），调试器因为某种原因希望被跟踪者在那些地址那里停止。 其原因以掩码方式被设定在 `DR7` 寄存器中。
* `DR4` 和 `DR5`：这些分别是 `DR6` 和 `DR7` 过时的别名。
* `DR6`：调试状态。包含有关 `DR0` 到 `DR3` 中的哪个寄存器导致调试异常被引发的信息。这被 Linux 用来计算与 `SIGTRAP` 信号一起传递给被跟踪者的信息。
* `DR7`：调试控制。通过使用这些寄存器中的位，调试器可以控制如何解释 `DR0` 至 `DR3` 中指定的地址。位掩码控制监视点的尺寸（监视1、2、4 或 8 个字节）以及是否在执行、读取、写入时引发异常，或在读取或写入时引发异常。

由于调试寄存器是进程的 `USER` 区域的一部分，调试器可以使用 `PTRACE_POKEUSER` 将值写入调试寄存器。调试寄存器只与特定进程相关，因此在进程抢占并重新获得 CPU 控制权之前，调试寄存器会被恢复。

### 冰山一角

我们已经浏览了一个调试器的“冰山”：我们已经介绍了 `ptrace`，了解了它的一些功能，然后我们看到了 `ptrace` 是如何实现的。 `ptrace` 的某些部分可以用软件实现，但其它部分必须用硬件来实现，否则实现代价会非常高甚至无法实现。

当然有很多我们没有涉及。例如“调试器如何知道变量在内存中的位置？”等问题由于空间和时间限制而尚未解答，但我希望你从本文中学到了一些东西；如果它激起你的兴趣，网上有足够的资源可以了解更多。

想要了解更多，请查看 [linux.conf.au](https://linux.conf.au/index.html) 中 Levente Kurusa 的演讲 [Let's Write a Debugger!](https://rego.linux.conf.au/schedule/presentation/91/)，于一月 22-26 日在悉尼举办。

---

via: <https://opensource.com/article/18/1/how-debuggers-really-work>

作者：[Levente Kurusa](https://opensource.com/users/lkurusa) 译者：[stephenxs](https://github.com/stephenxs) 校对：[wxy](https://github.com/wxy)

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


macOS 和 Linux 的内核有什么区别

有些人可能会认为 macOS 和 Linux 内核之间存在相似之处，因为它们可以处理类似的命令和类似的软件。有些人甚至认为苹果公司的 macOS 是基于 Linux 的。事实上是，两个内核有着截然不同的历史和特征。今天，我们来看看 macOS 和 Linux 的内核之间的区别。

![macOS vs Linux](/data/attachment/album/201807/20/001525dgb53g13bxb4vgbz.jpg)

### macOS 内核的历史

我们将从 macOS 内核的历史开始。1985 年，由于与首席执行官 John Sculley 和董事会不和，<ruby> 史蒂夫·乔布斯 <rt>  Steve Jobs </rt></ruby>离开了苹果公司。然后，他成立了一家名为 [NeXT](https://en.wikipedia.org/wiki/NeXT) 的新电脑公司。乔布斯希望将一款（带有新操作系统的）新计算机快速推向市场。为了节省时间，NeXT 团队使用了卡耐基梅隆大学的 [Mach 内核](https://en.wikipedia.org/wiki/Mach_(kernel)) 和部分 BSD 代码库来创建 [NeXTSTEP 操作系统](https://en.wikipedia.org/wiki/NeXTSTEP)。

NeXT 从来没有取得过财务上的成功，部分归因于乔布斯花钱的习惯，就像他还在苹果公司一样。与此同时，苹果公司曾多次试图更新其操作系统，甚至与 IBM 合作，但从未成功。1997年，苹果公司以 4.29 亿美元收购了 NeXT。作为交易的一部分，史蒂夫·乔布斯回到了苹果公司，同时 NeXTSTEP 成为了 macOS 和 iOS 的基础。

### Linux 内核的历史

与 macOS 内核不同，Linux 的创建并非源于商业尝试。相反，它是由[芬兰计算机科学专业学生<ruby> 林纳斯·托瓦兹 <rt>  Linus Torvalds </rt></ruby>于 1991 年创建的](https://www.cs.cmu.edu/%7Eawb/linux.history.html)。最初，内核是按照林纳斯自己的计算机的规格编写的，因为他想利用其新的 80386 处理器（的特性）。林纳斯[于 1991 年 8 月在 Usenet 上](https://groups.google.com/forum/#!original/comp.os.minix/dlNtH7RRrGA/SwRavCzVE7gJ)发布了他的新内核代码。很快，他就收到了来自世界各地的代码和功能建议。次年，Orest Zborowski 将 X Window 系统移植到 Linux，使其能够支持图形用户界面。

在过去的 27 年中，Linux 已经慢慢成长并增加了不少功能。这不再是一个学生的小型项目。现在它运行在[世界上](https://www.zdnet.com/article/sorry-windows-android-is-now-the-most-popular-end-user-operating-system/)大多数的[计算设备](https://www.linuxinsider.com/story/31855.html)和[超级计算机](https://itsfoss.com/linux-supercomputers-2017/)上。不错！

### macOS 内核的特性

macOS 内核被官方称为 XNU。这个[首字母缩写词](https://github.com/apple/darwin-xnu)代表“XNU is Not Unix”。根据 [苹果公司的 Github 页面](https://github.com/apple/darwin-xnu)，XNU 是“将卡耐基梅隆大学开发的 Mach 内核和 FreeBSD 组件整合而成的混合内核，加上用于编写驱动程序的 C++ API”。代码的 BSD 子系统部分[“在微内核系统中通常实现为用户空间的服务”](http://osxbook.com/book/bonus/ancient/whatismacosx/arch_xnu.html)。Mach 部分负责底层工作，例如多任务、内存保护、虚拟内存管理、内核调试支持和控制台 I/O。

### Linux 内核的特性

虽然 macOS 内核结合了微内核（[Mach](https://en.wikipedia.org/wiki/Mach_(kernel)）和宏内核（[BSD](https://en.wikipedia.org/wiki/FreeBSD)）的特性，但 Linux 只是一个宏内核。[宏内核](https://www.howtogeek.com/howto/31632/what-is-the-linux-kernel-and-what-does-it-do/)负责管理 CPU、内存、进程间通信、设备驱动程序、文件系统和系统服务调用（ LCTT 译注：原文为 system server calls，但结合 Linux 内核的构成，译者认为这里翻译成系统服务调用更合适，即 system service calls）。

### 用一句话总结 Linux 和 Mac 的区别

macOS 内核（XNU）比 Linux 历史更悠久，并且基于两个更古老一些的代码库的结合；另一方面，Linux 新一些，是从头开始编写的，并且在更多设备上使用。

如果您发现这篇文章很有趣，请花一点时间在社交媒体，黑客新闻或 [Reddit](http://reddit.com/r/linuxusersgroup) 上分享。

---

via: <https://itsfoss.com/mac-linux-difference/>

作者：[John Paul](https://itsfoss.com/author/john/) 选题：[lujun9972](https://github.com/lujun9972) 译者：[stephenxs](https://github.com/stephenxs) 校对：[wxy](https://github.com/wxy)

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


如何在 Linux 上检查用户所属组

![](/data/attachment/album/201807/20/120503zwzhfk70kbddfrwq.jpg)

将用户添加到现有组是 Linux 管理员的常规活动之一。这是一些在大环境中工作的管理员的日常活动。

甚至我会因为业务需求而在我的环境中每天都在进行这样的活动。它是帮助你识别环境中现有组的重要命令之一。

此外，这些命令还可以帮助你识别用户所属的组。所有用户都列在 `/etc/passwd` 中，组列在 `/etc/group` 中。

无论我们使用什么命令，都将从这些文件中获取信息。此外，每个命令都有其独特的功能，可帮助用户单独获取所需的信息。

### 什么是 /etc/passwd？

`/etc/passwd` 是一个文本文件，其中包含登录 Linux 系统所必需的每个用户信息。它维护有用的用户信息，如用户名、密码、用户 ID、组 ID、用户 ID 信息、家目录和 shell。passwd 每行包含了用户的详细信息，共有如上所述的 7 个字段。

```
$ grep "daygeek" /etc/passwd
daygeek:x:1000:1000:daygeek,,,:/home/daygeek:/bin/bash

```

### 什么是 /etc/group？

`/etc/group` 是一个文本文件，用于定义用户所属的组。我们可以将多个用户添加到单个组中。它允许用户访问其他用户文件和文件夹，因为 Linux 权限分为三类：用户、组和其他。它维护有关组的有用信息，例如组名、组密码，组 ID（GID）和成员列表。每个都在一个单独的行。组文件每行包含了每个组的详细信息，共有 4 个如上所述字段。

这可以通过使用以下方法来执行。

* `groups`： 显示一个组的所有成员。
* `id`： 打印指定用户名的用户和组信息。
* `lid`： 显示用户的组或组的用户。
* `getent`： 从 Name Service Switch 库中获取条目。
* `grep`： 代表“<ruby> 全局正则表达式打印 <rt>  global regular expression print </rt></ruby>”，它能打印匹配的模式。

### 什么是 groups 命令？

`groups` 命令打印每个给定用户名的主要组和任何补充组的名称。

```
$ groups daygeek
daygeek : daygeek adm cdrom sudo dip plugdev lpadmin sambashare

```

如果要检查与当前用户关联的组列表。只需运行 `groups` 命令，无需带任何用户名。

```
$ groups
daygeek adm cdrom sudo dip plugdev lpadmin sambashare

```

### 什么是 id 命令？

id 代表 “<ruby> 身份 <rt>  identity </rt></ruby>”。它打印真实有效的用户和组 ID。打印指定用户或当前用户的用户和组信息。

```
$ id daygeek
uid=1000(daygeek) gid=1000(daygeek) groups=1000(daygeek),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),118(lpadmin),128(sambashare)

```

如果要检查与当前用户关联的组列表。只运行 `id` 命令，无需带任何用户名。

```
$ id
uid=1000(daygeek) gid=1000(daygeek) groups=1000(daygeek),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),118(lpadmin),128(sambashare)

```

### 什么是 lid 命令？

它显示用户的组或组的用户。显示有关包含用户名的组或组名称中包含的用户的信息。此命令需要管理员权限。

```
$ sudo lid daygeek
 adm(gid=4)
 cdrom(gid=24)
 sudo(gid=27)
 dip(gid=30)
 plugdev(gid=46)
 lpadmin(gid=108)
 daygeek(gid=1000)
 sambashare(gid=124)

```

### 什么是 getent 命令？

`getent` 命令显示 Name Service Switch 库支持的数据库中的条目，它们在 `/etc/nsswitch.conf` 中配置。

```
$ getent group | grep daygeek
adm:x:4:syslog,daygeek
cdrom:x:24:daygeek
sudo:x:27:daygeek
dip:x:30:daygeek
plugdev:x:46:daygeek
lpadmin:x:118:daygeek
daygeek:x:1000:
sambashare:x:128:daygeek

```

如果你只想打印关联的组名称，请在上面的命令中使用 `awk`。

```
$ getent group | grep daygeek | awk -F: '{print $1}'
adm
cdrom
sudo
dip
plugdev
lpadmin
daygeek
sambashare

```

运行以下命令仅打印主群组信息。

```
$ getent group daygeek
daygeek:x:1000:

```

### 什么是 grep 命令？

`grep` 代表 “<ruby> 全局正则表达式打印 <rt>  global regular expression print </rt></ruby>”，它能打印文件匹配的模式。

```
$ grep "daygeek" /etc/group
adm:x:4:syslog,daygeek
cdrom:x:24:daygeek
sudo:x:27:daygeek
dip:x:30:daygeek
plugdev:x:46:daygeek
lpadmin:x:118:daygeek
daygeek:x:1000:
sambashare:x:128:daygeek

```

如果你只想打印关联的组名称，请在上面的命令中使用 `awk`。

```
$ grep "daygeek" /etc/group | awk -F: '{print $1}'
adm
cdrom
sudo
dip
plugdev
lpadmin
daygeek
sambashare

```

---

via: <https://www.2daygeek.com/how-to-check-which-groups-a-user-belongs-to-on-linux/>

作者：[Prakash Subramanian](https://www.2daygeek.com/author/prakash/) 选题：[lujun9972](https://github.com/lujun9972) 译者：[geekpi](https://github.com/geekpi) 校对：[wxy](https://github.com/wxy)

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