Julia Evans 发布的文章

我喜欢互联网的一点是在互联网放置静态页面是如此简单。今天有人问我该怎么做,所以我想我会快速地写下来!

只是一个 HTML 页面

我的所有网站都只是静态 HTML 和 CSS。我的网页设计技巧相对不高(https://wizardzines.com 是我自己开发的最复杂的网站),因此保持我所有的网站相对简单意味着我可以做一些改变/修复,而不会花费大量时间。

因此,我们将在此文章中采用尽可能简单的方式 —— 只需一个 HTML 页面。

HTML 页面

我们要放在互联网上的网站只是一个名为 index.html 的文件。你可以在 https://github.com/jvns/website-example 找到它,它是一个 Github 仓库,其中只包含一个文件。

HTML 文件中包含一些 CSS,使其看起来不那么无聊,部分复制自 https://example.com

如何将 HTML 页面放在互联网上

有以下几步:

  1. 注册 Neocities 帐户
  2. 将 index.html 复制到你自己 neocities 站点的 index.html 中
  3. 完成

上面的 index.html 页面位于 julia-example-website.neocities.com 中,如果你查看源代码,你将看到它与 github 仓库中的 HTML 相同。

我认为这可能是将 HTML 页面放在互联网上的最简单的方法(这是一次回归 Geocities,它是我在 2003 年制作我的第一个网站的方式):)。我也喜欢 Neocities (像 glitch,我也喜欢)它能实验、学习,并有乐趣。

其他选择

这绝不是唯一简单的方式,在你推送 Git 仓库时,Github pages 和 Gitlab pages 以及 Netlify 都将会自动发布站点,并且它们都非常易于使用(只需将它们连接到你的 GitHub 仓库即可)。我个人使用 Git 仓库的方式,因为 Git 不会让我感到紧张,我想知道我实际推送的页面发生了什么更改。但我想你如果第一次只想将 HTML/CSS 制作的站点放到互联网上,那么 Neocities 就是一个非常好的方法。

如果你不只是玩,而是要将网站用于真实用途,那么你或许会需要买一个域名,以便你将来可以更改托管服务提供商,但这有点不那么简单。

这是学习 HTML 的一个很好的起点

如果你熟悉在 Git 中编辑文件,同时想练习 HTML/CSS 的话,我认为将它放在网站中是一个有趣的方式!我真的很喜欢它的简单性 —— 实际上这只有一个文件,所以没有其他花哨的东西需要去理解。

还有很多方法可以复杂化/扩展它,比如这个博客实际上是用 Hugo 生成的,它生成了一堆 HTML 文件并放在网络中,但从基础开始总是不错的。


via: https://jvns.ca/blog/2019/09/06/how-to-put-an-html-page-on-the-internet/

作者:Julia Evans 选题:lujun9972 译者:geekpi 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

