Linux 系统编程实用手册(九)
原文:
zh.annas-archive.org/md5/9713B9F84CB12A4F8624F3E68B0D4320译者:飞龙
第十九章:故障排除和最佳实践
本章重点介绍了较新的 Linux 故障排除工具和实用程序,以及在设计、开发和部署真实世界 Linux 系统应用程序时应遵循的行业最佳实践。但我们要非常清楚,这是一本关于 Linux 系统编程的书;这里描述的故障排除技巧和最佳实践仅与 Linux 系统上应用程序的系统级开发有关(通常用 C/C++编写);我们不涉及 Linux 上的通用故障排除(例如故障排除网络或配置问题、系统管理技巧和窍门)。
特别是对于本章(主要是由于内容的广泛范围和规模,它只是顺便提到),我们在 GitHub 存储库的“进一步阅读”部分提供了几篇有用的在线文章和书籍。请浏览一下。
本章旨在结束本书;在这里,关于 Linux 系统编程,读者将得到以下内容:
-
(较新)故障排除工具和技术概述
-
设计、软件工程、编程实施和测试方面的行业最佳实践概述
故障排除工具
在本节中,我们将提到几个工具和实用程序,可以帮助应用程序开发人员识别系统瓶颈和性能问题。(请注意,在这里,为了节省空间和时间,我们不深入研究数十个常见的系统监控实用程序,如ps,pstree,top,htop,pidstat,vmstat,dstat,sar,nagios,iotop,iostat,ionice,lsof,nmon,iftop,ethtool,netstat,tcpdump,wireshark,而是提到了较新的工具)。在进行数据收集(或基准测试)以供后续分析时,有一件重要的事情需要记住:费心设置一个测试装置,并在使用时,尽可能只改变一个变量,以便在给定的运行中看到其影响。
perf
性能测量和分析是一个庞大的主题;对性能问题的识别、分析和确定根本原因并不是一项微不足道的任务。近年来,perf(1)和htop(1)实用程序已成为 Linux 平台上性能测量和分析的基本工具。
有时,您只需要查看哪些进程占用了最多的 CPU;传统上,我们使用众所周知的top(1)实用程序来做到这一点。请尝试使用非常有用的perf变体,如:sudo perf top。
此外,您还可以利用以下一些功能:
sudo perf top -r 90 --sort pid,comm,dso,symbol
(-r 90 => collect data with SCHED_FIFO RT scheduling class and priority 90 [1-99]).
基本上,这是perf工作流程:记录一个会话(数据文件被保存)并生成报告。(请参阅 GitHub 存储库中“进一步阅读”部分中的链接。)
Brendan Gregg 的博客上提供了出色的图表,清楚地展示了 Linux 上可用于观察、性能分析和动态跟踪的数十种工具:
-
Linux 性能工具:
www.brendangregg.com/Perf/linux_perf_tools_full.png -
Linux 性能可观测工具:
www.brendangregg.com/Perf/linux_observability_tools.png
由于其视觉冲击力,Brendan Gregg 的 Flame Graph 脚本也非常有趣;请查看 GitHub 存储库中“进一步阅读”部分中的链接。
Brendan Gregg 还领导了一个名为 perf-tools 的项目。以下是该项目的一些话:基于 Linux perf_events(又名 perf)和 Ftrace 的性能分析工具。这些工具包括几个非常有用的 shell 脚本包装程序(覆盖了 Perf、Ftrace 和 Kprobes);请克隆 GitHub 存储库并尝试它们。(*github.com/brendangregg/perf-tools.)
跟踪工具
深入追踪往往会产生令人渴望的副作用,即开发人员或测试人员可以发现性能瓶颈,以及调试系统级的延迟和问题。Linux 有大量的框架和工具可用于跟踪,无论是在用户空间还是在内核级别;这里提到了一些更相关的工具:
-
用户空间:
ltrace(1)(跟踪库 API),strace(1)(跟踪系统调用;也尝试使用sudo perf trace),LTTng-ust,uprobes。 -
内核空间:LTTng,ftrace(以及几个前端,如
tracecmd(1),kernelshark GUIm),Kprobes—(包括 Jprobes—直到内核版本 4.14),Kretprobes;SystemTaprm)eBPF。
Linux proc 文件系统
Linux 有一个非常丰富和强大的文件系统,称为procfs—proc代表进程。它通常挂载在/proc下,包含伪文件和目录,其中包含有关进程和内部信息的有价值的运行时生成的信息。简而言之,procfs 用于两个关键目的的用户界面:
-
它作为一个窗口,可以查看详细的进程、线程、操作系统和硬件信息。
-
它用于查询和设置内核级可调参数(核心内核、调度、内存和网络参数的开关和值)的地方。
花时间学习和使用 Linux 的 proc 文件系统是非常值得的。几乎所有用户空间的监控和分析工具最终都是基于 procfs 的。在 GitHub 存储库的进一步阅读部分提供的链接中可以找到更多信息。
最佳实践
在本节中,我们简要列举了我们认为是行业最佳实践的内容,尽管它们大多是通用的,因此范围广泛;我们将特别从 Linux 系统程序员的角度来看待它们。
经验主义方法
根据《剑桥英语词典》,经验主义一词意味着基于所经历或所见,而不是基于理论。这可能是应该遵循的关键原则。Gustavo Duarte 在一篇引人入胜的文章中提到(在这里提到:www.infoq.com/news/2008/02/realitydrivendevelopment):“行动和实验是经验主义的基石。不会通过广泛的分析和大量的文档来压制现实。通过实验邀请现实。非经验主义公司花费一年时间,由 43 人规划一个关机按钮设计。”在整本书中,我们也一直试图有意识地遵循经验主义方法;我们强烈建议读者在设计和开发中培养和嵌入经验主义原则。
软件工程的智慧
Frederick P Brooks 在 1975 年写了他著名的论著《神话般的程序员月份:软件工程论文》,这本书至今被誉为对软件项目管理最有影响力的书。这并不奇怪:某些真理就是真理。以下是这本书中的一些精彩之处:
-
计划扔掉一个;你无论如何都会扔掉一个。
-
没有银弹。
-
好菜需要时间。如果你被迫等待,那是为了更好地为你服务,让你满意。
-
生孩子需要九个月,无论分配多少个女人。
-
好判断来自经验,经验来自糟糕的判断。
有趣的是,当然,古老的 Unix 操作系统的设计哲学确实包含了伟大的设计原则,这些原则至今在 Linux 上仍然有效。我们在《Linux 系统架构》的章节《Unix 哲学概述》中有所涉及。
编程
现在让我们转向开发人员需要牢记的更世俗但非常重要的事情。
程序员的清单-七条规则
我们建议以下七条规则:
-
规则#1:检查所有 API 的失败情况。
-
规则#2:使用警告编译(
-Wall -Wextra)并尽可能消除所有警告。 -
规则#3:永远不要相信(用户)输入;验证它。
-
规则#4:在你的代码中使用断言。
-
规则#5:立即从代码库中消除未使用的(或死代码)。
-
规则#6:彻底测试;100%的代码覆盖率是目标。花时间和精力学习使用强大的工具:内存检查器(Valgrind,sanitizer 工具集),静态和动态分析器,安全检查器(checksec),模糊器(见下面的解释)。
-
规则#7:不要假设任何事情(假设会让你和我都成为驴)。
以下是一些不遵循规则可能导致严重失败的例子:阿丽亚娜 5 号无人火箭在发射初期坠毁(1996 年 6 月 4 日);最终发现错误是由于寄存器溢出问题,一个单一的类型转换错误(规则#5)。Knight Capital Group 在 45 分钟内损失了 4.6 亿美元。不要假设页面的大小。使用getpagesize(2)系统调用或sysconf(3)来获取它。此外,还可以查看名为Low-Level Software Design的博客文章(GitHub 存储库的进一步阅读部分中有链接)。
更好的测试
测试是一项关键活动;彻底和持续的测试(包括回归测试)会导致一个稳定的产品,工程团队和客户都对其深信不疑。
这里有一个经常被忽视的事实:完整的代码覆盖测试至关重要!为什么?简单——通常会有隐藏的缺陷潜伏在从未被测试过的代码部分(错误处理是典型的例子);事实是,它们总有一天会被触发,这可能导致严重的失败。
然而,不幸的是,测试只能揭示错误的存在,而不能揭示它们的缺失;尽管如此,良好和彻底的测试绝对至关重要。大多数进行的测试(编写的测试用例)往往是正面的测试用例;有趣的是,大多数软件(安全)漏洞都逃脱了这种测试的注意。负面测试用例有助于捕捉这些失败;一类名为fuzzing的软件测试在这方面大有帮助。在不同的机器架构上测试代码也可以帮助暴露隐藏的缺陷。
使用 Linux 内核的控制组
使用 Linux 内核的cgroups(控制组)技术来指定和限制资源分配和带宽。现代 Linux 系统上的 cgroup 控制器包括以下内容:CPU(限制 CPU 使用),CPU set(执行 CPU 亲和性的现代方式,将一组进程限制在一组 CPU 上),blkio(I/O 限制),devices(限制哪些进程可以使用哪些设备),freezer(暂停/恢复任务执行),memory(内存使用限制),net_cls(网络数据包标记 classid),net_prio(限制每个接口的网络流量),namespaces(ns),perf_event(性能分析)。
限制资源不仅在需求角度上至关重要,而且在安全角度上也是如此(想想恶意攻击者梦想中的[D]DoS 攻击)。顺便说一句,容器(基本上是一种轻量级虚拟化技术),现在是一个热门话题,主要是因为两个足够发展的 Linux 内核技术的结合:cgroups 和命名空间。
总结
问题:世界上最大的房间是什么?
答案:改进的空间!
总的来说,这应该总结出你在处理庞大项目时应该具有的态度,并且要终身学习诸如 Linux 之类的主题。我们再次敦促读者不仅要阅读以获得概念上的理解——这很重要!——还要动手编写代码。犯错误,修复错误,并从中学习。为开源项目做出贡献是一个绝佳的方式。