<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Linux | 酷 壳 - CoolShell</title>
	<atom:link href="https://coolshell.cn/tag/linux/feed" rel="self" type="application/rss+xml" />
	<link>https://coolshell.cn</link>
	<description>享受编程和技术所带来的快乐 - Coding Your Ambition</description>
	<lastBuildDate>Mon, 12 Dec 2022 02:56:31 +0000</lastBuildDate>
	<language>zh-CN</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.2</generator>
	<item>
		<title>eBPF 介绍</title>
		<link>https://coolshell.cn/articles/22320.html</link>
					<comments>https://coolshell.cn/articles/22320.html#comments</comments>
		
		<dc:creator><![CDATA[陈皓]]></dc:creator>
		<pubDate>Sat, 10 Dec 2022 02:38:51 +0000</pubDate>
				<category><![CDATA[C/C++语言]]></category>
		<category><![CDATA[Unix/Linux]]></category>
		<category><![CDATA[操作系统]]></category>
		<category><![CDATA[网络安全]]></category>
		<category><![CDATA[Debug]]></category>
		<category><![CDATA[eBPF]]></category>
		<category><![CDATA[Linux]]></category>
		<guid isPermaLink="false">https://coolshell.cn/?p=22320</guid>

					<description><![CDATA[<p>很早前就想写一篇关于eBPF的文章，但是迟迟没有动手，这两天有点时间，所以就来写一篇，这文章主要还是简单的介绍eBPF 是用来干什么的，并通过几个示例来介绍是怎...</p>
<p class="read-more"><a class="btn btn-default" href="https://coolshell.cn/articles/22320.html"> Read More<span class="screen-reader-text">  Read More</span></a></p>
The post <a href="https://coolshell.cn/articles/22320.html">eBPF 介绍</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></description>
										<content:encoded><![CDATA[<p><script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-3415450859608158"
     crossorigin="anonymous"></script><img decoding="async" loading="lazy" class="alignright wp-image-22329 size-full" src="https://coolshell.cn/wp-content/uploads/2022/12/eBPF.jpeg" alt="" width="301" height="167" />很早前就想写一篇关于eBPF的文章，但是迟迟没有动手，这两天有点时间，所以就来写一篇，这文章主要还是简单的介绍eBPF 是用来干什么的，并通过几个示例来介绍是怎么玩的，这个技术非常非常之强，Linux 操作系统的观测性实在是太强大了，并在 BCC 加持下变得一览无余。这个技术不是一般的运维人员或是系统管理员可以驾驭的，这个还是要有底层系统知识并有一定开发能力的技术人员才能驾驭的了的。<strong>我在这篇文章的最后给了个彩蛋。</strong></p>
<h4>介绍</h4>
<p>eBPF（extened Berkeley Packet Filter）是一种内核技术，它允许开发人员在不修改内核代码的情况下运行特定的功能。eBPF 的概念源自于 Berkeley Packet Filter（BPF），后者是由贝尔实验室开发的一种网络过滤器，可以捕获和过滤网络数据包。</p>
<p>出于对更好的 Linux 跟踪工具的需求，eBPF 从 <a href="https://illumos.org/books/dtrace/chp-intro.html">dtrace</a>中汲取灵感，dtrace 是一种主要用于 Solaris 和 BSD 操作系统的动态跟踪工具。与 dtrace 不同，Linux 无法全面了解正在运行的系统，因为它仅限于系统调用、库调用和函数的特定框架。<a href="https://www.kernel.org/doc/html/latest/bpf/index.html">在Berkeley Packet Filter</a>  (BPF)（一种使用内核 VM 编写打包过滤代码的工具）的基础上，一小群工程师开始扩展 BPF 后端以提供与 dtrace 类似的功能集。 eBPF 诞生了。<strong>2014 年随 Linux 3.18 首次限量发布，充分利用 eBPF 至少需要 Linux 4.4 以上版本</strong>。</p>
<p><span id="more-22320"></span></p>
<p>eBPF 比起传统的 BPF 来说，传统的 BPF 只能用于网络过滤，而 eBPF 则可以用于更多的应用场景，包括网络监控、安全过滤和性能分析等。另外，eBPF 允许常规用户空间应用程序将要在 Linux 内核中执行的逻辑打包为字节码，当某些事件（称为挂钩）发生时，内核会调用 eBPF 程序。此类挂钩的示例包括系统调用、网络事件等。用于编写和调试 eBPF 程序的最流行的工具链称为 <a href="https://github.com/iovisor/bcc">BPF 编译器集合</a> (BCC)，它基于 LLVM 和 CLang。</p>
<p>eBPF 有一些类似的工具。例如，SystemTap 是一种开源工具，可以帮助用户收集 Linux 内核的运行时数据。它通过动态加载内核模块来实现这一功能，类似于 eBPF。另外，DTrace 是一种动态跟踪和分析工具，可以用于收集系统的运行时数据，类似于 eBPF 和 SystemTap。<code>[1]</code></p>
<p>以下是一个简单的比较表格，可以帮助您更好地了解 eBPF、SystemTap 和 DTrace 这三种工具的不同之处：<code>[1]</code></p>
<table>
<thead>
<tr>
<th>工具</th>
<th>eBPF</th>
<th>SystemTap</th>
<th>DTrace</th>
</tr>
</thead>
<tbody>
<tr>
<td>定位</td>
<td>内核技术，可用于多种应用场景</td>
<td>内核模块</td>
<td>动态跟踪和分析工具</td>
</tr>
<tr>
<td>工作原理</td>
<td>动态加载和执行无损编译过的代码</td>
<td>动态加载内核模块</td>
<td>动态插接分析器，通过 probe 获取数据并进行分析</td>
</tr>
<tr>
<td>常见用途</td>
<td>网络监控、安全过滤、性能分析等</td>
<td>系统性能分析、故障诊断等</td>
<td>系统性能分析、故障诊断等</td>
</tr>
<tr>
<td>优点</td>
<td>灵活、安全、可用于多种应用场景</td>
<td>功能强大、可视化界面</td>
<td>功能强大、高性能、支持多种编程语言</td>
</tr>
<tr>
<td>缺点</td>
<td>学习曲线高，安全性依赖于编译器的正确性</td>
<td>学习曲线高，安全性依赖于内核模块的正确性</td>
<td>配置复杂，对系统性能影响较大</td>
</tr>
</tbody>
</table>
<p style="text-align: center;">对比表格<code>[1]</code></p>
<p>从上表可以看出，eBPF、SystemTap 和 DTrace 都是非常强大的工具，可以用于收集和分析系统的运行情况。<code>[1]</code></p>
<h4>用途</h4>
<p>eBPF 是一种非常灵活和强大的内核技术，可以用于多种应用场景。下面是 eBPF 的一些常见用途：<code>[1]</code></p>
<ul>
<li>网络监控：eBPF 可以用于捕获网络数据包，并执行特定的逻辑来分析网络流量。例如，可以使用 eBPF 程序来监控网络流量，并在发现异常流量时进行警报。<code>[1]</code></li>
<li>安全过滤：eBPF 可以用于对网络数据包进行安全过滤。例如，可以使用 eBPF 程序来阻止恶意流量的传播，或者在发现恶意流量时对其进行拦截。<code>[1]</code></li>
<li>性能分析：eBPF 可以用于对内核的性能进行分析。例如，可以使用 eBPF 程序来收集内核的性能指标，并通过特定的接口将其可视化。这样，可以更好地了解内核的性能瓶颈，并进行优化。<code>[1]</code></li>
<li>虚拟化：eBPF 可以用于虚拟化技术。例如，可以使用 eBPF 程序来收集虚拟机的性能指标，并进行负载均衡。这样，可以更好地利用虚拟化环境的资源，提高系统的性能和稳定性。<code>[1]</code></li>
</ul>
<p>总之，eBPF 的常见用途非常广泛，可以用于网络监控、安全过滤、性能分析和虚拟化等多种应用场景。<code>[1]</code></p>
<h4>工作原理</h4>
<p>eBPF 的工作原理主要分为三个步骤：加载、编译和执行。</p>
<p>eBPF 需要在内核中运行。这通常是由用户态的应用程序完成的，它会通过系统调用来加载 eBPF 程序。在加载过程中，内核会将 eBPF 程序的代码复制到内核空间。</p>
<p>eBPF 程序需要经过编译和执行。这通常是由Clang/LLVM的编译器完成，然后形成字节码后，将用户态的字节码装载进内核，Verifier会对要注入内核的程序进行一些内核安全机制的检查,这是为了确保 eBPF 程序不会破坏内核的稳定性和安全性。在检查过程中，内核会对 eBPF 程序的代码进行分析，以确保它不会进行恶意操作，如系统调用、内存访问等。如果 eBPF 程序通过了内核安全机制的检查，它就可以在内核中正常运行了，其会通过通过一个JIT编译步骤将程序的通用字节码转换为机器特定指令集，以优化程序的执行速度。</p>
<p>下图是其架构图。</p>
<p><img decoding="async" class="aligncenter" src="https://imgopt.infoq.com/fit-in/1200x2400/filters:quality(80)/filters:no_upscale()/articles/gentle-linux-ebpf-introduction/en/resources/47image005-1619704397592.jpg" /></p>
<p style="text-align: center;">（图片来自：<a href="https://www.infoq.com/articles/gentle-linux-ebpf-introduction/" target="_blank" rel="noopener">https://www.infoq.com/articles/gentle-linux-ebpf-introduction/</a>）</p>
<p>在内核中运行时，eBPF 程序通常会挂载到一个内核钩子（hook）上，以便在特定的事件发生时被执行。例如，</p>
<ul>
<li><span>系统调用——当用户空间函数将执行转移到内核时插入</span></li>
<li><span>函数进入和退出——拦截对预先存在的函数的调用</span></li>
<li><span>网络事件 &#8211; 在收到数据包时执行</span></li>
<li><span>Kprobes 和 uprobes &#8211; 附加到内核或用户函数的探测器</span></li>
</ul>
<p>最后 eBPF Maps，允许eBPF程序在调用之间保持状态，以便进行相关的数据统计，并与用户空间的应用程序共享数据。一个eBPF映射基本上是一个键值存储，其中的值通常被视为任意数据的二进制块。它们是通过带有BPF_MAP_CREATE参数的<code>bpf_cmd</code>系统调用来创建的，和Linux世界中的其他东西一样，它们是通过文件描述符来寻址。与地图的交互是通过查找/更新/删除系统调用进行的</p>
<p>总之，eBPF 的工作原理是通过动态加载、执行和检查<strong>无损编译</strong>过的代码来实现的。<code>[1]</code></p>
<h4>示例</h4>
<p>eBPF 可以用于对内核的性能进行分析。下面是一个基于 eBPF 的性能分析的 step-by-step 示例：</p>
<p>第一步：准备工作：首先，需要确保内核已经支持 eBPF 功能。这通常需要在内核配置文件中启用 eBPF 相关的选项，并重新编译内核。检查是否支持 eBPF，你可以用这两个命令查看 <code>ls /sys/fs/bpf</code> 和 <code>lsmod | grep bpf</code></p>
<p>第二步：写 eBPF 程序：接下来，需要编写 eBPF 程序，用于收集内核的性能指标。eBPF 程序的语言可以选择 C 或者 Python，它需要通过特定的接口访问内核的数据结构，并将收集到的数据保存到指定的位置。</p>
<p>下面是一个Python 示例（其实还是C语言，用python来加载一段C程序到Linux内核）</p>
<pre class="EnlighterJSRAW" data-enlighter-language="python">#!/usr/bin/python3

from bcc import BPF
from time import sleep

# 定义 eBPF 程序
bpf_text = """
#include &lt;uapi/linux/ptrace.h&gt;

BPF_HASH(stats, u32);

int count(struct pt_regs *ctx) {
    u32 key = 0;
    u64 *val, zero=0;
    val = stats.lookup_or_init(&amp;key, &amp;zero);
    (*val)++;
    return 0;
}
"""

# 编译 eBPF 程序
b = BPF(text=bpf_text, cflags=["-Wno-macro-redefined"])

# 加载 eBPF 程序
b.attach_kprobe(event="tcp_sendmsg", fn_name="count")

name = {
  0: "tcp_sendmsg"
}
# 输出统计结果
while True:
    try:
        #print("Total packets: %d" % b["stats"][0].value)
        for k, v in b["stats"].items():
           print("{}: {}".format(name[k.value], v.value))
        sleep(1)
    except KeyboardInterrupt:
        exit()</pre>
<p>这个 eBPF 程序的功能是统计网络中传输的数据包数量。它通过定义一个 <code>BPF_HASH</code> 数据结构来保存统计结果（eBPF Maps），并通过捕获 <code>tcp_sendmsg</code> 事件来实现实时统计。最后，它通过每秒输出一次统计结果来展示数据。这个 eBPF 程序只是一个简单的示例，实际应用中可能需要进行更复杂的统计和分析。</p>
<p>第三步：运行 eBPF 程序：接下来，需要使用 eBPF 编译器将 eBPF 程序编译成内核可执行的格式（这个在上面的Python程序里你可以看到——Python引入了一个bcc的包，然后用这个包，把那段 C语言的程序编译成字节码加载在内核中并把某个函数 attach 到某个事件上）。这个过程可以使用 BPF Compiler Collection（BCC）工具来完成。BCC 工具可以通过命令行的方式将 eBPF 程序编译成内核可执行的格式，并将其加载到内核中。</p>
<p>下面是运行上面的 Python3 程序的步骤：</p>
<pre class="EnlighterJSRAW" data-enlighter-language="shell">sudo apt install python3-bpfcc</pre>
<p>注：在Python3下请不要使用 <code>pip3 install bcc</code> （参看：<a href="https://github.com/iovisor/bcc/issues/2278#issuecomment-825356087" target="_blank" rel="noopener">这里</a>）</p>
<p>如果你是 Ubuntu 20.10 以上的版本，最好通过源码安装（否则程序会有编译问题），参看：<a href="https://github.com/iovisor/bcc/issues/3993#issuecomment-1228217609" target="_blank" rel="noopener">这里</a>：</p>
<pre class="EnlighterJSRAW" data-enlighter-language="bash">apt purge bpfcc-tools libbpfcc python3-bpfcc
wget https://github.com/iovisor/bcc/releases/download/v0.25.0/bcc-src-with-submodule.tar.gz
tar xf bcc-src-with-submodule.tar.gz
cd bcc/
apt install -y python-is-python3
apt install -y bison build-essential cmake flex git libedit-dev   libllvm11 llvm-11-dev libclang-11-dev zlib1g-dev libelf-dev libfl-dev python3-distutils
apt install -y checkinstall
mkdir build
cd build/
cmake -DCMAKE_INSTALL_PREFIX=/usr -DPYTHON_CMD=python3 ..
make
checkinstall</pre>
<p>接下来，需要将上面的 Python 程序保存到本地，例如保存到文件 netstat.py。运行程序：最后，可以通过执行以下命令来运行 Python 程序：</p>
<pre class="EnlighterJSRAW" data-enlighter-language="shell">$ chmod +x ./netstat.py
$ sudo ./netstat.py
tcp_sendmsg: 29
tcp_sendmsg: 216
tcp_sendmsg: 277
tcp_sendmsg: 379
tcp_sendmsg: 419
tcp_sendmsg: 468
tcp_sendmsg: 574
tcp_sendmsg: 645
tcp_sendmsg: 29
</pre>
<p>程序开始运行后，会在控制台输出网络数据包的统计信息。可以通过按 Ctrl+C 组合键来结束程序的运行。</p>
<p>下面我们再看一个比较复杂的示例，这个示例会计算TCP的发包时间（示例参考于Github上 <a href="https://github.com/iovisor/bcc/issues/2972" target="_blank" rel="noopener">这个issue</a>里的程序）：</p>
<pre class="EnlighterJSRAW" data-enlighter-language="python">#!/usr/bin/python3

from bcc import BPF
import time

# 定义 eBPF 程序
bpf_text = """
#include &lt;uapi/linux/ptrace.h&gt;
#include &lt;net/sock.h&gt;
#include &lt;net/inet_sock.h&gt;
#include &lt;bcc/proto.h&gt;

struct packet_t {
    u64 ts, size;
    u32 pid;
    u32 saddr, daddr;
    u16 sport, dport;
};

BPF_HASH(packets, u64, struct packet_t);

int on_send(struct pt_regs *ctx, struct sock *sk, struct msghdr *msg, size_t size)
{
    u64 id = bpf_get_current_pid_tgid();
    u32 pid = id;

    // 记录数据包的时间戳和信息
    struct packet_t pkt = {}; // 结构体一定要初始化，可以使用下面的方法
                              //__builtin_memset(&amp;pkt, 0, sizeof(pkt)); 
    pkt.ts = bpf_ktime_get_ns();
    pkt.size = size;
    pkt.pid = pid;
    pkt.saddr = sk-&gt;__sk_common.skc_rcv_saddr;
    pkt.daddr = sk-&gt;__sk_common.skc_daddr;
    struct inet_sock *sockp = (struct inet_sock *)sk;
    pkt.sport = sockp-&gt;inet_sport;
    pkt.dport = sk-&gt;__sk_common.skc_dport;

    packets.update(&amp;id, &amp;pkt);
    return 0;
}

int on_recv(struct pt_regs *ctx, struct sock *sk)
{
    u64 id = bpf_get_current_pid_tgid();
    u32 pid = id;

    // 获取数据包的时间戳和编号
    struct packet_t *pkt = packets.lookup(&amp;id);
    if (!pkt) {
        return 0;
    }

    // 计算传输时间
    u64 delta = bpf_ktime_get_ns() - pkt-&gt;ts;

    // 统计结果
    bpf_trace_printk("tcp_time: %llu.%llums, size: %llu\\n", 
       delta/1000, delta%1000%100, pkt-&gt;size);

    // 删除统计结果
    packets.delete(&amp;id);

    return 0;
}
"""

# 编译 eBPF 程序
b = BPF(text=bpf_text, cflags=["-Wno-macro-redefined"])

# 注册 eBPF 程序
b.attach_kprobe(event="tcp_sendmsg", fn_name="on_send")
b.attach_kprobe(event="tcp_v4_do_rcv", fn_name="on_recv")

# 输出统计信息
print("Tracing TCP latency... Hit Ctrl-C to end.")
while True:
    try:
        (task, pid, cpu, flags, ts, msg) = b.trace_fields()
        print("%-18.9f %-16s %-6d %s" % (ts, task, pid, msg))
    except KeyboardInterrupt:
        exit()</pre>
<p>上面这个程序通过捕获每个数据包的时间戳来统计传输时间。在捕获 <code>tcp_sendmsg</code> 事件时，记录数据包的发送时间；在捕获 <code>tcp_v4_do_rcv</code> 事件时，记录数据包的接收时间；最后，通过比较两个时间戳来计算传输时间。</p>
<p>从上面的两个程序我们可以看到，eBPF 的一个编程的基本方法，这样的在Python里向内核的某些事件挂载一段 &#8220;C语言” 的方式就是 eBPF 的编程方式。实话实说，这样的代码很不好写，而且有很多非常诡异的东西，一般人是很难驾驭的（上面的代码我也不是很容易都能写通的，把 Google 都用了个底儿掉，读了很多晦涩的文档……）好在这样的代码已经有人写了，我们不必再写了，在 <a href="https://github.com/iovisor/bcc/tree/master/tools" target="_blank" rel="noopener">Github 上的 bcc 库下的 tools 目录</a>有很多……</p>
<p>BCC（<a href="https://github.com/iovisor/bcc" target="_blank" rel="noopener">BPF Compiler Collection</a>）是一套开源的工具集，可以在 Linux 系统中使用 BPF（Berkeley Packet Filter）程序进行系统级性能分析和监测。BCC 包含了许多实用工具，如：</p>
<ol>
<li>bcc-tools：一个包含许多常用的 BCC 工具的软件包。</li>
<li>bpftrace：一个高级语言，用于编写和执行 BPF 程序。</li>
<li>tcptop：一个实时监控和分析 TCP 流量的工具。</li>
<li>execsnoop：一个用于监控进程执行情况的工具。</li>
<li>filetop：一个实时监控和分析文件系统流量的工具。</li>
<li>trace：一个用于跟踪和分析函数调用的工具。</li>
<li>funccount：一个用于统计函数调用次数的工具。</li>
<li>opensnoop：一个用于监控文件打开操作的工具。</li>
<li>pidstat：一个用于监控进程性能的工具。</li>
<li>profile：一个用于分析系统 CPU 使用情况的工具。</li>
</ol>
<p>下面这张图你可能见过多次了，你可以看看他可以干多少事，内核里发生什么事一览无余。</p>
<p><img decoding="async" src="https://github.com/iovisor/bcc/raw/master/images/bcc_tracing_tools_2019.png" /></p>
<h4>延伸阅读</h4>
<p>一些经典的文章和书籍关于 eBPF 包括：</p>
<ul>
<li>Brendan Gregg 的《<a href="https://book.douban.com/subject/34467459/" target="_blank" rel="noopener">BPF Performance Tools: Linux System and Application Observability</a>》一书是一个全面的指南，涵盖了 eBPF 的基础知识和实践应用。</li>
<li>eBPF 的官网：<a href="https://ebpf.io/" target="_blank" rel="noopener">https://ebpf.io/</a> 由 <a href="https://cilium.io/" rel="nofollow">Cilium</a> 建立</li>
<li><a href="http://docs.cilium.io/en/latest/bpf/" rel="nofollow">Cilium&#8217;s BPF and XDP Reference Guide</a></li>
<li><a href="https://www.kernel.org/doc/html/latest/bpf/index.html" rel="nofollow">BPF Documentation</a></li>
<li><a href="https://www.kernel.org/doc/html/latest/bpf/bpf_design_QA.html" rel="nofollow">BPF Design Q&amp;A</a></li>
<li>还有 Github 上的 <a href="https://github.com/zoidbergwill/awesome-ebpf" target="_blank" rel="noopener">Awesome eBPF</a></li>
</ul>
<h4>彩蛋</h4>
<p>最后来到彩蛋环节。因为最近 ChatGPT 很火，于是，我想通过 ChatGPT 来帮助我书写这篇文章，一开始我让ChatGPT 帮我列提纲，并根据提纲生成文章内容，并查找相关的资料，非常之顺利，包括生成的代码，我以为我们以很快地完成这篇文章。</p>
<p>但是，到了代码生成的时候，我发现，ChatGPT 生成的代码的思路和方法都是对的，但是是比较老的，而且是跑不起来的，<strong>出现了好些低级错误，如：使用了未声明的变量，没有引用完整的C语言的头文件，没有正确地初始化变量，错误地获取数据，类型没有匹配……等等</strong>，在程序调试上，挖了很多的坑，C语言本来就不好搞，挖的很多运行时的坑很难察觉，所以，耗费了我大量的时间来排除各种各样的问题，其中有环境上的问题，还有代码上的问题，这些问题即便是通过 Google 也不容易找到解决方案，我找到的解决方案都放在文章中了，尤其是第二个示例，让我调试了3个多小时，读了很多 bcc 上的issue和相关的晦涩的手册和文档，才让程序跑通。</p>
<p>到了文章收关的阶段，我让ChatGPT 给我几个延伸阅读，也是很好的，但是没有给出链接，于是我只得人肉 Google 了一下，然后让我吃惊的是，<strong>好多ChatGPT给出来的文章是根本不存在的，完全是它伪造的</strong>。我连让它干了两次都是这样，这个让我惊掉大牙。这让我开始怀疑它之前生成的内容，于是，我不得我返回仔细Review我的文章，尤其是“介绍”、“用途”和“工作原理”这三个章节，基本都是ChatGPT生成的，在Review完后，我发现了ChatGPT 给我生造了一个叫 “无损编译器”的术语，这个术语简直了，于是我开始重写我的文章。我把一些段落重写了，有一些没有，保留下来的我都标记上了 <code>[1]</code>，大家读的时候要小心阅读。</p>
<p>最后，<strong>我的结论是，ChatGPT只是一个不成熟的玩具，只能回答一些没有价值的日常聊天的问题，要说能取代Google，我觉得不可能，因为Google会基于基本的事实，而ChatGPT会基于内容生成的算法，在造假方面称得上是高手，可以列为电信诈骗的范畴了，我以后不会再使用ChatGPT生成文章内容或是作我的帮手了。StackOverflow把其ban了真是不能太赞了！</strong></p>
<p><strong>附件一：ChatGPT的造假载图和样本</strong></p>
<table width="100%">
<tbody>
<tr>
<td>
<p><figure id="attachment_22325" aria-describedby="caption-attachment-22325" style="width: 195px" class="wp-caption aligncenter"><a href="https://coolshell.cn/wp-content/uploads/2022/12/eBPF.ref_.1.png"><img decoding="async" loading="lazy" class="wp-image-22325 size-medium" src="https://coolshell.cn/wp-content/uploads/2022/12/eBPF.ref_.1-195x300.png" alt="" width="195" height="300" srcset="https://coolshell.cn/wp-content/uploads/2022/12/eBPF.ref_.1-195x300.png 195w, https://coolshell.cn/wp-content/uploads/2022/12/eBPF.ref_.1-665x1024.png 665w, https://coolshell.cn/wp-content/uploads/2022/12/eBPF.ref_.1-768x1183.png 768w, https://coolshell.cn/wp-content/uploads/2022/12/eBPF.ref_.1-997x1536.png 997w, https://coolshell.cn/wp-content/uploads/2022/12/eBPF.ref_.1-175x270.png 175w, https://coolshell.cn/wp-content/uploads/2022/12/eBPF.ref_.1.png 1096w" sizes="(max-width: 195px) 100vw, 195px" /></a><figcaption id="caption-attachment-22325" class="wp-caption-text">点击看大图</figcaption></figure></td>
<td>
<p><figure id="attachment_22326" aria-describedby="caption-attachment-22326" style="width: 286px" class="wp-caption aligncenter"><a href="https://coolshell.cn/wp-content/uploads/2022/12/eBPF.ref_.2.png"><img decoding="async" loading="lazy" class="wp-image-22326 size-medium" src="https://coolshell.cn/wp-content/uploads/2022/12/eBPF.ref_.2-286x300.png" alt="" width="286" height="300" srcset="https://coolshell.cn/wp-content/uploads/2022/12/eBPF.ref_.2-286x300.png 286w, https://coolshell.cn/wp-content/uploads/2022/12/eBPF.ref_.2-978x1024.png 978w, https://coolshell.cn/wp-content/uploads/2022/12/eBPF.ref_.2-768x804.png 768w, https://coolshell.cn/wp-content/uploads/2022/12/eBPF.ref_.2-258x270.png 258w, https://coolshell.cn/wp-content/uploads/2022/12/eBPF.ref_.2.png 1096w" sizes="(max-width: 286px) 100vw, 286px" /></a><figcaption id="caption-attachment-22326" class="wp-caption-text">点击看大图</figcaption></figure></td>
</tr>
</tbody>
</table>
<p><strong>ChatGPT 生成的样本一</strong></p>
<ul>
<li>Brendan Gregg 的《BPF Performance Tools: Linux System and Application Observability》一书是一个全面的指南，涵盖了 eBPF 的基础知识和实践应用。</li>
<li>Alexei Starovoitov 在 2015 年的 LPC 大会上发表的《eBPF and XDP: fast programmable datapath in the Linux kernel》一文，是一个介绍 eBPF 和 XDP（eXpress Data Path）的综述。</li>
<li>谷歌的《BPF: The Future of Linux Monitoring and Observability》一文介绍了 eBPF 的基本概念，并探讨了它对于 Linux 监控和可观察性的影响。</li>
<li>微软的《A Survey of Techniques for Hardware Acceleration of BPF》一文，介绍了 eBPF 在硬件加速方面的研究现状。</li>
<li>Daniel Borkmann 的《Linux kernel networking: implementation and theory》一书涵盖了 eBPF 的实现和理论。</li>
<li>Martin KaFai Lau 在 2015 年的 SOSP 大会上发表的《eBPF: a new execution environment for the Linux kernel》一文，介绍了 eBPF 在 Linux 内核中的新执行环境。</li>
<li>来自两名谷歌工程师的《eBPF: A New Frontier in Performance Analysis》一文，探讨了 eBPF 对性能分析的影响。</li>
<li>《BPF Performance Tools: Linux System and Application Observability》一书中的第三章，讨论了 eBPF 的编译器和运行时系统。</li>
<li>来自三名谷歌工程师的《eBPF: The New Solaris DTrace》一文，比较了 eBPF 和 Solaris DTrace 系统的区别。</li>
</ul>
<p><strong>ChatGPT 生成的样本二</strong></p>
<ol>
<li>eBPF 程序开发指南（<a href="https://cilium.io/doc/ebpf/ebpf-guide/" target="_new" rel="noopener">https://cilium.io/doc/ebpf/ebpf-guide/</a>）</li>
<li>eBPF 官方文档（<a href="https://www.kernel.org/doc/Documentation/networking/filter.txt" target="_new" rel="noopener">https://www.kernel.org/doc/Documentation/networking/filter.txt</a>）</li>
<li>Linux 内核 eBPF 介绍（<a href="https://www.ibm.com/developerworks/cn/linux/kernel/ebpf/index.html" target="_new" rel="noopener">https://www.ibm.com/developerworks/cn/linux/kernel/ebpf/index.html</a>）</li>
<li>eBPF 性能优化技巧（<a href="https://www.infoq.com/cn/news/2018/08/ebpf-performance-tips/" target="_new" rel="noopener">https://www.infoq.com/cn/news/2018/08/ebpf-performance-tips/</a>）</li>
<li>eBPF 实战经验分享（<a href="https://blog.cloudflare.com/ebpf-in-practice/" target="_new" rel="noopener">https://blog.cloudflare.com/ebpf-in-practice/</a>）</li>
<li>eBPF 在网络安全中的应用（<a href="https://www.zdnet.com/article/ebpf-how-to-use-it-for-network-security/" target="_new" rel="noopener">https://www.zdnet.com/article/ebpf-how-to-use-it-for-network-security/</a>）</li>
<li>eBPF 实现 Linux 系统性能监控（<a href="https://www.percona.com/blog/2017/08/15/how-to-use-ebpf-to-monitor-linux-system-performance/" target="_new" rel="noopener">https://www.percona.com/blog/2017/08/15/how-to-use-ebpf-to-monitor-linux-system-performance/</a>）</li>
<li>eBPF 入门教程（<a href="https://sysdig.com/blog/ebpf-getting-started/" target="_new" rel="noopener">https://sysdig.com/blog/ebpf-getting-started/</a>）</li>
<li>eBPF 与 BPF 比较（<a href="https://lwn.net/Articles/724647/" target="_new" rel="noopener">https://lwn.net/Articles/724647/</a>）</li>
<li>eBPF 提高课程（<a href="https://www.pluralsight.com/courses/ebpf-advanced" target="_new" rel="noopener">https://www.pluralsight.com/courses/ebpf-advanced</a>）</li>
</ol>
<p><strong>附件二：发明的术语：无损编译器</strong></p>
<table width="100%">
<tbody>
<tr>
<td>
<p><figure id="attachment_22328" aria-describedby="caption-attachment-22328" style="width: 180px" class="wp-caption aligncenter"><a href="https://coolshell.cn/wp-content/uploads/2022/12/fake.png"><img decoding="async" loading="lazy" class="wp-image-22328 size-medium" src="https://coolshell.cn/wp-content/uploads/2022/12/fake-180x300.png" alt="" width="180" height="300" srcset="https://coolshell.cn/wp-content/uploads/2022/12/fake-180x300.png 180w, https://coolshell.cn/wp-content/uploads/2022/12/fake-616x1024.png 616w, https://coolshell.cn/wp-content/uploads/2022/12/fake-768x1277.png 768w, https://coolshell.cn/wp-content/uploads/2022/12/fake-924x1536.png 924w, https://coolshell.cn/wp-content/uploads/2022/12/fake-162x270.png 162w, https://coolshell.cn/wp-content/uploads/2022/12/fake.png 1080w" sizes="(max-width: 180px) 100vw, 180px" /></a><figcaption id="caption-attachment-22328" class="wp-caption-text">点击看大图</figcaption></figure></td>
<td>
<p><figure id="attachment_22335" aria-describedby="caption-attachment-22335" style="width: 223px" class="wp-caption aligncenter"><a href="https://coolshell.cn/wp-content/uploads/2022/12/fake.term_.png"><img decoding="async" loading="lazy" class="wp-image-22335 size-medium" src="https://coolshell.cn/wp-content/uploads/2022/12/fake.term_-223x300.png" alt="" width="223" height="300" srcset="https://coolshell.cn/wp-content/uploads/2022/12/fake.term_-223x300.png 223w, https://coolshell.cn/wp-content/uploads/2022/12/fake.term_-761x1024.png 761w, https://coolshell.cn/wp-content/uploads/2022/12/fake.term_-768x1033.png 768w, https://coolshell.cn/wp-content/uploads/2022/12/fake.term_-1142x1536.png 1142w, https://coolshell.cn/wp-content/uploads/2022/12/fake.term_-201x270.png 201w, https://coolshell.cn/wp-content/uploads/2022/12/fake.term_.png 1176w" sizes="(max-width: 223px) 100vw, 223px" /></a><figcaption id="caption-attachment-22335" class="wp-caption-text">点击看大图</figcaption></figure></td>
</tr>
</tbody>
</table>
<p>（全文完）<!--



<p align="center"><a href= target=_blank><img decoding="async" src=""></a></p>





<p align="center"><img decoding="async" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.weixin.jpg"> <img decoding="async" loading="lazy" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.mini_.jpg" width="300" height="300"> <br />关注CoolShell微信公众账号和微信小程序</p>

 

--></p>
<div style="margin-top: 15px; font-size: 16px;color: #cc0000;">
<p align="center"><strong>（转载本站文章请注明作者和出处 <a href="https://coolshell.cn/">酷 壳 &#8211; CoolShell</a> ，请勿用于任何商业用途）</strong></p>
</div>

<div class="wp_rp_wrap  wp_rp_vertical_m" id="wp_rp_first"><div class="wp_rp_content"><h3 class="related_post_title">相关文章</h3><ul class="related_post wp_rp"><li ><a href="https://coolshell.cn/articles/1379.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/plugins/wordpress-23-related-posts-plugin/static/thumbs/13.jpg" alt="如何调试bash脚本" width="150" height="150" /></a><a href="https://coolshell.cn/articles/1379.html" class="wp_rp_title">如何调试bash脚本</a></li><li ><a href="https://coolshell.cn/articles/19219.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2019/03/linux.ninja_-150x150.png" alt="打造高效的工作环境 &#8211; Shell 篇" width="150" height="150" /></a><a href="https://coolshell.cn/articles/19219.html" class="wp_rp_title">打造高效的工作环境 &#8211; Shell 篇</a></li><li ><a href="https://coolshell.cn/articles/18654.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2018/12/docker-networking-1-150x150.png" alt="记一次Kubernetes/Docker网络排障" width="150" height="150" /></a><a href="https://coolshell.cn/articles/18654.html" class="wp_rp_title">记一次Kubernetes/Docker网络排障</a></li><li ><a href="https://coolshell.cn/articles/18360.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2018/05/300x262-150x150.jpg" alt="程序员练级攻略（2018)  与我的专栏" width="150" height="150" /></a><a href="https://coolshell.cn/articles/18360.html" class="wp_rp_title">程序员练级攻略（2018)  与我的专栏</a></li><li ><a href="https://coolshell.cn/articles/17998.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2017/07/systemd-1-150x150.jpeg" alt="Linux PID 1 和 Systemd" width="150" height="150" /></a><a href="https://coolshell.cn/articles/17998.html" class="wp_rp_title">Linux PID 1 和 Systemd</a></li><li ><a href="https://coolshell.cn/articles/17416.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2016/07/cache-150x150.png" alt="缓存更新的套路" width="150" height="150" /></a><a href="https://coolshell.cn/articles/17416.html" class="wp_rp_title">缓存更新的套路</a></li></ul></div></div>The post <a href="https://coolshell.cn/articles/22320.html">eBPF 介绍</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></content:encoded>
					
					<wfw:commentRss>https://coolshell.cn/articles/22320.html/feed</wfw:commentRss>
			<slash:comments>18</slash:comments>
		
		
			</item>
		<item>
		<title>打造高效的工作环境 &#8211; Shell 篇</title>
		<link>https://coolshell.cn/articles/19219.html</link>
					<comments>https://coolshell.cn/articles/19219.html#comments</comments>
		
		<dc:creator><![CDATA[陈皓]]></dc:creator>
		<pubDate>Sun, 17 Mar 2019 05:53:01 +0000</pubDate>
				<category><![CDATA[Unix/Linux]]></category>
		<category><![CDATA[操作系统]]></category>
		<category><![CDATA[杂项资源]]></category>
		<category><![CDATA[编程工具]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[Programmer]]></category>
		<category><![CDATA[Shell]]></category>
		<category><![CDATA[程序员]]></category>
		<guid isPermaLink="false">https://coolshell.cn/?p=19219</guid>

					<description><![CDATA[<p>注：本文由雷俊(Javaer/Emacser)和我一起编辑，所以文章版权归雷俊与我共同所有，转载者必需注明出处和我们两位作者。原文最早发于酷壳微信公众号，后来我...</p>
<p class="read-more"><a class="btn btn-default" href="https://coolshell.cn/articles/19219.html"> Read More<span class="screen-reader-text">  Read More</span></a></p>
The post <a href="https://coolshell.cn/articles/19219.html">打造高效的工作环境 – Shell 篇</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></description>
										<content:encoded><![CDATA[<p><script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-3415450859608158"
     crossorigin="anonymous"></script></p>
<blockquote><p><strong>注：本文由<a href="https://github.com/rayjun" target="_blank" rel="noopener noreferrer">雷俊</a>(Javaer/Emacser)和我一起编辑，所以文章版权归雷俊与我共同所有，转载者必需注明出处和我们两位作者。原文最早发于酷壳微信公众号，后来我又做了一些修改，再发到博客这边。</strong></p></blockquote>
<p><img decoding="async" loading="lazy" class="alignright size-full wp-image-19230" src="https://coolshell.cn/wp-content/uploads/2019/03/linux.ninja_.png" alt="" width="255" height="220" />程序员是一个很懒的群体，总想着能够让代码为自己干活，他们不断地把工作生活中的一些事情用代码自动化了，从而让整个社会的效率运作地越来越高。所以，程序员在准备去优化这个世界的时候，都会先要优化自己的工作环境，是所谓“工欲善其事，必先利其器”。</p>
<p>我们每个程序员都应该打造一套让自己更为高效的工作环境。那怕就是让你少输入一次命令，少按一次键，少在鼠标和键盘间切换一次，都会让程序员的工作变得更为的高效。所以，程序员一般需要一台性能比较好，不会因为开了太多的网页或程序就卡得不行的电脑，还要配备多个显示器，一个显示器写代码，一个查文档，一个测试运行结果，而不必在各种窗口来来回回的切换……在大量的窗口间切换经常会迷路，而且也容易出错（分不清线上或测试环境）……</p>
<p>除了硬件上的装备，软件上也是能够提升程序员生产力的地方，<strong>在软件层面提升程序员生产力的东西有一个很重要的事就是命令行和脚本</strong>，使用鼠标和图形界面则会大大降低程序员的生产力。酷壳以前也写过一些，如《<a href="https://coolshell.cn/articles/8619.html" target="_blank" rel="noopener noreferrer">你可能不知道的Shell</a>》和《 <a href="https://coolshell.cn/articles/8883.html" target="_blank" rel="noopener noreferrer">应该知道的Linux技巧</a>》，但是Unix/Linux Shell就是一个大宝库，怎么写也写不完，不然，怎么会有“Where is the Shell, there is a way”。</p>
<p><span id="more-19219"></span></p>
<h4>命令行</h4>
<p>在不同的操作系统下，都有着很不错的命令行工具，比如 Mac 下的 <strong>Iterm2</strong>，Linux 下的原生命令行，如果你是在 Windows 下工作，问题也不大，因为 Windows 下现在有了 <strong>WSL</strong>。WSL 提供了一个由微软开发的Linux兼容的内核接口（不包含Linux内核代码），然后可以在其上运行GNU用户空间，例如 Ubuntu，openSUSE，SUSE Linux Enterprise Server，Debian和Kali Linux。这样的用户空间可能包含 Bash shell 和命令语言，使用本机 GNU/Linux 命令行工具（sed，awk 等），编程语言解释器（Ruby，Python 等），甚至是图形应用程序（使用主机端的X窗口系统）。</p>
<p>使用命令行可以完成所有日常的操作，新建文件夹（mkdir）、新建文件（touch）、移动（mv）、复制（cp）、删除（rm）等等。而且使用 Linux/Unix 命令行最好的方式是可以用 <code>awk</code>、<code>sed</code>、<code>grep</code>、<code>xargs</code>、<code>find</code>、<code>sort</code> 等等这样的命令，然后用管道把其串起来，就可以完成一个你想要的功能，尤其是一些简单的数据统计功能。这是Linux命令行不可比拟的优势。比如：</p>
<ul>
<li>查看连接你服务器 top10 用户端的 IP 地址：</li>
</ul>
<p><code>netstat -nat | awk '{print $5}' | awk -F ':' '{print $1}' | sort | uniq -c | sort -rn | head -n 10</code></p>
<ul>
<li>查看一下你最常用的10个命令：</li>
</ul>
<p><code>cat .bash_history | sort | uniq -c | sort -rn | head -n 10 (or cat .zhistory | sort | uniq -c | sort -rn | head -n 10</code></p>
<p>（注：<code>awk</code> 和 <code>sed</code> 是两大神器，所以，我以前的也有两篇文章来介绍它们——《<a href="https://coolshell.cn/articles/9070.html" target="_blank" rel="noopener noreferrer">awk简明教程</a>》和《<a href="https://coolshell.cn/articles/9104.html" target="_blank" rel="noopener noreferrer">sed简明教程</a>》，你可以前往一读）</p>
<p>在命令行中使用 <strong>alias</strong> 可以将使用频率很高命令或者比较复杂的命令合并成一个命令，或者修改原生的命令。</p>
<p>下面这几个命令，可能是你天天都在敲的。所以，你应该设置成 alias 来提高效率</p>
<pre data-enlighter-language="shell" class="EnlighterJSRAW">
alias nis=&quot;npm install --save &quot;
alias svim=&#039;sudo vim&#039;
alias mkcd=&#039;foo(){ mkdir -p &quot;$1&quot;; cd &quot;$1&quot; }; foo &#039;
alias install=&#039;sudo apt get install&#039;
alias update=&#039;sudo apt-get update; sudo apt-get upgrade&#039;
alias ..=&quot;cd ..&quot;
alias ...=&quot;cd ..; cd ..&quot;
alias www=&#039;python -m SimpleHTTPServer 8000&#039;
alias sock5=&#039;ssh -D 8080 -q -C -N -f user@your.server&#039;
</pre>
<p>你还可以参考如下的一些文章，看看别人是怎么用好 <code>alias</code> 的</p>
<ul>
<li><a href="https://www.cyberciti.biz/tips/bash-aliases-mac-centos-linux-unix.html" rel="nofollow">30 Handy Bash Shell Aliases For Linux / Unix / Mac OS X</a></li>
<li><a href="https://www.digitalocean.com/community/questions/what-are-your-favorite-bash-aliases" rel="nofollow">What are your favorite bash aliases?</a></li>
<li><a href="https://www.linuxtrainingacademy.com/23-handy-bash-shell-aliases-for-unix-linux-and-mac-os-x/" rel="nofollow">23 Handy Bash Shell Aliases For Unix, Linux, and Mac OS X</a></li>
<li><a href="https://brettterpstra.com/2013/03/31/a-few-more-of-my-favorite-shell-aliases/" rel="nofollow">A few more of my favorite Bash aliases</a></li>
</ul>
<p>命令行中除了原生的命令之外，还有很多可以提升使用体验的工具。下面罗列一些很不错的命令，把原生的命令增强地很厉害:</p>
<ul>
<li><a href="https://github.com/clvv/fasd" target="_blank" rel="noopener noreferrer"><strong>fasd</strong></a> 增强了 <code>cd</code> 命令 。</li>
<li><a href="https://github.com/sharkdp/bat" target="_blank" rel="noopener noreferrer"><strong>bat</strong></a> 增强了 <code>cat</code> 命令 。如果你想要有语法高亮的 <code>cat</code>，可以试试 <a href="https://github.com/jingweno/ccat" target="_blank" rel="noopener noreferrer"><strong>ccat</strong></a> 命令。</li>
<li><a href="https://github.com/ogham/exa" target="_blank" rel="noopener noreferrer"><strong>exa</strong></a> 增强了 <code>ls</code> 命令，如果你需要在很多目录上浏览各种文件 ，<a href="https://github.com/ranger/ranger" target="_blank" rel="noopener noreferrer"><strong>ranger</strong></a> 命令可以比 <code>cd</code> 和 <code>cat</code> 更有效率，甚至可以在你的终端预览图片。</li>
<li><a href="https://github.com/sharkdp/fd" target="_blank" rel="noopener noreferrer"><strong>fd</strong></a> 是一个比 <code>find</code> 更简单更快的命令，他还会自动地忽略掉一些你配置在 <code>.gitignore</code> 中的文件，以及 <code>.git</code> 下的文件。</li>
<li><a href="https://github.com/junegunn/fzf" target="_blank" rel="noopener noreferrer"><strong>fzf</strong></a> 会是一个很好用的文件搜索神器，其主要是搜索当前目录以下的文件，还可以使用 <code>fzf --preview 'cat {}'</code>边搜索文件边浏览内容。</li>
<li><code>grep</code> 是一个上古神器，然而，<a href="https://beyondgrep.com/" target="_blank" rel="noopener noreferrer"><strong>ack</strong></a>、<a href="https://github.com/ggreer/the_silver_searcher" target="_blank" rel="noopener noreferrer"><strong>ag</strong></a> 和 <a href="https://github.com/BurntSushi/ripgrep" target="_blank" rel="noopener noreferrer"><strong>rg</strong></a> 是更好的grep，和上面的 <code>fd</code>一样，在递归目录匹配的时候，会使用你配置在 <code>.gitignore</code> 中的规则。</li>
<li><code>rm</code> 是一个危险的命令，尤其是各种 <code>rm -rf …</code>，所以，<a href="https://github.com/andreafrancia/trash-cli/" target="_blank" rel="noopener noreferrer"><strong>trash</strong></a> 是一个更好的删除命令。</li>
<li><code>man</code> 命令是好读文档的命令，但是man的文档有时候太长了，所以，你可以试试 <a href="https://github.com/tldr-pages/tldr" target="_blank" rel="noopener noreferrer"><strong>tldr</strong></a> 命令，把文档上的一些示例整出来给你看。</li>
<li>如果你想要一个图示化的<code>ping</code>，你可以试试 <a href="https://github.com/denilsonsa/prettyping" target="_blank" rel="noopener noreferrer"><strong>prettyping</strong></a> 。</li>
<li>如果你想搜索以前打过的命令，不要再用 Ctrl +R 了，你可以使用加强版的 <a href="https://github.com/dvorka/hstr" target="_blank" rel="noopener noreferrer"><strong>hstr</strong></a>  。</li>
<li><a href="https://hisham.hm/htop/" target="_blank" rel="noopener noreferrer"><strong>htop</strong></a>  是 top 的一个加强版。然而，还有很多的各式各样的top，比如：用于看IO负载的 <a href="http://guichaz.free.fr/iotop/" target="_blank" rel="noopener noreferrer"><strong>iotop</strong></a>，网络负载的 <a href="http://www.ex-parrot.com/~pdw/iftop/" target="_blank" rel="noopener noreferrer"><strong>iftop</strong></a>, 以及把这些top都集成在一起的 <a href="https://github.com/Atoptool/atop" target="_blank" rel="noopener noreferrer"><strong>atop</strong></a>。</li>
<li><a href="https://dev.yorhel.nl/ncdu" target="_blank" rel="noopener noreferrer"><strong>ncdu</strong></a>  比 du 好用多了用。另一个选择是 <a href="https://github.com/jarun/nnn" target="_blank" rel="noopener noreferrer">nnn</a>。</li>
<li>如果你想把你的命令行操作建录制成一个 SVG 动图，那么你可以尝试使用 <a href="https://asciinema.org/" target="_blank" rel="noopener noreferrer"><strong>asciinema</strong></a> 和 <a href="https://github.com/marionebl/svg-term-cli" target="_blank" rel="noopener noreferrer"><strong>svg-trem</strong></a> 。</li>
<li><a href="https://github.com/jakubroztocil/httpie" target="_blank" rel="noopener noreferrer"><strong>httpie</strong></a> 是一个可以用来替代 <code>curl</code> 和 <code>wget</code> 的 http 客户端，<code>httpie</code> 支持 json 和语法高亮，可以使用简单的语法进行 http 访问: <code>http -v github.com</code>。</li>
<li><a href="https://github.com/tmux/tmux" target="_blank" rel="noopener noreferrer"><strong>tmux</strong></a> 在需要经常登录远程服务器工作的时候会很有用，可以保持远程登录的会话，还可以在一个窗口中查看多个 shell 的状态。</li>
<li><a href="https://github.com/klaussinani/taskbook" target="_blank" rel="noopener noreferrer"><strong>Taskbook</strong></a> 是可以完全在命令行中使用的任务管理器 ，支持 ToDo 管理，还可以为每个任务加上优先级。</li>
<li><a href="https://github.com/Russell91/sshrc" target="_blank" rel="noopener noreferrer"><strong>sshrc</strong></a> 是个神器，在你登录远程服务器的时候也能使用本机的 shell 的 rc 文件中的配置。</li>
<li><a href="https://github.com/allinurl/goaccess" target="_blank" rel="noopener noreferrer"><strong>goaccess</strong></a>  这个是一个轻量级的分析统计日志文件的工具，主要是分析各种各样的 access log。</li>
</ul>
<p>关于这些增加命令，主要是参考自下面的这些文章</p>
<ol>
<li><a href="https://dev.to/_darrenburns/10-tools-to-power-up-your-command-line-4id4" target="_blank" rel="nofollow noopener noreferrer">10 Tools To Power Up Your Command Line</a></li>
<li><a href="https://dev.to/_darrenburns/tools-to-power-up-your-command-line-part-2-2737" target="_blank" rel="nofollow noopener noreferrer">5 More Tools To Power Up Your Command Line (Part 2 Of Series)</a></li>
<li><a href="https://dev.to/_darrenburns/power-up-your-command-line-part-3-4o53" target="_blank" rel="nofollow noopener noreferrer">Power Up Your Command Line, Part 3</a></li>
<li><a href="https://darrenburns.net/posts/tools/" target="_blank" rel="nofollow noopener noreferrer">Power Up Your Command Line</a></li>
<li><a href="https://hacker-tools.github.io/" target="_blank" rel="nofollow noopener noreferrer">Hacker Tools</a></li>
</ol>
<h4>Shell 和脚本</h4>
<p>shell 是可以与计算机进行高效交互的文本接口。shell 提供了一套交互式的编程语言（脚本），shell的种类很多，比如 <strong>sh</strong>、<strong>bash</strong>、<strong>zsh</strong> 等。</p>
<p>shell 的生命力很强，在各种高级编程语言大行其道的今天，很多的任务依然离不开 shell。比如可以使用 shell 来执行一些编译任务，或者做一些批处理任务，初始化数据、打包程序等等。</p>
<p>现在比较流行的是 <strong>zsh</strong> + <a href="https://ohmyz.sh/" target="_blank" rel="noopener noreferrer"><strong>oh-my-zsh</strong></a> + <a href="https://github.com/zsh-users/zsh-autosuggestions" target="_blank" rel="noopener noreferrer"><strong>zsh-autosuggestions</strong></a> 的组合，你也可以试试看。其中 zsh 和 oh-my-zsh 算是常规操作了，但是 zsh-autosuggestions 特别有用，可以超级快速的帮你补全你输入过的命令，让命令行的操作更加高效。</p>
<p>另外，<strong><a href="https://fishshell.com/" target="_blank" rel="noopener noreferrer">fish</a> </strong>也是另外一个牛逼的shell，比如：命令行自动完成（根据历史记录），命令行命令高亮，当你要输入命令行参数的时候，自动提示有哪些参数…… fish在很多地方也是用起来很爽的。和上面的 oh-my-zsh 有点不分伯仲了。</p>
<p>你也许会说，用 Python 脚本或 PHP 来写脚本会比 Shell 更好更没有 bug，但我要申辩一下:</p>
<ul>
<li>其一，如果你有一天要维护线上机器的时候，或是到了银行用户的系统（与外网完全隔离，而且服务器上没有安装 Python/PHP 或是他们的的高级库，那么，你只有 Shell 可以用了）。</li>
<li>其二，而且，如果要跟命令行交互很多的话，Shell 是不二之选，试想一下，如果你要去 100 台远程的机器上查access.log 日志中有没有某个错误，完成这个工作你是用 PHP/Python 写脚本快还是用 Shell 写脚本快呢？</li>
</ul>
<p>所以，<strong>我们还要学会只使用传统的grep/awk/sed等等这些POSIX的原生的系统默认安装的命令</strong>。</p>
<p>当然，要写好一个脚本并不容易，下面有一些小模板供你参考：</p>
<p>处理命令行参数的一个样例</p>
<pre data-enlighter-language="shell" class="EnlighterJSRAW">while [ &quot;$1&quot; != &quot;&quot; ]; do
    case $1 in
        -s  )   shift	
		SERVER=$1 ;;  
        -d  )   shift
		DATE=$1 ;;
	--paramter|p ) shift
		PARAMETER=$1;;
        -h|help  )   usage # function call
                exit ;;
        * )     usage # All other parameters
                exit 1
    esac
    shift
done </pre>
<p>命令行菜单的一个样例</p>
<pre data-enlighter-language="shell" class="EnlighterJSRAW">
#!/bin/bash
# Bash Menu Script Example

PS3=&#039;Please enter your choice: &#039;
options=(&quot;Option 1&quot; &quot;Option 2&quot; &quot;Option 3&quot; &quot;Quit&quot;)
select opt in &quot;${options[@]}&quot;
do
    case $opt in
        &quot;Option 1&quot;)
            echo &quot;you chose choice 1&quot;
            ;;
        &quot;Option 2&quot;)
            echo &quot;you chose choice 2&quot;
            ;;
        &quot;Option 3&quot;)
            echo &quot;you chose choice $REPLY which is $opt&quot;
            ;;
        &quot;Quit&quot;)
            break
            ;;
        *) echo &quot;invalid option $REPLY&quot;;;
    esac
done
</pre>
<p>颜色定义，你可以使用 <code>echo -e "${Blu}blue ${Red}red ${RCol}etc...."</code> 进行有颜色文本的输出</p>
<pre data-enlighter-language="shell" class="EnlighterJSRAW">
RCol=&#039;\e[0m&#039;    # Text Reset

# Regular           Bold                Underline           High Intensity      BoldHigh Intens     Background          High Intensity Backgrounds
Bla=&#039;\e[0;30m&#039;;     BBla=&#039;\e[1;30m&#039;;    UBla=&#039;\e[4;30m&#039;;    IBla=&#039;\e[0;90m&#039;;    BIBla=&#039;\e[1;90m&#039;;   On_Bla=&#039;\e[40m&#039;;    On_IBla=&#039;\e[0;100m&#039;;
Red=&#039;\e[0;31m&#039;;     BRed=&#039;\e[1;31m&#039;;    URed=&#039;\e[4;31m&#039;;    IRed=&#039;\e[0;91m&#039;;    BIRed=&#039;\e[1;91m&#039;;   On_Red=&#039;\e[41m&#039;;    On_IRed=&#039;\e[0;101m&#039;;
Gre=&#039;\e[0;32m&#039;;     BGre=&#039;\e[1;32m&#039;;    UGre=&#039;\e[4;32m&#039;;    IGre=&#039;\e[0;92m&#039;;    BIGre=&#039;\e[1;92m&#039;;   On_Gre=&#039;\e[42m&#039;;    On_IGre=&#039;\e[0;102m&#039;;
Yel=&#039;\e[0;33m&#039;;     BYel=&#039;\e[1;33m&#039;;    UYel=&#039;\e[4;33m&#039;;    IYel=&#039;\e[0;93m&#039;;    BIYel=&#039;\e[1;93m&#039;;   On_Yel=&#039;\e[43m&#039;;    On_IYel=&#039;\e[0;103m&#039;;
Blu=&#039;\e[0;34m&#039;;     BBlu=&#039;\e[1;34m&#039;;    UBlu=&#039;\e[4;34m&#039;;    IBlu=&#039;\e[0;94m&#039;;    BIBlu=&#039;\e[1;94m&#039;;   On_Blu=&#039;\e[44m&#039;;    On_IBlu=&#039;\e[0;104m&#039;;
Pur=&#039;\e[0;35m&#039;;     BPur=&#039;\e[1;35m&#039;;    UPur=&#039;\e[4;35m&#039;;    IPur=&#039;\e[0;95m&#039;;    BIPur=&#039;\e[1;95m&#039;;   On_Pur=&#039;\e[45m&#039;;    On_IPur=&#039;\e[0;105m&#039;;
Cya=&#039;\e[0;36m&#039;;     BCya=&#039;\e[1;36m&#039;;    UCya=&#039;\e[4;36m&#039;;    ICya=&#039;\e[0;96m&#039;;    BICya=&#039;\e[1;96m&#039;;   On_Cya=&#039;\e[46m&#039;;    On_ICya=&#039;\e[0;106m&#039;;
Whi=&#039;\e[0;37m&#039;;     BWhi=&#039;\e[1;37m&#039;;    UWhi=&#039;\e[4;37m&#039;;    IWhi=&#039;\e[0;97m&#039;;    BIWhi=&#039;\e[1;97m&#039;;   On_Whi=&#039;\e[47m&#039;;    On_IWhi=&#039;\e[0;107m&#039;;
</pre>
<p>取当前运行脚本绝对路径的示例：（注：Linux下可以用 <code>dirname $(readlink -f $0)</code> ）</p>
<pre data-enlighter-language="shell" class="EnlighterJSRAW">
FILE=&quot;$0&quot;
while [[ -h ${FILE} ]]; do
    FILE=&quot;`readlink &quot;${FILE}&quot;`&quot;
done
pushd &quot;`dirname &quot;${FILE}&quot;`&quot; &gt; /dev/null
DIR=`pwd -P`
popd &gt; /dev/null
</pre>
<p>如何在远程服务器运行一个本地脚本</p>
<pre data-enlighter-language="shell" class="EnlighterJSRAW">#无参数
ssh user@server &#039;bash -s&#039; &lt; local.script.sh

#有参数
ssh user@server ARG1=&quot;arg1&quot; ARG2=&quot;arg2&quot; &#039;bash -s&#039; &lt; local_script.sh
</pre>
<p>如何检查一个命令是否存在，用 <code>which</code> 吗？最好不要用，因为很多操作系统的 <code>which</code> 命令没有设置退出状态码，这样你不知道是否是有那个命令。所以，你应该使用下面的方式。</p>
<pre data-enlighter-language="shell" class="EnlighterJSRAW">
# POSIX 兼容:
command -v [the_command]

# bash 环境:
hash [the_command]
type [the_command]

# 示例：
gnudate() {
    if hash gdate 2&gt; /dev/null; then
        gdate &quot;$@&quot;
    else
        date &quot;$@&quot;
    fi
}
</pre>
<p>然后，如果要写出健壮性更好的脚本，下面是一些相关的技巧：</p>
<ul>
<li>使用 <code>-e</code> 参数，如：<code>set -e</code> 或是 <code>#!/bin/sh -e</code>，这样设置会让你的脚本出错就会停止运行，这样一来可以防止你的脚本在出错的情况下还在拼拿地干活停不下来。</li>
<li>使用 <code>-u</code> 参数，如： <code>set -eu</code>，这意味着，如果你代码中有变量没有定义，就会退出。</li>
<li>对一些变理，你可以使用默认值。如：<code>${FOO:-'default'}</code></li>
<li>处理你代码的退出码。这样方便你的脚本跟别的命令行或脚本集成。</li>
<li>尽量不要使用 <code>;</code> 来执行多个命令，而是使用 <code>&amp;&amp;</code>，这样会在出错的时候停止运行后续的命令。</li>
<li>对于一些字符串变量，使用引号括起，避免其中有空格或是别的什么诡异字符。</li>
<li>如果你的脚有参数，你需要检查脚本运行是否带了你想要的参数，或是，你的脚本可以在没有参数的情况下安全的运行。</li>
<li>为你的脚本设置 <code>-h</code> 和 <code>--help</code> 来显示帮助信息。千万不要把这两个参数用做为的功能。</li>
<li>使用 <code>$()</code> 而不是 <code data-enlighter-language="raw" class="EnlighterJSRAW"></code> 来获得命令行的输出，主要原因是易读。</li>
<li>小心不同的平台，尤其是 MacOS 和 Linux 的跨平台。</li>
<li>对于 <code>rm -rf</code> 这样的高危操作，需要检查后面的变量名是否为空，比如：<code>rm -rf $MYDIDR/*</code> 如果 <code>$MYDIR</code>为空，结果是灾难性的。</li>
<li>考虑使用 &#8220;find/while&#8221; 而不是 “for/find”。如：<code>for F in $(find . -type f) ; do echo $F; done</code> 写成 <code>find . -type f | while read F ; do echo $F ; done</code> 不但可以容忍空格，而且还更快。</li>
<li>防御式编程，在正式执行命令前，把相关的东西都检查好，比如，文件目录有没有存在。</li>
</ul>
<p>你还可以使用ShellCheck 来帮助你检查你的脚本。</p>
<ul>
<li><a href="https://www.shellcheck.net/" target="_blank" rel="noopener noreferrer">https://www.shellcheck.net/</a></li>
</ul>
<p>最后推荐一些 Shell 和脚本的参考资料。</p>
<p>各种有意思的命令拼装，一行命令走天涯:</p>
<ul>
<li><a href="http://www.bashoneliners.com/" target="_blank" rel="nofollow noopener noreferrer">http://www.bashoneliners.com/</a></li>
<li><a href="http://www.shell-fu.org/" target="_blank" rel="nofollow noopener noreferrer">http://www.shell-fu.org/</a></li>
<li><a href="http://www.commandlinefu.com/" target="_blank" rel="nofollow noopener noreferrer">http://www.commandlinefu.com/</a></li>
</ul>
<p>下面是一些脚本集中营，你可以在里面淘到各种牛X的脚本：</p>
<ul>
<li><a href="http://www.shelldorado.com/scripts/" target="_blank" rel="nofollow noopener noreferrer">http://www.shelldorado.com/scripts/</a></li>
<li><a href="https://snippets.siftie.com/public/tag/bash/" target="_blank" rel="nofollow noopener noreferrer">https://snippets.siftie.com/public/tag/bash/</a></li>
<li><a href="https://bash.cyberciti.biz/" target="_blank" rel="nofollow noopener noreferrer">https://bash.cyberciti.biz/</a></li>
<li><a href="https://github.com/alexanderepstein/Bash-Snippets" target="_blank" rel="noopener noreferrer">https://github.com/alexanderepstein/Bash-Snippets</a></li>
<li><a href="https://github.com/miguelgfierro/scripts" target="_blank" rel="noopener noreferrer">https://github.com/miguelgfierro/scripts</a></li>
<li><a href="https://github.com/epety/100-shell-script-examples" target="_blank" rel="noopener noreferrer">https://github.com/epety/100-shell-script-examples</a></li>
<li><a href="https://github.com/ruanyf/simple-bash-scripts" target="_blank" rel="noopener noreferrer">https://github.com/ruanyf/simple-bash-scripts</a></li>
</ul>
<p>甚至写脚本都可以使用框架:</p>
<ul>
<li>写bash脚本的框架 <a href="https://github.com/Bash-it/bash-it" target="_blank" rel="noopener noreferrer">https://github.com/Bash-it/bash-it</a></li>
</ul>
<p>Google的Shell脚本的代码规范：</p>
<ul>
<li><a href="https://google.github.io/styleguide/shell.xml" target="_blank" rel="nofollow noopener noreferrer">https://google.github.io/styleguide/shell.xml</a></li>
</ul>
<p>最后，别忘了几个和shell有关的索引资源：</p>
<ul>
<li><a href="https://github.com/alebcay/awesome-shell" target="_blank" rel="noopener noreferrer">https://github.com/alebcay/awesome-shell</a></li>
<li><a href="https://github.com/awesome-lists/awesome-bash" target="_blank" rel="noopener noreferrer">https://github.com/awesome-lists/awesome-bash</a></li>
<li><a href="https://terminalsare.sexy/" target="_blank" rel="nofollow noopener noreferrer">https://terminalsare.sexy/</a></li>
</ul>
<p>最后，如果你还有什么别的更好的玩的东西，欢迎在评论区留言，或是到 <a href="https://github.com/coolshellx/articles" target="_blank" rel="noopener noreferrer">coolshellx/ariticles @ github</a> 修改本文。</p>
<p>（全文完）<!--



<p align="center"><a href= target=_blank><img decoding="async" src=""></a></p>





<p align="center"><img decoding="async" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.weixin.jpg"> <img decoding="async" loading="lazy" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.mini_.jpg" width="300" height="300"> <br />关注CoolShell微信公众账号和微信小程序</p>

 

--></p>
<div style="margin-top: 15px; font-size: 16px;color: #cc0000;">
<p align="center"><strong>（转载本站文章请注明作者和出处 <a href="https://coolshell.cn/">酷 壳 &#8211; CoolShell</a> ，请勿用于任何商业用途）</strong></p>
</div>

<div class="wp_rp_wrap  wp_rp_vertical_m" ><div class="wp_rp_content"><h3 class="related_post_title">相关文章</h3><ul class="related_post wp_rp"><li ><a href="https://coolshell.cn/articles/18360.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2018/05/300x262-150x150.jpg" alt="程序员练级攻略（2018)  与我的专栏" width="150" height="150" /></a><a href="https://coolshell.cn/articles/18360.html" class="wp_rp_title">程序员练级攻略（2018)  与我的专栏</a></li><li ><a href="https://coolshell.cn/articles/8088.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/plugins/wordpress-23-related-posts-plugin/static/thumbs/7.jpg" alt="对技术的态度" width="150" height="150" /></a><a href="https://coolshell.cn/articles/8088.html" class="wp_rp_title">对技术的态度</a></li><li ><a href="https://coolshell.cn/articles/4990.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2011/07/programmer-150x150.png" alt="程序员技术练级攻略" width="150" height="150" /></a><a href="https://coolshell.cn/articles/4990.html" class="wp_rp_title">程序员技术练级攻略</a></li><li ><a href="https://coolshell.cn/articles/4102.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/plugins/wordpress-23-related-posts-plugin/static/thumbs/25.jpg" alt="如何学好C语言" width="150" height="150" /></a><a href="https://coolshell.cn/articles/4102.html" class="wp_rp_title">如何学好C语言</a></li><li ><a href="https://coolshell.cn/articles/22298.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2022/10/communication-150x150.png" alt="聊聊团队协同和协同工具" width="150" height="150" /></a><a href="https://coolshell.cn/articles/22298.html" class="wp_rp_title">聊聊团队协同和协同工具</a></li><li ><a href="https://coolshell.cn/articles/22173.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2022/02/http_method-150x150.png" alt="“一把梭：REST API 全用 POST”" width="150" height="150" /></a><a href="https://coolshell.cn/articles/22173.html" class="wp_rp_title">“一把梭：REST API 全用 POST”</a></li></ul></div></div>The post <a href="https://coolshell.cn/articles/19219.html">打造高效的工作环境 – Shell 篇</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></content:encoded>
					
					<wfw:commentRss>https://coolshell.cn/articles/19219.html/feed</wfw:commentRss>
			<slash:comments>46</slash:comments>
		
		
			</item>
		<item>
		<title>记一次Kubernetes/Docker网络排障</title>
		<link>https://coolshell.cn/articles/18654.html</link>
					<comments>https://coolshell.cn/articles/18654.html#comments</comments>
		
		<dc:creator><![CDATA[陈皓]]></dc:creator>
		<pubDate>Sat, 08 Dec 2018 03:57:35 +0000</pubDate>
				<category><![CDATA[Unix/Linux]]></category>
		<category><![CDATA[操作系统]]></category>
		<category><![CDATA[杂项资源]]></category>
		<category><![CDATA[Docker]]></category>
		<category><![CDATA[Kubernetes]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[network]]></category>
		<category><![CDATA[Systemd]]></category>
		<guid isPermaLink="false">https://coolshell.cn/?p=18654</guid>

					<description><![CDATA[<p>昨天周五晚上，临下班的时候，用户给我们报了一个比较怪异的Kubernetes集群下的网络不能正常访问的问题，让我们帮助查看一下，我们从下午5点半左右一直跟进到晚...</p>
<p class="read-more"><a class="btn btn-default" href="https://coolshell.cn/articles/18654.html"> Read More<span class="screen-reader-text">  Read More</span></a></p>
The post <a href="https://coolshell.cn/articles/18654.html">记一次Kubernetes/Docker网络排障</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></description>
										<content:encoded><![CDATA[<p><script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-3415450859608158"
     crossorigin="anonymous"></script><img decoding="async" loading="lazy" class="alignright size-full wp-image-18662" src="https://coolshell.cn/wp-content/uploads/2018/12/docker-networking-1.png" alt="" width="300" height="238" />昨天周五晚上，临下班的时候，用户给我们报了一个比较怪异的Kubernetes集群下的网络不能正常访问的问题，让我们帮助查看一下，我们从下午5点半左右一直跟进到晚上十点左右，在远程不能访问用户机器只能远程遥控用户的情况找到了的问题。这个问题比较有意思，我个人觉得其中的调查用到的的命令以及排障的一些方法可以分享一下，所以写下了这篇文章。</p>
<h4>问题的症状</h4>
<p>用户直接在微信里说，他们发现在Kuberbnetes下的某个pod被重启了几百次甚至上千次，于是开启调查这个pod，发现上面的服务时而能够访问，时而不能访问，也就是有一定概率不能访问，不知道是什么原因。而且并不是所有的pod出问题，而只是特定的一两个pod出了网络访问的问题。用户说这个pod运行着Java程序，为了排除是Java的问题，用户用 <code>docker exec -it</code> 命令直接到容器内启了一个 Python的 SimpleHttpServer来测试发现也是一样的问题。</p>
<p>我们大概知道用户的集群是这样的版本，Kuberbnetes 是1.7，网络用的是flannel的gw模式，Docker版本未知，操作系统CentOS 7.4，直接在物理机上跑docker，物理的配置很高，512GB内存，若干CPU核，上面运行着几百个Docker容器。</p>
<p><span id="more-18654"></span></p>
<h4>问题的排查</h4>
<h5>问题初查</h5>
<p>首先，我们排除了flannel的问题，因为整个集群的网络通信都正常，只有特定的某一两个pod有问题。而用 <code>telnet ip port</code> 的命令手工测试网络连接时有很大的概率出现 <code>connection refused</code> 错误，大约 1/4的概率，而3/4的情况下是可以正常连接的。</p>
<p>当时，我们让用户抓个包看看，然后，用户抓到了有问题的TCP连接是收到了 <code>SYN</code> 后，立即返回了 <code>RST, ACK</code></p>
<p><img decoding="async" loading="lazy" class="aligncenter wp-image-18655" src="https://coolshell.cn/wp-content/uploads/2018/12/tcpdump.png" alt="" width="700" height="80" /></p>
<p>我问一下用户这两个IP所在的位置，知道了，<code>10.233.14.129</code> 是 <code>docker0</code>，<code>10.233.14.145</code> 是容器内的IP。所以，这基本上可以排除了所有和kubernets或是flannel的问题，这就是本地的Docker上的网络的问题。</p>
<p>对于这样被直接 Reset 的情况，在 <code>telnet</code> 上会显示 <code>connection refused</code> 的错误信息，对于我个人的经验，这种 <code>SYN</code>完直接返回 <code>RST, ACK</code>的情况只会有三种情况：</p>
<ol>
<li> TCP链接不能建立，不能建立连接的原因基本上是标识一条TCP链接的那五元组不能完成，绝大多数情况都是服务端没有相关的端口号。</li>
<li>TCP链接建错误，有可能是因为修改了一些TCP参数，尤其是那些默认是关闭的参数，因为这些参数会导致TCP协议不完整。</li>
<li>有防火墙iptables的设置，其中有 <code>REJECT</code> 规则。</li>
</ol>
<p>因为当时还在开车，在等红灯的时候，我感觉到有点像 NAT 的网络中服务端开启了 <code>tcp_tw_recycle</code> 和 <code>tcp_tw_reuse</code> 的症况（详细参看《<a href="https://coolshell.cn/articles/11564.html" target="_blank" rel="noopener noreferrer">TCP的那些事（上）</a>》），所以，让用户查看了一上TCP参数，发现用户一个TCP的参数都没有改，全是默认的，于是我们排除了TCP参数的问题。</p>
<p>然后，我也不觉得容器内还会设置上iptables，而且如果有那就是100%的问题，不会时好时坏。所以，我怀疑容器内的端口号没有侦听上，但是马上又好了，这可能会是应用的问题。于是我让用户那边看一下，应用的日志，并用 <code>kublet describe</code>看一下运行的情况，并把宿主机的 iptables 看一下。</p>
<p>然而，我们发现并没有任何的问题。这时，<strong>我们失去了所有的调查线索，感觉不能继续下去了……</strong></p>
<h5>重新梳理</h5>
<p>这个时候，回到家，大家吃完饭，和用户通了一个电话，把所有的细节再重新梳理了一遍，这个时候，用户提供了一个比较关键的信息—— “<strong>抓包这个事，在 <code>docker0</code> 上可以抓到，然而到了容器内抓不到容器返回 <code>RST, ACK</code> </strong>” ！然而，根据我的知识，我知道在 <code>docker0</code> 和容器内的 <code>veth</code> 网卡上，中间再也没有什么网络设备了（参看《<a href="https://coolshell.cn/articles/17029.html" target="_blank" rel="noopener noreferrer">Docker基础技术：LINUX NAMESPACE（下）</a>》）!</p>
<p>于是这个事把我们逼到了最后一种情况 —— IP地址冲突了！</p>
<p>Linux下看IP地址冲突还不是一件比较简单事的，而在用户的生产环境下没有办法安装一些其它的命令，所以只能用已有的命令，这个时候，我们发现用户的机器上有 <code>arping</code> 于是我们用这个命令来检测有没有冲突的IP地址。使用了下面的命令：</p>
<pre data-enlighter-language="shell" class="EnlighterJSRAW">
$ arping -D -I docker0 -c 2 10.233.14.145
$ echo $?
</pre>
<p>根据文档，<code>-D</code> 参数是检测IP地址冲突模式，如果这个命令的退状态是 <code>0</code> 那么就有冲突。结果返回了 <code>1</code> 。而且，我们用 <code>arping</code> IP的时候，没有发现不同的mac地址。 <strong>这个时候，似乎问题的线索又断了</strong>。</p>
<p>因为客户那边还在处理一些别的事情，所以，我们在时断时续的情况下工作，而还一些工作都需要用户完成，所以，进展有点缓慢，但是也给我们一些时间思考问题。</p>
<h5>柳暗花明</h5>
<p>现在我们知道，IP冲突的可能性是非常大的，但是我们找不出来是和谁的IP冲突了。而且，我们知道只要把这台机器重启一下，问题一定就解决掉了，但是我们觉得这并不是解决问题的方式，因为重启机器可以暂时的解决掉到这个问题，而如果我们不知道这个问题怎么发生的，那么未来这个问题还会再来。而重启线上机器这个成本太高了。</p>
<p>于是，我们的好奇心驱使我们继续调查。我让用户 <code>kubectl delete</code> 其中两个有问题的pod，因为本来就服务不断重启，所以，删掉也没有什么问题。删掉这两个pod后（一个是IP为 <code>10.233.14.145</code> 另一个是 <code>10.233.14.137</code>），我们发现，kubernetes在其它机器上重新启动了这两个服务的新的实例。然而，<strong>在问题机器上，这两个IP地址居然还可以ping得通</strong>。</p>
<p>好了，IP地址冲突的问题可以确认了。因为<code>10.233.14.xxx</code> 这个网段是 docker 的，所以，这个IP地址一定是在这台机器上。所以，我们想看看所有的 network namespace 下的 veth 网卡上的IP。</p>
<p>在这个事上，我们费了点时间，因为对相关的命令也 很熟悉，所以花了点时间Google，以及看相关的man。</p>
<ul>
<li>首先，我们到 <code>/var/run/netns</code>目录下查看系统的network namespace，发现什么也没有。</li>
<li>然后，我们到 <code>/var/run/docker/netns</code> 目录下查看Docker的namespace，发现有好些。</li>
<li>于是，我们用指定位置的方式查看Docker的network namespace里的IP地址</li>
</ul>
<p>这里要动用 <code>nsenter</code> 命令，这个命令可以进入到namespace里执行一些命令。比如</p>
<pre data-enlighter-language="shell" class="EnlighterJSRAW">
$ nsenter --net=/var/run/docker/netns/421bdb2accf1 ifconfig -a
</pre>
<p>上述的命令，到 <code>var/run/docker/netns/421bdb2accf1</code> 这个network namespace里执行了 <code>ifconfig -a</code> 命令。于是我们可以用下面 命令来遍历所有的network namespace。</p>
<pre data-enlighter-language="shell" class="EnlighterJSRAW">
$ ls /var/run/docker/netns | xargs -I {} nsenter --net=/var/run/docker/netns/{} ip addr 
</pre>
<p>然后，我们发现了比较诡异的事情。</p>
<ul>
<li><code>10.233.14.145</code> 我们查到了这个IP，说明，docker的namespace下还有这个IP。</li>
<li><code>10.233.14.137</code>，这个IP没有在docker的network namespace下查到。</li>
</ul>
<p>有namespace leaking？于是我上网查了一下，发现了一个docker的bug &#8211; 在docker remove/stop 一个容器的时候，没有清除相应的network namespace，这个问题被报告到了 <a href="https://github.com/moby/moby/issues/31597">Issue#31597</a> 然后被fix在了 <a href="https://github.com/moby/moby/pull/31996">PR#31996</a>，并Merge到了 Docker的 17.05版中。而用户的版本是 17.09，应该包含了这个fix。不应该是这个问题，感觉又走不下去了。</p>
<p>不过， <code>10.233.14.137</code> 这个IP可以ping得通，说明这个IP一定被绑在某个网卡，而且被隐藏到了某个network namespace下。</p>
<p>到这里，要查看所有network namespace，只有最后一条路了，那就是到 <code>/proc/</code> 目录下，把所有的pid下的 <code>/proc/&lt;pid&gt;/ns</code> 目录给穷举出来。好在这里有一个比较方便的命令可以干这个事 ： <code>lsns</code></p>
<p>于是我写下了如下的命令：</p>
<pre data-enlighter-language="shell" class="EnlighterJSRAW">
$ lsns -t net | awk ‘{print $4}&#039; | xargs -t -I {} nsenter -t {}&amp;nbsp;-n ip addr | grep -C 4 &quot;10.233.14.137&quot;
</pre>
<p>解释一下。</p>
<ul>
<li><code>lsns -t net</code> 列出所有开了network namespace的进程，其第4列是进程PID</li>
<li>把所有开过network namespace的进程PID拿出来，转给 <code>xargs</code> 命令</li>
<li>由 <code>xargs</code> 命令把这些PID 依次传给 <code>nsenter</code> 命令，
<ul>
<li><code>xargs -t</code> 的意思是会把相关的执行命令打出来，这样我知道是那个PID。</li>
<li><code>xargs -I {}</code>  是声明一个占位符来替换相关的PID</li>
</ul>
</li>
</ul>
<p>最后，我们发现，虽然在 <code>/var/run/docker/netns</code> 下没有找到 <code>10.233.14.137</code> ，但是在 <code>lsns</code> 中找到了三个进程，他们都用了<code>10.233.14.137</code> 这个IP（冲突了这么多），<strong>而且他们的MAC地址全是一样的！</strong>（怪不得arping找不到）。通过<code>ps</code> 命令，可以查到这三个进程，有两个是java的，还有一个是<code>/pause</code> （这个应该是kubernetes的沙盒）。</p>
<p>我们继续乘胜追击，穷追猛打，用<code>pstree</code>命令把整个进程树打出来。发现上述的三个进程的父进程都在多个同样叫 <code>docker-contiane</code> 的进程下！</p>
<p><strong>这明显还是docker的，但是在<code>docker ps</code> 中却找不道相应的容器，什么鬼！快崩溃了……</strong></p>
<p>继续看进程树，发现，这些 <code>docker-contiane</code> 的进程的父进程不在 <code>dockerd</code> 下面，而是在 <code>systemd</code> 这个超级父进程PID 1下，我靠！进而发现了一堆这样的野进程（这种野进程或是僵尸进程对系统是有害的，至少也是会让系统进入亚健康的状态，因为他们还在占着资源）。</p>
<p><code>docker-contiane</code> 应该是 <code>dockerd</code> 的子进程，被挂到了 <code>pid 1</code> 只有一个原因，那就是父进程“飞”掉了，只能找 pid 1 当养父。这说明，这台机器上出现了比较严重的 <code>dockerd</code> 进程退出的问题，而且是非常规的，因为 <code>systemd</code> 之所以要成为 pid 1，其就是要监管所有进程的子子孙孙，居然也没有管理好，说明是个非常规的问题。（注，关于 systemd，请参看《<a href="https://coolshell.cn/articles/17998.html" target="_blank" rel="noopener noreferrer">Linux PID 1 和 Systemd </a>》，关于父子进程的事，请参看《Unix高级环境编程》一书）</p>
<p>接下来就要看看 <code>systemd</code> 为 <code>dockerd</code> 记录的日志了…… （然而日志只有3天的了，这3天<code>dockerd</code>没有任何异常）</p>
<h4>总结</h4>
<p>通过这个调查，可以总结一下，</p>
<p>1） 对于问题调查，需要比较扎实的基础知识，知道问题的成因和范围。</p>
<p>2）如果走不下去了，要重新梳理一下，回头仔细看一下一些蛛丝马迹，认真推敲每一个细节。</p>
<p>3） 各种诊断工具要比较熟悉，这会让你事半功倍。</p>
<p>4）系统维护和做清洁比较类似，需要经常看看系统中是否有一些僵尸进程或是一些垃圾东西，这些东西要及时清理掉。</p>
<p>最后，多说一下，很多人都说，<strong>Docker适合放在物理机内运行，这并不完全对，因为他们只考虑到了性能成本，没有考虑到运维成本，在这样512GB中启动几百个容器的玩法，其实并不好，因为这本质上是个大单体，因为你一理要重启某些关键进程或是机器，你的影响面是巨大的</strong>。</p>
<p>&nbsp;</p>
<p>———————— 更新 2018/12/10 —————————</p>
<h4>问题原因</h4>
<p>这两天在自己的环境下测试了一下，发现，只要是通过 <code>systemctl start/stop docker</code> 这样的命令来启停 Docker， 是可以把所有的进程和资源全部干掉的。这个是没有什么问题的。我唯一能重现用户问题的的操作就是直接 <code>kill -9 &lt;dockerd pid&gt;</code> 但是这个事用户应该不会干。而 Docker 如果有 crash 事件时，Systemd 是可以通过 <code>journalctl -u docker</code> 这样的命令查看相关的系统日志的。</p>
<p>于是，我找用户了解一下他们在Docker在启停时的问题，用户说，<strong>他们的执行 <code>systemctl stop docker</code> 这个命令的时候，发现这个命令不响应了，有可能就直接按了 <code>Ctrl +C</code> 了</strong>！</p>
<p>这个应该就是导致大量的 <code>docker-containe</code> 进程挂到 <code>PID 1</code> 下的原因了。前面说过，用户的一台物理机上运行着上百个容器，所以，那个进程树也是非常庞大的，我想，停服的时候，系统一定是要遍历所有的docker子进程来一个一个发退出信号的，这个过程可能会非常的长。导致操作员以为命令假死，而直接按了 <code>Ctrl + C</code> ，最后导致很多容器进程并没有终止……</p>
<p>&nbsp;</p>
<h4>其它事宜</h4>
<p>有同学问，为什么我在这个文章里写的是 <code>docker-containe</code> 而不是 <code>containd</code> 进程？这是因为被 <code>pstree</code> 给截断了，用 <code>ps</code> 命令可以看全，只是进程名的名字有一个 <code>docker-</code>的前缀。</p>
<p>下面是这两种不同安装包的进程树的差别（其中 <code>sleep</code> 是我用 <code>buybox</code> 镜像启动的）</p>
<pre data-enlighter-language="shell" class="EnlighterJSRAW">
systemd───dockerd─┬─docker-contained─┬─3*[docker-contained-shim─┬─sleep]
                  │                 │                    └─9*[{docker-containe}]]
                  │                 ├─docker-contained-shim─┬─sleep
                  │                 │                 └─10*[{docker-containe}]
                  │                 └─14*[{docker-contained-shim}]
                  └─17*[{dockerd}]
</pre>
<pre data-enlighter-language="shell" class="EnlighterJSRAW">
systemd───dockerd─┬─containerd─┬─3*[containerd-shim─┬─sleep]
                  │            │                 └─9*[{containerd-shim}]
                  │            ├─2*[containerd-shim─┬─sleep]
                  │            │                    └─9*[{containerd-shim}]]
                  │            └─11*[{containerd}]
                  └─10*[{dockerd}]

</pre>
<p>顺便说一下，自从 Docker 1.11版以后，Docker进程组模型就改成上面这个样子了.</p>
<ul>
<li><code>dockerd</code> 是 Docker Engine守护进程，直接面向操作用户。<code>dockerd</code> 启动时会启动 <code>containerd</code> 子进程，他们之前通过RPC进行通信。</li>
<li><code>containerd</code> 是<code>dockerd</code>和<code>runc</code>之间的一个中间交流组件。他与 <code>dockerd</code> 的解耦是为了让Docker变得更为的中立，而支持OCI 的标准 。</li>
<li><code>containerd-shim</code>  是用来真正运行的容器的，每启动一个容器都会起一个新的shim进程， 它主要通过指定的三个参数：容器id，boundle目录（containerd的对应某个容器生成的目录，一般位于：<code>/var/run/docker/libcontainerd/containerID</code>）， 和运行命令（默认为 <code>runc</code>）来创建一个容器。</li>
<li><code>docker-proxy</code> 你有可能还会在新版本的Docker中见到这个进程，这个进程是用户级的代理路由。只要你用 <code>ps -elf</code> 这样的命令把其命令行打出来，你就可以看到其就是做端口映射的。如果你不想要这个代理的话，你可以在 <code>dockerd</code> 启动命令行参数上加上：  <code>--userland-proxy=false</code> 这个参数。</li>
</ul>
<p>更多的细节，大家可以自行Google。这里推荐两篇文章：</p>
<ul>
<li><a href="https://hackernoon.com/docker-containerd-standalone-runtimes-heres-what-you-should-know-b834ef155426" target="_blank" rel="noopener noreferrer">Docker, Containerd &amp; Standalone Runtimes — Here’s What You Should Know</a></li>
<li><a href="http://alexander.holbreich.org/docker-components-explained/" target="_blank" rel="noopener noreferrer">Docker components explained</a></li>
</ul>
<p>（全文完）<!--



<p align="center"><a href= target=_blank><img decoding="async" src=""></a></p>





<p align="center"><img decoding="async" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.weixin.jpg"> <img decoding="async" loading="lazy" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.mini_.jpg" width="300" height="300"> <br />关注CoolShell微信公众账号和微信小程序</p>

 

--></p>
<div style="margin-top: 15px; font-size: 16px;color: #cc0000;">
<p align="center"><strong>（转载本站文章请注明作者和出处 <a href="https://coolshell.cn/">酷 壳 &#8211; CoolShell</a> ，请勿用于任何商业用途）</strong></p>
</div>

<div class="wp_rp_wrap  wp_rp_vertical_m" ><div class="wp_rp_content"><h3 class="related_post_title">相关文章</h3><ul class="related_post wp_rp"><li ><a href="https://coolshell.cn/articles/17998.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2017/07/systemd-1-150x150.jpeg" alt="Linux PID 1 和 Systemd" width="150" height="150" /></a><a href="https://coolshell.cn/articles/17998.html" class="wp_rp_title">Linux PID 1 和 Systemd</a></li><li ><a href="https://coolshell.cn/articles/17200.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2015/08/how_to_set_up_an_iSCSI_LUN_with_thin-150x150.jpg" alt="Docker基础技术：DeviceMapper" width="150" height="150" /></a><a href="https://coolshell.cn/articles/17200.html" class="wp_rp_title">Docker基础技术：DeviceMapper</a></li><li ><a href="https://coolshell.cn/articles/17061.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2015/08/docker-filesystems-busyboxrw-150x150.png" alt="Docker基础技术：AUFS" width="150" height="150" /></a><a href="https://coolshell.cn/articles/17061.html" class="wp_rp_title">Docker基础技术：AUFS</a></li><li ><a href="https://coolshell.cn/articles/17049.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2015/04/filter-150x150.png" alt="Docker基础技术：Linux CGroup" width="150" height="150" /></a><a href="https://coolshell.cn/articles/17049.html" class="wp_rp_title">Docker基础技术：Linux CGroup</a></li><li ><a href="https://coolshell.cn/articles/17010.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2015/04/isolation-150x150.jpg" alt="Docker基础技术：Linux Namespace（上）" width="150" height="150" /></a><a href="https://coolshell.cn/articles/17010.html" class="wp_rp_title">Docker基础技术：Linux Namespace（上）</a></li><li ><a href="https://coolshell.cn/articles/17029.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2015/04/jail_cell-150x150.jpg" alt="Docker基础技术：Linux Namespace（下）" width="150" height="150" /></a><a href="https://coolshell.cn/articles/17029.html" class="wp_rp_title">Docker基础技术：Linux Namespace（下）</a></li></ul></div></div>The post <a href="https://coolshell.cn/articles/18654.html">记一次Kubernetes/Docker网络排障</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></content:encoded>
					
					<wfw:commentRss>https://coolshell.cn/articles/18654.html/feed</wfw:commentRss>
			<slash:comments>51</slash:comments>
		
		
			</item>
		<item>
		<title>程序员练级攻略（2018)  与我的专栏</title>
		<link>https://coolshell.cn/articles/18360.html</link>
					<comments>https://coolshell.cn/articles/18360.html#comments</comments>
		
		<dc:creator><![CDATA[陈皓]]></dc:creator>
		<pubDate>Tue, 29 May 2018 04:38:23 +0000</pubDate>
				<category><![CDATA[技术读物]]></category>
		<category><![CDATA[杂项资源]]></category>
		<category><![CDATA[C++]]></category>
		<category><![CDATA[Java]]></category>
		<category><![CDATA[Javascript]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[Programmer]]></category>
		<category><![CDATA[Python]]></category>
		<category><![CDATA[React]]></category>
		<category><![CDATA[Unix]]></category>
		<category><![CDATA[Web]]></category>
		<category><![CDATA[分布式]]></category>
		<category><![CDATA[程序员]]></category>
		<guid isPermaLink="false">https://coolshell.cn/?p=18360</guid>

					<description><![CDATA[<p>写极客时间8个月了，我的专栏现在有一定的积累了，今天想自己推荐一下。因为最新的系列《程序员练级攻略（2018）版》正在连载中，而且文章积累量到了我也有比较足的自...</p>
<p class="read-more"><a class="btn btn-default" href="https://coolshell.cn/articles/18360.html"> Read More<span class="screen-reader-text">  Read More</span></a></p>
The post <a href="https://coolshell.cn/articles/18360.html">程序员练级攻略（2018)  与我的专栏</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></description>
										<content:encoded><![CDATA[<p><script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-3415450859608158"
     crossorigin="anonymous"></script><img decoding="async" loading="lazy" class="alignright size-medium" src="https://coolshell.cn/wp-content/uploads/2018/05/专栏-300x262.jpg" alt="" width="300" height="262" />写极客时间8个月了，我的专栏现在有一定的积累了，今天想自己推荐一下。因为最新的系列《程序员练级攻略（2018）版》正在连载中，而且文章积累量到了我也有比较足的自信向大家推荐我的这个专栏了。推荐就从最新的这一系统的文章开始。</p>
<p>2011年，我在 <a href="https://coolshell.cn/">CoolShell</a> 上发表了 《<a href="https://coolshell.cn/articles/4990.html">程序员技术练级攻略</a>》一文，得到了很多人的好评（转载的不算，在我的网站上都有近1000W的访问量了）。并且陆续收到了一些人的反馈，说跟着这篇文章找到了不错的工作。几年过去，也收到了好些邮件和私信，希望我把这篇文章更新一下，因为他们觉得有点落伍了。是的，<strong>老实说，抛开这几年技术的更新迭代不说，那篇文章写得也不算特别系统，同时标准也有点低，当时是给一个想要入门的朋友写的，所以，非常有必要从头更新一下《程序员练级攻略》这一主题</strong>。</p>
<p>目前，我在我极客时间的专栏上更新《程序员练级攻略（2018版）》。升级版的《程序员练级攻略》会比Coolshell上的内容更多，也更专业。这篇文章有【入门篇】、【修养篇】、【专业基础篇】、【软件设计篇】、【高手成长篇】五大篇章，它们会帮助你从零开始，一步步地，系统地，从陌生到熟悉，到理解掌握，从编码到设计再到架构，从码农到程序员再到工程师再到架构师的一步一步进阶，完成从普通到精通到卓越的完美转身……</p>
<p>这篇文章是我写得最累也是最痛苦的文章，原因如下：</p>
<ul>
<li> <strong>学习路径的梳理</strong>。这是一份计算编程相关知识地图，也是一份成长和学习路径。所以有太多的推敲了，知识的路径，体，地图……这让我费了很多工夫，感觉像在编写一本教材一样，即不能太高大上，也不能误人子弟。</li>
<li><strong>新旧知识的取舍。</strong>另外，因为我的成长经历中很多技术都成了过去时，所以对于新时代的程序员应该学习新的技术，然后，很多基础技术在今天依然管用，所以，在这点上，哪些要那些不要，也花了我很多的工夫。</li>
<li><strong>文章书籍的推荐</strong>。为了推荐最好的学习资料和资源，老实说，我几乎翻遍了整个互联网，进行了大量的阅读和比较。这个过程让我也受益非浅。一开始，这篇文章的大小居然在500K左右，太多的信息就是没有信息，所以在信息的筛选上我花费了很多的工夫，删掉了60%的内容。但是，依然很宠大。</li>
</ul>
<p><strong>总之，你一定会被这篇文章的内容所吓到的，是的，我就是故意这样做的，因为，这本来就没有什么捷径，也不可能速成，很多知识都是硬骨头，你只能一口一口的啃，我故意这样做就是为了让你不要有“速成”的幻想，也可以轻而一举的吓退那些不想用功不想努力的人</strong>。</p>
<p>但是，我们也要知道《易经》有云：“<strong>取法其上，得乎其中，取法其中，得乎其下，取法其下，法不得也</strong>”。所以，我这里会给你立个比较高标准，你要努力达到，相信我，就算是达不到，也会比你一开始期望的要高很多……</p>
<p>下面是这份练级攻略的目录，目前只在极客时间上发布，你需要付费阅读（在本文最后有相关的二维码）。</p>
<p><span id="more-18360"></span></p>
<p><img decoding="async" loading="lazy" class="alignnone size-full aligncenter" src="https://coolshell.cn/wp-content/uploads/2018/05/程序员练级攻略.png" alt="" width="290" height="1937" /></p>
<p>&nbsp;</p>
<p>那么，除程序员练级攻略外，我还写了哪些内容？下面是迄今为止我所有的文章的目录。你可以在下面看一下相关的目录。这也算是我开收费专栏来8个月给大家的一份答卷吧。我也没有想到，我居然写了这么多的文章，而且对很多人都很有用。</p>
<p>首先是个人成长和经验之谈的东西，在这里的文章还没有完全更新完，未来要更新什么我也不清楚，但是可以呈现出来的内容和方向如下所示，供你参考。对于个人成长中的内容，都是我多年来的心得和体会，从读者的反馈来看是非常不错的，你一定要要阅读的。</p>
<p><img decoding="async" loading="lazy" class="alignnone size-large aligncenter" src="https://coolshell.cn/wp-content/uploads/2018/05/个人成长和经验之谈-319x1024.png" alt="" width="319" height="1024" /></p>
<p>分布式系统架构，我一共出了两个系列，一个是分布式系统架构的本质，另一个是设计模式。前者偏概念，后者偏技术。这里旨在让你看到整个分布式系统设计的一个非常系统的蓝图，但是因为在手机端上，不可能写得非常细，所以，会缺失一些细节，这些细节我是故意缺失的，主要是有几方面的原因，</p>
<ul>
<li>一方面，这是为了阅读的效果，手机上的文章不过长，所以，不能有太多的细节。</li>
<li>另一方面，也是是想留给大家自行学习，而不是一定要我把饭喂到你的嘴里，你才能吃得着。<strong>学习不只是为要答案，而是学方法</strong></li>
<li>最后是我的私心，因为我也在创业，所以，技术细节上东西正是我在做的产品，所以，如果你想了解得更细，你需要和我有更商业合作。</li>
</ul>
<p><img decoding="async" loading="lazy" class="alignnone size-full aligncenter" src="https://coolshell.cn/wp-content/uploads/2018/05/分布式架构的本质.png" alt="" width="321" height="689" /></p>
<p>&nbsp;</p>
<p><img decoding="async" loading="lazy" class="alignnone  size-full aligncenter" src="https://coolshell.cn/wp-content/uploads/2018/05/分布式架构设计模式-弹力篇.png" alt="" width="331" height="1065" /></p>
<p><img decoding="async" loading="lazy" class="alignnone  size-full aligncenter" src="https://coolshell.cn/wp-content/uploads/2018/05/分布式架构设计模式-管理篇.png" alt="" width="353" height="669" /></p>
<p><img decoding="async" loading="lazy" class="alignnone  size-full aligncenter" src="https://coolshell.cn/wp-content/uploads/2018/05/分布式架构设计模式-性能篇.png" alt="" width="328" height="592" /></p>
<p>区块链的技术专栏本来不在我的写作计划中的，但是因为来问我这方面的技术人太多了，所以，就被问了一系列的文章，这里的文章除了一些技术上的科普，同样有有很多我的观点，你不但可以学到技术，还可以了解一些金融知识和相关的逻辑，我个人觉得这篇文章是让你有独立思考的文章。</p>
<p><img decoding="async" loading="lazy" class=" size-full alignnone aligncenter" src="https://coolshell.cn/wp-content/uploads/2018/05/区块链技术.png" alt="" width="304" height="771" /></p>
<p>我的专栏还在继续，接下来还有一个系列的文章——《从技术到管理》，欢迎关注，也欢迎扫码订阅。</p>
<p><strong>最后友情提示一下：在手机上学习并不是最好的学习方式，也不要在我的专栏上进行学习，把我的专栏当成一个你的助手，当成一个向导，当成一个跳板，真正的学习还是要在线下，专心的，系统地、有讨论地、不断实践地学习，这点希望大家切记！</strong></p>
<p>&nbsp;</p>
<p><img decoding="async" loading="lazy" class="aligncenter size-full " src="https://coolshell.cn/wp-content/uploads/2018/05/专栏.jpg" alt="" width="665" height="580" /></p>
<p>（全文完）<!--



<p align="center"><a href= target=_blank><img decoding="async" src=""></a></p>





<p align="center"><img decoding="async" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.weixin.jpg"> <img decoding="async" loading="lazy" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.mini_.jpg" width="300" height="300"> <br />关注CoolShell微信公众账号和微信小程序</p>

 

--></p>
<div style="margin-top: 15px; font-size: 16px;color: #cc0000;">
<p align="center"><strong>（转载本站文章请注明作者和出处 <a href="https://coolshell.cn/">酷 壳 &#8211; CoolShell</a> ，请勿用于任何商业用途）</strong></p>
</div>

<div class="wp_rp_wrap  wp_rp_vertical_m" ><div class="wp_rp_content"><h3 class="related_post_title">相关文章</h3><ul class="related_post wp_rp"><li ><a href="https://coolshell.cn/articles/4990.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2011/07/programmer-150x150.png" alt="程序员技术练级攻略" width="150" height="150" /></a><a href="https://coolshell.cn/articles/4990.html" class="wp_rp_title">程序员技术练级攻略</a></li><li ><a href="https://coolshell.cn/articles/8088.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/plugins/wordpress-23-related-posts-plugin/static/thumbs/7.jpg" alt="对技术的态度" width="150" height="150" /></a><a href="https://coolshell.cn/articles/8088.html" class="wp_rp_title">对技术的态度</a></li><li ><a href="https://coolshell.cn/articles/4102.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/plugins/wordpress-23-related-posts-plugin/static/thumbs/25.jpg" alt="如何学好C语言" width="150" height="150" /></a><a href="https://coolshell.cn/articles/4102.html" class="wp_rp_title">如何学好C语言</a></li><li ><a href="https://coolshell.cn/articles/4758.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/plugins/wordpress-23-related-posts-plugin/static/thumbs/30.jpg" alt="如何写出无法维护的代码" width="150" height="150" /></a><a href="https://coolshell.cn/articles/4758.html" class="wp_rp_title">如何写出无法维护的代码</a></li><li ><a href="https://coolshell.cn/articles/1992.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2009/12/language-fanboys-150x150.jpg" alt="程序员眼中的编程语言" width="150" height="150" /></a><a href="https://coolshell.cn/articles/1992.html" class="wp_rp_title">程序员眼中的编程语言</a></li><li ><a href="https://coolshell.cn/articles/19219.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2019/03/linux.ninja_-150x150.png" alt="打造高效的工作环境 &#8211; Shell 篇" width="150" height="150" /></a><a href="https://coolshell.cn/articles/19219.html" class="wp_rp_title">打造高效的工作环境 &#8211; Shell 篇</a></li></ul></div></div>The post <a href="https://coolshell.cn/articles/18360.html">程序员练级攻略（2018)  与我的专栏</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></content:encoded>
					
					<wfw:commentRss>https://coolshell.cn/articles/18360.html/feed</wfw:commentRss>
			<slash:comments>62</slash:comments>
		
		
			</item>
		<item>
		<title>Linux PID 1 和 Systemd</title>
		<link>https://coolshell.cn/articles/17998.html</link>
					<comments>https://coolshell.cn/articles/17998.html#comments</comments>
		
		<dc:creator><![CDATA[陈皓]]></dc:creator>
		<pubDate>Sun, 16 Jul 2017 13:40:55 +0000</pubDate>
				<category><![CDATA[Unix/Linux]]></category>
		<category><![CDATA[操作系统]]></category>
		<category><![CDATA[程序设计]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[Systemd]]></category>
		<category><![CDATA[Unix]]></category>
		<category><![CDATA[Upstart]]></category>
		<guid isPermaLink="false">http://coolshell.cn/?p=17998</guid>

					<description><![CDATA[<p>要说清 Systemd，得先从Linux操作系统的启动说起。Linux 操作系统的启动首先从 BIOS 开始，然后由 Boot Loader 载入内核，并初始化...</p>
<p class="read-more"><a class="btn btn-default" href="https://coolshell.cn/articles/17998.html"> Read More<span class="screen-reader-text">  Read More</span></a></p>
The post <a href="https://coolshell.cn/articles/17998.html">Linux PID 1 和 Systemd</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></description>
										<content:encoded><![CDATA[<p><script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-3415450859608158"
     crossorigin="anonymous"></script><img decoding="async" loading="lazy" class="alignright size-full" src="https://coolshell.cn/wp-content/uploads/2017/07/systemd.jpeg" alt="" width="275" height="183" />要说清 Systemd，得先从Linux操作系统的启动说起。Linux 操作系统的启动首先从 BIOS 开始，然后由 Boot Loader 载入内核，并初始化内核。内核初始化的最后一步就是启动 init 进程。这个进程是系统的第一个进程，PID 为 1，又叫超级进程，也叫根进程。它负责产生其他所有用户进程。所有的进程都会被挂在这个进程下，如果这个进程退出了，那么所有的进程都被 kill 。如果一个子进程的父进程退了，那么这个子进程会被挂到 PID 1 下面。（注：PID 0 是内核的一部分，主要用于内进换页，参看：<a href="http://en.wikipedia.org/wiki/Process_identifier" target="_blank" rel="noopener noreferrer">Process identifier</a>）</p>
<h4>SysV Init</h4>
<p>PID 1 这个进程非常特殊，其主要就任务是把整个操作系统带入可操作的状态。比如：启动 UI &#8211; Shell 以便进行人机交互，或者进入 X 图形窗口。传统上，PID 1 和传统的 Unix System V 相兼容的，所以也叫 <code>sysvinit</code>，这是使用得最悠久的 init 实现。Unix System V 于1983年 release。</p>
<p>在 <code>sysvint</code> 下，有好几个运行模式，又叫 <code>runlevel</code>。比如：常见的 3 级别指定启动到多用户的字符命令行界面，5 级别指定启起到图形界面，0 表示关机，6 表示重启。其配置在 <code>/etc/inittab</code> 文件中。</p>
<p><span id="more-17998"></span></p>
<p>与此配套的还有 <code>/etc/init.d/</code> 和 <code>/etc/rc[X].d</code>，前者存放各种进程的启停脚本（需要按照规范支持 <code>start</code>，<code>stop</code>子命令），后者的 X 表示不同的 runlevel 下相应的后台进程服务，如：<code>/etc/rc3.d</code> 是 runlevel=3 的。 里面的文件主要是 link 到  <code>/etc/init.d/</code> 里的启停脚本。其中也有一定的命名规范：S 或 K 打头的，后面跟一个数字，然后再跟一个自定义的名字，如：<code>S01rsyslog</code>，<code>S02ssh</code>。S 表示启动，K表示停止，数字表示执行的顺序。</p>
<h4>UpStart</h4>
<p>Unix 和 Linux 在 <code>sysvint</code> 运作多年后，大约到了2006年的时候，Linux内核进入2.6时代，Linux有了很多更新。并且，Linux开始进入桌面系统，而桌面系统和服务器系统不一样的是，桌面系统面临频繁重启，而且，用户会非常频繁的使用硬件的热插拔技术。于是，这些新的场景，让 <code>sysvint</code> 受到了很多挑战。</p>
<p>比如，打印机需要CUPS等服务进程，但是如果用户没有打机印，启动这个服务完全是一种浪费，而如果不启动，如果要用打印机了，就无法使用，因为<code>sysvint</code> 没有自动检测的机制，它只能一次性启动所有的服务。另外，还有网络盘挂载的问题。在 <code>/etc/fstab</code> 中，负责硬盘挂载，有时候还有网络硬盘（NFS 或 iSCSI）在其中，但是在桌面机上，有很可能开机的时候是没有网络的， 于是网络硬盘都不可以访问，也无法挂载，这会极大的影响启动速度。<code>sysvinit</code> 采用 <code>netdev</code> 的方式来解决这个问题，也就是说，需要用户自己在 <code>/etc/fstab</code> 中给相应的硬盘配置上 <code>netdev</code> 属性，于是 <code>sysvint</code> 启动时不会挂载它，只有在网络可用后，由专门的 <code>netfs</code> 服务进程来挂载。这种管理方式比较难以管理，也很容易让人掉坑。</p>
<p>所以，Ubuntu 开发人员在评估了当时几个可选的 init 系统后，决定重新设计这个系统，于是，这就是我们后面看到的 <code>upstart</code> 。 <code>upstart</code> 基于事件驱动的机制，把之前的完全串行的同步启动服务的方式改成了由事件驱动的异步的方式。比如：如果有U盘插入，<code>udev</code> 得到通知，<code>upstart</code> 感知到这个事件后触发相应的服务程序，比如挂载文件系统等等。因为使用一个事件驱动的玩法，所以，启动操作系统时，很多不必要的服务可以不用启动，而是等待通知，lazy 启动。而且事件驱动的好处是，可以并行启动服务，他们之间的依赖关系，由相应的事件通知完成。</p>
<p>upstart 有着很不错的设计，其中最重要的两个概念是 Job 和 Event。</p>
<p><strong>Job</strong> 有一般的Job，也有service的Job，并且，<code>upstart</code> 管理了整个 Job 的生命周期，比如：Waiting, Starting, pre-Start, Spawned, post-Start, Running, pre-Stop, Stopping, Killed, post-Stop等等，并维护着这个生命周期的状态机。</p>
<p><strong>Event</strong> 分成三类，<code>signal</code>, <code>method</code> 和 <code>hooks</code>。<code>signal</code> 就是异步消息，<code>method</code> 是同步阻塞的。<code>hooks</code> 也是同步的，但介于前面两者之间，发出hook事件的进程必须等到事件完成，但不检查是否成功。</p>
<p>但是，<code>upstart</code> 的事件非常复杂，也非常纷乱，各种各样的事件（事件没有归好类）导致有点凌乱。不过因为整个事件驱动的设计比之前的 <code>sysvinit</code> 来说好太多，所以，也深得欢迎。</p>
<h4>Systemd</h4>
<p>直到2010的有一天，一个在 RedHat工作的工程师 <a title="Lennart Poettering" href="https://en.wikipedia.org/wiki/Lennart_Poettering" target="_blank" rel="noopener noreferrer">Lennart Poettering</a> 和 <a title="Kay Sievers" href="https://en.wikipedia.org/wiki/Kay_Sievers">Kay Sievers</a> ，开始引入了一个新的 <code>init</code> 系统—— <code>systemd</code>。这是一个非常非常有野心的项目，这个项目几乎改变了所有的东西，<code>systemd</code> 不但想取代已有的 init 系统，而且还想干更多的东西。</p>
<p>Lennart 同意 <code>upstart</code> 干的不错，代码质量很好，基于事件的设计也很好。但是他觉得 <code>upstart</code> 也有问题，其中最大的问题还是不够快，虽然 <code>upstart</code> 用事件可以达到一定的启动并行度，但是，本质上来说，这些事件还是会让启动过程串行在一起。  如：<code>NetworkManager</code> 在等 <code>D-Bus</code> 的启动事件，而 <code>D-Bus</code> 在等 <code>syslog</code> 的启动事件。</p>
<p>Lennart 认为，实现上来说，<code>upstart</code> 其实是在管理一个逻辑上的服务依赖树，但是这个服务依赖树在表现形式上比较简单，你只需要配置——“启动 B好了就启动A”或是“停止了A后就停止B”这样的规则。但是，Lennart 说，这种简单其实是有害的（this simplification is actually detrimental）。他认为，</p>
<ul>
<li>从一个系统管理的角度出来，他一开始会设定好整个系统启动的服务依赖树，但是这个系统管理员要人肉的把这个本来就非常干净的服务依整树给翻译成计算机看的懂的 Event/Action 形式，而且 Event/Action 这种配置方式是运行时的，所以，你需要运行起来才知道是什么样的。</li>
</ul>
<ul>
<li>Event逻辑从头到脚到处都是，这个事件扩大了运维的复杂度，还不如之前的 <code>sysvint</code>。 也就是说，当用户配置了 “启动 <code>D-Bus</code> 后请启动 <code>NetworkManager</code>”， 这个 <code>upstart</code> 可以干，但是反过来，如果，用户启动 <code>NetworkManager</code>，我们应该先去启动他的前置依赖 <code>D-Bus</code>，然而你还要配置相应的反向 Event。本来，我只需要配置一条依赖的，结果现在我要配置很多很多情况下的Event。</li>
</ul>
<ul>
<li>最后，<code>upstart</code> 里的 Event 的并不标准，很混乱，没有良好的定义。比如：既有，进程启动，运行，停止的事件，也有USB设备插入、可用、拔出的事件，还有文件系统设备being mounted、 mounted 和 umounted 的事件，还有AC电源线连接和断开的事件。你会发现，这进程启停的、USB的、文件系统的、电源线的事件，看上去长得很像， 但是没有被标准化抽像出来掉，因为绝大多数的事件都是三元组：start, condition, stop 。这种概念设计模型并没有在 <code>upstart</code> 中出现。因为 <code>upstart</code> 被设计为单一的事件，而忽略了逻辑依赖。</li>
</ul>
<p>当然，如果 <code>systemd</code> 只是解决 <code>upstart</code> 的问题，他就改造 <code>upstart</code> 就好了，但是 Lennart 的野心不只是想干个这样的事，他想干的更多。</p>
<p>首先，<code>systemd</code> 清醒的认识到了 init 进程的首要目标是要让用户快速的进入可以操作OS的环境，所以，这个速度一定要快，越快越好，所以，<code>systemd</code> 的设计理念就是两条：</p>
<ul>
<li>To start <b>less</b>.</li>
<li>And to start <b>more</b> in <i>parallel</i>.</li>
</ul>
<p>也就是说，按需启动，能不启动就不启动，如果要启动，能并行启动就并行启动，包括你们之间有依赖，我也并行启动。按需启动还好理解，那么，有依赖关系的并行启动，它是怎么做到的？这里，<code>systemd</code> 借鉴了 MacOS 的 <code>Launchd</code> 的玩法（在Youtube上有一个分享——<a href="https://www.youtube.com/watch?v=SjrtySM9Dns" target="_blank" rel="noopener noreferrer">Launchd: One Program to Rule them All</a>，在苹果的开源网站上也有相关的设计文档——<a href="https://developer.apple.com/library/content/documentation/MacOSX/Conceptual/BPSystemStartup/Chapters/Introduction.html" target="_blank" rel="noopener noreferrer">About Daemons and Services</a>）</p>
<p>要解决这些依赖性，systemd 需要解决好三种底层依赖—— Socket， D-Bus ，文件系统。</p>
<ul>
<li><strong>Socket依赖</strong>。如果服务C依赖于服务S的socket，那么就要先启动S，然后再启动C，因为如果C启动时找不到S的Socket，那么C就会失败。<code>systemd</code> 可以帮你在S还没有启动好的时候，建立一个socket，用来接收所有的C的请求和数据，并缓存之，一旦S全部启动完成，把systemd替换好的这个缓存的数据和Socket描述符替换过去。</li>
</ul>
<p>&nbsp;</p>
<ul>
<li><strong>D-Bus依赖</strong>。<code>D-Bus</code> 全称 Desktop Bus，是一个用来在进程间通信的服务。除了用于用户态进程和内核态进程通信，也用于用户态的进程之前。现在，很多的现在的服务进程都用 <code>D-Bus</code> 而不是Socket来通信。比如：<code>NetworkManager</code> 就是通过 <code>D-Bus</code> 和其它服务进程通讯的，也就是说，如果一个进程需要知道网络的状态，那么就必需要通过 <code>D-Bus</code> 通信。<code>D-Bus</code> 支持 “Bus Activation”的特性。也就是说，A要通过 <code>D-Bus</code> 服务和B通讯，但是B没有启动，那么 <code>D-Bus</code> 可以把B起来，在B启动的过程中，<code>D-Bus</code> 帮你缓存数据。<code>systemd</code> 可以帮你利用好这个特性来并行启动 A 和 B。</li>
</ul>
<p>&nbsp;</p>
<ul>
<li><strong>文件系统依赖</strong>。系统启动过程中，文件系统相关的活动是最耗时的，比如挂载文件系统，对文件系统进行磁盘检查（fsck），磁盘配额检查等都是非常耗时的操作。在等待这些工作完成的同时，系统处于空闲状态。那些想使用文件系统的服务似乎必须等待文件系统初始化完成才可以启动。<code>systemd</code> 参考了 <code>autofs</code> 的设计思路，使得依赖文件系统的服务和文件系统本身初始化两者可以并发工作。<code>autofs</code> 可以监测到某个文件系统挂载点真正被访问到的时候才触发挂载操作，这是通过内核 <code>automounter</code> 模块的支持而实现的。比如一个 <code>open()</code> 系统调用作用在某个文件系统上的时候，而这个文件系统尚未执行挂载，此时 <code>open()</code> 调用被内核挂起等待，等到挂载完成后，控制权返回给 <code>open()</code> 系统调用，并正常打开文件。这个过程和 <code>autofs</code> 是相似的。</li>
</ul>
<p>&nbsp;</p>
<p>下图来自 Lennart 的演讲里的一页PPT，展示了不同 init 系统的启动。</p>
<p><img decoding="async" loading="lazy" class="aligncenter size-full" src="https://coolshell.cn/wp-content/uploads/2017/07/boot.png" alt="" width="467" height="308" /></p>
<p>除此之外，systemd 还在启动时管理好了一些下面的事。</p>
<p>用C语言取代传统的脚本式的启动。前面说过，<code>sysvint</code> 用 <code>/etc/rcX.d</code> 下的各种脚本启动。然而这些脚本中需要使用 <code>awk</code>, <code>sed</code>, <code>grep</code>, <code>find</code>, <code>xargs</code> 等等这些操作系统的命令，这些命令需要生成进程，生成进程的开销很大，关键是生成完这些进程后，这个进程就干了点屁大的事就退了。换句话说就是，我操作系统干了那么多事为你拉个进程起来，结果你就把个字串转成小写就退了，把我操作系统当什么了？</p>
<p>在正常的一个 <code>sysvinit</code> 的脚本里，可能会有成百上千个这样的命令。所以，慢死。因此，<code>systemd</code> 全面用 C 语言全部取代了。一般来说，<code>sysvinit</code> 下，操作系统启动完成后，用 <code>echo $$</code> 可以看到，pid 被分配到了上千的样子，而 <code>systemd</code> 的系统只是上百。</p>
<p>另外，systemd 是真正一个可以管住服务进程的——可以跟踪上服务进程所fork/exec出来的所有进程。</p>
<ul>
<li>我们知道， 传统 Unix/Linux 的 Daemon 服务进程的最佳实践基本上是这个样子的 （具体过程可参看这篇文章“<a href="http://0pointer.de/public/systemd-man/daemon.html#SysV%20Daemons" target="_blank" rel="noopener noreferrer">SysV Daemon</a>”）——
<ol>
<li>进程启动时，关闭所有的打开的文件描述符（除了标准描述符0,1,2），然后重置所有的信号处理。</li>
<li>调用 <code>fork()</code> 创建子进程，在子进程中 <code>setsid()</code>，然后父进程退出（为了后台执行）</li>
<li>在子进程中，再调用一次 <code>fork()</code>，创建孙子进程，确定没有交互终端。然后子进程退出。</li>
<li>在孙子进程中，把标准输入标准输出标准错误都连到 <code>/dev/null</code> 上，还要创建 pid 文件，日志文件，处理相关信号 ……</li>
<li>最后才是真正开始提供服务。</li>
</ol>
</li>
</ul>
<p>&nbsp;</p>
<ul>
<li>在上面的这个过程中，服务进程除了两次 <code>fork</code> 外还会 <code>fork</code> 出很多很多的子进程（比如说一些Web服务进程，会根据用户的请求链接来 <code>fork</code> 子进程），这个进程树是相当难以管理的，因为，一旦父进程退出来了，子进程就会被挂到 PID 1下，所以，基本上来说，你无法通过服务进程自已给定的一个pid文件来找到所有的相关进程（这个对开发者的要求太高了），所以，在传统的方式下用脚本启停服务是相当相当的 Buggy 的，因为无法做对所有的服务生出来的子子孙孙做到监控。</li>
</ul>
<p>&nbsp;</p>
<ul>
<li>为了解决这个问题，<code>upstart</code> 通过变态的 <code>strace</code> 来跟踪进程中的 <code>fork()</code> 和 <code>exec()</code> 或 <code>exit()</code> 等相关的系统调用。这种方法相当笨拙。 <code>systemd</code> 使用了一个非常有意思的玩法来 tracking 服务进程生出来的所有进程，那就是用 <code>cgroup</code> （我在 <a href="https://coolshell.cn/articles/17049.html" target="_blank" rel="noopener noreferrer">Docker 的基础技术“cgroup篇”</a>中讲过这个东西）。cgroup主要是用来管理进程组资源配额的事，所以，无论服务如何启动新的子进程，所有的这些相关进程都会同属于一个 <code>cgroup</code>，所以，<code>systemd</code> 只需要简单的去遍历一下相应的 <code>cgroup</code> 的那个虚文件系统目录下的文件，就可以正确的找到所有的相关进程，并将他们一一停止。</li>
</ul>
<p>&nbsp;</p>
<p>另外，<code>systemd</code> 简化了整个 daemon 开发的过程：</p>
<ul>
<li>不需要两次 <code>fork()</code>，只需要实现服务本身的主逻辑就可以了。</li>
<li>不需要 <code>setsid()</code>，<code>systemd</code> 会帮你干</li>
<li>不需要维护 <code>pid文件</code>，<code>systemd</code> 会帮处理。</li>
<li>不需要管理日志文件或是使用<code>syslog</code>，或是处理<code>HUP</code>的日志reload信号。把日志打到 <code>stderr</code> 上，<code>systemd</code> 帮你管理。</li>
<li>处理 <code>SIGTERM</code> 信号，这个信号就是正确退出当前服务，不要做其他的事。</li>
<li>……</li>
</ul>
<p>除此之外，<code>systemd</code> 还能——</p>
<ul>
<li>自动检测启动的服务间有没有环形依赖。</li>
<li>内建 autofs 自动挂载管理功能。</li>
<li>日志服务。<code>systemd</code> 改造了传统的 syslog 的问题，采用二进制格式保存日志，日志索引更快。</li>
<li>快照和恢复。对当前的系统运行的服务集合做快照，并可以恢复。</li>
<li>……</li>
</ul>
<p>还有好多好多，他接管很多很多东西，于是就让很多人不爽了，因为他在干了很多本不属于 PID 1 的事。</p>
<h4>Systemd 争论和八卦</h4>
<p>于是 <code>systemd</code> 这个东西成了可能是有史以来口水战最多的一个开源软件了。<code>systemd</code> 饱受各种争议，最大的争议就是他破坏了 Unix 的设计哲学（相关的哲学可以读一下《<a href="https://book.douban.com/subject/1467587/" target="_blank" rel="noopener noreferrer">Unix编程艺术</a>》），干了一个大而全而且相当复杂的东西。当然，Lennart 并不同意这样的说法，他后来又写一篇blog “<a href="http://0pointer.de/blog/projects/the-biggest-myths.html" target="_blank" rel="noopener noreferrer">The Biggest Myths</a>”来解释 <code>systemd</code> 并不是这样的，大家可以前往一读。</p>
<p>这个争议大到什么样子呢？2014 年，Debian Linux 因为想准备使用 <code>systemd</code> 来作为标准的 init 守护进程来替换 <code>sysvinit</code> 。而围绕这个事的争论达到了空前的热度，争论中充满着仇恨，<code>systemd</code> 的支持者和反对者都在互相辱骂，导致当时 Debian 阵营开始分裂。还有人给 Lennart 发了死亡威胁的邮件，用比特币雇凶买杀手，扬言要取他的性命，在Youbute上传了侮辱他的歌曲，在IRC和各种社交渠道上给他发下流和侮辱性的消息。这已经不是争议了，而是一种不折不扣的仇恨！</p>
<p><img decoding="async" loading="lazy" class="aligncenter size-full" src="https://coolshell.cn/wp-content/uploads/2017/07/systemd_shewantsit.jpg" alt="" width="1000" height="421" /></p>
<p>于是，Lennart 在 <a href="https://plus.google.com/+LennartPoetteringTheOneAndOnly/posts/J2TZrTvu7vd" target="_blank" rel="noopener noreferrer">Google Plus 上发了贴子</a>，批评整个 Linux 开源社区和 Linus 本人。他大意说，</p>
<blockquote><p>这个社区太病态了，全是 ass holes，你们不停用各种手段在各种地方用不同的语言和方式来侮辱和漫骂我。我还是一个年轻人，我从来没有经历过这样的场面，但是今天我已经对这种场面很熟悉了。我有时候说话可能不准确，但是我不会像他样那样说出那样的话，我也没有被这些事影响，因为我脸皮够厚，所以，为什么我可以在如何大的反对声面前让 <code>systemd</code> 成功，但是，你们 Linux 社区太可怕了。你们里面的有精神病的人太多了。另外，对于Linus Torvalds，你是这个社区的 Role Model，但可惜你是一个 Bad Role Model，你在社区里的刻薄和侮辱性的言行，基本从一定程度上鼓励了其它人跟你一样，当然，并不只是你一个人的问题，而是在你周围聚集了一群和你一样的这样干的人。送你一句话—— A fish rots from the head down ！一条鱼是从头往下腐烂的……</p></blockquote>
<p>这篇契文很长，喜欢八卦的同学可以前往一读。感受一下 Lennart 当时的心态（我觉得能算上是非常平稳了）。</p>
<p>Linus也在被一媒体问起 <code>systemd</code> 这个事来（参看“<a href="https://www.itwire.com/business-it-news/open-source/65402-torvalds-says-he-has-no-strong-opinions-on-systemd" target="_blank" rel="noopener noreferrer">Torvalds says he has no strong opinions on systemd</a>”），Linus在采访里说，</p>
<blockquote><p>我对 <code>systemd</code> 和 Lennart 的贴子没有什么强烈的想法。虽然，传统的 Unix 设计哲学—— “Do one thing and Do it well”，很不错，而且我们大多数人也实践了这么多年，但是这并不代表所有的真实世界。在历史上，也不只有<code>systemd</code> 这么干过。但是，我个人还是 old-fashioned 的人，至少我喜欢文本式的日志，而不是二进制的日志。但是 <code>systemd</code> 没有必要一定要有这样的品味。哦，我说细节了……</p></blockquote>
<p>今天，<code>systemd</code> 占据了几乎所有的主流的 Linux 发行版的默认配置，包括：Arch Linux、CentOS、CoreOS、Debian、Fedora、Megeia、OpenSUSE、RHEL、SUSE企业版和 Ubuntu。而且，对于 CentOS, CoreOS, Fedora, RHEL, SUSE这些发行版来说，不能没有 <code>systemd</code>。（Ubuntu 还有一个不错的wiki &#8211; <a href="https://wiki.ubuntu.com/SystemdForUpstartUsers" target="_blank" rel="noopener noreferrer">Systemd for Upstart Users</a> 阐述了如何在两者间切换）</p>
<p>&nbsp;</p>
<h4>其它</h4>
<p>还记得在《<a href="https://coolshell.cn/articles/17416.html" target="_blank" rel="noopener noreferrer">缓存更新的套路</a>》一文中，我说过，<strong>如果你要做好架构，首先你得把计算机体系结构以及很多老古董的基础技术吃透了</strong>。因为里面会有很多可以借鉴和相通的东西。那么，你是否从这篇文章里看到了一些有分布式架构相似的东西？</p>
<p>比如：从 <code>sysvinit</code> 到 <code>upstart</code> 再到 <code>systemd</code>，像不像是服务治理？Linux系统下的这些服务进程，是不是很像分布式架构中的微服务？还有那个D-Bus，是不是很像SOA里的ESB？而 init 系统是不是很像一个控制系统？甚至像一个服务编排（Service Orchestration）系统？</p>
<p>分布式系统中的服务之间也有很多依赖，所以，在启动一个架构的时候，如果我们可以做到像 systemd 那样并行启动的话，那么是不是就像是一个微服务的玩法了？</p>
<p>嗯，你会发现，技术上的很多东西是相通的，也是互相有对方的影子，所以，其实技术并不多。关键是我们学在了表面还是看到了本质。</p>
<p>&nbsp;</p>
<h4>延伸阅读</h4>
<ul>
<li>Lennert 的博文：<a href="http://0pointer.de/blog/projects/systemd.html" target="_blank" rel="noopener noreferrer">Rethinking PID 1</a></li>
<li>Lennert 的演讲：<a href="https://www.youtube.com/watch?v=TyMLi8QF6sw" target="_blank" rel="noopener noreferrer">systemd, beyond init</a> （ <a href="http://www.linux-kongress.org/2010/slides/systemd-poettering.pdf" target="_blank" rel="noopener noreferrer">PPT</a> ）</li>
<li><a href="https://en.wikipedia.org/wiki/Systemd" target="_blank" rel="noopener noreferrer">Wikipedia：Systemd</a></li>
<li>LinuxVoice：<a href="https://www.linuxvoice.com/interview-lennart-poettering/" target="_blank" rel="noopener noreferrer">Lennart Poettering 专访</a></li>
</ul>
<p>（全文完）<!--



<p align="center"><a href= target=_blank><img decoding="async" src=""></a></p>





<p align="center"><img decoding="async" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.weixin.jpg"> <img decoding="async" loading="lazy" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.mini_.jpg" width="300" height="300"> <br />关注CoolShell微信公众账号和微信小程序</p>

 

--></p>
<div style="margin-top: 15px; font-size: 16px;color: #cc0000;">
<p align="center"><strong>（转载本站文章请注明作者和出处 <a href="https://coolshell.cn/">酷 壳 &#8211; CoolShell</a> ，请勿用于任何商业用途）</strong></p>
</div>

<div class="wp_rp_wrap  wp_rp_vertical_m" ><div class="wp_rp_content"><h3 class="related_post_title">相关文章</h3><ul class="related_post wp_rp"><li ><a href="http://coolshell.cn/articles/12103.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2014/11/tux-fork-150x150.gif" alt="vfork 挂掉的一个问题" width="150" height="150" /></a><a href="http://coolshell.cn/articles/12103.html" class="wp_rp_title">vfork 挂掉的一个问题</a></li><li ><a href="http://coolshell.cn/articles/17061.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2015/08/docker-filesystems-busyboxrw-150x150.png" alt="Docker基础技术：AUFS" width="150" height="150" /></a><a href="http://coolshell.cn/articles/17061.html" class="wp_rp_title">Docker基础技术：AUFS</a></li><li ><a href="http://coolshell.cn/articles/17010.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2015/04/isolation-150x150.jpg" alt="Docker基础技术：Linux Namespace（上）" width="150" height="150" /></a><a href="http://coolshell.cn/articles/17010.html" class="wp_rp_title">Docker基础技术：Linux Namespace（上）</a></li><li ><a href="http://coolshell.cn/articles/2322.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2010/04/o_unixrichiethompson-150x150.jpg" alt="Unix传奇(上篇)" width="150" height="150" /></a><a href="http://coolshell.cn/articles/2322.html" class="wp_rp_title">Unix传奇(上篇)</a></li><li ><a href="http://coolshell.cn/articles/17029.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2015/04/jail_cell-150x150.jpg" alt="Docker基础技术：Linux Namespace（下）" width="150" height="150" /></a><a href="http://coolshell.cn/articles/17029.html" class="wp_rp_title">Docker基础技术：Linux Namespace（下）</a></li><li ><a href="http://coolshell.cn/articles/17049.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2015/04/filter-150x150.png" alt="Docker基础技术：Linux CGroup" width="150" height="150" /></a><a href="http://coolshell.cn/articles/17049.html" class="wp_rp_title">Docker基础技术：Linux CGroup</a></li></ul></div></div>The post <a href="https://coolshell.cn/articles/17998.html">Linux PID 1 和 Systemd</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></content:encoded>
					
					<wfw:commentRss>https://coolshell.cn/articles/17998.html/feed</wfw:commentRss>
			<slash:comments>49</slash:comments>
		
		
			</item>
		<item>
		<title>缓存更新的套路</title>
		<link>https://coolshell.cn/articles/17416.html</link>
					<comments>https://coolshell.cn/articles/17416.html#comments</comments>
		
		<dc:creator><![CDATA[陈皓]]></dc:creator>
		<pubDate>Wed, 27 Jul 2016 08:25:28 +0000</pubDate>
				<category><![CDATA[Unix/Linux]]></category>
		<category><![CDATA[程序设计]]></category>
		<category><![CDATA[cache]]></category>
		<category><![CDATA[Design]]></category>
		<category><![CDATA[design pattern]]></category>
		<category><![CDATA[Linux]]></category>
		<guid isPermaLink="false">http://coolshell.cn/?p=17416</guid>

					<description><![CDATA[<p>看到好些人在写更新缓存数据代码时，先删除缓存，然后再更新数据库，而后续的操作会把数据再装载的缓存中。然而，这个是逻辑是错误的。试想，两个并发操作，一个是更新操作...</p>
<p class="read-more"><a class="btn btn-default" href="https://coolshell.cn/articles/17416.html"> Read More<span class="screen-reader-text">  Read More</span></a></p>
The post <a href="https://coolshell.cn/articles/17416.html">缓存更新的套路</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></description>
										<content:encoded><![CDATA[<p><script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-3415450859608158"
     crossorigin="anonymous"></script><img decoding="async" loading="lazy" class="alignright size-medium wp-image-17422" src="https://coolshell.cn/wp-content/uploads/2016/07/cache-300x158.png" alt="cache" width="300" height="158" srcset="https://coolshell.cn/wp-content/uploads/2016/07/cache-300x158.png 300w, https://coolshell.cn/wp-content/uploads/2016/07/cache-514x270.png 514w, https://coolshell.cn/wp-content/uploads/2016/07/cache.png 600w" sizes="(max-width: 300px) 100vw, 300px" />看到好些人在写更新缓存数据代码时，<strong>先删除缓存，然后再更新数据库</strong>，而后续的操作会把数据再装载的缓存中。<strong>然而，这个是逻辑是错误的</strong>。试想，两个并发操作，一个是更新操作，另一个是查询操作，更新操作删除缓存后，查询操作没有命中缓存，先把老数据读出来后放到缓存中，然后更新操作更新了数据库。于是，在缓存中的数据还是老的数据，导致缓存中的数据是脏的，而且还一直这样脏下去了。</p>
<p>我不知道为什么这么多人用的都是这个逻辑，当我在微博上发了这个贴以后，我发现好些人给了好多非常复杂和诡异的方案，所以，我想写这篇文章说一下几个缓存更新的Design Pattern（让我们多一些套路吧）。</p>
<p>这里，我们先不讨论更新缓存和更新数据这两个事是一个事务的事，或是会有失败的可能，我们先假设更新数据库和更新缓存都可以成功的情况（我们先把成功的代码逻辑先写对）。</p>
<p>更新缓存的的Design Pattern有四种：Cache aside, Read through, Write through, Write behind caching，我们下面一一来看一下这四种Pattern。</p>
<p><span id="more-17416"></span></p>
<h4>Cache Aside Pattern</h4>
<p>这是最常用最常用的pattern了。其具体逻辑如下：</p>
<ul>
<li><strong>失效</strong>：应用程序先从cache取数据，没有得到，则从数据库中取数据，成功后，放到缓存中。</li>
</ul>
<ul>
<li><strong>命中</strong>：应用程序从cache中取数据，取到后返回。</li>
</ul>
<ul>
<li><strong>更新</strong>：先把数据存到数据库中，成功后，再让缓存失效。</li>
</ul>
<p><img decoding="async" loading="lazy" class="aligncenter wp-image-17438 size-full" src="https://coolshell.cn/wp-content/uploads/2016/07/Cache-Aside-Design-Pattern-Flow-Diagram-e1470471723210.png" alt="Cache-Aside-Design-Pattern-Flow-Diagram" width="600" height="188" /></p>
<p><img decoding="async" loading="lazy" class="aligncenter wp-image-17437 size-full" src="https://coolshell.cn/wp-content/uploads/2016/07/Updating-Data-using-the-Cache-Aside-Pattern-Flow-Diagram-1-e1470471761402.png" alt="Updating-Data-using-the-Cache-Aside-Pattern-Flow-Diagram-1" width="600" height="186" /></p>
<p>注意，我们的更新是先更新数据库，成功后，让缓存失效。那么，这种方式是否可以没有文章前面提到过的那个问题呢？我们可以脑补一下。</p>
<p>一个是查询操作，一个是更新操作的并发，首先，没有了删除cache数据的操作了，而是先更新了数据库中的数据，此时，缓存依然有效，所以，并发的查询操作拿的是没有更新的数据，但是，更新操作马上让缓存的失效了，后续的查询操作再把数据从数据库中拉出来。而不会像文章开头的那个逻辑产生的问题，后续的查询操作一直都在取老的数据。</p>
<p>这是标准的design pattern，包括Facebook的论文《<a href="https://www.usenix.org/system/files/conference/nsdi13/nsdi13-final170_update.pdf" target="_blank">Scaling Memcache at Facebook</a>》也使用了这个策略。为什么不是写完数据库后更新缓存？你可以看一下Quora上的这个问答《<a href="https://www.quora.com/Why-does-Facebook-use-delete-to-remove-the-key-value-pair-in-Memcached-instead-of-updating-the-Memcached-during-write-request-to-the-backend">Why does Facebook use delete to remove the key-value pair in Memcached instead of updating the Memcached during write request to the backend?</a>》，主要是怕两个并发的写操作导致脏数据。</p>
<p>那么，是不是Cache Aside这个就不会有并发问题了？不是的，比如，一个是读操作，但是没有命中缓存，然后就到数据库中取数据，此时来了一个写操作，写完数据库后，让缓存失效，然后，之前的那个读操作再把老的数据放进去，所以，会造成脏数据。</p>
<p>但，这个case理论上会出现，不过，实际上出现的概率可能非常低，因为这个条件需要发生在读缓存时缓存失效，而且并发着有一个写操作。而实际上数据库的写操作会比读操作慢得多，而且还要锁表，而读操作必需在写操作前进入数据库操作，而又要晚于写操作更新缓存，所有的这些条件都具备的概率基本并不大。</p>
<p><strong>所以，这也就是Quora上的那个答案里说的，要么通过2PC或是Paxos协议保证一致性，要么就是拼命的降低并发时脏数据的概率，而Facebook使用了这个降低概率的玩法，因为2PC太慢，而Paxos太复杂。当然，最好还是为缓存设置上过期时间。</strong></p>
<h4>Read/Write Through Pattern</h4>
<p>我们可以看到，在上面的Cache Aside套路中，我们的应用代码需要维护两个数据存储，一个是缓存（Cache），一个是数据库（Repository）。所以，应用程序比较啰嗦。而Read/Write Through套路是把更新数据库（Repository）的操作由缓存自己代理了，所以，对于应用层来说，就简单很多了。<strong>可以理解为，应用认为后端就是一个单一的存储，而存储自己维护自己的Cache。</strong></p>
<h5>Read Through</h5>
<p>Read Through 套路就是在查询操作中更新缓存，也就是说，当缓存失效的时候（过期或LRU换出），Cache Aside是由调用方负责把数据加载入缓存，而Read Through则用缓存服务自己来加载，从而对应用方是透明的。</p>
<h5>Write Through</h5>
<p>Write Through 套路和Read Through相仿，不过是在更新数据时发生。当有数据更新的时候，如果没有命中缓存，直接更新数据库，然后返回。如果命中了缓存，则更新缓存，然后再由Cache自己更新数据库（这是一个同步操作）</p>
<p>下图自来Wikipedia的<a href="https://en.wikipedia.org/wiki/Cache_(computing)">Cache词条</a>。其中的Memory你可以理解为就是我们例子里的数据库。</p>
<p><img decoding="async" loading="lazy" class="aligncenter size-full wp-image-17417" src="https://coolshell.cn/wp-content/uploads/2016/07/460px-Write-through_with_no-write-allocation.svg_.png" alt="Write-through_with_no-write-allocation" width="460" height="620" srcset="https://coolshell.cn/wp-content/uploads/2016/07/460px-Write-through_with_no-write-allocation.svg_.png 460w, https://coolshell.cn/wp-content/uploads/2016/07/460px-Write-through_with_no-write-allocation.svg_-223x300.png 223w" sizes="(max-width: 460px) 100vw, 460px" /></p>
<h4>Write Behind Caching Pattern</h4>
<p>Write Behind 又叫 Write Back。<strong>一些了解Linux操作系统内核的同学对write back应该非常熟悉，这不就是Linux文件系统的Page Cache的算法吗？是的，你看基础这玩意全都是相通的。</strong>所以，基础很重要，我已经不是一次说过基础很重要这事了。</p>
<p>Write Back套路，一句说就是，在更新数据的时候，只更新缓存，不更新数据库，而我们的缓存会异步地批量更新数据库。这个设计的好处就是让数据的I/O操作飞快无比（因为直接操作内存嘛 ），因为异步，write backg还可以合并对同一个数据的多次操作，所以性能的提高是相当可观的。</p>
<p>但是，其带来的问题是，数据不是强一致性的，而且可能会丢失（我们知道Unix/Linux非正常关机会导致数据丢失，就是因为这个事）。在软件设计上，我们基本上不可能做出一个没有缺陷的设计，就像算法设计中的时间换空间，空间换时间一个道理，有时候，强一致性和高性能，高可用和高性性是有冲突的。软件设计从来都是取舍Trade-Off。</p>
<p>另外，Write Back实现逻辑比较复杂，因为他需要track有哪数据是被更新了的，需要刷到持久层上。操作系统的write back会在仅当这个cache需要失效的时候，才会被真正持久起来，比如，内存不够了，或是进程退出了等情况，这又叫lazy write。</p>
<p>在wikipedia上有一张write back的流程图，基本逻辑如下：</p>
<p><img decoding="async" loading="lazy" class="aligncenter size-full wp-image-17428" src="https://coolshell.cn/wp-content/uploads/2016/07/Write-back_with_write-allocation.png" alt="Write-back_with_write-allocation" width="640" height="820" srcset="https://coolshell.cn/wp-content/uploads/2016/07/Write-back_with_write-allocation.png 640w, https://coolshell.cn/wp-content/uploads/2016/07/Write-back_with_write-allocation-234x300.png 234w" sizes="(max-width: 640px) 100vw, 640px" /></p>
<p>&nbsp;</p>
<h4>再多唠叨一些</h4>
<p>1）上面讲的这些Design Pattern，其实并不是软件架构里的mysql数据库和memcache/redis的更新策略，这些东西都是计算机体系结构里的设计，比如CPU的缓存，硬盘文件系统中的缓存，硬盘上的缓存，数据库中的缓存。<strong>基本上来说，这些缓存更新的设计模式都是非常老古董的，而且历经长时间考验的策略</strong>，所以这也就是，工程学上所谓的Best Practice，遵从就好了。</p>
<p>2）有时候，我们觉得能做宏观的系统架构的人一定是很有经验的，其实，宏观系统架构中的很多设计都来源于这些微观的东西。比如，云计算中的很多虚拟化技术的原理，和传统的虚拟内存不是很像么？Unix下的那些I/O模型，也放大到了架构里的同步异步的模型，还有Unix发明的管道不就是数据流式计算架构吗？TCP的好些设计也用在不同系统间的通讯中，仔细看看这些微观层面，你会发现有很多设计都非常精妙……所以，<strong>请允许我在这里放句观点鲜明的话——如果你要做好架构，首先你得把计算机体系结构以及很多老古董的基础技术吃透了</strong>。</p>
<p>3）在软件开发或设计中，我非常建议在之前先去参考一下已有的设计和思路，<strong>看看相应的guideline，best practice或design pattern，吃透了已有的这些东西，再决定是否要重新发明轮子</strong>。千万不要似是而非地，想当然的做软件设计。</p>
<p>4）上面，我们没有考虑缓存（Cache）和持久层（Repository）的整体事务的问题。比如，更新Cache成功，更新数据库失败了怎么吗？或是反过来。关于这个事，如果你需要强一致性，你需要使用“两阶段提交协议”——prepare, commit/rollback，比如Java 7 的<a href="http://docs.oracle.com/javaee/7/api/javax/transaction/xa/XAResource.html" target="_blank">XAResource</a>，还有MySQL 5.7的 <a href="http://dev.mysql.com/doc/refman/5.7/en/xa.html" target="_blank">XA Transaction</a>，有些cache也支持XA，比如<a href="http://www.ehcache.org/documentation/3.0/xa.html" target="_blank">EhCache</a>。当然，XA这样的强一致性的玩法会导致性能下降，关于分布式的事务的相关话题，你可以看看《<a href="https://coolshell.cn/articles/10910.html" target="_blank">分布式系统的事务处理</a>》一文。</p>
<p>（全文完）</p>
<p>&nbsp;</p>
<p>&nbsp;<!--



<p align="center"><a href= target=_blank><img decoding="async" src=""></a></p>





<p align="center"><img decoding="async" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.weixin.jpg"> <img decoding="async" loading="lazy" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.mini_.jpg" width="300" height="300"> <br />关注CoolShell微信公众账号和微信小程序</p>

 

--></p>
<div style="margin-top: 15px; font-size: 16px;color: #cc0000;">
<p align="center"><strong>（转载本站文章请注明作者和出处 <a href="https://coolshell.cn/">酷 壳 &#8211; CoolShell</a> ，请勿用于任何商业用途）</strong></p>
</div>

<div class="wp_rp_wrap  wp_rp_vertical_m" ><div class="wp_rp_content"><h3 class="related_post_title">相关文章</h3><ul class="related_post wp_rp"><li ><a href="https://coolshell.cn/articles/9949.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2013/07/inverted-bookshelf_thumb-150x150.jpg" alt="IoC/DIP其实是一种管理思想" width="150" height="150" /></a><a href="https://coolshell.cn/articles/9949.html" class="wp_rp_title">IoC/DIP其实是一种管理思想</a></li><li ><a href="https://coolshell.cn/articles/8961.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2013/01/kiss-150x150.png" alt="从面向对象的设计模式看软件设计" width="150" height="150" /></a><a href="https://coolshell.cn/articles/8961.html" class="wp_rp_title">从面向对象的设计模式看软件设计</a></li><li ><a href="https://coolshell.cn/articles/7236.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2012/05/Bannière-Unix-linux-150x150.jpg" alt="用Unix的设计思想来应对多变的需求" width="150" height="150" /></a><a href="https://coolshell.cn/articles/7236.html" class="wp_rp_title">用Unix的设计思想来应对多变的需求</a></li><li ><a href="https://coolshell.cn/articles/6950.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/plugins/wordpress-23-related-posts-plugin/static/thumbs/21.jpg" alt="需求变化与IoC" width="150" height="150" /></a><a href="https://coolshell.cn/articles/6950.html" class="wp_rp_title">需求变化与IoC</a></li><li ><a href="https://coolshell.cn/articles/22320.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2022/12/eBPF-150x150.jpeg" alt="eBPF 介绍" width="150" height="150" /></a><a href="https://coolshell.cn/articles/22320.html" class="wp_rp_title">eBPF 介绍</a></li><li ><a href="https://coolshell.cn/articles/21672.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2021/12/bachelor-mechanical-eng-icon@72x-150x150.png" alt="我做系统架构的一些原则" width="150" height="150" /></a><a href="https://coolshell.cn/articles/21672.html" class="wp_rp_title">我做系统架构的一些原则</a></li></ul></div></div>The post <a href="https://coolshell.cn/articles/17416.html">缓存更新的套路</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></content:encoded>
					
					<wfw:commentRss>https://coolshell.cn/articles/17416.html/feed</wfw:commentRss>
			<slash:comments>186</slash:comments>
		
		
			</item>
		<item>
		<title>Docker基础技术：DeviceMapper</title>
		<link>https://coolshell.cn/articles/17200.html</link>
					<comments>https://coolshell.cn/articles/17200.html#comments</comments>
		
		<dc:creator><![CDATA[陈皓]]></dc:creator>
		<pubDate>Wed, 26 Aug 2015 00:21:09 +0000</pubDate>
				<category><![CDATA[Unix/Linux]]></category>
		<category><![CDATA[操作系统]]></category>
		<category><![CDATA[杂项资源]]></category>
		<category><![CDATA[Device Mapper]]></category>
		<category><![CDATA[Docker]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[Thin Provisioning]]></category>
		<guid isPermaLink="false">http://coolshell.cn/?p=17200</guid>

					<description><![CDATA[<p>在上一篇介绍AUFS的文章中，大家可以看到，Docker的分层镜像是怎么通过UnionFS这种文件系统做到的，但是，因为Docker首选的AUFS并不在Linu...</p>
<p class="read-more"><a class="btn btn-default" href="https://coolshell.cn/articles/17200.html"> Read More<span class="screen-reader-text">  Read More</span></a></p>
The post <a href="https://coolshell.cn/articles/17200.html">Docker基础技术：DeviceMapper</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></description>
										<content:encoded><![CDATA[<p><script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-3415450859608158"
     crossorigin="anonymous"></script><img decoding="async" loading="lazy" class="alignright size-medium wp-image-17217" src="https://coolshell.cn/wp-content/uploads/2015/08/how_to_set_up_an_iSCSI_LUN_with_thin-300x150.jpg" alt="how_to_set_up_an_iSCSI_LUN_with_thin" width="300" height="150" srcset="https://coolshell.cn/wp-content/uploads/2015/08/how_to_set_up_an_iSCSI_LUN_with_thin-300x150.jpg 300w, https://coolshell.cn/wp-content/uploads/2015/08/how_to_set_up_an_iSCSI_LUN_with_thin-540x270.jpg 540w, https://coolshell.cn/wp-content/uploads/2015/08/how_to_set_up_an_iSCSI_LUN_with_thin.jpg 600w" sizes="(max-width: 300px) 100vw, 300px" />在上一篇<a href="https://coolshell.cn/articles/17061.html" target="_blank">介绍AUFS的文章</a>中，大家可以看到，Docker的分层镜像是怎么通过UnionFS这种文件系统做到的，但是，因为Docker首选的AUFS并不在Linux的内核主干里，所以，对于非Ubuntu的Linux分发包，比如CentOS，就无法使用AUFS作为Docker的文件系统了。于是作为第二优先级的DeviceMapper就被拿出来做分层镜像的一个实现。</p>
<h4>Device Mapper 简介</h4>
<p>DeviceMapper自Linux 2.6被引入成为了Linux最重要的一个技术。它在内核中支持逻辑卷管理的通用设备映射机制，它为实现用于存储资源管理的块设备驱动提供了一个高度模块化的内核架构，它包含三个重要的对象概念，Mapped Device、Mapping Table、Target device。</p>
<p>Mapped Device 是一个逻辑抽象，可以理解成为内核向外提供的逻辑设备，它通过Mapping Table描述的映射关系和 Target Device 建立映射。Target device 表示的是 Mapped Device 所映射的物理空间段，对 Mapped Device 所表示的逻辑设备来说，就是该逻辑设备映射到的一个物理设备。</p>
<p>Mapping Table里有 Mapped Device 逻辑的起始地址、范围、和表示在 Target Device 所在物理设备的地址偏移量以及Target 类型等信息（注：这些地址和偏移量都是以磁盘的扇区为单位的，即 512 个字节大小，所以，当你看到128的时候，其实表示的是128*512=64K）。</p>
<p><span id="more-17200"></span></p>
<p>DeviceMapper 中的逻辑设备Mapped Device不但可以映射一个或多个物理设备Target Device，还可以映射另一个Mapped Device，于是，就是构成了一个迭代或递归的情况，就像文件系统中的目录里除了文件还可以有目录，理论上可以无限嵌套下去。</p>
<p>DeviceMapper在内核中通过一个一个模块化的 Target Driver 插件实现对 IO 请求的过滤或者重新定向等工作，当前已经实现的插件包括软 Raid、加密、多路径、镜像、快照等，这体现了在 Linux 内核设计中策略和机制分离的原则。如下图所示。从图中，我们可以<strong>看到DeviceMapper只是一个框架，在这个框架上，我们可以插入各种各样的策略</strong>（让我不自然地想到了面向对象中的策略模式），在这诸多“插件”中，<strong>有一个东西叫Thin Provisioning Snapshot，这是Docker使用DeviceMapper中最重要的模块</strong>。</p>
<figure id="attachment_17204" aria-describedby="caption-attachment-17204" style="width: 640px" class="wp-caption aligncenter"><img decoding="async" loading="lazy" class="size-full wp-image-17204" src="https://coolshell.cn/wp-content/uploads/2015/08/device.mapper.2.gif" alt="图片来源：http://people.redhat.com/agk/talks/FOSDEM_2005/" width="640" height="494" /><figcaption id="caption-attachment-17204" class="wp-caption-text">图片来源：<a href="http://people.redhat.com/agk/talks/FOSDEM_2005/" target="_blank">http://people.redhat.com/agk/talks/FOSDEM_2005/</a></figcaption></figure>
<h4><strong>Thin Provisioning 简介</strong></h4>
<p>Thin Provisioning要怎么翻译成中文，真是一件令人头痛的事，我就不翻译了。这个技术是虚拟化技术中的一种。它是什么意思呢？<strong>你可以联想一下我们计算机中的内存管理中用到的——“虚拟内存技术”</strong>——操作系统给每个进程N多N多用不完的内址地址（32位下，每个进程可以有最多2GB的内存空间），但是呢，我们知道，物理内存是没有那么多的，如果按照进程内存和物理内存一一映射来玩的话，那么，我们得要多少的物理内存啊。所以，操作系统引入了虚拟内存的设计，<strong>意思是，我逻辑上给你无限多的内存，但是实际上是实报实销</strong>，因为我知道你一定用不了那么多，于是，达到了内存使用率提高的效果。（今天云计算中很多所谓的虚拟化其实完全都是在用和“虚拟内存”相似的Thin Provisioning的技术，所谓的超配，或是超卖）</p>
<p>&nbsp;</p>
<p>好了，话题拉回来，我们这里说的是存储。看下面两个图（<a href="http://www.architecting.it/2009/06/04/enterprise-computing-why-thin-provisioning-is-not-the-holy-grail-for-utilisation/" target="_blank">图片来源</a>），第一个是Fat Provisioning，第二个是Thin Provisioning，其很好的说明了是个怎么一回事（和虚拟内存是一个概念）</p>
<p><img decoding="async" loading="lazy" class="aligncenter size-full wp-image-17206" src="https://coolshell.cn/wp-content/uploads/2015/08/thin-provisioning-1.jpg" alt="thin-provisioning-1" width="606" height="399" srcset="https://coolshell.cn/wp-content/uploads/2015/08/thin-provisioning-1.jpg 606w, https://coolshell.cn/wp-content/uploads/2015/08/thin-provisioning-1-300x198.jpg 300w" sizes="(max-width: 606px) 100vw, 606px" /> <img decoding="async" loading="lazy" class="aligncenter size-full wp-image-17207" src="https://coolshell.cn/wp-content/uploads/2015/08/thin-provisioning-2.jpg" alt="thin-provisioning-2" width="606" height="389" srcset="https://coolshell.cn/wp-content/uploads/2015/08/thin-provisioning-2.jpg 606w, https://coolshell.cn/wp-content/uploads/2015/08/thin-provisioning-2-300x193.jpg 300w" sizes="(max-width: 606px) 100vw, 606px" /></p>
<p>那么，Docker是怎么使用Thin Provisioning这个技术做到像UnionFS那样的分层镜像的呢？答案是，Docker使用了Thin Provisioning的Snapshot的技术。下面我们来介绍一下Thin Provisioning的Snapshot。</p>
<h4>Thin Provisioning Snapshot 演示</h4>
<p>下面，我们用一系列的命令来演示一下Device Mapper的Thin Provisioning Snapshot是怎么玩的。</p>
<p>首先，我们需要先建两个文件，一个是data.img，一个是meta.data.img：</p>
<pre data-enlighter-language="shell" class="EnlighterJSRAW">~hchen$ sudo dd if=/dev/zero of=/tmp/data.img bs=1K count=1 seek=10M
1+0 records in
1+0 records out
1024 bytes (1.0 kB) copied, 0.000621428 s, 1.6 MB/s

~hchen$ sudo dd if=/dev/zero of=/tmp/meta.data.img bs=1K count=1 seek=1G
1+0 records in
1+0 records out
1024 bytes (1.0 kB) copied, 0.000140858 s, 7.3 MB/s</pre>
<p>注意命令中<code>seek</code>选项，其表示为略过<code>of</code>选项指定的输出文件的前10G个output的bloksize的空间后再写入内容。因为bs是1个字节，所以也就是10G的尺寸，但其实在硬盘上是没有占有空间的，占有空间只有1k的内容。当向其写入内容时，才会在硬盘上为其分配空间。我们可以用ls命令看一下，实际分配了12K和4K。</p>
<pre data-enlighter-language="shell" class="EnlighterJSRAW">~hchen$ sudo ls -lsh /tmp/data.img
12K -rw-r--r--. 1 root root 11G Aug 25 23:01 /tmp/data.img

~hchen$ sudo ls -slh /tmp/meta.data.img
4.0K -rw-r--r--. 1 root root 101M Aug 25 23:17 /tmp/meta.data.img</pre>
<p>然后，我们为这个文件创建一个loopback设备。（loop2015和loop2016是我乱取的两个名字）</p>
<pre data-enlighter-language="shell" class="EnlighterJSRAW">~hchen$ sudo losetup /dev/loop2015 /tmp/data.img
~hchen$ sudo losetup /dev/loop2016 /tmp/meta.data.img

~hchen$ sudo losetup -a
/dev/loop2015: [64768]:103991768 (/tmp/data.img)
/dev/loop2016: [64768]:103991765 (/tmp/meta.data.img)</pre>
<p>现在，我们为这个设备建一个Thin Provisioning的Pool，用dmsetup命令：</p>
<pre data-enlighter-language="shell" class="EnlighterJSRAW">~hchen$ sudo dmsetup create hchen-thin-pool \
                  --table &quot;0 20971522 thin-pool /dev/loop2016 /dev/loop2015 \
                           128 65536 1 skip_block_zeroing&quot;</pre>
<p>其中的参数解释如下（更多信息可参看<a href="https://github.com/torvalds/linux/blob/master/Documentation/device-mapper/thin-provisioning.txt" target="_blank">Thin Provisioning的man page</a>）：</p>
<ul>
<li>dmsetup create是用来创建thin pool的命令</li>
<li>hchen-thin-pool 是自定义的一个pool名，不冲突就好。</li>
<li>&#8211;table是这个pool的参数设置
<ul>
<li>0代表起的sector位置</li>
<li>20971522代码结句的sector号，前面说过，一个sector是512字节，所以，20971522个正好是10GB</li>
<li>/dev/loop2016是meta文件的设备（前面我们建好了）</li>
<li>/dev/loop2015是data文件的设备（前面我们建好了）</li>
<li>128是最小的可分配的sector数</li>
<li>65536是最少可用sector的water mark，也就是一个threshold</li>
<li>1 代表有一个附加参数</li>
<li>skip_block_zeroing是个附加参数，表示略过用0填充的块</li>
</ul>
</li>
</ul>
<p>然后，我们就可以看到一个Device Mapper的设备了：</p>
<pre data-enlighter-language="shell" class="EnlighterJSRAW">~hchen$ sudo ll /dev/mapper/hchen-thin-pool
lrwxrwxrwx. 1 root root 7 Aug 25 23:24 /dev/mapper/hchen-thin-pool -&gt; ../dm-4</pre>
<p>接下来，我们的初始还没有完成，还要创建一个Thin Provisioning 的 Volume：</p>
<pre data-enlighter-language="shell" class="EnlighterJSRAW">~hchen$ sudo dmsetup message /dev/mapper/hchen-thin-pool 0 &quot;create_thin 0&quot;
~hchen$ sudo dmsetup create hchen-thin-volumn-001 \
            --table &quot;0 2097152 thin /dev/mapper/hchen-thin-pool 0&quot;</pre>
<p>其中：</p>
<ul>
<li>第一个命令中的create_thin是关键字，后面的0表示这个Volume的device 的 id</li>
<li>第二个命令，是真正的为这个Volumn创建一个可以mount的设备，名字叫hchen-thin-volumn-001。2097152只有1GB</li>
</ul>
<p>好了，在mount前，我们还要格式化一下：</p>
<pre data-enlighter-language="shell" class="EnlighterJSRAW">~hchen$ sudo mkfs.ext4 /dev/mapper/hchen-thin-volumn-001
mke2fs 1.42.9 (28-Dec-2013)
Discarding device blocks: done
Filesystem label=
OS type: Linux
Block size=4096 (log=2)
Fragment size=4096 (log=2)
Stride=16 blocks, Stripe width=16 blocks
65536 inodes, 262144 blocks
13107 blocks (5.00%) reserved for the super user
First data block=0
Maximum filesystem blocks=268435456
8 block groups
32768 blocks per group, 32768 fragments per group
8192 inodes per group
Superblock backups stored on blocks:
32768, 98304, 163840, 229376

Allocating group tables: done
Writing inode tables: done
Creating journal (8192 blocks): done
Writing superblocks and filesystem accounting information: done</pre>
<p>好了，我们可以mount了（下面的命令中，我还创建了一个文件）</p>
<pre data-enlighter-language="shell" class="EnlighterJSRAW">~hchen$ sudo mkdir -p /mnt/base
~hchen$ sudo mount /dev/mapper/hchen-thin-volumn-001 /mnt/base
~hchen$ sudo echo &quot;hello world, I am a base&quot; &gt; /mnt/base/id.txt
~hchen$ sudo cat /mnt/base/id.txt
hello world, I am a base</pre>
<p>好了，接下来，我们来看看snapshot怎么搞：</p>
<pre data-enlighter-language="shell" class="EnlighterJSRAW">~hchen$ sudo dmsetup message /dev/mapper/hchen-thin-pool 0 &quot;create_snap 1 0&quot;
~hchen$ sudo dmsetup create mysnap1 \
                   --table &quot;0 2097152 thin /dev/mapper/hchen-thin-pool 1&quot;

~hchen$ sudo ll /dev/mapper/mysnap1
lrwxrwxrwx. 1 root root 7 Aug 25 23:49 /dev/mapper/mysnap1 -&gt; ../dm-5</pre>
<p>上面的命令中：</p>
<ul>
<li>第一条命令是向hchen-thin-pool发一个create_snap的消息，后面跟两个id，第一个是新的dev id，第二个是要从哪个已有的dev id上做snapshot（0这个dev id是我们前面就创建了了）</li>
</ul>
<ul>
<li>第二条命令是创建一个mysnap1的device，并可以被mount。</li>
</ul>
<p>下面我们来看看：</p>
<pre data-enlighter-language="shell" class="EnlighterJSRAW">~hchen$ sudo mkdir -p /mnt/mysnap1
~hchen$ sudo mount /dev/mapper/mysnap1 /mnt/mysnap1

~hchen$ sudo ll /mnt/mysnap1/
total 20
-rw-r--r--. 1 root root 25 Aug 25 23:46 id.txt
drwx------. 2 root root 16384 Aug 25 23:43 lost+found

~hchen$ sudo cat /mnt/mysnap1/id.txt
hello world, I am a base</pre>
<p>我们来修改一下/mnt/mysnap1/id.txt，并加上一个snap1.txt的文件：</p>
<pre data-enlighter-language="shell" class="EnlighterJSRAW">~hchen$ sudo echo &quot;I am snap1&quot; &gt;&gt; /mnt/mysnap1/id.txt
~hchen$ sudo echo &quot;I am snap1&quot; &gt; /mnt/mysnap1/snap1.txt

~hchen$ sudo cat /mnt/mysnap1/id.txt
hello world, I am a base
I am snap1

~hchen$ sudo cat /mnt/mysnap1/snap1.txt
I am snap1</pre>
<p>我们再看一下/mnt/base，你会发现没有什么变化：</p>
<pre data-enlighter-language="shell" class="EnlighterJSRAW">~hchen$ sudo ls /mnt/base
id.txt      lost+found
~hchen$ sudo cat /mnt/base/id.txt
hello world, I am a base</pre>
<p>你是不是已经看到了分层镜像的样子了？</p>
<p>你还要吧继续在刚才的snapshot上再建一个snapshot</p>
<pre data-enlighter-language="shell" class="EnlighterJSRAW">~hchen$ sudo dmsetup message /dev/mapper/hchen-thin-pool 0 &quot;create_snap 2 1&quot;
~hchen$ sudo dmsetup create mysnap2 \
                   --table &quot;0 2097152 thin /dev/mapper/hchen-thin-pool 2&quot;

~hchen$ sudo ll /dev/mapper/mysnap2
lrwxrwxrwx. 1 root root 7 Aug 25 23:52 /dev/mapper/mysnap1 -&gt; ../dm-7

~hchen$ sudo mkdir -p /mnt/mysnap2
~hchen$ sudo mount /dev/mapper/mysnap2 /mnt/mysnap2
~hchen$ sudo  ls /mnt/mysnap2
id.txt  lost+found  snap1.txt </pre>
<p>好了，我相信你看到了分层镜像的样子了。</p>
<p>看完演示，我们再来补点理论知识吧：</p>
<ul>
<li>Snapshot来自LVM（Logic Volumn Manager），它可以在不中断服务的情况下为某个device打一个快照。</li>
<li>Snapshot是Copy-On-Write的，也就是说，只有发生了修改，才会对对应的内存进行拷贝。</li>
</ul>
<p>另外，这里有篇文章<a href="http://searchstorage.techtarget.com/tip/Storage-thin-provisioning-benefits-and-challenges" target="_blank">Storage thin provisioning benefits and challenges</a>可以前往一读。</p>
<h4>Docker的DeviceMapper</h4>
<p>上面基本上就是Docker的玩法了，我们可以看一下docker的loopback设备：</p>
<pre data-enlighter-language="shell" class="EnlighterJSRAW">~hchen $ sudo losetup -a
/dev/loop0: [64768]:38050288 (/var/lib/docker/devicemapper/devicemapper/data)
/dev/loop1: [64768]:38050289 (/var/lib/docker/devicemapper/devicemapper/metadata)</pre>
<p>其中data 100GB，metadata 2.0GB</p>
<pre data-enlighter-language="shell" class="EnlighterJSRAW">~hchen $ sudo ls -alhs /var/lib/docker/devicemapper/devicemapper
506M -rw-------. 1 root root 100G Sep 10 20:15 data
1.1M -rw-------. 1 root root 2.0G Sep 10 20:15 metadata </pre>
<p>下面是相关的thin-pool。其中，有个当一大串hash串的device是正在启动的容器：</p>
<pre data-enlighter-language="shell" class="EnlighterJSRAW">~hchen $ sudo ll /dev/mapper/dock*
lrwxrwxrwx. 1 root root 7 Aug 25 07:57 /dev/mapper/docker-253:0-104108535-pool -&gt; ../dm-2
lrwxrwxrwx. 1 root root 7 Aug 25 11:13 /dev/mapper/docker-253:0-104108535-deefcd630a60aa5ad3e69249f58a68e717324be4258296653406ff062f605edf -&gt; ../dm-3</pre>
<p>我们可以看一下它的device id（Docker都把它们记下来了）：</p>
<pre data-enlighter-language="shell" class="EnlighterJSRAW">~hchen $ sudo cat /var/lib/docker/devicemapper/metadata/deefcd630a60aa5ad3e69249f58a68e717324be4258296653406ff062f605edf
{&quot;device_id&quot;:24,&quot;size&quot;:10737418240,&quot;transaction_id&quot;:26,&quot;initialized&quot;:false}</pre>
<p>device_id是24，size是10737418240，除以512，就是20971520 个 sector，我们用这些信息来做个snapshot看看（注：我用了一个比较大的dev id &#8211; 1024）：</p>
<pre data-enlighter-language="shell" class="EnlighterJSRAW">~hchen$ sudo dmsetup message &quot;/dev/mapper/docker-253:0-104108535-pool&quot; 0 \
                                    &quot;create_snap 1024 24&quot;
~hchen$ sudo dmsetup create dockersnap --table \
                    &quot;0 20971520 thin /dev/mapper/docker-253:0-104108535-pool 1024&quot;
~hchen$ sudo mkdir /mnt/docker
~hchen$ sudo mount /dev/mapper/dockersnap /mnt/docker/
~hchen$ sudo ls /mnt/docker/
id lost+found rootfs
~hchen$ sudo ls /mnt/docker/rootfs/
bin dev etc home lib lib64 lost+found media mnt opt proc root run sbin srv sys tmp usr var</pre>
<p>我们在docker的容器里用findmnt命令也可以看到相关的mount的情况（因为太长，下面只是摘要）：</p>
<pre data-enlighter-language="shell" class="EnlighterJSRAW"># findmnt
TARGET                SOURCE               
/                 /dev/mapper/docker-253:0-104108535-deefcd630a60[/rootfs]
/etc/resolv.conf  /dev/mapper/centos-root[/var/lib/docker/containers/deefcd630a60/resolv.conf]
/etc/hostname     /dev/mapper/centos-root[/var/lib/docker/containers/deefcd630a60/hostname]
/etc/hosts        /dev/mapper/centos-root[/var/lib/docker/containers/deefcd630a60/hosts]</pre>
<h4>Device Mapper 行不行？</h4>
<p>Thin Provisioning的文档中说，这还处理实验阶段，不要上Production.</p>
<blockquote><p>These targets are very much still in the EXPERIMENTAL state. Please do not yet rely on them in production.</p></blockquote>
<p>另外，Jeff Atwood在Twitter上发过这样的一推</p>
<p><a href="https://twitter.com/codinghorror/status/604096348682485760"><img decoding="async" loading="lazy" class="aligncenter size-full wp-image-17214" src="https://coolshell.cn/wp-content/uploads/2015/08/Jeff.Atwood.DeviceMapper.png" alt="Jeff.Atwood.DeviceMapper" width="607" height="311" srcset="https://coolshell.cn/wp-content/uploads/2015/08/Jeff.Atwood.DeviceMapper.png 607w, https://coolshell.cn/wp-content/uploads/2015/08/Jeff.Atwood.DeviceMapper-300x154.png 300w" sizes="(max-width: 607px) 100vw, 607px" /></a></p>
<p>这个推指向的<a href="https://forums.docker.com/t/rmi-not-freeing-disk-space-in-devicemapper-sparse-file-centos-6-6/1640/3" target="_blank">这个讨论</a>中，其中指向了这个<a href="https://github.com/discourse/discourse_docker/commit/48f22d14f39496c8df446cbc65ee04b258c5a1a0" target="_blank">code diff</a>，基本上就是说，DeviceMapper这种东西问题太多了，我们应该把其加入黑名单。Doker的Founder也这样回复到：</p>
<p><a href="https://twitter.com/solomonstre/status/604055267303636992"><img decoding="async" loading="lazy" class="aligncenter size-full wp-image-17215" src="https://coolshell.cn/wp-content/uploads/2015/08/Solomon.Hykeys.DeviceMapper.png" alt="" width="620" height="229" srcset="https://coolshell.cn/wp-content/uploads/2015/08/Solomon.Hykeys.DeviceMapper.png 620w, https://coolshell.cn/wp-content/uploads/2015/08/Solomon.Hykeys.DeviceMapper-300x111.png 300w" sizes="(max-width: 620px) 100vw, 620px" /></a></p>
<p>所以，如果你在使用loopback的devicemapper的话，当你的存储出现了问题后，正确的解决方案是：</p>
<p style="text-align: center;">rm -rf /var/lib/docker</p>
<p>（全文完）<!--



<p align="center"><a href= target=_blank><img decoding="async" src=""></a></p>





<p align="center"><img decoding="async" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.weixin.jpg"> <img decoding="async" loading="lazy" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.mini_.jpg" width="300" height="300"> <br />关注CoolShell微信公众账号和微信小程序</p>

 

--></p>
<div style="margin-top: 15px; font-size: 16px;color: #cc0000;">
<p align="center"><strong>（转载本站文章请注明作者和出处 <a href="https://coolshell.cn/">酷 壳 &#8211; CoolShell</a> ，请勿用于任何商业用途）</strong></p>
</div>

<div class="wp_rp_wrap  wp_rp_vertical_m" ><div class="wp_rp_content"><h3 class="related_post_title">相关文章</h3><ul class="related_post wp_rp"><li ><a href="https://coolshell.cn/articles/18654.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2018/12/docker-networking-1-150x150.png" alt="记一次Kubernetes/Docker网络排障" width="150" height="150" /></a><a href="https://coolshell.cn/articles/18654.html" class="wp_rp_title">记一次Kubernetes/Docker网络排障</a></li><li ><a href="https://coolshell.cn/articles/17061.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2015/08/docker-filesystems-busyboxrw-150x150.png" alt="Docker基础技术：AUFS" width="150" height="150" /></a><a href="https://coolshell.cn/articles/17061.html" class="wp_rp_title">Docker基础技术：AUFS</a></li><li ><a href="https://coolshell.cn/articles/17049.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2015/04/filter-150x150.png" alt="Docker基础技术：Linux CGroup" width="150" height="150" /></a><a href="https://coolshell.cn/articles/17049.html" class="wp_rp_title">Docker基础技术：Linux CGroup</a></li><li ><a href="https://coolshell.cn/articles/17010.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2015/04/isolation-150x150.jpg" alt="Docker基础技术：Linux Namespace（上）" width="150" height="150" /></a><a href="https://coolshell.cn/articles/17010.html" class="wp_rp_title">Docker基础技术：Linux Namespace（上）</a></li><li ><a href="https://coolshell.cn/articles/17029.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2015/04/jail_cell-150x150.jpg" alt="Docker基础技术：Linux Namespace（下）" width="150" height="150" /></a><a href="https://coolshell.cn/articles/17029.html" class="wp_rp_title">Docker基础技术：Linux Namespace（下）</a></li><li ><a href="https://coolshell.cn/articles/22320.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2022/12/eBPF-150x150.jpeg" alt="eBPF 介绍" width="150" height="150" /></a><a href="https://coolshell.cn/articles/22320.html" class="wp_rp_title">eBPF 介绍</a></li></ul></div></div>The post <a href="https://coolshell.cn/articles/17200.html">Docker基础技术：DeviceMapper</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></content:encoded>
					
					<wfw:commentRss>https://coolshell.cn/articles/17200.html/feed</wfw:commentRss>
			<slash:comments>25</slash:comments>
		
		
			</item>
		<item>
		<title>Docker基础技术：AUFS</title>
		<link>https://coolshell.cn/articles/17061.html</link>
					<comments>https://coolshell.cn/articles/17061.html#comments</comments>
		
		<dc:creator><![CDATA[陈皓]]></dc:creator>
		<pubDate>Mon, 24 Aug 2015 00:01:13 +0000</pubDate>
				<category><![CDATA[Unix/Linux]]></category>
		<category><![CDATA[操作系统]]></category>
		<category><![CDATA[杂项资源]]></category>
		<category><![CDATA[AUFS]]></category>
		<category><![CDATA[Docker]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[UnionFS]]></category>
		<guid isPermaLink="false">http://coolshell.cn/?p=17061</guid>

					<description><![CDATA[<p>AUFS是一种Union File System，所谓UnionFS就是把不同物理位置的目录合并mount到同一个目录中。UnionFS的一个最主要的应用是，把...</p>
<p class="read-more"><a class="btn btn-default" href="https://coolshell.cn/articles/17061.html"> Read More<span class="screen-reader-text">  Read More</span></a></p>
The post <a href="https://coolshell.cn/articles/17061.html">Docker基础技术：AUFS</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></description>
										<content:encoded><![CDATA[<p><script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-3415450859608158"
     crossorigin="anonymous"></script><a href="https://coolshell.cn/wp-content/uploads/2015/08/docker-filesystems-busyboxrw.png"><img decoding="async" loading="lazy" class="alignright size-medium wp-image-17194" src="https://coolshell.cn/wp-content/uploads/2015/08/docker-filesystems-busyboxrw-300x225.png" alt="docker-filesystems-busyboxrw" width="300" height="225" srcset="https://coolshell.cn/wp-content/uploads/2015/08/docker-filesystems-busyboxrw-300x225.png 300w, https://coolshell.cn/wp-content/uploads/2015/08/docker-filesystems-busyboxrw-768x576.png 768w, https://coolshell.cn/wp-content/uploads/2015/08/docker-filesystems-busyboxrw-360x270.png 360w, https://coolshell.cn/wp-content/uploads/2015/08/docker-filesystems-busyboxrw.png 800w" sizes="(max-width: 300px) 100vw, 300px" /></a>AUFS是一种Union File System，所谓UnionFS就是把不同物理位置的目录合并mount到同一个目录中。UnionFS的一个最主要的应用是，把一张CD/DVD和一个硬盘目录给联合 mount在一起，然后，你就可以对这个只读的CD/DVD上的文件进行修改（当然，修改的文件存于硬盘上的目录里）。</p>
<p>AUFS又叫Another UnionFS，后来叫Alternative UnionFS，后来可能觉得不够霸气，叫成Advance UnionFS。是个叫Junjiro Okajima（岡島順治郎）在2006年开发的，AUFS完全重写了早期的UnionFS 1.x，其主要目的是为了可靠性和性能，并且引入了一些新的功能，比如可写分支的负载均衡。AUFS在使用上全兼容UnionFS，而且比之前的UnionFS在稳定性和性能上都要好很多，后来的UnionFS 2.x开始抄AUFS中的功能。但是他居然没有进到Linux主干里，就是因为Linus不让，基本上是因为代码量比较多，而且写得烂（相对于只有3000行的union mount和10000行的UnionFS，以及其它平均下来只有6000行代码左右的VFS，AUFS居然有30000行代码），所以，岡島不断地改进代码质量，不断地提交，不断地被Linus拒掉，所以，到今天AUFS都还进不了Linux主干（今天你可以看到AUFS的代码其实还好了，比起OpenSSL好N倍，要么就是Linus对代码的质量要求非常高，要么就是Linus就是不喜欢AUFS）。</p>
<p>不过，好在有很多发行版都用了AUFS，比如：Ubuntu 10.04，Debian6.0, Gentoo Live CD支持AUFS，所以，也OK了。</p>
<p>好了，扯完这些闲话，我们还是看一个示例吧（环境：Ubuntu 14.04）</p>
<p><span id="more-17061"></span></p>
<p>首先，我们建上两个目录（水果和蔬菜），并在这两个目录中放上一些文件，水果中有苹果和蕃茄，蔬菜有胡萝卜和蕃茄。</p>
<pre data-enlighter-language="shell" class="EnlighterJSRAW">$ tree
.
├── fruits
│   ├── apple
│   └── tomato
└── vegetables
    ├── carrots
    └── tomato

</pre>
<p>然后，我们输入以下命令：</p>
<pre data-enlighter-language="shell" class="EnlighterJSRAW"># 创建一个mount目录
$ mkdir mnt

# 把水果目录和蔬菜目录union mount到 ./mnt目录中
$ sudo mount -t aufs -o dirs=./fruits:./vegetables none ./mnt

#  查看./mnt目录
$ tree ./mnt
./mnt
├── apple
├── carrots
└── tomato</pre>
<p>我们可以看到在./mnt目录下有三个文件，苹果apple、胡萝卜carrots和蕃茄tomato。水果和蔬菜的目录被union到了./mnt目录下了。</p>
<p>我们来修改一下其中的文件内容：</p>
<pre data-enlighter-language="shell" class="EnlighterJSRAW">$ echo mnt &gt; ./mnt/apple
$ cat ./mnt/apple
mnt
$ cat ./fruits/apple
mnt</pre>
<p>上面的示例，我们可以看到./mnt/apple的内容改了，./fruits/apple的内容也改了。</p>
<pre data-enlighter-language="shell" class="EnlighterJSRAW">$ echo mnt_carrots &gt; ./mnt/carrots
$ cat ./vegetables/carrots 

$ cat ./fruits/carrots
mnt_carrots
</pre>
<p>上面的示例，我们可以看到，我们修改了./mnt/carrots的文件内容，./vegetables/carrots并没有变化，反而是./fruits/carrots的目录中出现了carrots文件，其内容是我们在./mnt/carrots里的内容。</p>
<p>也就是说，我们在mount aufs命令中，我们没有指它vegetables和fruits的目录权限，默认上来说，命令行上第一个（最左边）的目录是可读可写的，后面的全都是只读的。（一般来说，最前面的目录应该是可写的，而后面的都应该是只读的）</p>
<p>所以，如果我们像下面这样指定权限来mount aufs，你就会发现有不一样的效果（记得先把上面./fruits/carrots的文件删除了）：</p>
<pre data-enlighter-language="shell" class="EnlighterJSRAW">$ sudo mount -t aufs -o dirs=./fruits=rw:./vegetables=rw none ./mnt

$ echo &quot;mnt_carrots&quot; &gt; ./mnt/carrots 

$ cat ./vegetables/carrots
mnt_carrots

$ cat ./fruits/carrots
cat: ./fruits/carrots: No such file or directory</pre>
<p>现在，在这情况下，如果我们要修改./mnt/tomato这个文件，那么究竟是哪个文件会被改写？</p>
<pre data-enlighter-language="shell" class="EnlighterJSRAW">$ echo &quot;mnt_tomato&quot; &gt; ./mnt/tomato 

$ cat ./fruits/tomato
mnt_tomato

$ cat ./vegetables/tomato
I am a vegetable</pre>
<p>可见，如果有重复的文件名，在mount命令行上，越往前的就优先级越高。</p>
<p>你可以用这个例子做一些各种各样的试验，我这里主要是给大家一个感性认识，就不展开试验下去了。</p>
<p>那么，这种UnionFS有什么用？</p>
<p>历史上，有一个叫<a href="http://zh.wikipedia.org/wiki/Knoppix" target="_blank">Knoppix的Linux发行版</a>，其主要用于Linux演示、光盘教学、系统急救，以及商业产品的演示，不需要硬盘安装，直接把CD/DVD上的image运行在一个可写的存储设备上（比如一个U盘上），其实，也就是把CD/DVD这个文件系统和USB这个可写的系统给联合mount起来，这样你对CD/DVD上的image做的任何改动都会在被应用在U盘上，于是乎，你可以对CD/DVD上的内容进行任意的修改，因为改动都在U盘上，所以你改不坏原来的东西。</p>
<p>我们可以再发挥一下想像力，你也可以把一个目录，比如你的源代码，作为一个只读的template，和另一个你的working directory给union在一起，然后你就可以做各种修改而不用害怕会把源代码改坏了。有点像一个ad hoc snapshot。</p>
<p>Docker把UnionFS的想像力发挥到了容器的镜像。你是否还记得我在<a title="Docker基础技术：Linux Namespace（上）" href="https://coolshell.cn/articles/17010.html" target="_blank">介绍Linux Namespace上篇</a>中用mount namespace和chroot山寨了一镜像。现在当你看过了这个UnionFS的技术后，你是不是就明白了，你完全可以用UnionFS这样的技术做出分层的镜像来。</p>
<p>下图来自Docker的官方文档<a href="http://docs.docker.com/terms/layer/" target="_blank">Layer</a>，其很好的展示了Docker用UnionFS搭建的分层镜像。</p>
<p><img decoding="async" loading="lazy" class="aligncenter size-full wp-image-17064" src="https://coolshell.cn/wp-content/uploads/2015/04/docker-filesystems-multilayer.png" alt="docker-filesystems-multilayer" width="500" height="375" srcset="https://coolshell.cn/wp-content/uploads/2015/04/docker-filesystems-multilayer.png 800w, https://coolshell.cn/wp-content/uploads/2015/04/docker-filesystems-multilayer-300x225.png 300w" sizes="(max-width: 500px) 100vw, 500px" /></p>
<p>关于docker的分层镜像，除了aufs，docker还支持btrfs, devicemapper和vfs，你可以使用 -s 或 &#8211;storage-driver= 选项来指定相关的镜像存储。在Ubuntu 14.04下，docker默认Ubuntu的 aufs（在CentOS7下，用的是devicemapper，关于devicemapper，我会以以后的文章中讲解）你可以在下面的目录中查看相关的每个层的镜像：</p>
<p><code data-enlighter-language="shell" class="EnlighterJSRAW">/var/lib/docker/aufs/diff/&lt;id&gt; </code></p>
<p>在docker执行起来后（比如：docker run -it ubuntu /bin/bash ），你可以从/sys/fs/aufs/si_[id]目录下查看aufs的mount的情况，下面是个示例：</p>
<pre data-enlighter-language="shell" class="EnlighterJSRAW">#ls /sys/fs/aufs/si_b71b209f85ff8e75/
br0      br2      br4      br6      brid1    brid3    brid5    xi_path
br1      br3      br5      brid0    brid2    brid4    brid6 

# cat /sys/fs/aufs/si_b71b209f85ff8e75/*
/var/lib/docker/aufs/diff/87315f1367e5703f599168d1e17528a0500bd2e2df7d2fe2aaf9595f3697dbd7=rw
/var/lib/docker/aufs/diff/87315f1367e5703f599168d1e17528a0500bd2e2df7d2fe2aaf9595f3697dbd7-init=ro+wh
/var/lib/docker/aufs/diff/d0955f21bf24f5bfffd32d2d0bb669d0564701c271bc3dfc64cfc5adfdec2d07=ro+wh
/var/lib/docker/aufs/diff/9fec74352904baf5ab5237caa39a84b0af5c593dc7cc08839e2ba65193024507=ro+wh
/var/lib/docker/aufs/diff/a1a958a248181c9aa6413848cd67646e5afb9797f1a3da5995c7a636f050f537=ro+wh
/var/lib/docker/aufs/diff/f3c84ac3a0533f691c9fea4cc2ceaaf43baec22bf8d6a479e069f6d814be9b86=ro+wh
/var/lib/docker/aufs/diff/511136ea3c5a64f264b78b5433614aec563103b4d4702f3ba7d4d2698e22c158=ro+wh
64
65
66
67
68
69
70
/run/shm/aufs.xino</pre>
<p>你会看到只有最顶上的层（branch）是rw权限，其它的都是ro+wh权限只读的。</p>
<p>关于docker的aufs的配置，你可以在/var/lib/docker/repositories-aufs这个文件中看到。</p>
<h4>AUFS的一些特性</h4>
<p>AUFS有所有Union FS的特性，把多个目录，合并成同一个目录，并可以为每个需要合并的目录指定相应的权限，实时的添加、删除、修改已经被mount好的目录。而且，他还能在多个可写的branch/dir间进行负载均衡。</p>
<p>上面的例子，我们已经看到AUFS的mount的示例了。下面我们来看一看被union的目录（分支）的相关权限：</p>
<ul>
<li>rw表示可写可读read-write。</li>
<li>ro表示read-only，如果你不指权限，那么除了第一个外ro是默认值，对于ro分支，其永远不会收到写操作，也不会收到查找whiteout的操作。</li>
<li>rr表示real-read-only，与read-only不同的是，rr标记的是天生就是只读的分支，这样，AUFS可以提高性能，比如不再设置inotify来检查文件变动通知。</li>
</ul>
<p>权限中，我们看到了一个术语：whiteout，下面我来解释一下这个术语。</p>
<p>一般来说ro的分支都会有wh的属性，比如 &#8220;[dir]=ro+wh&#8221;。所谓whiteout的意思，如果在union中删除的某个文件，实际上是位于一个readonly的分支（目录）上，那么，在mount的union这个目录中你将看不到这个文件，但是read-only这个层上我们无法做任何的修改，所以，我们就需要对这个readonly目录里的文件作whiteout。AUFS的whiteout的实现是通过在上层的可写的目录下建立对应的whiteout隐藏文件来实现的。</p>
<p>看个例子：</p>
<p>假设我们有三个目录和文件如下所示（test是个空目录）：</p>
<pre data-enlighter-language="shell" class="EnlighterJSRAW"># tree
.
├── fruits
│   ├── apple
│   └── tomato
├── test
└── vegetables
    ├── carrots
    └── tomato</pre>
<p>我们如下mount：</p>
<pre data-enlighter-language="shell" class="EnlighterJSRAW"># mkdir mnt

# mount -t aufs -o dirs=./test=rw:./fruits=ro:./vegetables=ro none ./mnt

# # ls ./mnt/
apple  carrots  tomato </pre>
<p>现在我们在权限为rw的test目录下建个whiteout的隐藏文件.wh.apple，你就会发现./mnt/apple这个文件就消失了:</p>
<pre data-enlighter-language="shell" class="EnlighterJSRAW"> # touch ./test/.wh.apple

# ls ./mnt
carrots  tomato</pre>
<p>上面这个操作和 rm ./mnt/apple是一样的。</p>
<h5>相关术语</h5>
<p><b>Branch</b> – 就是各个要被union起来的目录（就是我在上面使用的dirs的命令行参数）</p>
<ul>
<li>Branch根据被union的顺序形成一个stack，一般来说最上面的是可写的，下面的都是只读的。</li>
<li>Branch的stack可以在被mount后进行修改，比如：修改顺序，加入新的branch，或是删除其中的branch，或是直接修改branch的权限</li>
</ul>
<p><b>Whiteout</b> 和 <b>Opaque</b></p>
<ul>
<li>如果UnionFS中的某个目录被删除了，那么就应该不可见了，就算是在底层的branch中还有这个目录，那也应该不可见了。</li>
</ul>
<ul>
<li>Whiteout就是某个上层目录覆盖了下层的相同名字的目录。用于隐藏低层分支的文件，也用于阻止readdir进入低层分支。</li>
</ul>
<ul>
<li>Opaque的意思就是不允许任何下层的某个目录显示出来。</li>
</ul>
<ul>
<li>在隐藏低层档的情况下，whiteout的名字是’.wh.&lt;filename&gt;’。</li>
</ul>
<ul>
<li>在阻止readdir的情况下，名字是’.wh..wh..opq’或者 ’.wh.__dir_opaque’。</li>
</ul>
<h5>相关问题</h5>
<p>看到上面这些，你一定会有几个问题：</p>
<p><strong>其一、你可能会问，要有文件在原来的地方被修改了会怎么样？</strong>mount的目录会一起改变吗？答案是会的，也可以是不会的。因为你可以指定一个叫udba的参数（全称：User’s Direct Branch Access），这个参数有三个取值：</p>
<ul>
<li><strong>udba=none</strong> – 设置上这个参数后，AUFS会运转的更快，因为那些不在mount目录里发生的修改，aufs不会同步过来了，所以会有数据出错的问题。</li>
<li><strong>udba=reval</strong> – 设置上这个参数后，AUFS会去查文件有没有被更新，如果有的话，就会把修改拉到mount目录内。</li>
<li><strong>udba=notify</strong> – 这个参数会让AUFS为所有的branch注册inotify，这样可以让AUFS在更新文件修改的性能更高一些。</li>
</ul>
<p><strong>其二、如果有多个rw的branch（目录）被union起来了，那么，当我创建文件的时候，aufs会创建在哪里呢？</strong> aufs提供了一个叫create的参数可以供你来配置相当的创建策略，下面有几个例子。</p>
<p><strong>create=rr | round−robin</strong> 轮询。下面的示例可以看到，新创建的文件轮流写到三个目录中</p>
<pre data-enlighter-language="shell" class="EnlighterJSRAW">
hchen$ sudo mount -t aufs  -o dirs=./1=rw:./2=rw:./3=rw -o create=rr none ./mnt
hchen$ touch ./mnt/a ./mnt/b ./mnt/c
hchen$ tree
.
├── 1
│   └── a
├── 2
│   └── c
└── 3
    └── b</pre>
<p><strong>create=mfs[:second] | most−free−space[:second]</strong> 选一个可用空间最好的分支。可以指定一个检查可用磁盘空间的时间。</p>
<p><strong>create=mfsrr:low[:second]</strong> 选一个空间大于low的branch，如果空间小于low了，那么aufs会使用 round-robin 方式。</p>
<p>更多的关于AUFS的细节使用参数，大家可以直接在Ubuntu 14.04下通过<a href="http://aufs.sourceforge.net/aufs3/man.html" target="_blank"> man aufs </a>来看一下其中的各种参数和命令。</p>
<h4>AUFS的性能</h4>
<p>AUFS的性能慢吗？也慢也不慢。因为AUFS会把所有的分支mount起来，所以，在查找文件上是比较慢了。因为它要遍历所有的branch。是个O(n)的算法（很明显，这个算法有很大的改进空间的）所以，branch越多，查找文件的性能也就越慢。但是，一旦AUFS找到了这个文件的inode，那后以后的读写和操作原文件基本上是一样的。</p>
<p>所以，如果你的程序跑在在AUFS下，open和stat操作会有明显的性能下降，branch越多，性能越差，但是在write/read操作上，性能没有什么变化。</p>
<p>IBM的研究中心对Docker的性能给了一份非常不错的性能报告（PDF）《<a href="http://domino.research.ibm.com/library/cyberdig.nsf/papers/0929052195DD819C85257D2300681E7B/$File/rc25482.pdf" target="_blank">An Updated Performance Comparison of Virtual Machinesand Linux Containers</a>》</p>
<p>我截了两张图出来，第一张是顺序读写，第二张是随机读写。基本没有什么性能损失的问题。而KVM在随机读写的情况也就有点慢了（但是，如果硬盘是SSD的呢？）</p>
<p><a href="https://coolshell.cn/wp-content/uploads/2015/08/docker.seq_.jpg"><img decoding="async" loading="lazy" class="aligncenter size-full wp-image-17191" src="https://coolshell.cn/wp-content/uploads/2015/08/docker.seq_.jpg" alt="" width="368" height="256" srcset="https://coolshell.cn/wp-content/uploads/2015/08/docker.seq_.jpg 368w, https://coolshell.cn/wp-content/uploads/2015/08/docker.seq_-300x209.jpg 300w" sizes="(max-width: 368px) 100vw, 368px" /></a></p>
<p>&nbsp;</p>
<p style="text-align: center;"><strong>顺序读写</strong></p>
<p><a href="https://coolshell.cn/wp-content/uploads/2015/08/docker.rand_.jpg"><img decoding="async" loading="lazy" class="aligncenter size-full wp-image-17190" src="https://coolshell.cn/wp-content/uploads/2015/08/docker.rand_.jpg" alt="" width="363" height="236" srcset="https://coolshell.cn/wp-content/uploads/2015/08/docker.rand_.jpg 363w, https://coolshell.cn/wp-content/uploads/2015/08/docker.rand_-300x195.jpg 300w" sizes="(max-width: 363px) 100vw, 363px" /></a></p>
<p>&nbsp;</p>
<p style="text-align: center;"><strong>随机读写</strong></p>
<h4>延伸阅读</h4>
<ul>
<li><a href="http://www.linuxjournal.com/article/7714" target="_blank">Introduce UnionFS</a></li>
<li><a href="http://lwn.net/Articles/325369/" target="_blank">Union file systems: Implementations, part I</a></li>
<li><a href="http://lwn.net/Articles/327738/" target="_blank">Union file systems: Implementations, part 2</a></li>
<li><a href="http://lwn.net/Articles/403012/" target="_blank">Another union filesystem approach</a></li>
<li><a href="http://lwn.net/Articles/324291/" target="_blank">Unioning file systems: Architecture, features, and design choices</a></li>
</ul>
<p>（全文完）<!--



<p align="center"><a href= target=_blank><img decoding="async" src=""></a></p>





<p align="center"><img decoding="async" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.weixin.jpg"> <img decoding="async" loading="lazy" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.mini_.jpg" width="300" height="300"> <br />关注CoolShell微信公众账号和微信小程序</p>

 

--></p>
<div style="margin-top: 15px; font-size: 16px;color: #cc0000;">
<p align="center"><strong>（转载本站文章请注明作者和出处 <a href="https://coolshell.cn/">酷 壳 &#8211; CoolShell</a> ，请勿用于任何商业用途）</strong></p>
</div>

<div class="wp_rp_wrap  wp_rp_vertical_m" ><div class="wp_rp_content"><h3 class="related_post_title">相关文章</h3><ul class="related_post wp_rp"><li ><a href="https://coolshell.cn/articles/18654.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2018/12/docker-networking-1-150x150.png" alt="记一次Kubernetes/Docker网络排障" width="150" height="150" /></a><a href="https://coolshell.cn/articles/18654.html" class="wp_rp_title">记一次Kubernetes/Docker网络排障</a></li><li ><a href="https://coolshell.cn/articles/17200.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2015/08/how_to_set_up_an_iSCSI_LUN_with_thin-150x150.jpg" alt="Docker基础技术：DeviceMapper" width="150" height="150" /></a><a href="https://coolshell.cn/articles/17200.html" class="wp_rp_title">Docker基础技术：DeviceMapper</a></li><li ><a href="https://coolshell.cn/articles/17049.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2015/04/filter-150x150.png" alt="Docker基础技术：Linux CGroup" width="150" height="150" /></a><a href="https://coolshell.cn/articles/17049.html" class="wp_rp_title">Docker基础技术：Linux CGroup</a></li><li ><a href="https://coolshell.cn/articles/17010.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2015/04/isolation-150x150.jpg" alt="Docker基础技术：Linux Namespace（上）" width="150" height="150" /></a><a href="https://coolshell.cn/articles/17010.html" class="wp_rp_title">Docker基础技术：Linux Namespace（上）</a></li><li ><a href="https://coolshell.cn/articles/17029.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2015/04/jail_cell-150x150.jpg" alt="Docker基础技术：Linux Namespace（下）" width="150" height="150" /></a><a href="https://coolshell.cn/articles/17029.html" class="wp_rp_title">Docker基础技术：Linux Namespace（下）</a></li><li ><a href="https://coolshell.cn/articles/22320.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2022/12/eBPF-150x150.jpeg" alt="eBPF 介绍" width="150" height="150" /></a><a href="https://coolshell.cn/articles/22320.html" class="wp_rp_title">eBPF 介绍</a></li></ul></div></div>The post <a href="https://coolshell.cn/articles/17061.html">Docker基础技术：AUFS</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></content:encoded>
					
					<wfw:commentRss>https://coolshell.cn/articles/17061.html/feed</wfw:commentRss>
			<slash:comments>42</slash:comments>
		
		
			</item>
		<item>
		<title>Docker基础技术：Linux CGroup</title>
		<link>https://coolshell.cn/articles/17049.html</link>
					<comments>https://coolshell.cn/articles/17049.html#comments</comments>
		
		<dc:creator><![CDATA[陈皓]]></dc:creator>
		<pubDate>Fri, 17 Apr 2015 01:03:57 +0000</pubDate>
				<category><![CDATA[Unix/Linux]]></category>
		<category><![CDATA[操作系统]]></category>
		<category><![CDATA[杂项资源]]></category>
		<category><![CDATA[cgroup]]></category>
		<category><![CDATA[Docker]]></category>
		<category><![CDATA[Linux]]></category>
		<guid isPermaLink="false">http://coolshell.cn/?p=17049</guid>

					<description><![CDATA[<p>前面，我们介绍了Linux Namespace，但是Namespace解决的问题主要是环境隔离的问题，这只是虚拟化中最最基础的一步，我们还需要解决对计算机资源使...</p>
<p class="read-more"><a class="btn btn-default" href="https://coolshell.cn/articles/17049.html"> Read More<span class="screen-reader-text">  Read More</span></a></p>
The post <a href="https://coolshell.cn/articles/17049.html">Docker基础技术：Linux CGroup</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></description>
										<content:encoded><![CDATA[<p><script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-3415450859608158"
     crossorigin="anonymous"></script><img decoding="async" loading="lazy" src="https://coolshell.cn/wp-content/uploads/2015/04/filter.png" alt="filter" width="224" height="225" class="alignright size-full wp-image-17097" srcset="https://coolshell.cn/wp-content/uploads/2015/04/filter.png 224w, https://coolshell.cn/wp-content/uploads/2015/04/filter-150x150.png 150w, https://coolshell.cn/wp-content/uploads/2015/04/filter-200x200.png 200w" sizes="(max-width: 224px) 100vw, 224px" />前面，我们介绍了<a title="Docker基础技术：Linux Namespace" href="https://coolshell.cn/articles/17010.html" target="_blank">Linux Namespace</a>，但是Namespace解决的问题主要是环境隔离的问题，这只是虚拟化中最最基础的一步，我们还需要解决对计算机资源使用上的隔离。也就是说，虽然你通过Namespace把我Jail到一个特定的环境中去了，但是我在其中的进程使用用CPU、内存、磁盘等这些计算资源其实还是可以随心所欲的。所以，我们希望对进程进行资源利用上的限制或控制。这就是Linux CGroup出来了的原因。</p>
<p>Linux CGroup全称Linux Control Group， 是Linux内核的一个功能，用来限制，控制与分离一个进程组群的资源（如CPU、内存、磁盘输入输出等）。这个项目最早是由Google的工程师在2006年发起（主要是Paul Menage和Rohit Seth），最早的名称为进程容器（process containers）。在2007年时，因为在Linux内核中，容器（container）这个名词太过广泛，为避免混乱，被重命名为cgroup，并且被合并到2.6.24版的内核中去。然后，其它开始了他的发展。</p>
<p>Linux CGroupCgroup 可​​​让​​​您​​​为​​​系​​​统​​​中​​​所​​​运​​​行​​​任​​​务​​​（进​​​程​​​）的​​​用​​​户​​​定​​​义​​​组​​​群​​​分​​​配​​​资​​​源​​​ &#8212; 比​​​如​​​ CPU 时​​​间​​​、​​​系​​​统​​​内​​​存​​​、​​​网​​​络​​​带​​​宽​​​或​​​者​​​这​​​些​​​资​​​源​​​的​​​组​​​合​​​。​​​您​​​可​​​以​​​监​​​控​​​您​​​配​​​置​​​的​​​ cgroup，拒​​​绝​​​ cgroup 访​​​问​​​某​​​些​​​资​​​源​​​，甚​​​至​​​在​​​运​​​行​​​的​​​系​​​统​​​中​​​动​​​态​​​配​​​置​​​您​​​的​​​ cgroup。</p>
<p>主要提供了如下功能：</p>
<p><span id="more-17049"></span></p>
<ul>
<li><strong>Resource limitation</strong>: 限制资源使用，比如内存使用上限以及文件系统的缓存限制。</li>
<li><strong>Prioritization</strong>: 优先级控制，比如：CPU利用和磁盘IO吞吐。</li>
<li><strong>Accounting</strong>: 一些审计或一些统计，主要目的是为了计费。</li>
<li><strong>Control</strong>: 挂起进程，恢复执行进程。</li>
</ul>
<p>使​​​用​​​ cgroup，系​​​统​​​管​​​理​​​员​​​可​​​更​​​具​​​体​​​地​​​控​​​制​​​对​​​系​​​统​​​资​​​源​​​的​​​分​​​配​​​、​​​优​​​先​​​顺​​​序​​​、​​​拒​​​绝​​​、​​​管​​​理​​​和​​​监​​​控​​​。​​​可​​​更​​​好​​​地​​​根​​​据​​​任​​​务​​​和​​​用​​​户​​​分​​​配​​​硬​​​件​​​资​​​源​​​，提​​​高​​​总​​​体​​​效​​​率​​​。</p>
<p>在实践中，系统管理员一般会利用CGroup做下面这些事（有点像为某个虚拟机分配资源似的）：</p>
<ul>
<li>隔离一个进程集合（比如：nginx的所有进程），并限制他们所消费的资源，比如绑定CPU的核。</li>
<li>为这组进程 分配其足够使用的内存</li>
<li>为这组进程分配相应的网络带宽和磁盘存储限制</li>
<li>限制访问某些设备（通过设置设备的白名单）</li>
</ul>
<p>那么CGroup是怎么干的呢？我们先来点感性认识吧。</p>
<p>首先，Linux把CGroup这个事实现成了一个file system，你可以mount。在我的Ubuntu 14.04下，你输入以下命令你就可以看到cgroup已为你mount好了。</p>
<pre data-enlighter-language="shell" class="EnlighterJSRAW">hchen@ubuntu:~$ mount -t cgroup
cgroup on /sys/fs/cgroup/cpuset type cgroup (rw,relatime,cpuset)
cgroup on /sys/fs/cgroup/cpu type cgroup (rw,relatime,cpu)
cgroup on /sys/fs/cgroup/cpuacct type cgroup (rw,relatime,cpuacct)
cgroup on /sys/fs/cgroup/memory type cgroup (rw,relatime,memory)
cgroup on /sys/fs/cgroup/devices type cgroup (rw,relatime,devices)
cgroup on /sys/fs/cgroup/freezer type cgroup (rw,relatime,freezer)
cgroup on /sys/fs/cgroup/blkio type cgroup (rw,relatime,blkio)
cgroup on /sys/fs/cgroup/net_prio type cgroup (rw,net_prio)
cgroup on /sys/fs/cgroup/net_cls type cgroup (rw,net_cls)
cgroup on /sys/fs/cgroup/perf_event type cgroup (rw,relatime,perf_event)
cgroup on /sys/fs/cgroup/hugetlb type cgroup (rw,relatime,hugetlb)</pre>
<p>或者使用lssubsys命令：</p>
<pre data-enlighter-language="shell" class="EnlighterJSRAW">$ lssubsys  -m
cpuset /sys/fs/cgroup/cpuset
cpu /sys/fs/cgroup/cpu
cpuacct /sys/fs/cgroup/cpuacct
memory /sys/fs/cgroup/memory
devices /sys/fs/cgroup/devices
freezer /sys/fs/cgroup/freezer
blkio /sys/fs/cgroup/blkio
net_cls /sys/fs/cgroup/net_cls
net_prio /sys/fs/cgroup/net_prio
perf_event /sys/fs/cgroup/perf_event
hugetlb /sys/fs/cgroup/hugetlb</pre>
<p>我们可以看到，在/sys/fs下有一个cgroup的目录，这个目录下还有很多子目录，比如： cpu，cpuset，memory，blkio……这些，这些都是cgroup的子系统。分别用于干不同的事的。</p>
<p>如果你没有看到上述的目录，你可以自己mount，下面给了一个示例：</p>
<pre data-enlighter-language="shell" class="EnlighterJSRAW">mkdir cgroup
mount -t tmpfs cgroup_root ./cgroup
mkdir cgroup/cpuset
mount -t cgroup -ocpuset cpuset ./cgroup/cpuset/
mkdir cgroup/cpu
mount -t cgroup -ocpu cpu ./cgroup/cpu/
mkdir cgroup/memory
mount -t cgroup -omemory memory ./cgroup/memory/</pre>
<p>一旦mount成功，你就会看到这些目录下就有好文件了，比如，如下所示的cpu和cpuset的子系统：</p>
<pre data-enlighter-language="shell" class="EnlighterJSRAW">hchen@ubuntu:~$ ls /sys/fs/cgroup/cpu /sys/fs/cgroup/cpuset/ 
/sys/fs/cgroup/cpu:
cgroup.clone_children  cgroup.sane_behavior  cpu.shares         release_agent
cgroup.event_control   cpu.cfs_period_us     cpu.stat           tasks
cgroup.procs           cpu.cfs_quota_us      notify_on_release  user

/sys/fs/cgroup/cpuset/:
cgroup.clone_children  cpuset.mem_hardwall             cpuset.sched_load_balance
cgroup.event_control   cpuset.memory_migrate           cpuset.sched_relax_domain_level
cgroup.procs           cpuset.memory_pressure          notify_on_release
cgroup.sane_behavior   cpuset.memory_pressure_enabled  release_agent
cpuset.cpu_exclusive   cpuset.memory_spread_page       tasks
cpuset.cpus            cpuset.memory_spread_slab       user
cpuset.mem_exclusive   cpuset.mems</pre>
<p>你可以到/sys/fs/cgroup的各个子目录下去make个dir，你会发现，一旦你创建了一个子目录，这个子目录里又有很多文件了。</p>
<pre data-enlighter-language="shell" class="EnlighterJSRAW">hchen@ubuntu:/sys/fs/cgroup/cpu$ sudo mkdir haoel
[sudo] password for hchen: 
hchen@ubuntu:/sys/fs/cgroup/cpu$ ls ./haoel
cgroup.clone_children  cgroup.procs       cpu.cfs_quota_us  cpu.stat           tasks
cgroup.event_control   cpu.cfs_period_us  cpu.shares        notify_on_release</pre>
<p>好了，我们来看几个示例。</p>
<h4>CPU 限制</h4>
<p>假设，我们有一个非常吃CPU的程序，叫deadloop，其源码如下：</p>
<pre data-enlighter-language="c" class="EnlighterJSRAW">int main(void)
{
    int i = 0;
    for(;;) i++;
    return 0;
}</pre>
<p>用sudo执行起来后，毫无疑问，CPU被干到了100%（下面是top命令的输出）</p>
<pre data-enlighter-language="shell" class="EnlighterJSRAW">  PID USER      PR  NI    VIRT    RES    SHR S %CPU %MEM     TIME+ COMMAND     
 3529 root      20   0    4196    736    656 R 99.6  0.1   0:23.13 deadloop   </pre>
<p>然后，我们这前不是在/sys/fs/cgroup/cpu下创建了一个haoel的group。我们先设置一下这个group的cpu利用的限制：</p>
<pre data-enlighter-language="shell" class="EnlighterJSRAW">hchen@ubuntu:~# cat /sys/fs/cgroup/cpu/haoel/cpu.cfs_quota_us 
-1
root@ubuntu:~# echo 20000 &gt; /sys/fs/cgroup/cpu/haoel/cpu.cfs_quota_us</pre>
<p>我们看到，这个进程的PID是3529，我们把这个进程加到这个cgroup中：</p>
<p><code data-enlighter-language="shell" class="EnlighterJSRAW"># echo 3529 &gt;&gt; /sys/fs/cgroup/cpu/haoel/tasks</code></p>
<p>然后，就会在top中看到CPU的利用立马下降成20%了。（前面我们设置的20000就是20%的意思）</p>
<pre data-enlighter-language="shell" class="EnlighterJSRAW">  PID USER      PR  NI    VIRT    RES    SHR S %CPU %MEM     TIME+ COMMAND     
 3529 root      20   0    4196    736    656 R 19.9  0.1   8:06.11 deadloop    </pre>
<p>下面的代码是一个线程的示例：</p>
<pre data-enlighter-language="c" class="EnlighterJSRAW">#define _GNU_SOURCE         /* See feature_test_macros(7) */

#include &lt;pthread.h&gt;
#include &lt;stdio.h&gt;
#include &lt;stdlib.h&gt;
#include &lt;sys/stat.h&gt;
#include &lt;sys/types.h&gt;
#include &lt;unistd.h&gt;
#include &lt;sys/syscall.h&gt;


const int NUM_THREADS = 5;

void *thread_main(void *threadid)
{
    /* 把自己加入cgroup中（syscall(SYS_gettid)为得到线程的系统tid） */
    char cmd[128];
    sprintf(cmd, &quot;echo %ld &gt;&gt; /sys/fs/cgroup/cpu/haoel/tasks&quot;, syscall(SYS_gettid));
    system(cmd); 
    sprintf(cmd, &quot;echo %ld &gt;&gt; /sys/fs/cgroup/cpuset/haoel/tasks&quot;, syscall(SYS_gettid));
    system(cmd);

    long tid;
    tid = (long)threadid;
    printf(&quot;Hello World! It&#039;s me, thread #%ld, pid #%ld!\n&quot;, tid, syscall(SYS_gettid));
    
    int a=0; 
    while(1) {
        a++;
    }
    pthread_exit(NULL);
}
int main (int argc, char *argv[])
{
    int num_threads;
    if (argc &gt; 1){
        num_threads = atoi(argv[1]);
    }
    if (num_threads&lt;=0 || num_threads&gt;=100){
        num_threads = NUM_THREADS;
    }

    /* 设置CPU利用率为50% */
    mkdir(&quot;/sys/fs/cgroup/cpu/haoel&quot;, 755);
    system(&quot;echo 50000 &gt; /sys/fs/cgroup/cpu/haoel/cpu.cfs_quota_us&quot;);

    mkdir(&quot;/sys/fs/cgroup/cpuset/haoel&quot;, 755);
    /* 限制CPU只能使用#2核和#3核 */
    system(&quot;echo \&quot;2,3\&quot; &gt; /sys/fs/cgroup/cpuset/haoel/cpuset.cpus&quot;);

    pthread_t* threads = (pthread_t*) malloc (sizeof(pthread_t)*num_threads);
    int rc;
    long t;
    for(t=0; t&lt;num_threads; t++){
        printf(&quot;In main: creating thread %ld\n&quot;, t);
        rc = pthread_create(&amp;threads[t], NULL, thread_main, (void *)t);
        if (rc){
            printf(&quot;ERROR; return code from pthread_create() is %d\n&quot;, rc);
            exit(-1);
        }
    }

    /* Last thing that main() should do */
    pthread_exit(NULL);
    free(threads);
}
</pre>
<h4>内存使用限制</h4>
<p>我们再来看一个限制内存的例子（下面的代码是个死循环，其它不断的分配内存，每次512个字节，每次休息一秒）：</p>
<pre data-enlighter-language="c" class="EnlighterJSRAW">#include &lt;stdio.h&gt;
#include &lt;stdlib.h&gt;
#include &lt;string.h&gt;
#include &lt;sys/types.h&gt;
#include &lt;unistd.h&gt;

int main(void)
{
    int size = 0;
    int chunk_size = 512;
    void *p = NULL;

    while(1) {

        if ((p = malloc(p, chunk_size)) == NULL) {
            printf(&quot;out of memory!!\n&quot;);
            break;
        }
        memset(p, 1, chunk_size);
        size += chunk_size;
        printf(&quot;[%d] - memory is allocated [%8d] bytes \n&quot;, getpid(), size);
        sleep(1);
    }
    return 0;
}</pre>
<p>然后，在我们另外一边：</p>
<pre data-enlighter-language="shell" class="EnlighterJSRAW"># 创建memory cgroup
$ mkdir /sys/fs/cgroup/memory/haoel
$ echo 64k &gt; /sys/fs/cgroup/memory/haoel/memory.limit_in_bytes

# 把上面的进程的pid加入这个cgroup
$ echo [pid] &gt; /sys/fs/cgroup/memory/haoel/tasks </pre>
<p>你会看到，一会上面的进程就会因为内存问题被kill掉了。</p>
<h4>磁盘I/O限制</h4>
<p>我们先看一下我们的硬盘IO，我们的模拟命令如下：（从/dev/sda1上读入数据，输出到/dev/null上）</p>
<p><code data-enlighter-language="shell" class="EnlighterJSRAW">sudo dd if=/dev/sda1 of=/dev/null</code></p>
<p>我们通过iotop命令我们可以看到相关的IO速度是55MB/s（虚拟机内）：</p>
<pre data-enlighter-language="shell" class="EnlighterJSRAW">  TID  PRIO  USER     DISK READ  DISK WRITE  SWAPIN     IO&gt;    COMMAND          
 8128 be/4 root       55.74 M/s    0.00 B/s  0.00 % 85.65 % dd if=/de~=/dev/null...</pre>
<p>然后，我们先创建一个blkio（块设备IO）的cgroup</p>
<p><code data-enlighter-language="shell" class="EnlighterJSRAW">mkdir /sys/fs/cgroup/blkio/haoel</code></p>
<p>并把读IO限制到1MB/s，并把前面那个dd命令的pid放进去（注：8:0 是设备号，你可以通过ls -l /dev/sda1获得）：</p>
<pre data-enlighter-language="shell" class="EnlighterJSRAW">root@ubuntu:~# echo &#039;8:0 1048576&#039;  &gt; /sys/fs/cgroup/blkio/haoel/blkio.throttle.read_bps_device 
root@ubuntu:~# echo 8128 &gt; /sys/fs/cgroup/blkio/haoel/tasks</pre>
<p>再用iotop命令，你马上就能看到读速度被限制到了1MB/s左右。</p>
<pre data-enlighter-language="shell" class="EnlighterJSRAW">  TID  PRIO  USER     DISK READ  DISK WRITE  SWAPIN     IO&gt;    COMMAND          
 8128 be/4 root      973.20 K/s    0.00 B/s  0.00 % 94.41 % dd if=/de~=/dev/null...</pre>
<h4>CGroup的子系统</h4>
<p>好了，有了以上的感性认识我们来，我们来看看control group有哪些子系统：</p>
<ul>
<li>blkio &#8212; 这​​​个​​​子​​​系​​​统​​​为​​​块​​​设​​​备​​​设​​​定​​​输​​​入​​​/输​​​出​​​限​​​制​​​，比​​​如​​​物​​​理​​​设​​​备​​​（磁​​​盘​​​，固​​​态​​​硬​​​盘​​​，USB 等​​​等​​​）。</li>
<li>cpu &#8212; 这​​​个​​​子​​​系​​​统​​​使​​​用​​​调​​​度​​​程​​​序​​​提​​​供​​​对​​​ CPU 的​​​ cgroup 任​​​务​​​访​​​问​​​。​​​</li>
<li>cpuacct &#8212; 这​​​个​​​子​​​系​​​统​​​自​​​动​​​生​​​成​​​ cgroup 中​​​任​​​务​​​所​​​使​​​用​​​的​​​ CPU 报​​​告​​​。​​​</li>
<li>cpuset &#8212; 这​​​个​​​子​​​系​​​统​​​为​​​ cgroup 中​​​的​​​任​​​务​​​分​​​配​​​独​​​立​​​ CPU（在​​​多​​​核​​​系​​​统​​​）和​​​内​​​存​​​节​​​点​​​。​​​</li>
<li>devices &#8212; 这​​​个​​​子​​​系​​​统​​​可​​​允​​​许​​​或​​​者​​​拒​​​绝​​​ cgroup 中​​​的​​​任​​​务​​​访​​​问​​​设​​​备​​​。​​​</li>
<li>freezer &#8212; 这​​​个​​​子​​​系​​​统​​​挂​​​起​​​或​​​者​​​恢​​​复​​​ cgroup 中​​​的​​​任​​​务​​​。​​​</li>
<li>memory &#8212; 这​​​个​​​子​​​系​​​统​​​设​​​定​​​ cgroup 中​​​任​​​务​​​使​​​用​​​的​​​内​​​存​​​限​​​制​​​，并​​​自​​​动​​​生​​​成​​​​​内​​​存​​​资​​​源使用​​​报​​​告​​​。​​​</li>
<li>net_cls &#8212; 这​​​个​​​子​​​系​​​统​​​使​​​用​​​等​​​级​​​识​​​别​​​符​​​（classid）标​​​记​​​网​​​络​​​数​​​据​​​包​​​，可​​​允​​​许​​​ Linux 流​​​量​​​控​​​制​​​程​​​序​​​（tc）识​​​别​​​从​​​具​​​体​​​ cgroup 中​​​生​​​成​​​的​​​数​​​据​​​包​​​。​​​</li>
<li>net_prio &#8212; 这个子系统用来设计网络流量的优先级</li>
<li>hugetlb &#8212; 这个子系统主要针对于HugeTLB系统进行限制，这是一个大页文件系统。</li>
<p>​​​</ul>
<p>注意，你可能在Ubuntu 14.04下看不到net_cls和net_prio这两个cgroup，你需要手动mount一下：</p>
<pre data-enlighter-language="shell" class="EnlighterJSRAW">$ sudo modprobe cls_cgroup
$ sudo mkdir /sys/fs/cgroup/net_cls
$ sudo mount -t cgroup -o net_cls none /sys/fs/cgroup/net_cls

$ sudo modprobe netprio_cgroup
$ sudo mkdir /sys/fs/cgroup/net_prio
$ sudo mount -t cgroup -o net_prio none /sys/fs/cgroup/net_prio</pre>
<p>关于各个子系统的参数细节，以及更多的Linux CGroup的文档，你可以看看下面的文档：</p>
<ul>
<li><a href="https://www.kernel.org/doc/Documentation/cgroups/" target="_blank">Linux Kernel的官方文档</a></li>
<li><a href="https://access.redhat.com/documentation/zh-CN/Red_Hat_Enterprise_Linux/6/html-single/Resource_Management_Guide/index.html#ch-Subsystems_and_Tunable_Parameters" target="_blank">Redhat的官方文档</a></li>
</ul>
<h4>CGroup的术语</h4>
<p>CGroup有下述术语：</p>
<ul>
<li><strong>任务（Tasks）</strong>：就是系统的一个进程。</li>
<li><strong>控制组（Control Group）</strong>：一组按照某种标准划分的进程，比如官方文档中的Professor和Student，或是WWW和System之类的，其表示了某进程组。Cgroups中的资源控制都是以控制组为单位实现。一个进程可以加入到某个控制组。而资源的限制是定义在这个组上，就像上面示例中我用的haoel一样。简单点说，cgroup的呈现就是一个目录带一系列的可配置文件。</li>
<li><strong>层级（Hierarchy）</strong>：控制组可以组织成hierarchical的形式，既一颗控制组的树（目录结构）。控制组树上的子节点继承父结点的属性。简单点说，hierarchy就是在一个或多个子系统上的cgroups目录树。</li>
<li><strong>子系统（Subsystem）</strong>：一个子系统就是一个资源控制器，比如CPU子系统就是控制CPU时间分配的一个控制器。子系统必须附加到一个层级上才能起作用，一个子系统附加到某个层级以后，这个层级上的所有控制族群都受到这个子系统的控制。Cgroup的子系统可以有很多，也在不断增加中。</li>
</ul>
<h4>下一代的CGroup</h4>
<p>上面，我们可以看到，CGroup的一些常用方法和相关的术语。一般来说，这样的设计在一般情况下还是没什么问题的，除了操作上的用户体验不是很好，但基本满足我们的一般需求了。</p>
<p>不过，对此，有个叫Tejun Heo的同学非常不爽，他在Linux社区里<a href="https://lwn.net/Articles/484254/" target="_blank">对cgroup吐了一把槽</a>，还引发了内核组的各种讨论。</p>
<p>对于Tejun Heo同学来说，cgroup设计的相当糟糕。他给出了些例子，大意就是说，如果有多种层级关系，也就是说有多种对进程的分类方式，比如，我们可以按用户来分，分成Professor和Student，同时，也有按应用类似来分的，比如WWW和NFS等。那么，当一个进程即是Professor的，也是WWW的，那么就会出现多层级正交的情况，从而出现对进程上管理的混乱。另外，一个case是，如果有一个层级A绑定cpu，而层级B绑定memory，还有一个层级C绑定cputset，而有一些进程有的需要AB，有的需要AC，有的需要ABC，管理起来就相当不易。 </p>
<p>层级操作起来比较麻烦，而且如果层级变多，更不易于操作和管理，虽然那种方式很好实现，但是在使用上有很多的复杂度。你可以想像一个图书馆的图书分类问题，你可以有各种不同的分类，分类和图书就是一种多对多的关系。</p>
<p>所以，在Kernel 3.16后，引入了<a href="http://lwn.net/Articles/601840/" target="_blank">unified hierarchy</a>的新的设计，这个东西引入了一个叫<strong>__DEVEL__sane_behavior</strong>的特性（这个名字很明显意味目前还在开发试验阶段），它可以把所有子系统都挂载到根层级下，只有叶子节点可以存在tasks，非叶子节点只进行资源控制。</p>
<p>我们mount一下看看：</p>
<pre data-enlighter-language="shell" class="EnlighterJSRAW">$ sudo mount -t cgroup -o __DEVEL__sane_behavior cgroup ./cgroup

$ ls ./cgroup
cgroup.controllers  cgroup.procs  cgroup.sane_behavior  cgroup.subtree_control 

$ cat ./cgroup/cgroup.controllers
cpuset cpu cpuacct memory devices freezer net_cls blkio perf_event net_prio hugetlb</pre>
<p>我们可以看到有四个文件，然后，你在这里mkdir一个子目录，里面也会有这四个文件。<strong>上级的cgroup.subtree_control控制下级的cgroup.controllers。</strong></p>
<p>举个例子：假设我们有以下的目录结构，b代表blkio，m代码memory，其中，A是root，包括所有的子系统（）。</p>
<pre data-enlighter-language="shell" class="EnlighterJSRAW">
# A(b,m) - B(b,m) - C (b)
#               \ - D (b) - E

# 下面的命令中， +表示enable， -表示disable

# 在B上的enable blkio
# echo +blkio &gt; A/cgroup.subtree_control

# 在C和D上enable blkio 
# echo +blkio &gt; A/B/cgroup.subtree_control

# 在B上enable memory  
# echo +memory &gt; A/cgroup.subtree_control</pre>
<p>在上述的结构中，</p>
<ul>
<li>cgroup只有上线控制下级，无法传递到下下级。所以，C和D中没有memory的限制，E中没有blkio和memory的限制。而本层的cgroup.controllers文件是个只读的，其中的内容就看上级的subtree_control里有什么了。</li>
<li><strong>任何被配置过subtree_control的目录都不能绑定进程，根结点除外</strong>。所以，A,C,D,E可以绑上进程，但是B不行。</li>
</ul>
<p>我们可以看到，<strong>这种方式干净的区分开了两个事，一个是进程的分组，一个是对分组的资源控制</strong>（以前这两个事完全混在一起），在目录继承上增加了些限制，这样可以避免一些模棱两可的情况。</p>
<p>当然，这个事还在演化中，cgroup的这些问题这个事目前由cgroup的吐槽人Tejun Heo和华为的Li Zefan同学负责解决中。总之，这是一个系统管理上的问题，而且改变会影响很多东西，但一旦方案确定，老的cgroup方式将一去不复返。</p>
<h4>参考</h4>
<ul>
<li><a href="https://www.kernel.org/doc/Documentation/cgroups/" target="_blank">Linux Kernel Cgroup Documents</a></li>
<li><a href="https://access.redhat.com/documentation/zh-CN/Red_Hat_Enterprise_Linux/6/html-single/Resource_Management_Guide/index.html" target="_blank">Reahat Resource Management Guide</a></li>
<li><a href="https://lwn.net/Articles/484251/" target="_blank">Fixing control groups</a></li>
<li><a href="http://lwn.net/Articles/601840/" target="_blank">The unified control group hierarchy in 3.16</a></li>
<li><a href="http://events.linuxfoundation.org/sites/events/files/slides/2014-KLF.pdf" target="_blank">Cgroup v2(PDF)</a></li>
</ul>
<p>（全文完）<br />
<!--



<p align="center"><a href= target=_blank><img decoding="async" src=""></a></p>





<p align="center"><img decoding="async" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.weixin.jpg"> <img decoding="async" loading="lazy" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.mini_.jpg" width="300" height="300"> <br />关注CoolShell微信公众账号和微信小程序</p>

 

--></p>
<div style="margin-top: 15px; font-size: 16px;color: #cc0000;">
<p align="center"><strong>（转载本站文章请注明作者和出处 <a href="https://coolshell.cn/">酷 壳 &#8211; CoolShell</a> ，请勿用于任何商业用途）</strong></p>
</div>

<div class="wp_rp_wrap  wp_rp_vertical_m" ><div class="wp_rp_content"><h3 class="related_post_title">相关文章</h3><ul class="related_post wp_rp"><li ><a href="https://coolshell.cn/articles/18654.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2018/12/docker-networking-1-150x150.png" alt="记一次Kubernetes/Docker网络排障" width="150" height="150" /></a><a href="https://coolshell.cn/articles/18654.html" class="wp_rp_title">记一次Kubernetes/Docker网络排障</a></li><li ><a href="https://coolshell.cn/articles/17200.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2015/08/how_to_set_up_an_iSCSI_LUN_with_thin-150x150.jpg" alt="Docker基础技术：DeviceMapper" width="150" height="150" /></a><a href="https://coolshell.cn/articles/17200.html" class="wp_rp_title">Docker基础技术：DeviceMapper</a></li><li ><a href="https://coolshell.cn/articles/17061.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2015/08/docker-filesystems-busyboxrw-150x150.png" alt="Docker基础技术：AUFS" width="150" height="150" /></a><a href="https://coolshell.cn/articles/17061.html" class="wp_rp_title">Docker基础技术：AUFS</a></li><li ><a href="https://coolshell.cn/articles/17010.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2015/04/isolation-150x150.jpg" alt="Docker基础技术：Linux Namespace（上）" width="150" height="150" /></a><a href="https://coolshell.cn/articles/17010.html" class="wp_rp_title">Docker基础技术：Linux Namespace（上）</a></li><li ><a href="https://coolshell.cn/articles/17029.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2015/04/jail_cell-150x150.jpg" alt="Docker基础技术：Linux Namespace（下）" width="150" height="150" /></a><a href="https://coolshell.cn/articles/17029.html" class="wp_rp_title">Docker基础技术：Linux Namespace（下）</a></li><li ><a href="https://coolshell.cn/articles/22320.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2022/12/eBPF-150x150.jpeg" alt="eBPF 介绍" width="150" height="150" /></a><a href="https://coolshell.cn/articles/22320.html" class="wp_rp_title">eBPF 介绍</a></li></ul></div></div>The post <a href="https://coolshell.cn/articles/17049.html">Docker基础技术：Linux CGroup</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></content:encoded>
					
					<wfw:commentRss>https://coolshell.cn/articles/17049.html/feed</wfw:commentRss>
			<slash:comments>87</slash:comments>
		
		
			</item>
		<item>
		<title>Docker基础技术：Linux Namespace（上）</title>
		<link>https://coolshell.cn/articles/17010.html</link>
					<comments>https://coolshell.cn/articles/17010.html#comments</comments>
		
		<dc:creator><![CDATA[陈皓]]></dc:creator>
		<pubDate>Thu, 16 Apr 2015 02:20:08 +0000</pubDate>
				<category><![CDATA[Unix/Linux]]></category>
		<category><![CDATA[Docker]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[Mount]]></category>
		<category><![CDATA[Namespace]]></category>
		<guid isPermaLink="false">http://coolshell.cn/?p=17010</guid>

					<description><![CDATA[<p>时下最热的技术莫过于Docker了，很多人都觉得Docker是个新技术，其实不然，Docker除了其编程语言用go比较新外，其实它还真不是个新东西，也就是个新瓶...</p>
<p class="read-more"><a class="btn btn-default" href="https://coolshell.cn/articles/17010.html"> Read More<span class="screen-reader-text">  Read More</span></a></p>
The post <a href="https://coolshell.cn/articles/17010.html">Docker基础技术：Linux Namespace（上）</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></description>
										<content:encoded><![CDATA[<p><script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-3415450859608158"
     crossorigin="anonymous"></script><img decoding="async" loading="lazy" class="alignright size-full wp-image-17085" src="https://coolshell.cn/wp-content/uploads/2015/04/isolation.jpg" alt="isolation" width="359" height="237" srcset="https://coolshell.cn/wp-content/uploads/2015/04/isolation.jpg 359w, https://coolshell.cn/wp-content/uploads/2015/04/isolation-300x198.jpg 300w" sizes="(max-width: 359px) 100vw, 359px" />时下最热的技术莫过于Docker了，很多人都觉得Docker是个新技术，其实不然，Docker除了其编程语言用go比较新外，其实它还真不是个新东西，也就是个新瓶装旧酒的东西，所谓的The New &#8220;Old Stuff&#8221;。Docker和Docker衍生的东西用到了很多很酷的技术，我会用几篇 文章来把这些技术给大家做个介绍，希望通过这些文章大家可以自己打造一个山寨版的docker。</p>
<p>当然，文章的风格一定会尊重时下的“流行”——<strong>我们再也没有整块整块的时间去看书去专研，而我们只有看微博微信那样的碎片时间</strong>（那怕我们有整块的时间，也被那些在手机上的APP碎片化了）。所以，这些文章的风格必然坚持“马桶风格”（希望简单到占用你拉一泡屎就时间，而且你还不用动脑子，并能学到些东西）</p>
<p>废话少说，我们开始。先从Linux Namespace开始。</p>
<h4> 简介</h4>
<p>Linux Namespace是Linux提供的一种内核级别环境隔离的方法。不知道你是否还记得很早以前的Unix有一个叫chroot的系统调用（通过修改根目录把用户jail到一个特定目录下），chroot提供了一种简单的隔离模式：chroot内部的文件系统无法访问外部的内容。Linux Namespace在此基础上，提供了对UTS、IPC、mount、PID、network、User等的隔离机制。</p>
<p><span id="more-17010"></span></p>
<p>举个例子，我们都知道，Linux下的超级父亲进程的PID是1，所以，同chroot一样，如果我们可以把用户的进程空间jail到某个进程分支下，并像chroot那样让其下面的进程 看到的那个超级父进程的PID为1，于是就可以达到资源隔离的效果了（不同的PID namespace中的进程无法看到彼此）</p>
<p><b>Linux Namespace 有如下种类</b>，官方文档在这里《<a href="http://lwn.net/Articles/531114/" target="_blank" rel="noopener noreferrer">Namespace in Operation</a>》</p>
<table width="100%">
<thead>
<tr>
<th>分类</th>
<th>系统调用参数</th>
<th>相关内核版本</th>
</tr>
</thead>
<tbody>
<tr>
<td><b>Mount namespaces</b></td>
<td>CLONE_NEWNS</td>
<td><a href="http://lwn.net/2001/0301/a/namespaces.php3" target="_blank" rel="noopener noreferrer">Linux 2.4.19</a></td>
</tr>
<tr>
<td><b>UTS namespaces</b></td>
<td>CLONE_NEWUTS</td>
<td><a href="http://lwn.net/Articles/179345/" target="_blank" rel="noopener noreferrer">Linux 2.6.19</a></td>
</tr>
<tr>
<td><b>IPC namespaces</b></td>
<td>CLONE_NEWIPC</td>
<td><a href="http://lwn.net/Articles/187274/" target="_blank" rel="noopener noreferrer">Linux 2.6.19</a></td>
</tr>
<tr>
<td><b>PID namespaces</b></td>
<td>CLONE_NEWPID</td>
<td><a href="http://lwn.net/Articles/259217/" target="_blank" rel="noopener noreferrer">Linux 2.6.24</a></td>
</tr>
<tr>
<td><b>Network namespaces</b></td>
<td>CLONE_NEWNET</td>
<td><a href="http://lwn.net/Articles/219794/" target="_blank" rel="noopener noreferrer">始于Linux 2.6.24 完成于 Linux 2.6.29</a></td>
</tr>
<tr>
<td><b>User namespaces</b></td>
<td>CLONE_NEWUSER</td>
<td><a href="http://lwn.net/Articles/528078/" target="_blank" rel="noopener noreferrer">始于 Linux 2.6.23 完成于 Linux 3.8)</a></td>
</tr>
</tbody>
</table>
<p>主要是三个系统调用</p>
<ul>
<li><b><code>clone</code></b><b>() </b>&#8211; 实现线程的系统调用，用来创建一个新的进程，并可以通过设计上述参数达到隔离。</li>
<li><b><code>unshare</code></b><b>() </b>&#8211; 使某进程脱离某个namespace</li>
<li><b><code>setns</code></b><b>() </b>&#8211; 把某进程加入到某个namespace</li>
</ul>
<p>unshare() 和 setns() 都比较简单，大家可以自己man，我这里不说了。</p>
<p>下面还是让我们来看一些示例（以下的测试程序最好在Linux 内核为3.8以上的版本中运行，我用的是ubuntu 14.04）。</p>
<h4>clone()系统调用</h4>
<p>首先，我们来看一下一个最简单的clone()系统调用的示例，（后面，我们的程序都会基于这个程序做修改）：</p>
<pre class="EnlighterJSRAW" data-enlighter-language="c">#define _GNU_SOURCE
#include &lt;sys/types.h&gt;
#include &lt;sys/wait.h&gt;
#include &lt;stdio.h&gt;
#include &lt;sched.h&gt;
#include &lt;signal.h&gt;
#include &lt;unistd.h&gt;

/* 定义一个给 clone 用的栈，栈大小1M */
#define STACK_SIZE (1024 * 1024)
static char container_stack[STACK_SIZE];

char* const container_args[] = {
    "/bin/bash",
    NULL
};

int container_main(void* arg)
{
    printf("Container - inside the container!\n");
    /* 直接执行一个shell，以便我们观察这个进程空间里的资源是否被隔离了 */
    execv(container_args[0], container_args); 
    printf("Something's wrong!\n");
    return 1;
}

int main()
{
    printf("Parent - start a container!\n");
    /* 调用clone函数，其中传出一个函数，还有一个栈空间的（为什么传尾指针，因为栈是反着的） */
    int container_pid = clone(container_main, container_stack+STACK_SIZE, SIGCHLD, NULL);
    /* 等待子进程结束 */
    waitpid(container_pid, NULL, 0);
    printf("Parent - container stopped!\n");
    return 0;
}</pre>
<p>从上面的程序，我们可以看到，这和pthread基本上是一样的玩法。但是，对于上面的程序，父子进程的进程空间是没有什么差别的，父进程能访问到的子进程也能。</p>
<p>下面， 让我们来看几个例子看看，Linux的Namespace是什么样的。</p>
<h4>UTS Namespace</h4>
<p>下面的代码，我略去了上面那些头文件和数据结构的定义，只有最重要的部分。</p>
<pre class="EnlighterJSRAW" data-enlighter-language="c" data-enlighter-highlight="4,14">int container_main(void* arg)
{
    printf("Container - inside the container!\n");
    sethostname("container",10); /* 设置hostname */
    execv(container_args[0], container_args);
    printf("Something's wrong!\n");
    return 1;
}

int main()
{
    printf("Parent - start a container!\n");
    int container_pid = clone(container_main, container_stack+STACK_SIZE, 
            CLONE_NEWUTS | SIGCHLD, NULL); /*启用CLONE_NEWUTS Namespace隔离 */
    waitpid(container_pid, NULL, 0);
    printf("Parent - container stopped!\n");
    return 0;
}</pre>
<pre>运行上面的程序你会发现（需要root权限），子进程的hostname变成了 container。</pre>
<pre class="EnlighterJSRAW" data-enlighter-language="shell">hchen@ubuntu:~$ sudo ./uts
Parent - start a container!
Container - inside the container!
root@container:~# hostname
container
root@container:~# uname -n
container</pre>
<h4>IPC Namespace</h4>
<p>IPC全称 Inter-Process Communication，是Unix/Linux下进程间通信的一种方式，IPC有共享内存、信号量、消息队列等方法。所以，为了隔离，我们也需要把IPC给隔离开来，这样，只有在同一个Namespace下的进程才能相互通信。如果你熟悉IPC的原理的话，你会知道，IPC需要有一个全局的ID，即然是全局的，那么就意味着我们的Namespace需要对这个ID隔离，不能让别的Namespace的进程看到。</p>
<p>要启动IPC隔离，我们只需要在调用clone时加上CLONE_NEWIPC参数就可以了。</p>
<pre class="EnlighterJSRAW" data-enlighter-language="c">int container_pid = clone(container_main, container_stack+STACK_SIZE, 
            CLONE_NEWUTS | CLONE_NEWIPC | SIGCHLD, NULL);</pre>
<p>首先，我们先创建一个IPC的Queue（如下所示，全局的Queue ID是0）</p>
<pre class="EnlighterJSRAW" data-enlighter-language="shell">hchen@ubuntu:~$ ipcmk -Q 
Message queue id: 0

hchen@ubuntu:~$ ipcs -q
------ Message Queues --------
key        msqid      owner      perms      used-bytes   messages    
0xd0d56eb2 0          hchen      644        0            0</pre>
<p>如果我们运行没有CLONE_NEWIPC的程序，我们会看到，在子进程中还是能看到这个全启的IPC Queue。</p>
<pre class="EnlighterJSRAW" data-enlighter-language="shell">hchen@ubuntu:~$ sudo ./uts 
Parent - start a container!
Container - inside the container!

root@container:~# ipcs -q

------ Message Queues --------
key        msqid      owner      perms      used-bytes   messages    
0xd0d56eb2 0          hchen      644        0            0</pre>
<p>但是，如果我们运行加上了CLONE_NEWIPC的程序，我们就会下面的结果：</p>
<pre class="EnlighterJSRAW" data-enlighter-language="shell">root@ubuntu:~$ sudo./ipc
Parent - start a container!
Container - inside the container!

root@container:~/linux_namespace# ipcs -q

------ Message Queues --------
key        msqid      owner      perms      used-bytes   messages</pre>
<p>我们可以看到IPC已经被隔离了。</p>
<h4>PID Namespace</h4>
<p>我们继续修改上面的程序：</p>
<pre class="EnlighterJSRAW" data-enlighter-language="c" data-enlighter-highlight="4,16">
int container_main(void* arg)
{
    /* 查看子进程的PID，我们可以看到其输出子进程的 pid 为 1 */
    printf("Container [%5d] - inside the container!\n", getpid());
    sethostname("container",10);
    execv(container_args[0], container_args);
    printf("Something's wrong!\n");
    return 1;
}

int main()
{
    printf("Parent [%5d] - start a container!\n", getpid());
    /*启用PID namespace - CLONE_NEWPID*/
    int container_pid = clone(container_main, container_stack+STACK_SIZE, 
            CLONE_NEWUTS | CLONE_NEWPID | SIGCHLD, NULL); 
    waitpid(container_pid, NULL, 0);
    printf("Parent - container stopped!\n");
    return 0;
}</pre>
<p>运行结果如下（我们可以看到，子进程的pid是1了）：</p>
<pre class="EnlighterJSRAW" data-enlighter-language="shell">
hchen@ubuntu:~$ sudo ./pid
Parent [ 3474] - start a container!
Container [ 1] - inside the container!
root@container:~# echo $$
1</pre>
<p>你可能会问，PID为1有个毛用啊？我们知道，在传统的UNIX系统中，PID为1的进程是init，地位非常特殊。他作为所有进程的父进程，有很多特权（比如：屏蔽信号等），另外，其还会为检查所有进程的状态，我们知道，如果某个子进程脱离了父进程（父进程没有wait它），那么init就会负责回收资源并结束这个子进程。所以，要做到进程空间的隔离，首先要创建出PID为1的进程，最好就像chroot那样，把子进程的PID在容器内变成1。</p>
<p><strong>但是，我们会发现，在子进程的shell里输入ps,top等命令，我们还是可以看得到所有进程</strong>。说明并没有完全隔离。这是因为，像ps, top这些命令会去读/proc文件系统，所以，因为/proc文件系统在父进程和子进程都是一样的，所以这些命令显示的东西都是一样的。</p>
<p>所以，我们还需要对文件系统进行隔离。</p>
<h4>Mount Namespace</h4>
<p>下面的例程中，我们在启用了mount namespace并在子进程中重新mount了/proc文件系统。</p>
<pre class="EnlighterJSRAW" data-enlighter-language="c" data-enlighter-highlight="6,17">int container_main(void* arg)
{
    printf("Container [%5d] - inside the container!\n", getpid());
    sethostname("container",10);
    /* 重新mount proc文件系统到 /proc下 */
    system("mount -t proc proc /proc");
    execv(container_args[0], container_args);
    printf("Something's wrong!\n");
    return 1;
}

int main()
{
    printf("Parent [%5d] - start a container!\n", getpid());
    /* 启用Mount Namespace - 增加CLONE_NEWNS参数 */
    int container_pid = clone(container_main, container_stack+STACK_SIZE, 
            CLONE_NEWUTS | CLONE_NEWPID | CLONE_NEWNS | SIGCHLD, NULL);
    waitpid(container_pid, NULL, 0);
    printf("Parent - container stopped!\n");
    return 0;
}</pre>
<p>运行结果如下：</p>
<pre class="EnlighterJSRAW" data-enlighter-language="shell">
hchen@ubuntu:~$ sudo ./pid.mnt
Parent [ 3502] - start a container!
Container [    1] - inside the container!
root@container:~# ps -elf 
F S UID        PID  PPID  C PRI  NI ADDR SZ WCHAN  STIME TTY          TIME CMD
4 S root         1     0  0  80   0 -  6917 wait   19:55 pts/2    00:00:00 /bin/bash
0 R root        14     1  0  80   0 -  5671 -      19:56 pts/2    00:00:00 ps -elf
</pre>
<p>上面，我们可以看到只有两个进程 ，而且pid=1的进程是我们的/bin/bash。我们还可以看到/proc目录下也干净了很多：</p>
<pre class="EnlighterJSRAW" data-enlighter-language="shell">
root@container:~# ls /proc
1          dma          key-users   net            sysvipc
16         driver       kmsg        pagetypeinfo   timer_list
acpi       execdomains  kpagecount  partitions     timer_stats
asound     fb           kpageflags  sched_debug    tty
buddyinfo  filesystems  loadavg     schedstat      uptime
bus        fs           locks       scsi           version
cgroups    interrupts   mdstat      self           version_signature
cmdline    iomem        meminfo     slabinfo       vmallocinfo
consoles   ioports      misc        softirqs       vmstat
cpuinfo    irq          modules     stat           zoneinfo
crypto     kallsyms     mounts      swaps
devices    kcore        mpt         sys
diskstats  keys         mtrr        sysrq-trigger
</pre>
<p>下图，我们也可以看到在子进程中的top命令只看得到两个进程了。</p>
<p><img decoding="async" loading="lazy" class="aligncenter size-full wp-image-17020" src="https://coolshell.cn/wp-content/uploads/2015/04/mount.namespace.jpg" alt="" width="570" height="300" srcset="https://coolshell.cn/wp-content/uploads/2015/04/mount.namespace.jpg 740w, https://coolshell.cn/wp-content/uploads/2015/04/mount.namespace-300x158.jpg 300w" sizes="(max-width: 570px) 100vw, 570px" /></p>
<p>这里，多说一下。在通过CLONE_NEWNS创建mount namespace后，父进程会把自己的文件结构复制给子进程中。而子进程中新的namespace中的所有mount操作都只影响自身的文件系统，而不对外界产生任何影响。这样可以做到比较严格地隔离。</p>
<p><!--另外，如果你熟悉mount命令，你会知道，mount命令有以下这些参数：


<ul>


<ol>--make-shared ： 共享方式的mount，主要是为了文件的共享和镜像。</ol>




<ol>--make-slave ： 这种mount方式更大的意义是为了“只读”的场景，也就是从动式的mount。</ol>




<ol>--make-private：这种mount方式主要就是为了隔离。如proc文件系统。</ol>




<ol>--make-unbindable：标记为不可绑定。</ol>


</ul>


--></p>
<p>你可能会问，我们是不是还有别的一些文件系统也需要这样mount? 是的。</p>
<h4>Docker的 Mount Namespace</h4>
<p>下面我将向演示一个“山寨镜像”，其模仿了Docker的Mount Namespace。</p>
<p>首先，我们需要一个rootfs，也就是我们需要把我们要做的镜像中的那些命令什么的copy到一个rootfs的目录下，我们模仿Linux构建如下的目录：</p>
<pre class="EnlighterJSRAW" data-enlighter-language="shell">hchen@ubuntu:~/rootfs$ ls
bin  dev  etc  home  lib  lib64  mnt  opt  proc  root  run  sbin  sys  tmp  usr  var</pre>
<p>然后，我们把一些我们需要的命令copy到 rootfs/bin目录中（sh命令必需要copy进去，不然我们无法 chroot ）</p>
<pre class="EnlighterJSRAW" data-enlighter-language="shell">
hchen@ubuntu:~/rootfs$ ls ./bin ./usr/bin
 
./bin:
bash   chown  gzip      less  mount       netstat  rm     tabs  tee      top       tty
cat    cp     hostname  ln    mountpoint  ping     sed    tac   test     touch     umount
chgrp  echo   ip        ls    mv          ps       sh     tail  timeout  tr        uname
chmod  grep   kill      more  nc          pwd      sleep  tar   toe      truncate  which

./usr/bin:
awk  env  groups  head  id  mesg  sort  strace  tail  top  uniq  vi  wc  xargs
</pre>
<p>注：你可以使用ldd命令把这些命令相关的那些so文件copy到对应的目录：</p>
<pre class="EnlighterJSRAW" data-enlighter-language="shell">
hchen@ubuntu:~/rootfs/bin$ ldd bash
  linux-vdso.so.1 =>  (0x00007fffd33fc000)
  libtinfo.so.5 => /lib/x86_64-linux-gnu/libtinfo.so.5 (0x00007f4bd42c2000)
  libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007f4bd40be000)
  libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f4bd3cf8000)
  /lib64/ld-linux-x86-64.so.2 (0x00007f4bd4504000)
</pre>
<p>下面是我的rootfs中的一些so文件：</p>
<pre class="EnlighterJSRAW" data-enlighter-language="shell">
hchen@ubuntu:~/rootfs$ ls ./lib64 ./lib/x86_64-linux-gnu/

./lib64:
ld-linux-x86-64.so.2

./lib/x86_64-linux-gnu/:
libacl.so.1      libmemusage.so         libnss_files-2.19.so    libpython3.4m.so.1
libacl.so.1.1.0  libmount.so.1          libnss_files.so.2       libpython3.4m.so.1.0
libattr.so.1     libmount.so.1.1.0      libnss_hesiod-2.19.so   libresolv-2.19.so
libblkid.so.1    libm.so.6              libnss_hesiod.so.2      libresolv.so.2
libc-2.19.so     libncurses.so.5        libnss_nis-2.19.so      libselinux.so.1
libcap.a         libncurses.so.5.9      libnss_nisplus-2.19.so  libtinfo.so.5
libcap.so        libncursesw.so.5       libnss_nisplus.so.2     libtinfo.so.5.9
libcap.so.2      libncursesw.so.5.9     libnss_nis.so.2         libutil-2.19.so
libcap.so.2.24   libnsl-2.19.so         libpcre.so.3            libutil.so.1
libc.so.6        libnsl.so.1            libprocps.so.3          libuuid.so.1
libdl-2.19.so    libnss_compat-2.19.so  libpthread-2.19.so      libz.so.1
libdl.so.2       libnss_compat.so.2     libpthread.so.0
libgpm.so.2      libnss_dns-2.19.so     libpython2.7.so.1
libm-2.19.so     libnss_dns.so.2        libpython2.7.so.1.0
</pre>
<p>包括这些命令依赖的一些配置文件：</p>
<pre class="EnlighterJSRAW" data-enlighter-language="shell">
hchen@ubuntu:~/rootfs$ ls ./etc
bash.bashrc  group  hostname  hosts  ld.so.cache  nsswitch.conf  passwd  profile  
resolv.conf  shadow
</pre>
<p>你现在会说，我靠，有些配置我希望是在容器起动时给他设置的，而不是hard code在镜像中的。比如：/etc/hosts，/etc/hostname，还有DNS的/etc/resolv.conf文件。好的。那我们在rootfs外面，我们再创建一个conf目录，把这些文件放到这个目录中。</p>
<pre class="EnlighterJSRAW" data-enlighter-language="shell">hchen@ubuntu:~$ ls ./conf
hostname     hosts     resolv.conf</pre>
<p>这样，我们的父进程就可以动态地设置容器需要的这些文件的配置， 然后再把他们mount进容器，这样，容器的镜像中的配置就比较灵活了。</p>
<p>好了，终于到了我们的程序。</p>
<pre class="EnlighterJSRAW" data-enlighter-language="c">
#define _GNU_SOURCE
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/mount.h>
#include <stdio.h>
#include <sched.h>
#include <signal.h>
#include <unistd.h>

#define STACK_SIZE (1024 * 1024)

static char container_stack[STACK_SIZE];
char* const container_args[] = {
    "/bin/bash",
    "-l",
    NULL
};

int container_main(void* arg)
{
    printf("Container [%5d] - inside the container!\n", getpid());

    //set hostname
    sethostname("container",10);

    //remount "/proc" to make sure the "top" and "ps" show container's information
    if (mount("proc", "rootfs/proc", "proc", 0, NULL) !=0 ) {
        perror("proc");
    }
    if (mount("sysfs", "rootfs/sys", "sysfs", 0, NULL)!=0) {
        perror("sys");
    }
    if (mount("none", "rootfs/tmp", "tmpfs", 0, NULL)!=0) {
        perror("tmp");
    }
    if (mount("udev", "rootfs/dev", "devtmpfs", 0, NULL)!=0) {
        perror("dev");
    }
    if (mount("devpts", "rootfs/dev/pts", "devpts", 0, NULL)!=0) {
        perror("dev/pts");
    }
    if (mount("shm", "rootfs/dev/shm", "tmpfs", 0, NULL)!=0) {
        perror("dev/shm");
    }
    if (mount("tmpfs", "rootfs/run", "tmpfs", 0, NULL)!=0) {
        perror("run");
    }
    /* 
     * 模仿Docker的从外向容器里mount相关的配置文件 
     * 你可以查看：/var/lib/docker/containers/<container_id>/目录，
     * 你会看到docker的这些文件的。
     */
    if (mount("conf/hosts", "rootfs/etc/hosts", "none", MS_BIND, NULL)!=0 ||
          mount("conf/hostname", "rootfs/etc/hostname", "none", MS_BIND, NULL)!=0 ||
          mount("conf/resolv.conf", "rootfs/etc/resolv.conf", "none", MS_BIND, NULL)!=0 ) {
        perror("conf");
    }
    /* 模仿docker run命令中的 -v, --volume=[] 参数干的事 */
    if (mount("/tmp/t1", "rootfs/mnt", "none", MS_BIND, NULL)!=0) {
        perror("mnt");
    }

    /* chroot 隔离目录 */
    if ( chdir("./rootfs") != 0 || chroot("./") != 0 ){
        perror("chdir/chroot");
    }

    execv(container_args[0], container_args);
    perror("exec");
    printf("Something's wrong!\n");
    return 1;
}

int main()
{
    printf("Parent [%5d] - start a container!\n", getpid());
    int container_pid = clone(container_main, container_stack+STACK_SIZE, 
            CLONE_NEWUTS | CLONE_NEWIPC | CLONE_NEWPID | CLONE_NEWNS | SIGCHLD, NULL);
    waitpid(container_pid, NULL, 0);
    printf("Parent - container stopped!\n");
    return 0;
}
</pre>
<p>sudo运行上面的程序，你会看到下面的挂载信息以及一个所谓的“镜像”：</p>
<pre class="EnlighterJSRAW" data-enlighter-language="shell">
hchen@ubuntu:~$ sudo ./mount 
Parent [ 4517] - start a container!
Container [    1] - inside the container!
root@container:/# mount
proc on /proc type proc (rw,relatime)
sysfs on /sys type sysfs (rw,relatime)
none on /tmp type tmpfs (rw,relatime)
udev on /dev type devtmpfs (rw,relatime,size=493976k,nr_inodes=123494,mode=755)
devpts on /dev/pts type devpts (rw,relatime,mode=600,ptmxmode=000)
tmpfs on /run type tmpfs (rw,relatime)
/dev/disk/by-uuid/18086e3b-d805-4515-9e91-7efb2fe5c0e2 on /etc/hosts type ext4 (rw,relatime,errors=remount-ro,data=ordered)
/dev/disk/by-uuid/18086e3b-d805-4515-9e91-7efb2fe5c0e2 on /etc/hostname type ext4 (rw,relatime,errors=remount-ro,data=ordered)
/dev/disk/by-uuid/18086e3b-d805-4515-9e91-7efb2fe5c0e2 on /etc/resolv.conf type ext4 (rw,relatime,errors=remount-ro,data=ordered)

root@container:/# ls /bin /usr/bin
/bin:
bash   chmod  echo  hostname  less  more  mv   ping  rm   sleep  tail  test    top   truncate  uname
cat    chown  grep  ip        ln    mount  nc   ps    sed  tabs   tar   timeout  touch  tty     which
chgrp  cp     gzip  kill      ls    mountpoint  netstat  pwd   sh   tac    tee   toe    tr   umount

/usr/bin:
awk  env  groups  head  id  mesg  sort  strace  tail  top  uniq  vi  wc  xargs
</pre>
<p>关于如何做一个chroot的目录，这里有个工具叫<a href="https://wiki.ubuntu.com/DebootstrapChroot" target="_blank" rel="noopener noreferrer">DebootstrapChroot</a>，你可以顺着链接去看看（英文的哦）</p>
<p>接下来的事情，你可以自己玩了，我相信你的想像力 。：）</p>
<p>在下一篇，我将向你介绍User Namespace、Network Namespace以及Namespace的其它东西。</p>
<p align="center"><strong> <a title="Docker基础技术：Linux Namespace（下）" href="https://coolshell.cn/articles/17029.html" target="_blank" rel="noopener noreferrer"> &lt;&lt;&lt;&lt; Docker基础技术：Linux Namespace（下）&gt;&gt;&gt;&gt; </a></strong></p>
<p>（上篇完，<a title="Docker基础技术：Linux Namespace（下）" href="https://coolshell.cn/articles/17029.html" target="_blank" rel="noopener noreferrer">请参看下篇</a>）<!--



<p align="center"><a href= target=_blank><img decoding="async" src=""></a></p>





<p align="center"><img decoding="async" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.weixin.jpg"> <img decoding="async" loading="lazy" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.mini_.jpg" width="300" height="300"> <br />关注CoolShell微信公众账号和微信小程序</p>

 

--></p>
<div style="margin-top: 15px; font-size: 16px;color: #cc0000;">
<p align="center"><strong>（转载本站文章请注明作者和出处 <a href="https://coolshell.cn/">酷 壳 &#8211; CoolShell</a> ，请勿用于任何商业用途）</strong></p>
</div>

<div class="wp_rp_wrap  wp_rp_vertical_m" ><div class="wp_rp_content"><h3 class="related_post_title">相关文章</h3><ul class="related_post wp_rp"><li ><a href="https://coolshell.cn/articles/17029.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2015/04/jail_cell-150x150.jpg" alt="Docker基础技术：Linux Namespace（下）" width="150" height="150" /></a><a href="https://coolshell.cn/articles/17029.html" class="wp_rp_title">Docker基础技术：Linux Namespace（下）</a></li><li ><a href="https://coolshell.cn/articles/18654.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2018/12/docker-networking-1-150x150.png" alt="记一次Kubernetes/Docker网络排障" width="150" height="150" /></a><a href="https://coolshell.cn/articles/18654.html" class="wp_rp_title">记一次Kubernetes/Docker网络排障</a></li><li ><a href="https://coolshell.cn/articles/17200.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2015/08/how_to_set_up_an_iSCSI_LUN_with_thin-150x150.jpg" alt="Docker基础技术：DeviceMapper" width="150" height="150" /></a><a href="https://coolshell.cn/articles/17200.html" class="wp_rp_title">Docker基础技术：DeviceMapper</a></li><li ><a href="https://coolshell.cn/articles/17061.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2015/08/docker-filesystems-busyboxrw-150x150.png" alt="Docker基础技术：AUFS" width="150" height="150" /></a><a href="https://coolshell.cn/articles/17061.html" class="wp_rp_title">Docker基础技术：AUFS</a></li><li ><a href="https://coolshell.cn/articles/17049.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2015/04/filter-150x150.png" alt="Docker基础技术：Linux CGroup" width="150" height="150" /></a><a href="https://coolshell.cn/articles/17049.html" class="wp_rp_title">Docker基础技术：Linux CGroup</a></li><li ><a href="https://coolshell.cn/articles/22320.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2022/12/eBPF-150x150.jpeg" alt="eBPF 介绍" width="150" height="150" /></a><a href="https://coolshell.cn/articles/22320.html" class="wp_rp_title">eBPF 介绍</a></li></ul></div></div>The post <a href="https://coolshell.cn/articles/17010.html">Docker基础技术：Linux Namespace（上）</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></content:encoded>
					
					<wfw:commentRss>https://coolshell.cn/articles/17010.html/feed</wfw:commentRss>
			<slash:comments>113</slash:comments>
		
		
			</item>
		<item>
		<title>Docker基础技术：Linux Namespace（下）</title>
		<link>https://coolshell.cn/articles/17029.html</link>
					<comments>https://coolshell.cn/articles/17029.html#comments</comments>
		
		<dc:creator><![CDATA[陈皓]]></dc:creator>
		<pubDate>Thu, 16 Apr 2015 02:19:23 +0000</pubDate>
				<category><![CDATA[Unix/Linux]]></category>
		<category><![CDATA[操作系统]]></category>
		<category><![CDATA[Docker]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[Namespace]]></category>
		<guid isPermaLink="false">http://coolshell.cn/?p=17029</guid>

					<description><![CDATA[<p>在 Docker基础技术：Linux Namespace（上篇）中我们了解了，UTD、IPC、PID、Mount 四个namespace，我们模仿Docker做...</p>
<p class="read-more"><a class="btn btn-default" href="https://coolshell.cn/articles/17029.html"> Read More<span class="screen-reader-text">  Read More</span></a></p>
The post <a href="https://coolshell.cn/articles/17029.html">Docker基础技术：Linux Namespace（下）</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></description>
										<content:encoded><![CDATA[<p><script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-3415450859608158"
     crossorigin="anonymous"></script><img decoding="async" loading="lazy" class="alignright size-full wp-image-17084" src="https://coolshell.cn/wp-content/uploads/2015/04/jail_cell.jpg" alt="jail_cell" width="350" height="252" srcset="https://coolshell.cn/wp-content/uploads/2015/04/jail_cell.jpg 350w, https://coolshell.cn/wp-content/uploads/2015/04/jail_cell-300x216.jpg 300w" sizes="(max-width: 350px) 100vw, 350px" />在 <strong><a title="Docker基础技术：Linux Namespace（上）" href="https://coolshell.cn/articles/17010.html" target="_blank" rel="noopener noreferrer">Docker基础技术：Linux Namespace（上篇）</a></strong>中我们了解了，UTD、IPC、PID、Mount 四个namespace，我们模仿Docker做了一个相当相当山寨的镜像。在这一篇中，主要想向大家介绍Linux的User和Network的Namespace。</p>
<p>好，下面我们就介绍一下还剩下的这两个Namespace。</p>
<h4>User Namespace</h4>
<p>User Namespace主要是用了CLONE_NEWUSER的参数。使用了这个参数后，内部看到的UID和GID已经与外部不同了，默认显示为65534。那是因为容器找不到其真正的UID所以，设置上了最大的UID（其设置定义在/proc/sys/kernel/overflowuid）。</p>
<p>要把容器中的uid和真实系统的uid给映射在一起，需要修改 <strong>/proc/&lt;pid&gt;/uid_map</strong> 和 <strong>/proc/&lt;pid&gt;/gid_map</strong> 这两个文件。这两个文件的格式为：</p>
<p><code><code></code></code><strong>ID-inside-ns ID-outside-ns length</strong></p>
<p>其中：</p>
<p><span id="more-17029"></span></p>
<ul>
<li>第一个字段ID-inside-ns表示在容器显示的UID或GID，</li>
<li>第二个字段ID-outside-ns表示容器外映射的真实的UID或GID。</li>
<li>第三个字段表示映射的范围，一般填1，表示一一对应。</li>
</ul>
<p>比如，把真实的uid=1000映射成容器内的uid=0</p>
<pre class="EnlighterJSRAW" data-enlighter-language="shell">$ cat /proc/2465/uid_map
         0       1000          1</pre>
<p>再比如下面的示例：表示把namespace内部从0开始的uid映射到外部从0开始的uid，其最大范围是无符号32位整形</p>
<pre class="EnlighterJSRAW" data-enlighter-language="shell">$ cat /proc/$$/uid_map
         0          0          4294967295</pre>
<p>另外，需要注意的是：</p>
<ul>
<li>写这两个文件的进程需要这个namespace中的CAP_SETUID (CAP_SETGID)权限（可参看<a href="http://man7.org/linux/man-pages/man7/capabilities.7.html" target="_blank" rel="noopener noreferrer">Capabilities</a>）</li>
<li>写入的进程必须是此user namespace的父或子的user namespace进程。</li>
<li>另外需要满如下条件之一：1）父进程将effective uid/gid映射到子进程的user namespace中，2）父进程如果有CAP_SETUID/CAP_SETGID权限，那么它将可以映射到父进程中的任一uid/gid。</li>
</ul>
<p>这些规则看着都烦，我们来看程序吧（下面的程序有点长，但是非常简单，如果你读过《Unix网络编程》上卷，你应该可以看懂）：</p>
<pre class="EnlighterJSRAW" data-enlighter-language="c">#define _GNU_SOURCE
#include &lt;stdio.h&gt;
#include &lt;stdlib.h&gt;
#include &lt;sys/types.h&gt;
#include &lt;sys/wait.h&gt;
#include &lt;sys/mount.h&gt;
#include &lt;sys/capability.h&gt;
#include &lt;stdio.h&gt;
#include &lt;sched.h&gt;
#include &lt;signal.h&gt;
#include &lt;unistd.h&gt;

#define STACK_SIZE (1024 * 1024)

static char container_stack[STACK_SIZE];
char* const container_args[] = {
    "/bin/bash",
    NULL
};

int pipefd[2];

void set_map(char* file, int inside_id, int outside_id, int len) {
    FILE* mapfd = fopen(file, "w");
    if (NULL == mapfd) {
        perror("open file error");
        return;
    }
    fprintf(mapfd, "%d %d %d", inside_id, outside_id, len);
    fclose(mapfd);
}

void set_uid_map(pid_t pid, int inside_id, int outside_id, int len) {
    char file[256];
    sprintf(file, "/proc/%d/uid_map", pid);
    set_map(file, inside_id, outside_id, len);
}

void set_gid_map(pid_t pid, int inside_id, int outside_id, int len) {
    char file[256];
    sprintf(file, "/proc/%d/gid_map", pid);
    set_map(file, inside_id, outside_id, len);
}

int container_main(void* arg)
{

    printf("Container [%5d] - inside the container!\n", getpid());

    printf("Container: eUID = %ld;  eGID = %ld, UID=%ld, GID=%ld\n",
            (long) geteuid(), (long) getegid(), (long) getuid(), (long) getgid());

    /* 等待父进程通知后再往下执行（进程间的同步） */
    char ch;
    close(pipefd[1]);
    read(pipefd[0], &amp;ch, 1);

    printf("Container [%5d] - setup hostname!\n", getpid());
    //set hostname
    sethostname("container",10);

    //remount "/proc" to make sure the "top" and "ps" show container's information
    mount("proc", "/proc", "proc", 0, NULL);

    execv(container_args[0], container_args);
    printf("Something's wrong!\n");
    return 1;
}

int main()
{
    const int gid=getgid(), uid=getuid();

    printf("Parent: eUID = %ld;  eGID = %ld, UID=%ld, GID=%ld\n",
            (long) geteuid(), (long) getegid(), (long) getuid(), (long) getgid());

    pipe(pipefd);
 
    printf("Parent [%5d] - start a container!\n", getpid());

    int container_pid = clone(container_main, container_stack+STACK_SIZE, 
            CLONE_NEWUTS | CLONE_NEWPID | CLONE_NEWNS | CLONE_NEWUSER | SIGCHLD, NULL);

    
    printf("Parent [%5d] - Container [%5d]!\n", getpid(), container_pid);

    //To map the uid/gid, 
    //   we need edit the /proc/PID/uid_map (or /proc/PID/gid_map) in parent
    //The file format is
    //   ID-inside-ns   ID-outside-ns   length
    //if no mapping, 
    //   the uid will be taken from /proc/sys/kernel/overflowuid
    //   the gid will be taken from /proc/sys/kernel/overflowgid
    set_uid_map(container_pid, 0, uid, 1);
    set_gid_map(container_pid, 0, gid, 1);

    printf("Parent [%5d] - user/group mapping done!\n", getpid());

    /* 通知子进程 */
    close(pipefd[1]);

    waitpid(container_pid, NULL, 0);
    printf("Parent - container stopped!\n");
    return 0;
}</pre>
<p>上面的程序，我们用了一个pipe来对父子进程进行同步，为什么要这样做？因为子进程中有一个execv的系统调用，这个系统调用会把当前子进程的进程空间给全部覆盖掉，我们希望在execv之前就做好user namespace的uid/gid的映射，这样，execv运行的/bin/bash就会因为我们设置了uid为0的inside-uid而变成#号的提示符。</p>
<p>整个程序的运行效果如下：</p>
<pre class="EnlighterJSRAW" data-enlighter-language="shell">hchen@ubuntu:~$ id
uid=1000(hchen) gid=1000(hchen) groups=1000(hchen)

hchen@ubuntu:~$ ./user #&lt;--以hchen用户运行
Parent: eUID = 1000;  eGID = 1000, UID=1000, GID=1000 
Parent [ 3262] - start a container!
Parent [ 3262] - Container [ 3263]!
Parent [ 3262] - user/group mapping done!
Container [    1] - inside the container!
Container: eUID = 0;  eGID = 0, UID=0, GID=0 #&lt;---Container里的UID/GID都为0了
Container [    1] - setup hostname!

root@container:~# id #&lt;----我们可以看到容器里的用户和命令行提示符是root用户了
uid=0(root) gid=0(root) groups=0(root),65534(nogroup)</pre>
<p>虽然容器里是root，但其实这个容器的/bin/bash进程是以一个普通用户hchen来运行的。这样一来，我们容器的安全性会得到提高。</p>
<p>我们注意到，User Namespace是以普通用户运行，但是别的Namespace需要root权限，那么，如果我要同时使用多个Namespace，该怎么办呢？一般来说，我们先用一般用户创建User Namespace，然后把这个一般用户映射成root，在容器内用root来创建其它的Namesapce。</p>
<h4>Network Namespace</h4>
<p>Network的Namespace比较啰嗦。在Linux下，我们一般用ip命令创建Network Namespace（Docker的源码中，它没有用ip命令，而是自己实现了ip命令内的一些功能——是用了Raw Socket发些“奇怪”的数据，呵呵）。这里，我还是用ip命令讲解一下。</p>
<p>首先，我们先看个图，下面这个图基本上就是Docker在宿主机上的网络示意图（其中的物理网卡并不准确，因为docker可能会运行在一个VM中，所以，这里所谓的“物理网卡”其实也就是一个有可以路由的IP的网卡）</p>
<p><img decoding="async" loading="lazy" class="aligncenter size-full wp-image-17040" src="https://coolshell.cn/wp-content/uploads/2015/04/network.namespace.jpg" alt="network.namespace" width="407" height="300" srcset="https://coolshell.cn/wp-content/uploads/2015/04/network.namespace.jpg 886w, https://coolshell.cn/wp-content/uploads/2015/04/network.namespace-300x221.jpg 300w" sizes="(max-width: 407px) 100vw, 407px" /></p>
<p>上图中，Docker使用了一个私有网段，172.40.1.0，docker还可能会使用10.0.0.0和192.168.0.0这两个私有网段，关键看你的路由表中是否配置了，如果没有配置，就会使用，如果你的路由表配置了所有私有网段，那么docker启动时就会出错了。</p>
<p>当你启动一个Docker容器后，你可以使用ip link show或ip addr show来查看当前宿主机的网络情况（我们可以看到有一个docker0，还有一个veth22a38e6的虚拟网卡——给容器用的）：</p>
<pre class="EnlighterJSRAW" data-enlighter-language="c" data-enlighter-highlight="6,8">hchen@ubuntu:~$ ip link show
1: lo: &lt;LOOPBACK,UP,LOWER_UP&gt; mtu 65536 qdisc noqueue state ... 
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: eth0: &lt;BROADCAST,MULTICAST,UP,LOWER_UP&gt; mtu 1500 qdisc ...
    link/ether 00:0c:29:b7:67:7d brd ff:ff:ff:ff:ff:ff
3: docker0: &lt;BROADCAST,MULTICAST,UP,LOWER_UP&gt; mtu 1500 ...
    link/ether 56:84:7a:fe:97:99 brd ff:ff:ff:ff:ff:ff
5: veth22a38e6: &lt;BROADCAST,UP,LOWER_UP&gt; mtu 1500 qdisc ...
    link/ether 8e:30:2a:ac:8c:d1 brd ff:ff:ff:ff:ff:ff</pre>
<p>那么，要做成这个样子应该怎么办呢？我们来看一组命令：</p>
<pre class="EnlighterJSRAW" data-enlighter-language="shell">## 首先，我们先增加一个网桥lxcbr0，模仿docker0
brctl addbr lxcbr0
brctl stp lxcbr0 off
ifconfig lxcbr0 192.168.10.1/24 up #为网桥设置IP地址

## 接下来，我们要创建一个network namespace - ns1

# 增加一个namesapce 命令为 ns1 （使用ip netns add命令）
ip netns add ns1 

# 激活namespace中的loopback，即127.0.0.1（使用ip netns exec ns1来操作ns1中的命令）
ip netns exec ns1   ip link set dev lo up 

## 然后，我们需要增加一对虚拟网卡

# 增加一个pair虚拟网卡，注意其中的veth类型，其中一个网卡要按进容器中
ip link add veth-ns1 type veth peer name lxcbr0.1

# 把 veth-ns1 按到namespace ns1中，这样容器中就会有一个新的网卡了
ip link set veth-ns1 netns ns1

# 把容器里的 veth-ns1改名为 eth0 （容器外会冲突，容器内就不会了）
ip netns exec ns1  ip link set dev veth-ns1 name eth0 

# 为容器中的网卡分配一个IP地址，并激活它
ip netns exec ns1 ifconfig eth0 192.168.10.11/24 up


# 上面我们把veth-ns1这个网卡按到了容器中，然后我们要把lxcbr0.1添加上网桥上
brctl addif lxcbr0 lxcbr0.1

# 为容器增加一个路由规则，让容器可以访问外面的网络
ip netns exec ns1     ip route add default via 192.168.10.1

# 在/etc/netns下创建network namespce名称为ns1的目录，
# 然后为这个namespace设置resolv.conf，这样，容器内就可以访问域名了
mkdir -p /etc/netns/ns1
echo "nameserver 8.8.8.8" &gt; /etc/netns/ns1/resolv.conf</pre>
<p>上面基本上就是docker网络的原理了，只不过，</p>
<ul>
<li>Docker的resolv.conf没有用这样的方式，而是用了<a title="Docker基础技术：Linux Namespace（上）" href="https://coolshell.cn/articles/17010.html" target="_blank" rel="noopener noreferrer">上篇中的Mount Namesapce的那种方式</a></li>
<li>另外，docker是用进程的PID来做Network Namespace的名称的。</li>
</ul>
<p>了解了这些后，你甚至可以为正在运行的docker容器增加一个新的网卡：</p>
<pre class="EnlighterJSRAW" data-enlighter-language="shell">ip link add peerA type veth peer name peerB 
brctl addif docker0 peerA 
ip link set peerA up 
ip link set peerB netns ${container-pid} 
ip netns exec ${container-pid} ip link set dev peerB name eth1 
ip netns exec ${container-pid} ip link set eth1 up ; 
ip netns exec ${container-pid} ip addr add ${ROUTEABLE_IP} dev eth1 ;</pre>
<p>上面的示例是我们为正在运行的docker容器，增加一个eth1的网卡，并给了一个静态的可被外部访问到的IP地址。</p>
<p>这个需要把外部的“物理网卡”配置成混杂模式，这样这个eth1网卡就会向外通过ARP协议发送自己的Mac地址，然后外部的交换机就会把到这个IP地址的包转到“物理网卡”上，因为是混杂模式，所以eth1就能收到相关的数据，一看，是自己的，那么就收到。这样，Docker容器的网络就和外部通了。</p>
<p>当然，无论是Docker的NAT方式，还是混杂模式都会有性能上的问题，NAT不用说了，存在一个转发的开销，混杂模式呢，网卡上收到的负载都会完全交给所有的虚拟网卡上，于是就算一个网卡上没有数据，但也会被其它网卡上的数据所影响。</p>
<p>这两种方式都不够完美，我们知道，真正解决这种网络问题需要使用VLAN技术，于是Google的同学们为Linux内核实现了一个<a href="https://lwn.net/Articles/620087/" target="_blank" rel="noopener noreferrer">IPVLAN的驱动</a>，这基本上就是为Docker量身定制的。</p>
<h4>Namespace文件</h4>
<p>上面就是目前Linux Namespace的玩法。 现在，我来看一下其它的相关东西。</p>
<p>让我们运行一下上篇中的那个pid.mnt的程序（也就是PID Namespace中那个mount proc的程序），然后不要退出。</p>
<pre class="EnlighterJSRAW" data-enlighter-language="c">$ sudo ./pid.mnt 
[sudo] password for hchen: 
Parent [ 4599] - start a container!
Container [    1] - inside the container!</pre>
<p>我们到另一个shell中查看一下父子进程的PID：</p>
<pre class="EnlighterJSRAW" data-enlighter-language="shell">hchen@ubuntu:~$ pstree -p 4599
pid.mnt(4599)───bash(4600)</pre>
<p>我们可以到proc下（/proc//ns）查看进程的各个namespace的id（内核版本需要3.8以上）。</p>
<p>下面是父进程的：</p>
<pre class="EnlighterJSRAW" data-enlighter-language="shell">hchen@ubuntu:~$ sudo ls -l /proc/4599/ns
total 0
lrwxrwxrwx 1 root root 0  4月  7 22:01 ipc -&gt; ipc:[4026531839]
lrwxrwxrwx 1 root root 0  4月  7 22:01 mnt -&gt; mnt:[4026531840]
lrwxrwxrwx 1 root root 0  4月  7 22:01 net -&gt; net:[4026531956]
lrwxrwxrwx 1 root root 0  4月  7 22:01 pid -&gt; pid:[4026531836]
lrwxrwxrwx 1 root root 0  4月  7 22:01 user -&gt; user:[4026531837]
lrwxrwxrwx 1 root root 0  4月  7 22:01 uts -&gt; uts:[4026531838]</pre>
<p>下面是子进程的：</p>
<pre class="EnlighterJSRAW" data-enlighter-language="shell">hchen@ubuntu:~$ sudo ls -l /proc/4600/ns
total 0
lrwxrwxrwx 1 root root 0  4月  7 22:01 ipc -&gt; ipc:[4026531839]
lrwxrwxrwx 1 root root 0  4月  7 22:01 mnt -&gt; mnt:[4026532520]
lrwxrwxrwx 1 root root 0  4月  7 22:01 net -&gt; net:[4026531956]
lrwxrwxrwx 1 root root 0  4月  7 22:01 pid -&gt; pid:[4026532522]
lrwxrwxrwx 1 root root 0  4月  7 22:01 user -&gt; user:[4026531837]
lrwxrwxrwx 1 root root 0  4月  7 22:01 uts -&gt; uts:[4026532521]</pre>
<p>我们可以看到，其中的ipc，net，user是同一个ID，而mnt,pid,uts都是不一样的。如果两个进程指向的namespace编号相同，就说明他们在同一个namespace下，否则则在不同namespace里面。</p>
<p>这些文件还有另一个作用，那就是，一旦这些文件被打开，只要其fd被占用着，那么就算PID所属的所有进程都已经结束，创建的namespace也会一直存在。比如：我们可以通过：mount &#8211;bind /proc/4600/ns/uts ~/uts 来hold这个namespace。</p>
<p>另外，我们在上篇中讲过一个setns的系统调用，其函数声明如下：</p>
<pre class="EnlighterJSRAW" data-enlighter-language="c">int setns(int fd, int nstype);</pre>
<p>其中第一个参数就是一个fd，也就是一个open()系统调用打开了上述文件后返回的fd，比如：</p>
<pre class="EnlighterJSRAW" data-enlighter-language="c">fd = open("/proc/4600/ns/nts", O_RDONLY);  // 获取namespace文件描述符
setns(fd, 0); // 加入新的namespace</pre>
<h4>参考文档</h4>
<ul>
<li style="list-style-type: none;">
<ul>
<li><a href="http://lwn.net/Articles/531114/" target="_blank" rel="noopener noreferrer">Namespaces in operation</a></li>
<li><a href="http://man7.org/linux/man-pages/man7/namespaces.7.html" target="_blank" rel="noopener noreferrer">Linux Namespace Man Page</a></li>
<li><a href="http://crosbymichael.com/creating-containers-part-1.html" target="_blank" rel="noopener noreferrer">Creat Containers &#8211; Part 1</a></li>
<li><a href="https://blog.jtlebi.fr/2013/12/22/introduction-to-linux-namespaces-part-1-uts/" target="_blank" rel="noopener noreferrer">Introduction to Linux namespaces</a></li>
</ul>
</li>
</ul>
<p>（应网友card323加入）</p>
<p>（全文完）<!--



<p align="center"><a href= target=_blank><img decoding="async" src=""></a></p>





<p align="center"><img decoding="async" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.weixin.jpg"> <img decoding="async" loading="lazy" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.mini_.jpg" width="300" height="300"> <br />关注CoolShell微信公众账号和微信小程序</p>

 

--></p>
<div style="margin-top: 15px; font-size: 16px;color: #cc0000;">
<p align="center"><strong>（转载本站文章请注明作者和出处 <a href="https://coolshell.cn/">酷 壳 &#8211; CoolShell</a> ，请勿用于任何商业用途）</strong></p>
</div>

<div class="wp_rp_wrap  wp_rp_vertical_m" ><div class="wp_rp_content"><h3 class="related_post_title">相关文章</h3><ul class="related_post wp_rp"><li ><a href="https://coolshell.cn/articles/17010.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2015/04/isolation-150x150.jpg" alt="Docker基础技术：Linux Namespace（上）" width="150" height="150" /></a><a href="https://coolshell.cn/articles/17010.html" class="wp_rp_title">Docker基础技术：Linux Namespace（上）</a></li><li ><a href="https://coolshell.cn/articles/18654.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2018/12/docker-networking-1-150x150.png" alt="记一次Kubernetes/Docker网络排障" width="150" height="150" /></a><a href="https://coolshell.cn/articles/18654.html" class="wp_rp_title">记一次Kubernetes/Docker网络排障</a></li><li ><a href="https://coolshell.cn/articles/17200.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2015/08/how_to_set_up_an_iSCSI_LUN_with_thin-150x150.jpg" alt="Docker基础技术：DeviceMapper" width="150" height="150" /></a><a href="https://coolshell.cn/articles/17200.html" class="wp_rp_title">Docker基础技术：DeviceMapper</a></li><li ><a href="https://coolshell.cn/articles/17061.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2015/08/docker-filesystems-busyboxrw-150x150.png" alt="Docker基础技术：AUFS" width="150" height="150" /></a><a href="https://coolshell.cn/articles/17061.html" class="wp_rp_title">Docker基础技术：AUFS</a></li><li ><a href="https://coolshell.cn/articles/17049.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2015/04/filter-150x150.png" alt="Docker基础技术：Linux CGroup" width="150" height="150" /></a><a href="https://coolshell.cn/articles/17049.html" class="wp_rp_title">Docker基础技术：Linux CGroup</a></li><li ><a href="https://coolshell.cn/articles/22320.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2022/12/eBPF-150x150.jpeg" alt="eBPF 介绍" width="150" height="150" /></a><a href="https://coolshell.cn/articles/22320.html" class="wp_rp_title">eBPF 介绍</a></li></ul></div></div>The post <a href="https://coolshell.cn/articles/17029.html">Docker基础技术：Linux Namespace（下）</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></content:encoded>
					
					<wfw:commentRss>https://coolshell.cn/articles/17029.html/feed</wfw:commentRss>
			<slash:comments>51</slash:comments>
		
		
			</item>
		<item>
		<title>vfork 挂掉的一个问题</title>
		<link>https://coolshell.cn/articles/12103.html</link>
					<comments>https://coolshell.cn/articles/12103.html#comments</comments>
		
		<dc:creator><![CDATA[陈皓]]></dc:creator>
		<pubDate>Thu, 20 Nov 2014 16:48:27 +0000</pubDate>
				<category><![CDATA[C/C++语言]]></category>
		<category><![CDATA[Unix/Linux]]></category>
		<category><![CDATA[操作系统]]></category>
		<category><![CDATA[fork]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[Unix]]></category>
		<category><![CDATA[vfork]]></category>
		<guid isPermaLink="false">http://coolshell.cn/?p=12103</guid>

					<description><![CDATA[<p>在知乎上，有个人问了这样的一个问题——为什么vfork的子进程里用return，整个程序会挂掉，而且exit()不会？并给出了如下的代码，下面的代码一运行就挂掉...</p>
<p class="read-more"><a class="btn btn-default" href="https://coolshell.cn/articles/12103.html"> Read More<span class="screen-reader-text">  Read More</span></a></p>
The post <a href="https://coolshell.cn/articles/12103.html">vfork 挂掉的一个问题</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></description>
										<content:encoded><![CDATA[<p><script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-3415450859608158"
     crossorigin="anonymous"></script><img decoding="async" loading="lazy" class="alignright wp-image-12105" src="https://coolshell.cn/wp-content/uploads/2014/11/tux-fork-298x300.gif" alt="tux-fork" width="199" height="200" srcset="https://coolshell.cn/wp-content/uploads/2014/11/tux-fork-298x300.gif 298w, https://coolshell.cn/wp-content/uploads/2014/11/tux-fork-150x150.gif 150w, https://coolshell.cn/wp-content/uploads/2014/11/tux-fork-200x200.gif 200w, https://coolshell.cn/wp-content/uploads/2014/11/tux-fork-268x270.gif 268w" sizes="(max-width: 199px) 100vw, 199px" />在知乎上，有个人问了这样的<a href="http://www.zhihu.com/question/26591968" target="_blank">一个问题</a>——为什么vfork的子进程里用return，整个程序会挂掉，而且exit()不会？并给出了如下的代码，下面的代码一运行就挂掉了，但如果把子进程的return改成exit(0)就没事。</p>
<p>我受邀后本来不想回答这个问题的，因为这个问题明显就是RTFM的事，后来，发现这个问题放在那里好长时间，而挂在下面的几个答案又跑偏得比较严重，我觉得可能有些朋友看到那样的答案会被误导，所以就上去回答了一下这个问题。</p>
<p>下面我把问题和我的回答发布在这里，也供更多的人查看。</p>
<pre data-enlighter-language="c" class="EnlighterJSRAW">#include &lt;stdio.h&gt;
#include &lt;stdlib.h&gt;
#include &lt;unistd.h&gt;
int main(void) {
    int var;
    var = 88;
    if ((pid = vfork()) &lt; 0) {
        printf(&quot;vfork error&quot;);
        exit(-1);
    } else if (pid == 0) { /* 子进程 */
        var++;
        return 0;
    }
    printf(&quot;pid=%d, glob=%d, var=%d\n&quot;, getpid(), glob, var);
    return 0;
}
</pre>
<p><span id="more-12103"></span></p>
<h4><b>基础知识</b></h4>
<p>首先说一下fork和vfork的差别：</p>
<ul>
<li>fork 是 创建一个子进程，并把父进程的内存数据copy到子进程中。</li>
<li>vfork是 创建一个子进程，并和父进程的内存数据share一起用。</li>
</ul>
<p>这两个的差别是，一个是copy，一个是share。（关于fork，可以参看酷壳之前的《<a title="一个fork的面试题" href="https://coolshell.cn/articles/7965.html" target="_blank">一道fork的面试题</a>》）</p>
<p>你 man vfork 一下，你可以看到，vfork是这样的工作的，</p>
<p style="padding-left: 30px;">1）保证子进程先执行。<br />
2）当子进程调用exit()或exec()后，父进程往下执行。</p>
<p>那么，为什么要干出一个vfork这个玩意？ 原因在man page也讲得很清楚了：</p>
<blockquote><p><strong>Historic Description</strong></p>
<p>Under Linux, fork(2) is implemented using copy-on-write pages, so the only penalty incurred by fork(2) is the time and memory required to duplicate the parent’s page tables, and to create a unique task structure for the child. <b>However, in the bad old days a fork(2) would require making </b><b>a complete copy of the caller’s data space, often needlessly, since usually immediately afterwards an exec(3) is done. Thus, for greater efficiency, BSD introduced the vfork() system call, which did not fully copy the address space of the parent process, but borrowed the parent’s mem</b><b>ory and thread of control until a call to execve(2) or an exit occurred.</b> The parent process was suspended while the child was using its resources. The use of vfork() was tricky: for example, not modifying data in the parent process depended on knowing which variables are held in a register.</p></blockquote>
<p>意思是这样的—— <b>起初只有fork，但是很多程序在fork一个子进程后就exec一个外部程序，于是fork需要copy父进程的数据这个动作就变得毫无意了，而且这样干还很重</b>（注：后来，fork做了优化，详见本文后面）<b>，所以，BSD搞出了个父子进程共享的 vfork，这样成本比较低。因此，vfork本就是为了exec而生。</b></p>
<h4><b>为什么return会挂掉，exit()不会？</b></h4>
<p>从上面我们知道，<b>结束子进程的调用是exit()而不是return，如果你在vfork中return了，那么，这就意味main()函数return了，注意因为函数栈父子进程共享，所以整个程序的栈就跪了。</b></p>
<p>如果你在子进程中return，那么基本是下面的过程：</p>
<p style="padding-left: 30px;"><b>1）子进程的main() 函数 return了，于是程序的函数栈发生了变化。</b></p>
<p style="padding-left: 30px;"><b>2）而main()函数return后，通常会调用 exit()或相似的函数</b>（如：_exit()，exitgroup()）</p>
<p style="padding-left: 30px;"><b>3）这时，父进程收到子进程exit()，开始从vfork返回，但是尼玛，老子的栈都被你子进程给return干废掉了，你让我怎么执行？</b>（注：栈会返回一个诡异一个栈地址，对于某些内核版本的实现，直接报“栈错误”就给跪了，然而，对于某些内核版本的实现，于是有可能会再次调用main()，于是进入了一个无限循环的结果，直到vfork 调用返回 error）</p>
<p>好了，现在再回到 return 和 exit，return会释放局部变量，并弹栈，回到上级函数执行。exit直接退掉。如果你用c++ 你就知道，return会调用局部对象的析构函数，exit不会。（注：exit不是系统调用，是glibc对系统调用 _exit()或_exitgroup()的封装）</p>
<p>可见，<b>子进程调用exit() 没有修改函数栈，所以，父进程得以顺利执行</b>。</p>
<p><strong>但是！注意！如果你调用 exit() 函数，还是会有问题的，正确的方法应该是调用 _exit() 函数，因为 exit() 函数 会 flush 并 close 所有的 标准 I/O ，这样会导致父进程受到影响。（这个情况在fork下也会受到影响，会导致一些被buffer的数据被flush两次，这里可以参看《<a href="https://coolshell.cn/articles/7965.html" target="_blank">一个fork的面试题</a>》）</strong></p>
<h4>关于fork的优化</h4>
<p>很明显，fork太重，而vfork又太危险，所以，就有人开始优化fork这个系统调用。优化的技术用到了著名的<b>写时拷贝（COW）</b>。</p>
<p>也就是说，<strong>对于fork后并不是马上拷贝内存，而是只有你在需要改变的时候，才会从父进程中拷贝到子进程中，这样fork后立马执行exec的成本就非常小了</strong>。所以，Linux的Man Page中并不鼓励使用vfork() ——</p>
<blockquote><p>“ It is rather unfortunate that Linux revived this specter from the past. The BSD man page states: &#8220;This system call will be eliminated when proper system sharing mechanisms are implemented. Users should not depend on the memory sharing semantics of vfork() as it will, in that case, be made synonymous to fork(2).&#8221;”</p></blockquote>
<p>于是，从BSD4.4开始，他们让vfork和fork变成一样的了</p>
<p>但在后来，NetBSD 1.3 又把传统的vfork给捡了回来，说是vfork的性能在 Pentium Pro 200MHz 的机器（这机器好古董啊）上有可以提高几秒钟的性能。详情见——“<a class=" wrap external" href="http://www.netbsd.org/docs/kernel/vfork.html" target="_blank" rel="nofollow noreferrer">NetBSD Documentation: Why implement traditional vfork()<i class="icon-external"></i></a>”</p>
<p>今天的Linux下，fork和vfork还是各是各的，不过，还是建议你不要用vfork，除非你非常关注性能。</p>
<p>（全文完）<!--



<p align="center"><a href= target=_blank><img decoding="async" src=""></a></p>





<p align="center"><img decoding="async" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.weixin.jpg"> <img decoding="async" loading="lazy" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.mini_.jpg" width="300" height="300"> <br />关注CoolShell微信公众账号和微信小程序</p>

 

--></p>
<div style="margin-top: 15px; font-size: 16px;color: #cc0000;">
<p align="center"><strong>（转载本站文章请注明作者和出处 <a href="https://coolshell.cn/">酷 壳 &#8211; CoolShell</a> ，请勿用于任何商业用途）</strong></p>
</div>

<div class="wp_rp_wrap  wp_rp_vertical_m" ><div class="wp_rp_content"><h3 class="related_post_title">相关文章</h3><ul class="related_post wp_rp"><li ><a href="https://coolshell.cn/articles/18360.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2018/05/300x262-150x150.jpg" alt="程序员练级攻略（2018)  与我的专栏" width="150" height="150" /></a><a href="https://coolshell.cn/articles/18360.html" class="wp_rp_title">程序员练级攻略（2018)  与我的专栏</a></li><li ><a href="https://coolshell.cn/articles/17998.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2017/07/systemd-1-150x150.jpeg" alt="Linux PID 1 和 Systemd" width="150" height="150" /></a><a href="https://coolshell.cn/articles/17998.html" class="wp_rp_title">Linux PID 1 和 Systemd</a></li><li ><a href="https://coolshell.cn/articles/11847.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2014/08/puzzle-150x150.png" alt="谜题的答案和活动的心得体会" width="150" height="150" /></a><a href="https://coolshell.cn/articles/11847.html" class="wp_rp_title">谜题的答案和活动的心得体会</a></li><li ><a href="https://coolshell.cn/articles/9104.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2013/02/sed-superman-150x150.png" alt="sed 简明教程" width="150" height="150" /></a><a href="https://coolshell.cn/articles/9104.html" class="wp_rp_title">sed 简明教程</a></li><li ><a href="https://coolshell.cn/articles/9070.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2013/02/awk-150x150.jpg" alt="AWK 简明教程" width="150" height="150" /></a><a href="https://coolshell.cn/articles/9070.html" class="wp_rp_title">AWK 简明教程</a></li><li ><a href="https://coolshell.cn/articles/8883.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2013/01/linux-bash-300x225-150x150.jpg" alt="应该知道的Linux技巧" width="150" height="150" /></a><a href="https://coolshell.cn/articles/8883.html" class="wp_rp_title">应该知道的Linux技巧</a></li></ul></div></div>The post <a href="https://coolshell.cn/articles/12103.html">vfork 挂掉的一个问题</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></content:encoded>
					
					<wfw:commentRss>https://coolshell.cn/articles/12103.html/feed</wfw:commentRss>
			<slash:comments>46</slash:comments>
		
		
			</item>
		<item>
		<title>谜题的答案和活动的心得体会</title>
		<link>https://coolshell.cn/articles/11847.html</link>
					<comments>https://coolshell.cn/articles/11847.html#comments</comments>
		
		<dc:creator><![CDATA[陈皓]]></dc:creator>
		<pubDate>Tue, 05 Aug 2014 23:47:50 +0000</pubDate>
				<category><![CDATA[杂项资源]]></category>
		<category><![CDATA[趣味问题]]></category>
		<category><![CDATA[Algorithm]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[Puzzle]]></category>
		<category><![CDATA[Unix]]></category>
		<guid isPermaLink="false">http://coolshell.cn/?p=11847</guid>

					<description><![CDATA[<p>我于2014年8月3日周六的上午在微博、twitter、CoolShell上发布了一个和程序员有关的解谜题的活动——【活动】解谜题送礼物。我使用了二级域名fun...</p>
<p class="read-more"><a class="btn btn-default" href="https://coolshell.cn/articles/11847.html"> Read More<span class="screen-reader-text">  Read More</span></a></p>
The post <a href="https://coolshell.cn/articles/11847.html">谜题的答案和活动的心得体会</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></description>
										<content:encoded><![CDATA[<p><script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-3415450859608158"
     crossorigin="anonymous"></script>我于2014年8月3日周六的上午在微博、twitter、CoolShell上发布了一个和程序员有关的解谜题的活动——<a title="【活动】解迷题送礼物" href="https://coolshell.cn/articles/11832.html" target="_blank">【活动】解谜题送礼物</a>。我使用了二级域名fun.coolshell.cn做为这次活动的页面。</p>
<p><img decoding="async" loading="lazy" class="aligncenter size-full wp-image-11848" src="https://coolshell.cn/wp-content/uploads/2014/08/puzzle.png" alt="" width="543" height="206" srcset="https://coolshell.cn/wp-content/uploads/2014/08/puzzle.png 543w, https://coolshell.cn/wp-content/uploads/2014/08/puzzle-300x114.png 300w" sizes="(max-width: 543px) 100vw, 543px" /></p>
<p>截止这篇文章发布的时候，fun.coolshell.cn的访问量UV大约有4万左右，通关人数大约有200人，但因为在活动的第二天网上就出了一些答题攻略，通过分析，实际靠自己能力通过的人数在130人左右。通过率大约不到4‰的样子。</p>
<p>在这里我把整个谜题和做这个活动的东西写一下，算是给自己的一个总结。</p>
<h4>谜题的答案和花絮</h4>
<p>fun.coolshell.cn上一共有十道谜题，<strong>要设计这些东西还真是费尽脑汁，这让我对那些设计谜题式游戏的人相当敬佩</strong>。</p>
<p><span id="more-11847"></span></p>
<p style="padding-left: 30px;"><strong>第0关：</strong>很多人可能一头雾水，完全不知道这是什么，其实只要Google一下，你会知道这是一个叫BrainFuck的语言。在Coolshell.cn上我也介绍了过——《<a title="BT雷人的程序语言" href="https://coolshell.cn/articles/1142.html" target="_blank">BT雷人的程序语言</a>》《<a title="BT雷人的程序语言（大全）" href="https://coolshell.cn/articles/4458.html" target="_blank">BT雷人的程序语言（大全）</a>》，要通过这关，你需要把那段程序编译一下。要编译这段程序其实很简单，Google一个在线的编译器就可以了。（关于其它更多的古怪的编程语言请参看这里：<a href="http://esolangs.org/wiki/Language_list" target="_blank">http://esolangs.org/wiki/Language_list</a>）</p>
<p style="padding-left: 30px;"><strong>第1关：</strong>这一关也是很简单的，你需要在网页上找到两个数，一个是X，一个是Y，然后求得X和Y的乘积。对于X，你可以观察一下那个数列游戏，对于Y，你可以Google一下就知道了（我在Coolshell的《<a title="如何用最有创造力的方式输出42" href="https://coolshell.cn/articles/11170.html" target="_blank">如何用最有创造力的方式输出42</a>》说过这个事）。</p>
<p style="padding-left: 30px;"><strong>第2关：</strong>上面显示了一个不一样的键盘，我给了这个键盘的Wikipedia的链接。这个键盘叫Dvorak键，不同于我们的Qwert键。通过这个两个键盘的布局映射，你可以把下面那段读不懂的文字解出来（其实，你还是可以Google，有在线的转换）。把下面那段文字转成Qwert键的，你就会发现这是一段代码，这段代码非常著名，<span style="color: #000000;">是1987年国际<a href="http://www.di-mgt.com.au/src/korn_ioccc.txt" target="_blank">C语言混乱大赛一等奖的一段代码</a>（你可Google “IOCCC 87 unix”）。（关于IOCCC你可以参看Coolshell之前的《<a title="6个变态的C语言Hello World程序" href="https://coolshell.cn/articles/914.html" target="_blank">6个变态的HelloWorld</a>》、《<a title="如何加密/混乱C源代码" href="https://coolshell.cn/articles/933.html" target="_blank">如何混乱代码</a>》、《<a title="如何写出无法维护的代码" href="https://coolshell.cn/articles/4758.html" target="_blank">如何写出无法维护的代码</a>》这几篇文章）</span></p>
<p style="padding-left: 30px;"><strong>第3关：</strong>扫描二维码以后，你会得到一个码表转换，你可以使用Shell的tr命令来转一下下面的话。转完后你就可以读懂了，读懂了你还需要使用rot13来转一下“shell”（Google一下，你会发现也有在线的转换器，另外还有其它的rot）</p>
<p style="padding-left: 30px;"><strong>第4关</strong>：这是众多同学被卡在的地方。很多同学吐槽这题太坑了，别忘了这是游戏啊。我问了几个早先通关的同学，他们都说还好了，只要静一下心来多观察一下，你就会找出规律的。这个回文的模式是，一个大写字符和一个数字（顺序不限）把一个小字母套起来。于是，写成正则表达式是：</p>
<p><code data-enlighter-language="shell" class="EnlighterJSRAW">([A-Z])([0-9])[a-z]\2\1|([0-9])([A-Z])[a-z]\4\3</code></p>
<p style="padding-left: 30px;">用shell命令可以很快地找到9个匹配，然后，像“cat”一样，取中间的小写字母组成一个单词。写成Shell命令是：</p>
<p><code data-enlighter-language="shell" class="EnlighterJSRAW">grep -o &quot;\([A-Z]\)\([0-9]\)[a-z]\2\1\|\([0-9]\)\([A-Z]\)[a-z]\4\3&quot; cat.txt | sed -E &quot;s/(.)(.)(.)\2\1/\3/g&quot; | awk &#039;{printf(&quot;%s&quot;,$1)}&#039; &amp;&amp; echo &quot;&quot;</code></p>
<p style="padding-left: 30px;">这题主要考的是你的观察能力和正则表达式。</p>
<p style="padding-left: 30px;"><strong>第5关</strong>：如果你点了一下图片后，你就知道，这个连接http://fun.coolshell.cn/n/2014返回了一个数字，如果你把这个数字放到那个URL中，不断地替换其中的数字，你会得到一个新的数字。于是你就会得到最终的答案。</p>
<p style="padding-left: 30px;">这道题本来我是想让大家写程序的，我原来设置了一共512个序列，但是考虑到服务受不了，所以，我把它降到了128个，这样保证你的程序可以在几秒钟内得到结果，而不会对我的服务器造成压力。但是我还是看到好几个同学人肉地copy+paste+回车刷了100多下，得到了最终答案。</p>
<p style="padding-left: 30px;"><strong>第6关：</strong>通过中序和后序遍历还原一棵二叉树，然后再找到其最深的路径，然后得到一个字符串后，把这个字符串做为一个passcode代入那个openssl的命令行中。你就可以解密密文得到下一关的答案。</p>
<p style="padding-left: 30px;">这个题，我本想设计得更隐晦一些，用一个“心脏流血”的图片来暗示openssl，然后用别的东西暗示AES-128-CBC，后来想想算了，主要还是考大家在大学里的二叉树的最基本的算法。并介绍一下openssl的shell命令行加解密的方法。</p>
<p style="padding-left: 30px;">在网上的一些攻略中我看到了大家没有用程序，而是手动地花了一棵树出来。（其实，这设计这道的时候，我本来想设计成随机树，也就每个人看到的答案都不一样，我随机建树并且找最深路径的程序都写好了，但是我最终还是没有这样做，因为这无疑增加我对这个网页游戏的代码复杂度，而我又没有太多的时间，而谜题的各种形式已经够让我花精力的了，你虽然看到了10道题，但是其实我设计了一共有16道题，我反复斟酌，即不想为难大家，又不想太简单和无聊，所以最终release了这十道题）</p>
<p style="padding-left: 30px;"><strong>第7关：</strong>N皇后问题，这个问题也是大学里的题。9皇后一共有352个解，你需要把这352个解代到那个sha1的公式中（需要上一关用于解密的passcode），这样你就会得到一个解。然后这就是通关口令。</p>
<p style="padding-left: 30px;">第6关和第7关的算法题你要是不会写的话，Google一下，反正我们是“大自然的搬运工”，不是吗？呵呵。</p>
<p style="padding-left: 30px;">第7关这题啊，我看到一个同学用穷举的1-9的排序组合的方式来向服务发请求，从123456789开始，我都看SB了，因为这关的通答案是9开头，我勒了个去！你得对我的服务器发多少次请求啊，才能得到一个200的回复啊。TNND。服了。不过这个同学我最终还是给通过了，没有判定成作弊。</p>
<p style="padding-left: 30px;"><strong>第8关：</strong>Excel的列号编程，这一关写成代码其实并不难的。但我看到网上给的好些答案，大家都是用手算。也OK，这题本身就没有什么难度，但是因为这个26进制是从1开始的，写出来的代码并不非常容易，一些边界条件很容易就break掉了。这题完全考的是编码。把COOLSHELL除以SHELL的数转成字符串。然后就进入最后一关了。</p>
<p style="padding-left: 30px;">然后，我又见到有个同学用了穷举的方式，TNND，其实每道题都有人在用穷举的方式，我勒个去。他从AAA开始穷举，不一会就穷举出正确答案了。尼玛！</p>
<p style="padding-left: 30px;"><strong>第9关：</strong>一个猪圈和一个共济会的logo，你Google一下，你就知道答案了。这题纯粹就是介绍知识的。不知道大家有没有去wikipedia上了解了一下这个猪圈密码和共济会是怎么一回事吗？这样的密文叫图片密文，还有很多类似的图片密文的。你知道吗？有相应的字库哦。也有在线的生成器哦。（因为我最近在学各种安全的基础知识，所以了解到了这个东西）</p>
<p style="padding-left: 30px;"><strong>通关：</strong>于是你就通关了。你会发现你得到了一个helloworld，这个字符串，在我一放出来这个谜题的时候，就有很多人在尝试helloworld就是那段brainfuck的代码的输出。我汗啊。还好我做了一个比较复杂的防作弊检查……</p>
<p>总体来说，这些关卡都不难，但是你最少也得用2-3个小时。<a href="http://fun.coolshell.cn/top100.html" target="_blank">Top100页面</a>时统计的平均时间是10个半小时。</p>
<p>再说一个花絮，自从，8月3日上线后，8月4日在网上就有了相关的解答攻略，还是在V2EX上，于是出现了好些只花了几分钟就做完了的人。不过好在事先我就预料到了这个事，事先预备好了“反作弊分析”的脚本，细节不想说太多，反正就是说，我会记录你答案的整个过程和行为，以此来确保TOP100中的人基本都是用自己能力答的，当然，可能会有漏判，但至少也是写过代码的。</p>
<h4>活动心得</h4>
<p>因为是第一次做活动，所以有很多感想，下面写下一些做这个活动的心得，供大家参考：</p>
<p><strong>1）要做好一个这样的解题游戏并不简单</strong>。</p>
<ul>
<li><strong>关卡设计：</strong>最花力气的地方就是设计每个关卡，我不能设计得太过隐晦，也不能设计得太过明显。最好是要符合参与者的能力，但又要高于平均以上水平的能力，最好在90%以上。这样会让大家有挑战感，但是又不会有挫败感。这个度相当难把握。总体而言，本次设计的谜题中还有很多可以改进的地方。但这毕竟是我的第一次，也算是我用其来感受一下应该怎么设计游戏。</li>
</ul>
<ul>
<li><strong>游戏黏性：</strong>除了设计谜题，还需要针对用户可能会答错的地方来给用户一些提示，原因也是为了不让用户有挫败感，虽然用户没有答对，但是需要用这些页面来鼓励用户You made some progress，这个很重要。这会让用户对游戏更有粘性，并且更愿意有更多的投入。找到这些地方也不是一件容易的事，因为做为游戏的设计者来说，很难从一个不知到答案的角度去思考。所以需要试玩，在fun.coolshell.cn正式release之前，我找了几个人比较聪明的人来试玩了一下，对这个游戏的帮助很大。</li>
</ul>
<ul>
<li><strong>游戏管理：</strong>这样的一个在线游戏自然会出一些作弊者，为了游戏的公平性，你需要剔除这些作弊者。所以，我设计了一些比较简单的记录用户所有过程的监测的算法。通过cookie和后台的http log来一同分析。这个部分也比较地花时间。我上周六的时候写这些代码写到了凌晨4点，导致脑子不清楚，出了些bug，导致在大家游戏过程中重置cookie等伤害用户体验的事件。所以说啊，不能赶啊，也不能加班啊。</li>
</ul>
<p><strong>2）关于怎么做一个活动的感想。</strong></p>
<ul>
<li><b>这次活动的背景</b>。首先，想做这个活动的起因是这样的。我一个朋友在微博上做活动——“转发微博或@几个人怎么怎么滴就有机获得什么什么的”，<strong>我在这里把这种活动简称为“转就送”活动</strong>。于是遭到了水军的刷奖品，导致他根本分不清楚哪些是正常人，哪些不是，因为新浪微博上有大量的这要瓣机器人，所以他这次活动最后失败了。我说，你得加点难度啊，要加点智商啊。<strong>而且，我看过太多的活动都是这样的，而且很多公司的活动也是这样的，我觉得太low了</strong>。于是，我就萌生了自己尝试一下的念头。</li>
</ul>
<ul>
<li><strong>我对做活动的理解</strong>。我一直觉得网上那些诸如“转就送”或是“抽奖”这样的活动都比较SB，这些人根本就不知道怎么做活动。这样做活动不需要智商，简单粗暴，效果一点也不好，活动做完了，人就走了，人们马上就忘了。我以为做活动的精髓是这样的：</li>
</ul>
<ul>
<ul>
<li><strong>真正的价值</strong>。其实，好的活动并不只是物品的价格，而是参与这个过程的感觉和体会。如果你让人觉得这是碰运气的，那么这个活动除了用物品价格来吸引人，也就没别的什么了。<strong>如果这个活动的参与过程是让人有成就感的，要有成就感那么就需要有一定难度的挑战，而且这种挑战也是让众人认可和佩服的，那么这个奖品的价格再小，价值也会很大</strong>。比如：Olympic Game，World Cup之流的，世界顶尖，四年一次，来之不易。这才是活动的价值。本次的fun.coolshell.cn上的活动，我希望让大家在做题的过程中学到一些东西，另外也希望做出来的人有一种成就感。</li>
</ul>
</ul>
<ul>
<ul>
<li><strong>让人有回味</strong>。那些简单的“转就送”式的活动不会让人产生任何的回味，只会让人产生很大的反感。就像那些“让你转发，不转就死全家”的东西，相当的让人反感。真正的回味是人们对活动参与过程的讨论和交互。在fun.coolshell.cn上线后，我就看到好几个社区在讨论这些谜题，这就是所谓的回味。<strong>只有人们对过程的回味，对参与的回味，才会让这个活动真正的成功</strong>。</li>
</ul>
</ul>
<ul>
<ul>
<li><strong>暴露活动过程</strong>。有挑战的活动，一定要有一个Who&#8217;s Who的东西，而且是随时动态更新的可以让大家查询的，这样才会从另一个侧面激发大家的热情。因为fun.coolshell.cn一开始说了只给前十个人送东西，结果在过程中，我发现了就半天时间就差不多满了，那时我在想，如果没有奖品了，剩下的人还会不会玩了？于是我飞快地开发了一个TOP100的排行榜，让大家可以看得到这个过程，虽然前十以后就没有奖品了，但是，能上这TOP100也不错。于是乎，在没有奖品情况下，依然在激发着大家的解题热情。<strong>有竞争总是一件有意思的事情，因为成就感总是来自竞争</strong>。（注：为什么top100中会有“xxxxxx”的用户，因为一开始我用的是用户提交的name，但是后来有人告诉我，这个名字可能是真名，所以，我就改成了weibo或twitter的ID，而xxxxx则是没有留下微博或twitter的）</li>
</ul>
</ul>
<p>最后吐个槽，<strong>我真的觉得那些“纯靠运气的活动”相当的SB，我看到好些公司的运营部门招了多少个所谓的高学历和高能力的人，结果干出来的运营活动的水平，其实，也就是个有小学文化水平的人就可以做的了</strong>。那些“转就送式的”、“抽奖式的”的活动，是个人都会干，根本不需要高学历的人。</p>
<h4>其它</h4>
<p>1）<strong>本次活动中，有一个隐藏关卡，还没有人找出来</strong>。要能达到隐藏关卡，需要完成所有的题目。</p>
<p>2）<strong>活动的通关页是HelloWorld，这意味着——这仅仅是个开始</strong>。</p>
<p>最后感谢大家为这个活动付出的时间！</p>
<p>（全文完）</p>
<p>&nbsp;<!--



<p align="center"><a href= target=_blank><img decoding="async" src=""></a></p>





<p align="center"><img decoding="async" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.weixin.jpg"> <img decoding="async" loading="lazy" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.mini_.jpg" width="300" height="300"> <br />关注CoolShell微信公众账号和微信小程序</p>

 

--></p>
<div style="margin-top: 15px; font-size: 16px;color: #cc0000;">
<p align="center"><strong>（转载本站文章请注明作者和出处 <a href="https://coolshell.cn/">酷 壳 &#8211; CoolShell</a> ，请勿用于任何商业用途）</strong></p>
</div>

<div class="wp_rp_wrap  wp_rp_vertical_m" ><div class="wp_rp_content"><h3 class="related_post_title">相关文章</h3><ul class="related_post wp_rp"><li ><a href="https://coolshell.cn/articles/18360.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2018/05/300x262-150x150.jpg" alt="程序员练级攻略（2018)  与我的专栏" width="150" height="150" /></a><a href="https://coolshell.cn/articles/18360.html" class="wp_rp_title">程序员练级攻略（2018)  与我的专栏</a></li><li ><a href="https://coolshell.cn/articles/17998.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2017/07/systemd-1-150x150.jpeg" alt="Linux PID 1 和 Systemd" width="150" height="150" /></a><a href="https://coolshell.cn/articles/17998.html" class="wp_rp_title">Linux PID 1 和 Systemd</a></li><li ><a href="https://coolshell.cn/articles/12103.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2014/11/tux-fork-150x150.gif" alt="vfork 挂掉的一个问题" width="150" height="150" /></a><a href="https://coolshell.cn/articles/12103.html" class="wp_rp_title">vfork 挂掉的一个问题</a></li><li ><a href="https://coolshell.cn/articles/11832.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2014/08/538efefbgw1eiz9cvx78fj20rm0fmdi8-150x150.jpg" alt="【活动】解迷题送礼物" width="150" height="150" /></a><a href="https://coolshell.cn/articles/11832.html" class="wp_rp_title">【活动】解迷题送礼物</a></li><li ><a href="https://coolshell.cn/articles/9104.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2013/02/sed-superman-150x150.png" alt="sed 简明教程" width="150" height="150" /></a><a href="https://coolshell.cn/articles/9104.html" class="wp_rp_title">sed 简明教程</a></li><li ><a href="https://coolshell.cn/articles/9070.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2013/02/awk-150x150.jpg" alt="AWK 简明教程" width="150" height="150" /></a><a href="https://coolshell.cn/articles/9070.html" class="wp_rp_title">AWK 简明教程</a></li></ul></div></div>The post <a href="https://coolshell.cn/articles/11847.html">谜题的答案和活动的心得体会</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></content:encoded>
					
					<wfw:commentRss>https://coolshell.cn/articles/11847.html/feed</wfw:commentRss>
			<slash:comments>98</slash:comments>
		
		
			</item>
		<item>
		<title>Alan Cox：大教堂、市集与市议会</title>
		<link>https://coolshell.cn/articles/9917.html</link>
					<comments>https://coolshell.cn/articles/9917.html#comments</comments>
		
		<dc:creator><![CDATA[Leo]]></dc:creator>
		<pubDate>Mon, 08 Jul 2013 07:42:27 +0000</pubDate>
				<category><![CDATA[Unix/Linux]]></category>
		<category><![CDATA[技术管理]]></category>
		<category><![CDATA[杂项资源]]></category>
		<category><![CDATA[Alan Cox]]></category>
		<category><![CDATA[Linus Torvalds]]></category>
		<category><![CDATA[Linux]]></category>
		<guid isPermaLink="false">http://coolshell.cn/?p=9917</guid>

					<description><![CDATA[<p>（感谢网友 @我的上铺叫路遥 投稿） 在网上搜到的Cox大叔于1998年在开源社区写的一篇文章，当时很轰动，明眼人一看就知道是针对ESR那篇《大教堂与市集》，从...</p>
<p class="read-more"><a class="btn btn-default" href="https://coolshell.cn/articles/9917.html"> Read More<span class="screen-reader-text">  Read More</span></a></p>
The post <a href="https://coolshell.cn/articles/9917.html">Alan Cox：大教堂、市集与市议会</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></description>
										<content:encoded><![CDATA[<p><script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-3415450859608158"
     crossorigin="anonymous"></script><strong>（感谢网友 </strong><a href="http://weibo.com/fullofbull" target="_blank"><strong>@我的上铺叫路遥</strong></a><strong> 投稿）</strong></p>
<p>在网上搜到的Cox大叔于1998年在开源社区写的一篇文章，当时很轰动，明眼人一看就知道是针对ESR那篇《大教堂与市集》，从中可见Alan在项目管理风格上乃至个人性格上都与ESR、Linus等人不同之处。顺便说一句，Alan现在出于“家庭原因”已经离开了Linux项目，他曾经评价Linus是<a href="http://apolyton.net/showthread.php/130212-Linus-Torvalds-is-a-terrible-engineer-Alan-Cox" target="_blank">a good developer but a terrible engineer</a>，甚至在Google+上直接说Linus就是一a*sh**e。不管如何，两位曾经十余年里并肩战斗惺惺相惜的大牛就此分道扬镳还是惹人唏嘘。</p>
<p>言归正传，以下为slashdot收录的英文原文：<a href="http://news.slashdot.org/story/98/10/13/1423253/featurecathedrals-bazaars-and-the-town-council" target="_blank">Cathedrals, Bazaars and the Town Council</a>。</p>
<p>以下是一些我对市集模式的想法，我认为这值得分享，这种模式会教你如何完全毁掉一个自由软件项目。我还举了一个我称之为“市议会”(Town Council)效应的实例（虽然那些市议员们可不这么认为，注：此处指Linux项目开发者）。</p>
<p>关于软件开发人员，你必须去了解一些情况。首先要了解的是真正优秀的程序员相对来说并不普遍，不仅如此，在很多其它专业领域里“真正的程序员”和一些捣乱的家伙之间的区别要比“伟大”和“普通”之间的区别要大得多，研究表明生产效率上最好的同其余的比重是30:1。</p>
<p>其次，你需要了解的是一大堆妄想型码农(wannabe programmer)总是善于发表意见。其中很多人患上了一种叫做“流行性热词”(buzzword)疾病，或者对他们“非黑即白”(one true path)的思考方式有着特殊的偏执，网上很多讨论都是廉价的。</p>
<p><span id="more-9917"></span></p>
<p>第三个关于软件项目的事情就是我们所谓的“闲杂人员”(the masses)。他们不是编程人员，而在其它方面有着大量贡献——文档编辑、用户支持，以及对那类经常争论你应该获得许可证才能上网的人的说服工作。</p>
<p>我想以Linux 8086（注，Intel设计的16位处理器架构）为例来说明如何将整个工程全部搞砸。将Linux的一个子集移植到8086上大体是这世上最无聊的活动之一。整件事的发起就像个笑话并走向失控。</p>
<p>只有极少数真正的程序员会将时间及其良好的精神状态（或许那是假的）花费在那些唯一价值在于“黑客精神”(Hack Value)的项目上，故而在任何时候那种项目也就两三个核心贡献人员而已。</p>
<p>不幸的是大批人认为将Linux运行在8086上是干净的，为此义不容辞地想要“入伙”。这类人大多属于妄想型码农之流，以至于连闲杂人员在一个安全距离之外都会沾染上这个项目的“愚蠢”因子。</p>
<p>问题的导火索在于一大批充满（大多善意的）危险的一知半解的人们的意识观念——不是代码，而是意识观念。他们似乎很懂得如何去编程，但很多人连“Hello World”这样的C程序都不会。他们花了几星期时间去争论并投票该使用什么编译器，甚至在项目开展一年后还在争论是否去写个充分完美的编译器。他们热衷于辩论如何生成大量二进制文件，却又对内核swapper（注，即idle task）设计一无所知。</p>
<p>Linux 8086项目仍然进行着，真正的开发人员将邮件列表里许多其他成员加入到清除文件(kill files)中，以便他们之间可以顺畅地通过邮件列表沟通，只因半吊子打酱油的家伙实在太多了。这一切不再是市集模式，而是形成了一个核心小组，对圈子里许多人而言这是一种礼貌用语。在这种情形下人们不可避免地处于被动位置。</p>
<p>像Linux这种基于用户/程序员的项目成长缓慢，虽然它是靠着一群贡献代码的人得以成长起来，但这些人的背景要么是从原始的Minix（注，一种微内核操作系统）黑客社区起家，要么通过艰难的方式不断从头学起。随着项目增长，人们本应该形成一个“Linux内核结构规划管理委员会”，而不是掉入将人们招来唤去，不将失败视为问题的怪圈，用Linus的话来说就是“给我看源码”。</p>
<p>如果有人陷入困境，他可以发帖询问，在这之前包括现在很大程度上都基于人们正常地拥有时间并具备知识来回复他。在Linux 8086的案例中，开发人员很长一段时间身陷囹圄。假使主动活跃的程序员对只有潜在用处的妄想型码农的比例更高一点的话，我们就可以将一些杂音转化成生产力。项目也就获得更多有用的程序员，他们可以轮流向他人传授经验，任何学习活动都会让你变得更好，哪怕只有一些少量实习生。</p>
<p>一些人会认为你无法将那些“次要程序员”(lesser programmer)训练成真正的程序员。就Linux项目的个人经验而言，很多人员只要获得一丁点儿的帮助和自信鼓励都将成为世界上最好的开发人员之一。只要帮助和鼓励足够多，很多人就能成功。</p>
<p>Linux 8086总算大部分从“侵扰”中恢复过来，可至今仍是个不起眼的小项目。你可以从CVS目录树上下载这个由Alistair Riddich领导的项目，他做了很多优秀的工作。随着市议员的撤出，人们可以询问、参与并改善这个项目。</p>
<p>我们从这个项目，还有其它相同命运的早期Linux 16位处理器项目（有的已死）中很清楚地学到以下几点教训。</p>
<ul>
<li>从项目一开始就发布源代码。哪怕不是很有用也无关紧要，将市议会排序分类的最好方式就是发布源代码并告知人们。Linux、KDE以及GNOME都遵循这种方式并获益良多。你可以花一辈子时间去争论怎样写代码才是正确的。只要代码公布，人们（不管水平怎样）都会把玩它。</li>
</ul>
<ul>
<li>要欣赏那些给一点帮助就会对项目做出巨大贡献的人。如果他们最初的补丁有错误，不要盛气凌人，向其解释问题出在哪里并给出解决方案的建议，或者可以查询解决方法的地方。解答真正的问题，帮助别人，你所花费的一分一秒都会成十倍地回报在项目上，对社会也会带来无法估量的好处。[注]</li>
</ul>
<ul>
<li>不要忘记那些非开发人员。我难过地发现许多人问起“前5名最重要的内核成员”时却极少涉及在所有人中最重要的一些——他们负责维护网站，更新日志和邮件列表，还有编辑文档，这些都是同等重要。<br />
Linus那句“给我看源代码”对真正的项目来说是个狭隘的视角。当你听到人们说“我很想帮忙，可我不会编程”，那么他可以从事文档编写。当人们说“但英语不是我的第一语言”，这时你需要的是一位文档编辑或另一门语言翻译者。</li>
</ul>
<ul>
<li>尝试将有用的人从杂音中分离出来，将有意愿帮忙的人从一大堆无聊评论中分离出来是很难的。在Linux 8086项目中我的确错误地放弃了这一目标，如何将那些只会空谈而又无所事事的人弄走是一门学问。</li>
</ul>
<p>下次碰到人们在项目上投票，或者问题讨论了一个月才实现这类情况，给予他们警告。这样才能使人正确地解决问题。在你看来如果一些稀奇古怪的事务不顾一切地运行着，要求他们给你发个补丁，只要能够生效的话。</p>
<p>小心地说“我们应该怎样”之类的话，对“我该如何做”这样的人伸出援手。</p>
<p>Alan</p>
<p>[注]这段话举个例子说明一下。Linux IPv6源码作者以前在葡萄牙上网聊天，只会简单讨论和问一些基本问题。我们助其弄明白一些内核原理之后，他写了大约75%的IPv6协议栈代码，他最近受聘于美国思科公司。</p>
<p>附录一：一篇针对本文的<a href="http://tech.groups.yahoo.com/group/java-os-project/message/2358?var=1&amp;p=1" target="_blank">吐槽贴</a></p>
<p>附录二：2009年Cox回复Torvalds的<a href="https://lkml.org/lkml/2009/7/28/375" target="_blank">邮件</a>，事情起因是Cox的一个tty patch导致<a href="https://lkml.org/lkml/2009/7/11/125" target="_blank">kdesu(KDE project&#8217;s su utility)</a>程序无法工作，该问题争论长达两个星期，此后Alan离开了Linux项目投奔Intel。<!--



<p align="center"><a href= target=_blank><img decoding="async" src=""></a></p>





<p align="center"><img decoding="async" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.weixin.jpg"> <img decoding="async" loading="lazy" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.mini_.jpg" width="300" height="300"> <br />关注CoolShell微信公众账号和微信小程序</p>

 

--></p>
<div style="margin-top: 15px; font-size: 16px;color: #cc0000;">
<p align="center"><strong>（转载本站文章请注明作者和出处 <a href="https://coolshell.cn/">酷 壳 &#8211; CoolShell</a> ，请勿用于任何商业用途）</strong></p>
</div>

<div class="wp_rp_wrap  wp_rp_vertical_m" ><div class="wp_rp_content"><h3 class="related_post_title">相关文章</h3><ul class="related_post wp_rp"><li ><a href="https://coolshell.cn/articles/9859.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2013/06/Alan-Cox-150x150.jpg" alt="Alan Cox：单向链表中prev指针的妙用" width="150" height="150" /></a><a href="https://coolshell.cn/articles/9859.html" class="wp_rp_title">Alan Cox：单向链表中prev指针的妙用</a></li><li ><a href="https://coolshell.cn/articles/8990.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2013/02/linus_pointer_to_pointer-150x150.jpg" alt="Linus：利用二级指针删除单向链表" width="150" height="150" /></a><a href="https://coolshell.cn/articles/8990.html" class="wp_rp_title">Linus：利用二级指针删除单向链表</a></li><li ><a href="https://coolshell.cn/articles/2322.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2010/04/o_unixrichiethompson-150x150.jpg" alt="Unix传奇(上篇)" width="150" height="150" /></a><a href="https://coolshell.cn/articles/2322.html" class="wp_rp_title">Unix传奇(上篇)</a></li><li ><a href="https://coolshell.cn/articles/1278.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/plugins/wordpress-23-related-posts-plugin/static/thumbs/20.jpg" alt="Linus Torvalds 语录 Top 10" width="150" height="150" /></a><a href="https://coolshell.cn/articles/1278.html" class="wp_rp_title">Linus Torvalds 语录 Top 10</a></li><li ><a href="https://coolshell.cn/articles/22320.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2022/12/eBPF-150x150.jpeg" alt="eBPF 介绍" width="150" height="150" /></a><a href="https://coolshell.cn/articles/22320.html" class="wp_rp_title">eBPF 介绍</a></li><li ><a href="https://coolshell.cn/articles/19219.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2019/03/linux.ninja_-150x150.png" alt="打造高效的工作环境 &#8211; Shell 篇" width="150" height="150" /></a><a href="https://coolshell.cn/articles/19219.html" class="wp_rp_title">打造高效的工作环境 &#8211; Shell 篇</a></li></ul></div></div>The post <a href="https://coolshell.cn/articles/9917.html">Alan Cox：大教堂、市集与市议会</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></content:encoded>
					
					<wfw:commentRss>https://coolshell.cn/articles/9917.html/feed</wfw:commentRss>
			<slash:comments>20</slash:comments>
		
		
			</item>
		<item>
		<title>Alan Cox：单向链表中prev指针的妙用</title>
		<link>https://coolshell.cn/articles/9859.html</link>
					<comments>https://coolshell.cn/articles/9859.html#comments</comments>
		
		<dc:creator><![CDATA[Leo]]></dc:creator>
		<pubDate>Sun, 30 Jun 2013 04:27:04 +0000</pubDate>
				<category><![CDATA[C/C++语言]]></category>
		<category><![CDATA[Unix/Linux]]></category>
		<category><![CDATA[网络安全]]></category>
		<category><![CDATA[Alan Cox]]></category>
		<category><![CDATA[C++]]></category>
		<category><![CDATA[Kernel]]></category>
		<category><![CDATA[Linux]]></category>
		<category><![CDATA[network]]></category>
		<category><![CDATA[TCP]]></category>
		<guid isPermaLink="false">http://coolshell.cn/?p=9859</guid>

					<description><![CDATA[<p>（感谢网友 @我的上铺叫路遥 投稿） 之前发过一篇二级指针操作单向链表的例子，显示了C语言指针的灵活性，这次再探讨一个指针操作链表的例子，而且是一种完全不同的用...</p>
<p class="read-more"><a class="btn btn-default" href="https://coolshell.cn/articles/9859.html"> Read More<span class="screen-reader-text">  Read More</span></a></p>
The post <a href="https://coolshell.cn/articles/9859.html">Alan Cox：单向链表中prev指针的妙用</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></description>
										<content:encoded><![CDATA[<p><script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js?client=ca-pub-3415450859608158"
     crossorigin="anonymous"></script><figure id="attachment_9906" aria-describedby="caption-attachment-9906" style="width: 200px" class="wp-caption alignright"><img decoding="async" loading="lazy" class="size-medium wp-image-9906" title="Alan Cox" alt="Alan Cox" src="https://coolshell.cn/wp-content/uploads/2013/06/Alan-Cox-200x300.jpg" width="200" height="300" srcset="https://coolshell.cn/wp-content/uploads/2013/06/Alan-Cox-200x300.jpg 200w, https://coolshell.cn/wp-content/uploads/2013/06/Alan-Cox-180x270.jpg 180w, https://coolshell.cn/wp-content/uploads/2013/06/Alan-Cox.jpg 667w" sizes="(max-width: 200px) 100vw, 200px" /><figcaption id="caption-attachment-9906" class="wp-caption-text">Alan Cox</figcaption></figure></p>
<p><span style="color: #cc0000;"><strong> （感谢网友 </strong></span><a href="http://weibo.com/fullofbull" target="_blank"><strong>@我的上铺叫路遥</strong></a><span style="color: #cc0000;"><strong> 投稿）</strong></span></p>
<p>之前发过一篇<a href="https://coolshell.cn/articles/8990.html" target="_blank">二级指针操作单向链表</a>的例子，显示了C语言指针的灵活性，这次再探讨一个指针操作链表的例子，而且是一种完全不同的用法。</p>
<p>这个例子是linux-1.2.13网络协议栈里的，关于链表遍历&#038;数据拷贝的一处实现。源文件是/net/inet/dev.c，你可以从<a href="https://www.kernel.org/pub/linux/kernel/v1.2/" target="_blank">kernel.org</a>官网上下载。</p>
<p>从最早的0.96c版本开始，linux网络部分一直采取TCP/IP协议族实现，这是最为广泛应用的网络协议，整个架构就是经典的OSI七层模型的描述，其中dev.c是属于链路层实现。从功能上看，其位于网络设备驱动程序和网络层协议实现模块之间，作为二者之间的数据包传输通道，一种接口模块而存在——对驱动层的接口函数netif_rx, 以及对网络层的接口函数net_bh。前者提供给驱动模块的中断例程调用，用于链路数据帧的封装；后者作为驱动中断例程<strong>底半部(buttom half)</strong>，用于对数据帧的解析处理并向上层传送。</p>
<p>为了便于理解，这里补充一下网络通信原理和linux驱动中断机制的背景知识。从最底层的物理层说起，当主机和路由器相互之间进行通信的时候，在物理介质上（同轴、光纤等）以电平信号进行传输。主机或路由器的<strong>硬件接口（网卡）</strong>负责收发这些信号，当信号发送到接口，再由内置的<strong>调制解调器(modem)</strong>将数字信号转换成二进制码，这样才能驻留在主机的硬件缓存中。这时接口（网卡）设备驱动程序将通过<strong>硬中断</strong>来获取硬件缓存中的数据，驱动程序是操作系统中负责直接同硬件设备打交道的模块，硬中断的触发是初始化时通过设置控制寄存器实现的，用于通知驱动程序硬件缓存中有新的数据到来。linux卡设备驱动就是在<strong>中断处理例程(ISR)</strong>中将硬件缓存数据拷贝到内核缓存中，打包成数据链路帧进行解析处理，再向上分发到各种协议层。由于ISR上下文是原子性的、中断屏蔽的，整个步骤又较为繁琐，因此全部放在ISR中处理会影响到其它中断响应实时性，于是linux有实现一种bottom half的<strong>软中断</strong>处理机制，将整个ISR一分为二，前半部上下文屏蔽所有中断，专门处理紧急的、实时性强的事务，如拷贝硬件缓存并打包封装，后半部上下文没有屏蔽中断（但代码不可重入），用于处理比较耗时且非紧急事务，包括数据帧的解析处理和分发。下面要讲的net_bh就属于后半部。</p>
<p>我们主要关心的是将链路帧分发到协议层那一段逻辑，下面摘自net_bh函数中的一段代码：</p>
<p><span id="more-9859"></span></p>
<pre data-enlighter-language="c" class="EnlighterJSRAW">526 void net_bh(void *tmp)
527 {
       ...
577
578    /*
579    * We got a packet ID.  Now loop over the &quot;known protocols&quot;
580    * table (which is actually a linked list, but this will
581    * change soon if I get my way- FvK), and forward the packet
582    * to anyone who wants it.
583    *
584    * [FvK didn&#039;t get his way but he is right this ought to be
585    * hashed so we typically get a single hit. The speed cost
586    * here is minimal but no doubt adds up at the 4,000+ pkts/second
587    * rate we can hit flat out]
588    */
589   pt_prev = NULL;
590   for (ptype = ptype_base; ptype != NULL; ptype = ptype-&gt;next)
591   {
592    if ((ptype-&gt;type == type || ptype-&gt;type == htons(ETH_P_ALL)) &amp;&amp; (!ptype-&gt;dev || ptype-&gt;dev==skb-&gt;dev))
593    {
594      /*
595      * We already have a match queued. Deliver
596      * to it and then remember the new match
597      */
598      if(pt_prev)
599      {
600        struct sk_buff *skb2;
601        skb2=skb_clone(skb, GFP_ATOMIC);
602        /*
603        * Kick the protocol handler. This should be fast
604        * and efficient code.
605        */
606        if(skb2)
607          pt_prev-&gt;func(skb2, skb-&gt;dev, pt_prev);
608      }
609      /* Remember the current last to do */
610      pt_prev=ptype;
611    }
612   } /* End of protocol list loop */
613   /*
614   * Is there a last item to send to ?
615   */
616   if(pt_prev)
617     pt_prev-&gt;func(skb, skb-&gt;dev, pt_prev);
618   /*
619    *  Has an unknown packet has been received ?
620    */
621   else
622     kfree_skb(skb, FREE_WRITE);
623
      ...
640 }</pre>
<p>在此稍稍解说一下数据结构，skb就是内核缓存中sock数据封装，协议栈里从链路层到传输层都会用到，只不过封装格式不同，主要是对<strong>协议首部(header)</strong>的由下而上层层剥离（反之由上而下是层层创建），在此你只需理解为一个链路数据帧即可。这段代码的逻辑是解析skb中的协议字段，从协议类型链表（由ptype_base维护）中查询对应的协议节点进行函数指针func回调，以便将数据帧分发到相应的协议层（如ARP、IP、8022、8023等）。</p>
<p>第一眼看上去是不是有点奇怪？这段代码竟然用一个pt_prev指针去维护ptype链表中前一个节点，从而产生了额外的条件分支判断，咋一看是否多了很多“余”了？回顾一下那篇<a href="https://coolshell.cn/articles/8990.html" target="_blank">二级指针操作单向链表</a>的博文，简直完全是反其道而行之的。如果把pt_prev去掉，代码可以精简为：</p>
<pre data-enlighter-language="c" class="EnlighterJSRAW">  for (ptype = ptype_base; ptype != NULL; ptype = ptype-&gt;next)
  {
    if ((ptype-&gt;type == type || ptype-&gt;type == htons(ETH_P_ALL)) &amp;&amp; (!ptype-&gt;dev || ptype-&gt;dev==skb-&gt;dev))
    {
        /*
        * We already have a match queued. Deliver
        * to it and then remember the new match
        */
        struct sk_buff *skb2;
        skb2=skb_clone(skb, GFP_ATOMIC);
        /*
        * Kick the protocol handler. This should be fast
        * and efficient code.
        */
        if(skb2)
            pt_prev-&gt;func(skb2, skb-&gt;dev, pt_prev);
    }
} /* End of protocol list loop */

kfree_skb(skb, FREE_WRITE);</pre>
<p>咋看一下“干净”了很多，不是吗？但我们要记住一点，凡是网上发布的linux内核源代码，都是都是经过众多黑客高手们重重检视并验证过的，人家这么写肯定有十分充足的理由，所以不要太过于相信自己的直觉了，让我们再好好review一下代码吧！看看这段循环里做了什么事情？特别是第592~611行。</p>
<p>由于从网络上拷贝过来skb是唯一的，而分发的协议对象可能是多个，所以在回调之前要做一次clone动作（注意这里是深度拷贝，相当于一次kmalloc）。分发之后还需要调用kfree_skb释放掉原始skb数据块，它的历史使命到此完成了，没有保留的必要（第622行）。<strong>注意，这两个动作都是存在内核开销的。</strong></p>
<p>然而这里为啥要pt_prev维护一个后向节点呢？这是有深意的，它的作用就是将当前匹配协议项的回调操作延时了。举个例子，如果链表遍历中找到某个匹配项，当前循环仅仅用pt_prev去记录这个匹配项，除此之外不做任何事情，待到下一次匹配项找到时，才去做上一个匹配项pt_prev的回调操作，直到循环结束，才会去做最后的匹配项的回调（当然pt_prev==NULL表示没有一次匹配，直接释放掉），所以这是一种<strong>拖延战术</strong>。有什么好处呢？就是比原先节省了很多不必要的操作。那么哪些操作是不必要的呢？这里我们逆向思考一下，我们看到clone是在协议字段匹配并且pt_prev!=NULL的前提条件下执行的，而kfree是在pt_prev==NULL的前提条件下执行的。在此可以假设一下，如果ptype链表中存在N项协议与之匹配，那么这段代码只会执行N-1次clone，而没有pt_prev时将会执行N次clone和1次kfree，再如果ptype链表中有且仅有一项协议与之匹配，那么整个循环既不会执行到第601行的clone，也不会执行到第622行的kfree。</p>
<p>也就是说，<strong>当整个链表至少有一项匹配的一般情况下，pt_prev存在比没有时减少了一次clone和一次kfree的开销；只有全部不匹配的最差情况下，两者都只做一次kfree动作，持平。这就是延迟策略产生的效益</strong>。</p>
<p>熟悉TCP/IP协议族的开发人员应该知道<strong>MTU（最大传输单元）</strong>这个概念，遵循不同协议的MTU值是不同的。比如以太网帧MTU是1500个字节，802.3帧MTU是1492字节，PPP链路帧MTU是269字节，而超通道MTU理论上是65535字节。要知道在一个高速吞吐量通信网络环境下，在大块数据分片传输线路里，在内核级别代码中，减少一处系统开销意味着什么？</p>
<p>其实我们完全可以抛开一切网络协议相关知识，这不过是一段极其普通的单向链表操作而已，逻辑并不复杂。但是看看人家顶级黑客是怎么思考和coding的，对比一下自己写过的代码，多少次数据处理是用一个简单的for循环匆匆敷衍了事而没有进一步思考其中的粗陋和不合理之处？面对真正的编程高手这种“心计”与“城府”，你是不是有种莫名不安感？你会怀疑你真的了解怎么去使用和操作C语言中基本的链表数据结构么？如果答案是肯定的，那就开始颤抖吧（哈，别误会，其实上面这段话不过是笔者的自我告解罢了）~~~</p>
<p>最后，让我们感谢尊敬的<a href="http://en.wikipedia.org/wiki/Alan_Cox" target="_blank">Alan Cox</a>大大对Linux社区卓越精细、无与伦比的贡献！（Alan是图中中部戴红帽子的那位）</p>
<p><img decoding="async" loading="lazy" class="aligncenter" alt="Linux Kernel Team" src="http://old.lwn.net/images/ks/group2.jpg" width="704" height="323" /></p>
<p><strong>附注：</strong></p>
<p>最新的Linux-2.6.x版本中协议栈实现部分变动很大，但/net/core/dev.c的netif_receive_skb函数里仍然保留了pt_prev这种用法，目的是一样的，都是为了减少一次系统开销的优化操作。</p>
<p>关于Alan，他在斯旺西大学工作时，在学校服务器上安装了一个早期的linux版本，供学校使用。他修正了许多的问题，重写了网络系统中的许多部份。随后成为linux内核开发小组中的重要成员。<a href="http://en.wikipedia.org/wiki/Alan_Cox" target="_blank">Alan Cox</a>负责维持2.2版，在2.4版上拥有自己的分支（在版本号上会冠上ac，如 2.4.13-ac1）。他的分支版本非常稳定，修正许多错误，许多厂商都使用他的版本。在他去进修工商管理硕士之前，涉入许多linux内核开发的事务，在社群中有很高的地位，有时会被视为是Linus之下的第二号领导者。</p>
<p>不过，今年1月28日的时候，Alan因为家庭原因宣布退出Linux项目了，下面是他Google+的声明：</p>
<blockquote><p>“I’m leaving the Linux world and Intel for a bit for family reasons, I’m aware that ‘family reasons’ is usually management speak for ‘I think the boss is an asshole’ but I’d like to assure everyone that while I frequently think Linus is an asshole (and therefore very good as kernel dictator) I am departing quite genuinely for family reasons and not because I’ve fallen out with Linus or Intel or anyone else. Far from it I’ve had great fun working there.”</p></blockquote>
<p>（全文完）<!--



<p align="center"><a href= target=_blank><img decoding="async" src=""></a></p>





<p align="center"><img decoding="async" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.weixin.jpg"> <img decoding="async" loading="lazy" src="https://coolshell.cn/wp-content/uploads/2020/03/coolshell.mini_.jpg" width="300" height="300"> <br />关注CoolShell微信公众账号和微信小程序</p>

 

--></p>
<div style="margin-top: 15px; font-size: 16px;color: #cc0000;">
<p align="center"><strong>（转载本站文章请注明作者和出处 <a href="https://coolshell.cn/">酷 壳 &#8211; CoolShell</a> ，请勿用于任何商业用途）</strong></p>
</div>

<div class="wp_rp_wrap  wp_rp_vertical_m" ><div class="wp_rp_content"><h3 class="related_post_title">相关文章</h3><ul class="related_post wp_rp"><li ><a href="https://coolshell.cn/articles/8990.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2013/02/linus_pointer_to_pointer-150x150.jpg" alt="Linus：利用二级指针删除单向链表" width="150" height="150" /></a><a href="https://coolshell.cn/articles/8990.html" class="wp_rp_title">Linus：利用二级指针删除单向链表</a></li><li ><a href="https://coolshell.cn/articles/18654.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2018/12/docker-networking-1-150x150.png" alt="记一次Kubernetes/Docker网络排障" width="150" height="150" /></a><a href="https://coolshell.cn/articles/18654.html" class="wp_rp_title">记一次Kubernetes/Docker网络排障</a></li><li ><a href="https://coolshell.cn/articles/18360.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2018/05/300x262-150x150.jpg" alt="程序员练级攻略（2018)  与我的专栏" width="150" height="150" /></a><a href="https://coolshell.cn/articles/18360.html" class="wp_rp_title">程序员练级攻略（2018)  与我的专栏</a></li><li ><a href="https://coolshell.cn/articles/9917.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/plugins/wordpress-23-related-posts-plugin/static/thumbs/10.jpg" alt="Alan Cox：大教堂、市集与市议会" width="150" height="150" /></a><a href="https://coolshell.cn/articles/9917.html" class="wp_rp_title">Alan Cox：大教堂、市集与市议会</a></li><li ><a href="https://coolshell.cn/articles/8088.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/plugins/wordpress-23-related-posts-plugin/static/thumbs/7.jpg" alt="对技术的态度" width="150" height="150" /></a><a href="https://coolshell.cn/articles/8088.html" class="wp_rp_title">对技术的态度</a></li><li ><a href="https://coolshell.cn/articles/7490.html" class="wp_rp_thumbnail"><img src="https://coolshell.cn/wp-content/uploads/2012/06/f1-150x150.jpg" alt="性能调优攻略" width="150" height="150" /></a><a href="https://coolshell.cn/articles/7490.html" class="wp_rp_title">性能调优攻略</a></li></ul></div></div>The post <a href="https://coolshell.cn/articles/9859.html">Alan Cox：单向链表中prev指针的妙用</a> first appeared on <a href="https://coolshell.cn">酷 壳 - CoolShell</a>.]]></content:encoded>
					
					<wfw:commentRss>https://coolshell.cn/articles/9859.html/feed</wfw:commentRss>
			<slash:comments>56</slash:comments>
		
		
			</item>
	</channel>
</rss>