嗨!就在上周,我还自认为对 Linux 上的用户和组的工作机制了如指掌。我认为它们的关系是这样的:

  1. 每个进程都属于一个用户(比如用户 julia
  2. 当这个进程试图读取一个被某个组所拥有的文件时, Linux 会 a. 先检查用户julia 是否有权限访问文件。(LCTT 译注:此处应该是指检查文件的所有者是否就是 julia) b. 检查 julia 属于哪些组,并进一步检查在这些组里是否有某个组拥有这个文件或者有权限访问这个文件。
  3. 如果上述 a、b 任一为真(或者“其它”位设为有权限访问),那么这个进程就有权限访问这个文件。

比如说,如果一个进程被用户 julia 拥有并且 juliaawesome 组,那么这个进程就能访问下面这个文件。

r--r--r-- 1 root awesome     6872 Sep 24 11:09 file.txt

然而上述的机制我并没有考虑得非常清楚,如果你硬要我阐述清楚,我会说进程可能会在运行时去检查 /etc/group 文件里是否有某些组拥有当前的用户。

然而这并不是 Linux 里“组”的工作机制

我在上个星期的工作中发现了一件有趣的事,事实证明我前面的理解错了,我对组的工作机制的描述并不准确。特别是 Linux 并不会在进程每次试图访问一个文件时就去检查这个进程的用户属于哪些组。

我在读了《Linux 编程接口》这本书的第九章(“进程资格”)后才恍然大悟(这本书真是太棒了),这才是组真正的工作方式!我意识到之前我并没有真正理解用户和组是怎么工作的,我信心满满的尝试了下面的内容并且验证到底发生了什么,事实证明现在我的理解才是对的。

用户和组权限检查是怎么完成的

现在这些关键的知识在我看来非常简单! 这本书的第九章上来就告诉我如下事实:用户和组 ID 是进程的属性,它们是:

  • 真实用户 ID 和组 ID;
  • 有效用户 ID 和组 ID;
  • 保存的 set-user-ID 和保存的 set-group-ID;
  • 文件系统用户 ID 和组 ID(特定于 Linux);
  • 补充的组 ID;

这说明 Linux 实际上检查一个进程能否访问一个文件所做的组检查是这样的:

  • 检查一个进程的组 ID 和补充组 ID(这些 ID 就在进程的属性里,并不是实时在 /etc/group 里查找这些 ID)
  • 检查要访问的文件的访问属性里的组设置
  • 确定进程对文件是否有权限访问(LCTT 译注:即文件的组是否是以上的组之一)

通常当访问控制的时候使用的是有效用户/组 ID,而不是真实用户/组 ID。技术上来说当访问一个文件时使用的是文件系统的 ID,它们通常和有效用户/组 ID 一样。(LCTT 译注:这句话针对 Linux 而言。)

将一个用户加入一个组并不会将一个已存在的进程(的用户)加入那个组

下面是一个有趣的例子:如果我创建了一个新的组:panda 组并且将我自己(bork)加入到这个组,然后运行 groups 来检查我是否在这个组里:结果是我(bork)竟然不在这个组?!

bork@kiwi~> sudo addgroup panda
Adding group `panda' (GID 1001) ...
Done.
bork@kiwi~> sudo adduser bork panda
Adding user `bork' to group `panda' ...
Adding user bork to group panda
Done.
bork@kiwi~> groups
bork adm cdrom sudo dip plugdev lpadmin sambashare docker lxd

panda 并不在上面的组里!为了再次确定我们的发现,让我们建一个文件,这个文件被 panda 组拥有,看看我能否访问它。

$  touch panda-file.txt
$  sudo chown root:panda panda-file.txt
$  sudo chmod 660 panda-file.txt
$  cat panda-file.txt
cat: panda-file.txt: Permission denied

好吧,确定了,我(bork)无法访问 panda-file.txt。这一点都不让人吃惊,我的命令解释器并没有将 panda 组作为补充组 ID,运行 adduser bork panda 并不会改变这一点。

那进程一开始是怎么得到用户的组的呢?

这真是个非常令人困惑的问题,对吗?如果进程会将组的信息预置到进程的属性里面,进程在初始化的时候怎么取到组的呢?很明显你无法给你自己指定更多的组(否则就会和 Linux 访问控制的初衷相违背了……)

有一点还是很清楚的:一个新的进程是怎么从我的命令行解释器(/bash/fish)里被执行而得到它的组的。(新的)进程将拥有我的用户 ID(bork),并且进程属性里还有很多组 ID。从我的命令解释器里执行的所有进程是从这个命令解释器里 fork() 而来的,所以这个新进程得到了和命令解释器同样的组。

因此一定存在一个“第一个”进程来把你的组设置到进程属性里,而所有由此进程而衍生的进程将都设置这些组。而那个“第一个”进程就是你的 登录程序 login shell ,在我的笔记本电脑上,它是由 login 程序(/bin/login)实例化而来。登录程序以 root 身份运行,然后调用了一个 C 的库函数 —— initgroups 来设置你的进程的组(具体来说是通过读取 /etc/group 文件),因为登录程序是以 root 运行的,所以它能设置你的进程的组。

让我们再登录一次

好了!假如说我们正处于一个登录程序中,而我又想刷新我的进程的组设置,从我们前面所学到的进程是怎么初始化组 ID 的,我应该可以通过再次运行登录程序来刷新我的进程组并启动一个新的登录命令!

让我们试试下边的方法:

$ sudo login bork
$ groups
bork adm cdrom sudo dip plugdev lpadmin sambashare docker lxd panda
$ cat panda-file.txt # it works! I can access the file owned by `panda` now!

当然,成功了!现在由登录程序衍生的程序的用户是组 panda 的一部分了!太棒了!这并不会影响我其他的已经在运行的登录程序(及其子进程),如果我真的希望“所有的”进程都能对 panda 组有访问权限。我必须完全的重启我的登录会话,这意味着我必须退出我的窗口管理器然后再重新登录。(LCTT 译注:即更新进程树的树根进程,这里是窗口管理器进程。)

newgrp 命令

在 Twitter 上有人告诉我如果只是想启动一个刷新了组信息的命令解释器的话,你可以使用 newgrp(LCTT 译注:不启动新的命令解释器),如下:

sudo addgroup panda
sudo adduser bork panda
newgrp panda # starts a new shell, and you don't have to be root to run it!

你也可以用 sg panda bash 来完成同样的效果,这个命令能启动一个bash 登录程序,而这个程序就有 panda 组。

seduid 将设置有效用户 ID

其实我一直对一个进程如何以 setuid root 的权限来运行意味着什么有点似是而非。现在我知道了,事实上所发生的是:setuid 设置了
“有效用户 ID”! 如果我(julia)运行了一个 setuid root 的进程( 比如 passwd),那么进程的真实用户 ID 将为 julia,而有效用户 ID 将被设置为 root

passwd 需要以 root 权限来运行,但是它能看到进程的真实用户 ID 是 julia ,是 julia 启动了这个进程,passwd 会阻止这个进程修改除了 julia 之外的用户密码。

就是这些了!

在《Linux 编程接口》这本书里有很多 Linux 上一些功能的罕见使用方法以及 Linux 上所有的事物到底是怎么运行的详细解释,这里我就不一一展开了。那本书棒极了,我上面所说的都在该书的第九章,这章在 1300 页的书里只占了 17 页。

我最爱这本书的一点是我只用读 17 页关于用户和组是怎么工作的内容,而这区区 17 页就能做到内容完备、详实有用。我不用读完所有的 1300 页书就能得到有用的东西,太棒了!


via: https://jvns.ca/blog/2017/11/20/groups/

作者:Julia Evans 译者:DavidChen 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

最近我一直在研究 Kubernetes 网络。我注意到一件事情就是,虽然关于如何设置 Kubernetes 网络的文章很多,也写得很不错,但是却没有看到关于如何去运维 Kubernetes 网络的文章、以及如何完全确保它不会给你造成生产事故。

在本文中,我将尽力让你相信三件事情(我觉得这些都很合理 :)):

  • 避免生产系统网络中断非常重要
  • 运维联网软件是很难的
  • 有关你的网络基础设施的重要变化值得深思熟虑,以及这种变化对可靠性的影响。虽然非常“牛x”的谷歌人常说“这是我们在谷歌正在用的”(谷歌工程师在 Kubernetes 上正做着很重大的工作!但是我认为重要的仍然是研究架构,并确保它对你的组织有意义)。

我肯定不是 Kubernetes 网络方面的专家,但是我在配置 Kubernetes 网络时遇到了一些问题,并且比以前更加了解 Kubernetes 网络了。

运维联网软件是很难的

在这里,我并不讨论有关运维物理网络的话题(对于它我不懂),而是讨论关于如何让像 DNS 服务、负载均衡以及代理这样的软件正常工作方面的内容。

我在一个负责很多网络基础设施的团队工作过一年时间,并且因此学到了一些运维网络基础设施的知识!(显然我还有很多的知识需要继续学习)在我们开始之前有三个整体看法:

  • 联网软件经常重度依赖 Linux 内核。因此除了正确配置软件之外,你还需要确保许多不同的系统控制(sysctl)配置正确,而一个错误配置的系统控制就很容易让你处于“一切都很好”和“到处都出问题”的差别中。
  • 联网需求会随时间而发生变化(比如,你的 DNS 查询或许比上一年多了五倍!或者你的 DNS 服务器突然开始返回 TCP 协议的 DNS 响应而不是 UDP 的,它们是完全不同的内核负载!)。这意味着之前正常工作的软件突然开始出现问题。
  • 修复一个生产网络的问题,你必须有足够的经验。(例如,看这篇 由 Sophie Haskins 写的关于 kube-dns 问题调试的文章)我在网络调试方面比以前进步多了,但那也是我花费了大量时间研究 Linux 网络知识之后的事了。

我距离成为一名网络运维专家还差得很远,但是我认为以下几点很重要:

  1. 对生产网络的基础设施做重要的更改是很难得的(因为它会产生巨大的混乱)
  2. 当你对网络基础设施做重大更改时,真的应该仔细考虑如果新网络基础设施失败该如何处理
  3. 是否有很多人都能理解你的网络配置

切换到 Kubernetes 显然是个非常大的更改!因此,我们来讨论一下可能会导致错误的地方!

Kubernetes 网络组件

在本文中我们将要讨论的 Kubernetes 网络组件有:

  • 覆盖网络 overlay network 的后端(像 flannel/calico/weave 网络/romana)
  • kube-dns
  • kube-proxy
  • 入站控制器 / 负载均衡器
  • kubelet

如果你打算配置 HTTP 服务,或许这些你都会用到。这些组件中的大部分我都不会用到,但是我尽可能去理解它们,因此,本文将涉及它们有关的内容。

最简化的方式:为所有容器使用宿主机网络

让我们从你能做到的最简单的东西开始。这并不能让你在 Kubernetes 中运行 HTTP 服务。我认为它是非常安全的,因为在这里面可以让你动的东西很少。

如果你为所有容器使用宿主机网络,我认为需要你去做的全部事情仅有:

  1. 配置 kubelet,以便于容器内部正确配置 DNS
  2. 没了,就这些!

如果你为每个 pod 直接使用宿主机网络,那就不需要 kube-dns 或者 kube-proxy 了。你都不需要一个作为基础的覆盖网络。

这种配置方式中,你的 pod 们都可以连接到外部网络(同样的方式,你的宿主机上的任何进程都可以与外部网络对话),但外部网络不能连接到你的 pod 们。

这并不是最重要的(我认为大多数人想在 Kubernetes 中运行 HTTP 服务并与这些服务进行真实的通讯),但我认为有趣的是,从某种程度上来说,网络的复杂性并不是绝对需要的,并且有时候你不用这么复杂的网络就可以实现你的需要。如果可以的话,尽可能地避免让网络过于复杂。

运维一个覆盖网络

我们将要讨论的第一个网络组件是有关覆盖网络的。Kubernetes 假设每个 pod 都有一个 IP 地址,这样你就可以与那个 pod 中的服务进行通讯了。我在说到“覆盖网络”这个词时,指的就是这个意思(“让你通过它的 IP 地址指向到 pod 的系统)。

所有其它的 Kubernetes 网络的东西都依赖正确工作的覆盖网络。更多关于它的内容,你可以读 这里的 kubernetes 网络模型

Kelsey Hightower 在 kubernetes 艰难之路 中描述的方式看起来似乎很好,但是,事实上它的作法在超过 50 个节点的 AWS 上是行不通的,因此,我不打算讨论它了。

有许多覆盖网络后端(calico、flannel、weaveworks、romana)并且规划非常混乱。就我的观点来看,我认为一个覆盖网络有 2 个职责:

  1. 确保你的 pod 能够发送网络请求到外部的集群
  2. 保持一个到子网络的稳定的节点映射,并且保持集群中每个节点都可以使用那个映射得以更新。当添加和删除节点时,能够做出正确的反应。

Okay! 因此!你的覆盖网络可能会出现的问题是什么呢?

  • 覆盖网络负责设置 iptables 规则(最基本的是 iptables -A -t nat POSTROUTING -s $SUBNET -j MASQUERADE),以确保那个容器能够向 Kubernetes 之外发出网络请求。如果在这个规则上有错误,你的容器就不能连接到外部网络。这并不很难(它只是几条 iptables 规则而已),但是它非常重要。我发起了一个 拉取请求,因为我想确保它有很好的弹性。
  • 添加或者删除节点时可能会有错误。我们使用 flannel hostgw 后端,我们开始使用它的时候,节点删除功能 尚未开始工作
  • 你的覆盖网络或许依赖一个分布式数据库(etcd)。如果那个数据库发生什么问题,这将导致覆盖网络发生问题。例如,https://github.com/coreos/flannel/issues/610 上说,如果在你的 flannel etcd 集群上丢失了数据,最后的结果将是在容器中网络连接会丢失。(现在这个问题已经被修复了)
  • 你升级 Docker 以及其它东西导致的崩溃
  • 还有更多的其它的可能性!

我在这里主要讨论的是过去发生在 Flannel 中的问题,但是我并不是要承诺不去使用 Flannel —— 事实上我很喜欢 Flannel,因为我觉得它很简单(比如,类似 vxlan 在后端这一块的部分 只有 500 行代码),对我来说,通过代码来找出问题的根源成为了可能。并且很显然,它在不断地改进。他们在审查拉取请求方面做的很好。

到目前为止,我运维覆盖网络的方法是:

  • 学习它的工作原理的详细内容以及如何去调试它(比如,Flannel 用于创建路由的 hostgw 网络后端,因此,你只需要使用 sudo ip route list 命令去查看它是否正确即可)
  • 如果需要的话,维护一个内部构建版本,这样打补丁比较容易
  • 有问题时,向上游贡献补丁

我认为去遍历所有已合并的拉取请求以及过去已修复的 bug 清单真的是非常有帮助的 —— 这需要花费一些时间,但这是得到一个其它人遇到的各种问题的清单的好方法。

对其他人来说,他们的覆盖网络可能工作的很好,但是我并不能从中得到任何经验,并且我也曾听说过其他人报告类似的问题。如果你有一个类似配置的覆盖网络:a) 在 AWS 上并且 b) 在多于 50-100 节点上运行,我想知道你运维这样的一个网络有多大的把握。

运维 kube-proxy 和 kube-dns?

现在,我有一些关于运维覆盖网络的想法,我们来讨论一下。

这个标题的最后面有一个问号,那是因为我并没有真的去运维过。在这里我还有更多的问题要问答。

这里的 Kubernetes 服务是如何工作的!一个服务是一群 pod 们,它们中的每个都有自己的 IP 地址(像 10.1.0.3、10.2.3.5、10.3.5.6 这样)

  1. 每个 Kubernetes 服务有一个 IP 地址(像 10.23.1.2 这样)
  2. kube-dns 去解析 Kubernetes 服务 DNS 名字为 IP 地址(因此,my-svc.my-namespace.svc.cluster.local 可能映射到 10.23.1.2 上)
  3. kube-proxy 配置 iptables 规则是为了在它们之间随机进行均衡负载。Kube-proxy 也有一个用户空间的轮询负载均衡器,但是在我的印象中,他们并不推荐使用它。

因此,当你发出一个请求到 my-svc.my-namespace.svc.cluster.local 时,它将解析为 10.23.1.2,然后,在你本地主机上的 iptables 规则(由 kube-proxy 生成)将随机重定向到 10.1.0.3 或者 10.2.3.5 或者 10.3.5.6 中的一个上。

在这个过程中我能想像出的可能出问题的地方:

  • kube-dns 配置错误
  • kube-proxy 挂了,以致于你的 iptables 规则没有得以更新
  • 维护大量的 iptables 规则相关的一些问题

我们来讨论一下 iptables 规则,因为创建大量的 iptables 规则是我以前从没有听过的事情!

kube-proxy 像如下这样为每个目标主机创建一个 iptables 规则:这些规则来自 这里

-A KUBE-SVC-LI77LBOOMGYET5US -m comment --comment "default/showreadiness:showreadiness" -m statistic --mode random --probability 0.20000000019 -j KUBE-SEP-E4QKA7SLJRFZZ2DD[b][c]  
-A KUBE-SVC-LI77LBOOMGYET5US -m comment --comment "default/showreadiness:showreadiness" -m statistic --mode random --probability 0.25000000000 -j KUBE-SEP-LZ7EGMG4DRXMY26H  
-A KUBE-SVC-LI77LBOOMGYET5US -m comment --comment "default/showreadiness:showreadiness" -m statistic --mode random --probability 0.33332999982 -j KUBE-SEP-RKIFTWKKG3OHTTMI  
-A KUBE-SVC-LI77LBOOMGYET5US -m comment --comment "default/showreadiness:showreadiness" -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-CGDKBCNM24SZWCMS 
-A KUBE-SVC-LI77LBOOMGYET5US -m comment --comment "default/showreadiness:showreadiness" -j KUBE-SEP-RI4SRNQQXWSTGE2Y 

因此,kube-proxy 创建了许多 iptables 规则。它们都是什么意思?它对我的网络有什么样的影响?这里有一个来自华为的非常好的演讲,它叫做 支持 50,000 个服务的可伸缩 Kubernetes,它说如果在你的 Kubernetes 集群中有 5,000 服务,增加一个新规则,将需要 11 分钟。如果这种事情发生在真实的集群中,我认为这将是一件非常糟糕的事情。

在我的集群中肯定不会有 5,000 个服务,但是 5,000 并不是那么大的一个数字。为解决这个问题,他们给出的解决方案是 kube-proxy 用 IPVS 来替换这个 iptables 后端,IPVS 是存在于 Linux 内核中的一个负载均衡器。

看起来,像 kube-proxy 正趋向于使用各种基于 Linux 内核的负载均衡器。我认为这只是一定程度上是这样,因为他们支持 UDP 负载均衡,而其它类型的负载均衡器(像 HAProxy)并不支持 UDP 负载均衡。

但是,我觉得使用 HAProxy 更舒服!它能够用于去替换 kube-proxy!我用谷歌搜索了一下,然后发现了这个 thread on kubernetes-sig-network,它说:

kube-proxy 是很难用的,我们在生产系统中使用它近一年了,它在大部分的时间都表现的很好,但是,随着我们集群中的服务越来越多,我们发现它的排错和维护工作越来越难。在我们的团队中没有 iptables 方面的专家,我们只有 HAProxy & LVS 方面的专家,由于我们已经使用它们好几年了,因此我们决定使用一个中心化的 HAProxy 去替换分布式的代理。我觉得这可能会对在 Kubernetes 中使用 HAProxy 的其他人有用,因此,我们更新了这个项目,并将它开源:https://github.com/AdoHe/kube2haproxy。如果你发现它有用,你可以去看一看、试一试。

因此,那是一个有趣的选择!我在这里确实没有答案,但是,有一些想法:

  • 负载均衡器是很复杂的
  • DNS 也很复杂
  • 如果你有运维某种类型的负载均衡器(比如 HAProxy)的经验,与其使用一个全新的负载均衡器(比如 kube-proxy),还不如做一些额外的工作去使用你熟悉的那个来替换,或许更有意义。
  • 我一直在考虑,我们希望在什么地方能够完全使用 kube-proxy 或者 kube-dns —— 我认为,最好是只在 Envoy 上投入,并且在负载均衡&服务发现上完全依赖 Envoy 来做。因此,你只需要将 Envoy 运维好就可以了。

正如你所看到的,我在关于如何运维 Kubernetes 中的内部代理方面的思路还是很混乱的,并且我也没有使用它们的太多经验。总体上来说,kube-proxy 和 kube-dns 还是很好的,也能够很好地工作,但是我仍然认为应该去考虑使用它们可能产生的一些问题(例如,”你不能有超出 5000 的 Kubernetes 服务“)。

入口

如果你正在运行着一个 Kubernetes 集群,那么到目前为止,很有可能的是,你事实上需要 HTTP 请求去进入到你的集群中。这篇博客已经太长了,并且关于入口我知道的也不多,因此,我们将不讨论关于入口的内容。

有用的链接

几个有用的链接,总结如下:

我认为网络运维很重要

我对 Kubernetes 的所有这些联网软件的感觉是,它们都仍然是非常新的,并且我并不能确定我们(作为一个社区)真的知道如何去把它们运维好。这让我作为一个操作者感到很焦虑,因为我真的想让我的网络运行的很好!:) 而且我觉得作为一个组织,运行你自己的 Kubernetes 集群需要相当大的投入,以确保你理解所有的代码片段,这样当它们出现问题时你可以去修复它们。这不是一件坏事,它只是一个事而已。

我现在的计划是,继续不断地学习关于它们都是如何工作的,以尽可能多地减少对我动过的那些部分的担忧。

一如继往,我希望这篇文章对你有帮助,并且如果我在这篇文章中有任何的错误,我非常喜欢你告诉我。


via: https://jvns.ca/blog/2017/10/10/operating-a-kubernetes-network/

作者:Julia Evans 译者:qhwdw 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

嗨!今天是 netdev 会议的第 2 天,我只参加了早上的会议,但它非常有趣。今早会议的主角是 Van Jacobson 给出的一场名为 “从尽可能快中变化:教网卡以时间”的演讲,它的主题是关于互联网中拥塞控制的未来!!!

下面我将尝试着对我从这次演讲中学到的东西做总结,我几乎肯定下面的内容有些错误,但不管怎样,让我们开始吧!

这次演讲是关于互联网是如何从 1988 开始改变的,为什么现在我们需要新的算法,以及我们可以怎样改变 Linux 的网络栈来更容易地实现这些算法。

什么是拥塞控制?

在网络上的任何成员总是无时无刻地发送信息包,而在互联网上的连接之间有着极其不同的速度(某些相比其他极其缓慢),而有时候它们将被塞满!当互联网的一个设备以超过它能处理的速率接收信息包时,它将丢弃某些信息包。

你所能想象的最天真的发送信息包方式是:

  1. 将你必须发送的信息包一次性发送完。
  2. 假如你发现其中有的信息包被丢弃了,就马上重新发送这些包。

结果表明假如你按照上面的思路来实现 TCP,互联网将会崩溃并停止运转。我们知道它会崩溃是因为在 1986 年确实发生了崩溃的现象。为了解决这个问题,专家发明了拥塞控制算法 —— 描述如何避免互联网的崩溃的原始论文是 Van Jacobson 于 1988 年发表的 拥塞避免与控制(30 年前!)。

从 1988 年后互联网发生了什么改变?

在演讲中,Van Jacobson 说互联网的这些已经发生了改变:在以前的互联网上,交换机可能总是拥有比服务器更快的网卡,所以这些位于互联网中间层的服务器也可能比客户端更快,并且并不能对客户端发送信息包的速率有多大影响。

很显然今天已经不是这样的了!众所周知,今天的计算机相比于 5 年前的计算机在速度上并没有多大的提升(我们遇到了某些有关光速的问题)。所以我想路由器上的大型交换机并不会在速度上大幅领先于数据中心里服务器上的网卡。

这听起来有些糟糕,因为这意味着客户端更容易在中间层的连接中达到饱和,而这将导致互联网变慢(而且 缓冲膨胀 将带来更高的延迟)。

所以为了提高互联网的性能且不让每个路由上的任务队列都达到饱和,客户端需要表现得更好并且在发送信息包的时候慢一点。

以更慢的速率发送更多的信息包以达到更好的性能

下面的结论真的让我非常意外 —— 以更慢的速率发送信息包实际上可能会带来更好的性能(即便你是在整个传输过程中,这样做的唯一的人),下面是原因:

假设你打算发送 10MB 的数据,在你和你需要连接的客户端之间有一个中间层,并且它的传输速率非常低,例如 1MB/s。假设你可以辨别这个慢连接(或者更多的后续中间层)的速度,那么你有 2 个选择:

  1. 一次性将这 10MB 的数据发送完,然后看看会发生什么。
  2. 减慢速率使得你能够以 1MB/s 的速率传给它。

现在,无论你选择何种方式,你可能都会发生丢包的现象。所以这样看起来,你可能需要选择一次性发送所有的信息包这种方式,对吧?不!!实际上在你的数据流的中间环节丢包要比在你的数据流的最后丢包要好得多。假如在中间环节有些包被丢弃了,你需要送往的那个客户端可以察觉到这个事情,然后再告诉你,这样你就可以再次发送那些被丢弃的包,这样便没有多大的损失。但假如信息包在最末端被丢弃,那么客户端将完全没有办法知道你一次性发送了所有的信息包!所以基本上在某个时刻被丢弃的包没有让你收到 ACK 信号时,你需要启用超时机制,并且还得重新发送它们。而超时往往意味着需要花费很长时间!

所以为什么以更慢的速率发送数据会更好呢?假如你发送数据的速率快于连接中的瓶颈,这时所有的信息包将会在某个地方堆积成一个队列,这个队列将会被塞满,然后在你的数据流的最末端的信息包将会被丢弃。并且像我们刚才解释的那样,处于数据流最后面的信息包很有可能丢弃!所以相比于最初以合适的速率发送信息包,一次性发送它们将会触发超时机制,发送 10MB 的数据将会花费更长的时间。

我认为这非常酷,因为这个过程并不需要与互联网中的其他人合作 —— 即便其他的所有人都已非常快的速率传送他们的信息包,对你来说以合适的速率(中间层的瓶颈速率)传送你自己的信息包仍然更有优势。

如何辨别发送数据的合适速率:BBR!

在上面我说过:“假设你可以辨别出位于你的终端和服务器之间慢连接的速率……”,那么如何做到呢?来自 Google(Jacobson 工作的地方)的某些专家已经提出了一个算法来估计瓶颈的速率!它叫做 BBR,由于本次的分享已经很长了,所以这里不做具体介绍,但你可以参考 BBR:基于拥塞的拥塞控制来自晨读论文的总结 这两处链接。

(另外,https://blog.acolyer.org 的每日“晨读论文”总结基本上是我学习和理解计算机科学论文的唯一方式,它有可能是整个互联网上最好的博客之一!)

网络代码被设计为运行得“尽可能快“

所以,假设我们相信我们想以一个更慢的速率(例如以我们连接中的瓶颈速率)来传输数据。这很好,但网络软件并不是被设计为以一个可控速率来传输数据的!下面是我所理解的大多数网络软件怎么做的:

  1. 现在有一个队列的信息包来临;
  2. 然后软件读取队列并尽可能快地发送信息包;
  3. 就这样,没有了。

这个过程非常呆板 —— 假设我以一个非常快的速率发送信息包,而另一端的连接却非常慢。假如我所拥有的就是一个放置所有信息包的队列,当我实际要发送数据时,我并没有办法来控制这个发送过程,所以我便不能减慢这个队列传输的速率。

一个更好的方式:给每个信息包一个“最早的出发时间”

BBR 协议将会修改 Linux 内核中 skb 的数据结构(这个数据结构被用来表达网络信息包),使得它有一个时间戳,这个时间戳代表着这个信息包应该被发送出去的最早时间。

对于 Linux 网络栈我不知道更多的详情了,但对于我来说,这个协议最有趣的地方是这个改动并不是一个非常大的改动!它只是添加了一个额外的时间戳而已。

用时间轮盘替换队列!!!

一旦我们将时间戳打到这些信息包上,我们怎样在合适的时间将它们发送出去呢?使用时间轮盘

在前不久的“我们喜爱的论文”活动中(这是关于这次聚会描述的某些好的链接),有一个演讲谈论了关于时间轮盘的话题。时间轮盘是一类用来指导 Linux 的进程调度器决定何时运行进程的算法。

Van Jacobson 说道:时间轮盘实际上比队列调度工作得更好 —— 它们都提供常数时间的操作,但因为某些缓存机制,时间轮盘的常数要更小一些。我真的没有太明白这里他说的关于性能的解释。

他说道:关于时间轮盘的一个关键点是你可以很轻松地用时间轮盘实现一个队列(但反之不能!)—— 假如每次你增加一个新的信息包,在最开始你说我想让它现在就被发送走,很显然这样你就可以得到一个队列了。而这个时间轮盘方法是向后兼容的,它使得你可以更容易地实现某些更加复杂的对流量非常敏感的算法,例如让你针对不同的信息包以不同的速率去发送它们。

或许我们可以通过改善 Linux 来修复互联网!

对于任何影响到整个互联网规模的问题,最为棘手的问题是当你要做出改善时,例如改变互联网协议的实现,你需要面对各种不同的设备。你要面对 Linux 的机器、BSD 的机器、Windows 的机器、各种各样的手机、瞻博或者思科的路由器以及数量繁多的其他设备!

但是在网络环境中 Linux 处于某种有趣的位置上!

  • Android 手机运行着 Linux
  • 大多数的消费级 WiFi 路由器运行着 Linux
  • 无数的服务器运行着 Linux

所以在任何给定的网络连接中,实际上很有可能在两端都有一台 Linux 机器(例如一个 Linux 服务器、或者一个 Linux 路由器、一台 Android 设备)。

所以重点是假如你想大幅改善互联网上的拥塞状况,只需要改变 Linux 网络栈就会大所不同(或许 iOS 网络栈也是类似的)。这也就是为什么在本次的 Linux 网络会议上有这样的一个演讲!

互联网仍在改变!酷!

通常我以为 TCP/IP 仍然是上世纪 80 年代的东西,所以当从这些专家口中听说这些我们正在设计的网路协议仍然有许多严重的问题时,真的是非常有趣,并且听说现在有不同的方式来设计它们。

当然也确实是这样 —— 网络硬件以及和速度相关的任何设备,以及人们使用网络来干的各种事情(例如观看 Netflix 的节目)等等,一直都在随着时间发生着改变,所以正因为这样,我们需要为 2018 年的互联网而不是为 1988 年的互联网设计我们不同的算法。


via: https://jvns.ca/blog/2018/07/12/netdev-day-2--moving-away-from--as-fast-as-possible/

作者:Julia Evans 译者:FSSlc 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

嗨!和去年一样,今年我又参加了 netdev 会议。(这里是我上一年所做的笔记)。

在今天的会议中,我学到了很多有关 IPsec 的知识,所以下面我将介绍它们!其中 Sowmini Varadhan 和 Paul Wouters 做了一场关于 IPsec 的专题研讨会。本文中的错误 100% 都是我的错 :)。

什么是 IPsec?

IPsec 是一个用来加密 IP 包的协议。某些 VPN 已经是通过使用 IPsec 来实现的。直到今天我才真正意识到 VPN 使用了不只一种协议,原来我以为 VPN 只是一个通用术语,指的是“你的数据包将被加密,然后通过另一台服务器去发送“。VPN 可以使用一系列不同的协议(OpenVPN、PPTP、SSTP、IPsec 等)以不同的方式来实现。

为什么 IPsec 和其他的 VPN 协议如此不同呢?(或者说,为什么在本次 netdev 会议会有 IPsec 的教程,而不是其他的协议呢?)我的理解是有 2 点使得它如此不同:

  • 它是一个 IETF 标准,例如可以在文档 RFC 6071 等中查到(你知道 IETF 是制定 RFC 标准的组织吗?我也是直到今天才知道的!)。
  • 它在 Linux 内核中被实现了(所以这才是为什么本次 netdev 会议中有关于它的教程,因为 netdev 是一个跟 Linux 内核网络有关的会议 :))。

IPsec 是如何工作的?

假如说你的笔记本正使用 IPsec 来加密数据包并通过另一台设备来发送它们,那这是怎么工作的呢?对于 IPsec 来说,它有 2 个部分:一个是用户空间部分,另一个是内核空间部分。

IPsec 的用户空间部分负责密钥的交换,使用名为 IKE 网络密钥传输 internet key exchange )的协议。总的来说,当你打开一个 VPN 连接的时候,你需要与 VPN 服务器通信,并且和它协商使用一个密钥来进行加密。

IPsec 的内核部分负责数据包的实际加密工作 —— 一旦使用 IKE 生成了一个密钥,IPsec 的用户空间部分便会告诉内核使用哪个密钥来进行加密。然后内核便会使用该密钥来加密数据包!

安全策略以及安全关联

(LCTT 译注:security association 我翻译为安全关联, 参考自 https://zh.wikipedia.org/wiki/%E5%AE%89%E5%85%A8%E9%97%9C%E8%81%AF

IPSec 的内核部分有两个数据库:安全策略数据库(SPD)和安全关联数据库(SAD)。

安全策略数据库包含 IP 范围和用于该范围的数据包需要执行的操作(对其执行 IPsec、丢弃数据包、让数据包通过)。对于这点我有点迷糊,因为针对不同 IP 范围的数据包所采取的规则已经在路由表(sudo ip route list)中使用过,但显然你也可以设定 IPsec 规则,但它们位于不同的地方!

而在我眼中,安全关联数据库存放有用于各种不同 IP 的加密密钥。

查看这些数据库的方式却是非常不直观的,需要使用一个名为 ip xfrm 的命令,至于 xfrm 是什么意思呢?我也不知道!

(LCTT 译注:我在 https://www.allacronyms.com/XFMR/Transformer 上查到 xfmr 是 Transformer 的简写,又根据 man7 上的简介, 我认为这个说法可信。)

# security policy database
$ sudo ip xfrm policy
$ sudo ip x p

# security association database
$ sudo ip xfrm state
$ sudo ip x s

为什么 IPsec 被实现在 Linux 内核中而 TLS 没有?

对于 TLS 和 IPsec 来说,当打开一个连接时,它们都需要做密钥交换(使用 Diffie-Hellman 或者其他算法)。基于某些可能很明显但我现在还没有理解(??)的原因,在内核中人们并不想做密钥的交换。

IPsec 更容易在内核实现的原因是使用 IPsec 你可以更少频率地协商密钥的交换(对于每个你想通过 VPN 来连接的 IP 只需要一次),并且 IPsec 会话存活得更长。所以对于用户空间来说,使用 IPsec 来做密钥交换、密钥的获取和将密钥传递给内核将更容易,内核得到密钥后将使用该密钥来处理每个 IP 数据包。

而对于 TLS 来说,则存在一些问题:

a. 当你每打开一个 TLS 连接时,每次你都要做新的密钥交换,并且 TLS 连接存活时间较短。 b. 当你需要开始做加密时,使用 IPsec 没有一个自然的协议边界,你只需要加密给定 IP 范围内的每个 IP 包即可,但如果使用 TLS,你需要查看 TCP 流,辨别 TCP 包是否是一个数据包,然后决定是否加密它。

实际上有一个补丁用于 在 Linux 内核中实现 TLS,它让用户空间做密钥交换,然后传给内核密钥,所以很明显,使用 TLS 不是不可能的,但它是一个新事物,并且我认为相比使用 IPsec,使用 TLS 更加复杂。

使用什么软件来实现 IPsec 呢?

据我所知有 Libreswan 和 Strongswan 两个软件。今天的教程关注的是 libreswan。

有些让人迷糊的是,尽管 Libreswan 和 Strongswan 是不同的程序包,但它们都会安装一个名为 ipsec 的二进制文件来管理 IPsec 连接,并且这两个 ipsec 二进制文件并不是相同的程序(尽管它们担任同样的角色)。

在上面的“IPsec 如何工作”部分,我已经描述了 Strongswan 和 Libreswan 做了什么 —— 使用 IKE 做密钥交换,并告诉内核有关如何使用密钥来做加密。

VPN 不是只能使用 IPsec 来实现!

在本文的开头我说“IPsec 是一个 VPN 协议”,这是对的,但你并不必须使用 IPsec 来实现 VPN!实际上有两种方式来使用 IPsec:

  1. “传输模式”,其中 IP 表头没有改变,只有 IP 数据包的内容被加密。这种模式有点类似于使用 TLS —— 你直接告诉服务器你正在通信(而不是通过一个 VPN 服务器或其他设备),只有 IP 包里的内容被加密。
  2. ”隧道模式“,其中 IP 表头和它的内容都被加密了,并且被封装进另一个 UDP 包内。这个模式被 VPN 所使用 —— 你获取你正传送给一个秘密网站的包,然后加密它,并将它送给你的 VPN 服务器,然后 VPN 服务器再传送给你。

投机的 IPsec

今天我学到了 IPsec “传输模式”的一个有趣应用,它叫做 “投机的 IPsec”(通过它,你可以通过开启一个 IPsec 连接来直接和你要通信的主机连接,而不是通过其他的中介服务器),现在已经有一个“投机的 IPsec” 服务器了,它位于 http://oe.libreswan.org/

我认为当你在你的电脑上设定好 libreswanunbound DNS 程序后,当你连接到 http://oe.libreswan.org 时,主要发生了如下的几件事:

  1. unbound 做一次 DNS 查询来获取 oe.libreswan.org (dig ipseckey oe.libreswan.org) 的 IPSECKEY 记录,以便获取到公钥来用于该网站(这需要 DNSSEC 是安全的,并且当我获得足够多这方面的知识后,我将用另一篇文章来说明它。假如你想看到相关的结果,并且如果你只是使用 dig 命令来运行此次 DNS 查询的话,它也可以工作)。
  2. unbound 将公钥传给 libreswan 程序,然后 libreswan 使用它来和运行在 oe.libreswan.org 网站上的 IKE 服务器做一次密钥交换。
  3. libreswan 完成了密钥交换,将加密密钥传给内核并告诉内核当和 oe.libreswan.org 做通信时使用该密钥。
  4. 你的连接现在被加密了!即便它是 HTTP 连接!有趣吧!

IPsec 和 TLS 相互借鉴

在今天的教程中听到一个有趣的花絮是 IPsec 和 TLS 协议实际上总是从对方学习 —— 正如他们说在 TLS 出现前, IPsec 的 IKE 协议有着完美的正向加密,而 IPsec 也从 TLS 那里学了很多。很高兴能听到不同的网络协议之间是如何从对方那里学习并与时俱进的事实!

IPsec 是有趣的!

我已经花了很长时间来学习 TLS,很明显它是一个超级重要的网络协议(让我们来加密网络吧!:D)。但 IPsec 也是一个很重要的网络加密协议,它与 TLS 有着不同的角色!很明显有些移动电话协议(例如 5G/LTE)使用 IPsec 来加密它们的网络流量!

现在我很高兴我知道更多关于 IPsec 的知识!和以前一样可能本文有些错误,但希望不会错的太多 :)


via: https://jvns.ca/blog/2018/07/11/netdev-day-1--ipsec/

作者:Julia Evans 译者:FSSlc 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

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

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

什么是段错误?

段错误 segmentation fault ”是指你的程序尝试访问不允许访问的内存地址的情况。这可能是由于:

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

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

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

步骤1:运行 valgrind

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

valgrind -v your-program

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

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

如何获得一个核心转储

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

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

这就是我最终做的事情:

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

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

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

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

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

内核在决定写入多大的核心转储文件时使用 软限制 soft limit (在这种情况下,max core file size = 0)。 您可以使用 shell 内置命令 ulimitulimit -c unlimited) 将软限制增加到 硬限制 hard limit

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

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

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

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

如果你想知道这些形如 %e%p 的参数都表示什么,请参考 man core

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

kernel.core\_pattern 和 Ubuntu

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

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

这引起了我的迷惑(这 apport 是干什么的,它对我的核心转储做了什么?)。以下关于这个我了解到的:

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

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

现在你有了核心转储,接下来干什么?

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

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

从 gdb 中得到堆栈调用序列

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

$ gdb -c my_core_file

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

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

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

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

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

查看每个线程的堆栈

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

thread apply all bt full

gdb + 核心转储 = 惊喜

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

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

ASAN

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

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

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

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

  1. 试试用 valgrind

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

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

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


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

作者:Julia Evans 译者:stephenxs 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出