[译] 等了 20 年,实时 Linux 终于进入了主线

原文

实时 Linux 相关的工作已经让开源操作系统受益了好多年,但直到这周 Linus Torvalds 才终于接受它的最后一部分进入主线内核。为什么会花这么久呢?

作者: Steven Vaughan-Nichols 资深特约编辑

2024 年 9 月 18 号

维也纳 – 实时 Linux (PREEMPT_RT) 终于在 20 年后进入了主线内核。Linus Torvalds 在欧洲开源峰会上为这份代码祈福。为什么这是个大事件?让我们首先介绍一下实时操作系统(RTOS)和它适用场景。

什么是 RTOS ?

RTOS 是一种专门的操作系统,旨在精准和可靠地处理时间关键任务。与 Windows 或者 MacOS 这类通用操作系统不同,RTOS 建立之初是为了在严格的时间限制下响应事件和处理数据,通常是以毫秒或微秒为单位。正如著名的实时 Linux 开发者、谷歌员工 Steven Rostedet 所说:“实时是最快的最坏场景。”

他的意思是 RTOS 最基本的特征是确定性行为。RTOS 能够保证关键任务在指定期限内完成。很多人认为 RTOS 就是为了快速处理。其实不然,速度不是 RTOS 的关键,可靠性才是。这种可预测性在时间关键应用中尤为重要,例如工业控制系统,医疗设备和航空航天器材。

当今一个正被使用的 RTOS 例子是 VxWorks,它在 NASA 火星探测器上被用来导航,并在波音 787 梦想号客机上用作飞行控制系统,确保飞行控制的实时响应。另一个例子是 QNX Neutrino,它被广泛应用与车载信息娱乐系统和高级辅助驾驶系统中,比如防抱死刹车系统。

实时 Linux 的历史

实时 Linux 的代码现在已经通过即将发布的 Linux 6.12 内核被纳入到了所有的 Linux 发行版中。这意味着 Linux 很快会出现在更多的关键任务设备和工业硬件上。 这着实花了不少时间。

实时 Linux 起源于 1990 年代对实时应用的不断增长的需求。早期工作主要致力于在 Linux 内核之外创建一个单独的内核。它包括若干个学术项目,比如堪萨斯大学的 KURT 项目,米兰大学的 RTAI 项目和新墨西哥矿业理工学院的 RTLinux。

到 2004 年,Ingo Molnar,一个资深的 Linux 内核开发者开始收集和重塑这些技术碎片,并奠定了实时抢占补丁集 PREEMPT_RT 的基础。

这个方法和早期的实时 Linux 的方案不同,因为它修改了已有的 Linux 内核而不是创建另外一个单独的实时内核。到 2006 年,它已经获得了足够的关注,以至于 Linus Torvalds 说 “用 Linux 控制一束激光很疯狂,但这个屋子每个人都或多或少有一些疯狂。所以如果你想用 Linux 来控制工业焊接激光器,我对你使用 PREEPT_RT 没意见”

到 2009 年,一个包含了 Thomas Gleixner, Peter Ziljstra 和 Rostedt 的内核开发者小队,已经把之前的若干原型开发整合成了一个单独的树外补丁集。也就是从这个时候开始,很多公司开始使用这个补丁集来创建需要精确到毫秒的硬实时工业系统。

随着项目推进,它的很多组件已经进入了内核。 Rostedt 告诉我,说实时现在才进入 Linux 在某种程度上是错误的。它的很多功能已经进入了主线 Linux 很多年。有些对于你日常使用的 Linux 已经是必不可少。

比如,你可能从来没听说过 “NO_HZ” ,它能够减少系统在空闲状态下的功耗。“NO_HZ” 使得 Linux 能够在有几千个 CPU 的机器上高效运行。“你感受不到实时补丁给 Linux 带来了多大的改进” Rostedt 强调说 “我们的工作是 Linux 能够在数据中心里运行的唯一原因”

因此,如果没有 “NO_HZ”,Linux 就基本上不能在所有数据中运行。这也反过来解释了为什么 Linux 能在云上运行。我不知道没有这个实时补丁的贡献世界将会怎么样,但是它一定不像今天这样。

起初,谁做梦都不会想到实时 Linux 被证明有用的方式。 Rostedt 回忆道 “早在 2005 年的时候,我收到一个关于实时的错误报告,我回复:‘嘿这是修复的代码,你能应用它吗?’那个家伙说‘我不知道我在做什么’,我回复:‘等等你不是一个内核开发者吗?’他答道:‘我是一个吉他手’”

原来,他之所以会用到早期的实时性补丁是因为他在用一个叫做 JACK 的系统,这是一个低延迟音频链接的声音服务器。他和大多数音乐人一样,穷到没有钱买高级的设备,Rostedt 继续说道 “他搞到一台便宜的笔记本,上面装着 Linux 和 JACK,正因为实时性补丁,它能够录到很好的音频,没有由于硬盘写导致的声音跳跃”

结果就是很多音乐家都是实时 Linux 的早期用户,因为这让他们能够在便宜的设备上录制高质量的唱片。这谁会知道?近几年,其它悄然进入内核的实时特性还包括

  • 互斥锁的引入
  • Ftrace,可以说是最重要的 Linux 调试工具
  • 用户空间应用程序的优先级继承

实时 Linux 怎么用了这么长时间?

那么为什么实时 Linux 直到现在才进入内核呢?“实际上我们不会推上去任何东西,除非我们觉得准备好了” Rostedt 解释到 “几乎所有的代码都会被重写三次以上才会进入到主干,因为我们的准入门槛非常高”

此外,通往主线之路并不只是一个技术挑战。政治和观念也起了很重要的作用,“开始的时候我们甚至不能提实时” Rostedt 回忆道,每个人都会说 “哦,我们不在乎实时”

另外一个问题是资金。实时 Linux 开发经费在好几年里飘忽不定。在 2015 年, Linux 基金会成立了实时 Linux (RTL) 的合作项目,来协调围绕主线 PREEMPT_RT 的维护工作。

全面集成前最后一个关卡是内核 print_k 函数的重构,这是一个可以追溯到 1991 年的重要调试工具。Torvalds 对 print_k 有很强的保护欲,他写的原始的代码并且一直用到今天。然而,每当 print_k 在被调用的时候也导致了 Linux 程序产生严重的延迟。这种延迟在实时系统里是无法接受的。

Rostedt 解释说:“print_k 为了处理上千种不同的场景有上千个 Hack。每当我们想要修改 print_k 做些什么的时候,它就会破坏其中某个场景。print_k 实际上是非常棒的调试工具,他能让你准确知道一个进程在哪里挂掉了。当我非常努力想要锻造系统的时候,延迟总是在 30 毫秒左右,然后突然的延迟就降到了 5 微秒” 这个延迟就是 print_k 消息。

经过大量工作,多次激烈讨论和若干被毙掉的提案后,今年早些时候终于达成了折中方案。这下 Torvalds 高兴了,实时 Linux 开发者高兴了,print_k 用户也高兴了,到此实时 Linux 才终成真。

经历了 20 年的不懈努力,Linux 实时补丁终被纳入主线内核。这一里程碑标志着内核开发者将确定性、低延迟性能带进了 Linux 的多年的努力达到了顶峰。

有了它,Linux 内核才可以完全抢占,从而能在微秒之间相应事件。这个能力对于要求精确计时的如工业控制系统、机器人和音频制作的应用来所是非常关键的。

随着实时性补丁的合并, Linux 现在已经成为 RTOS 世界一个重要玩家。这不只是实时开发者的胜利更是所有 Linux 用户的胜利。

我的第一个 Alpine OS Commit

最近特别爱用 Alpine OS, 试着在上面使用 K3S 搭建一个 Kubernetes 集群。

当时使用的版本是 Alpine v3.19 ,包管理器里自带的 Kubernetes 版本是 k3s-1.28.8.1-r1,安装过程没有报错,但是启动的时候有一个 Fatal Error

1
time="2024-04-10T00:58:17+08:00" level=fatal msg="Failed to validate golang version: kubernetes golang build version not set - see 'golang: upstream version' in https://github.com/kubernetes/kubernetes/blob/v1.28.8/build/dependencies.yaml"

之后定位到是 Alpine OS 包管理器没有及时同步上游的改动的问题,看到有人已经在 Edge 频道里修复了 #15748,我就基于这个改动,在 Community 频道里也修复了 #63824

Read More

我的 2022 书单

今年上半年突然多了很多时间看书,先把以前囤的纸质书翻了翻,后来又买了一个华为出品的墨水屏,立刻荣升阅读主力。
总的来说搭配微信阅读 App 的付费会员还是比较省钱的,预计今年底就能回本。

Read More

[译] GPS 工作原理的可视化

之前订阅了 ciechanowski 大神的博客,虽然更新不频繁(一年几篇)但是每一篇都属于精品。大神总是能结合前端可视化技术把一些天文、物理什么的知识点讲解的很透彻。

GPS 这篇文章刚发布没几天我就注意到了,赶紧拜读了一下。从三角定位讲到相对论再到卫星信号的编解码,内容非常详实,交互动画的制作精美无比可以说是教案水准。

因为某些原因,2022 这个春节能够在家待上好长一阵子。所以我就利用这个机会前后花了大概 10 天时间把这篇文章翻译成了 中文版,希望能够对所有对 GPS 工作原理感兴趣的朋友带来帮助。

文章地址

pages.longtian.info/gps

为什么 Docker Pull 显示的 Digest 和 Docker Hub 的不一样

这个问题困扰自己很久了,今天终于搞明白了。

复现: 在 Docker Hub 查看 alpine 的镜像信息,并在主机上执行 docker pull alpine:3.12
问题: 在网站上看到的 Digest 和控制台里显示的 Digest 不一样

控制台显示的 Digestd9459083f962de6bd980ae6a05be2a4cf670df6a1d898157bceb420342bec280

1
2
3
4
3.12: Pulling from library/alpine
Digest: sha256:d9459083f962de6bd980ae6a05be2a4cf670df6a1d898157bceb420342bec280
Status: Image is up to date for alpine:3.12
docker.io/library/alpine:3.12

这个 Digest 并不在 alpine:3.12 官方仓库对应不同平台的镜像列表

DIGEST OS/ARCH COMPRESSED SIZE
7893969ec350 linux/386 2.67 MB
74e5ad84a67e linux/amd64 2.68 MB
8a6e4b2093ee linux/arm/v6 2.49 MB
45a18fc6f681 linux/arm/v7 2.31 MB
29ba524fa7f5 linux/arm64/v8 2.59 MB
08340ab4a605 linux/ppc64le 2.69 MB
9a7276e7579f linux/s390x 2.46 MB

那么这两个 Digest,到底以那个为准呢

  • d9459083f962de6bd980ae6a05be2a4cf670df6a1d898157bceb420342bec280
  • 74e5ad84a67e9b8ed7609b32dc2460b76175020923d7f494a73a851446222d18

Read More

Kubernetes 资源分配

最近遇到了 Kubernetes 上的 Web 应用响应时间长尾的问题,上网收集资料,分析了一下原因。

  • CGroup 节流导致 Web 请求相应时间增加
  • 当前执行的 CPU 不断变动
  • 核数越多 CGroup 补偿碎片越严重
  • 跨越 NUMA 节点

请求和限制计算资源

Kubernetes 对资源的细粒度管理功能可以说是吸引传统应用上 Kubernetes 的重要原因之一

我们在定义 Pod 的时候通过以下两个字段可以控制计算资源的分配:

字段 备注
requests 请求的最少资源
limits 资源限制
  • requests 无法满足时,Pod 会一直停在 Pending 状态,触发 FailedScheduling 事件
  • 当使用内存资源超过 limits 时,如果有其他 POD 需要内存,则使用内存最多的容器会被 sacrifice,触发 OOMKilled 事件
  • 如果不指定 limits , 在没有命名空间默认值的情况下可以无限制地使用资源
  • 如果指定了 limits , 但是没有指定 requests , 则 requests 值默认为 limits
  • 从分配角度讲 CPU 属于可以压缩的资源、内存属于不可以压缩的资源

例如

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
apiVersion: v1
kind: Pod
metadata:
name: memory-demo
namespace: mem-example
spec:
containers:
- name: memory-demo-ctr
image: polinux/stress
resources:
limits:
memory: "200Mi"
requests:
memory: "100Mi"
command: ["stress"]
args: ["--vm", "1", "--vm-bytes", "150M", "--vm-hang", "1"]

命名空间默认值、最小值和最大值

有些 Kubernetes 集群环境下会出现这样的问题:定义 Pod 的时候明明没有给计算资源的限制,但是在实际运行的 Pod 上出现了资源限制的定义。

这很有可能是命名空间的默认值造成的。 通过 LimitRange 对象可以设置命名空间资源的默认值、最小值和最大值。

关键字 备注
default limit
defaultRequest request
min 最小值
max 最大值
maxLimitRequestRatio 比率
type 对象

例如

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
apiVersion: v1
kind: LimitRange
metadata:
name: mem-min-max-demo-lr
spec:
limits:
- default:
memory: 1Gi
defaultRequest:
memory: 1Gi
max:
memory: 1Gi
min:
memory: 500Mi
type: Container

Pod 服务质量

当系统资源不足时就会有 Pod 遭殃,但是那么多 Pod 怎么知道要干掉哪一个呢。这就和 Pod 的服务质量有关。

查看运行中的 Pod 会有一个 qosClass 字段,它有以下三种取值:

类型 备注
Guaranteed 被保证的,优先级最高,除非超过限制不然不会被干掉
Burstable 允许突发,位于中间, 当系统资源不足且没有 Best-Effort 级别时
BestEffort 尽力保证,优先级最低,当系统资源不足时最先被干掉

需要注意 qosClass 的值无法直接设置,而是通过 requestslimits 组合隐式设置

Guaranteed

  • Pod 中的每个容器都要指定 CPU 请求和限制,并且两者相等
  • Pod 中的每个容器都要指定内存请求和限制,并且两者相等

Burstable

  • Pod 不符合 Guaranteed QoS 类的标准
  • Pod 至少有一个容器具有 CPU 或内存请求

BestEffort

  • 没有 CPU、内存请求和限制

CPU 管理策略

在现代操作系统的设计下,负载运行可能会不断地迁移到不同的 CPU 核心,Kubernetes 上也是一样。

Kubernetes 提供了 CPU 管理策略来实现负载和 CPU 核的绑定。

通过 kubelet 参数 --cpu-manager-policy 来指定 CPU 管理策略。

取值 备注
none 默认策略,按照 CFS 来执行调度
static 允许为节点上具有某些资源特征的 Pod 赋予增强的 CPU 亲和性和独占性

为了将 Pod 绑定到 CPU 需要

  • Kubelet 开启 --cpu-manager-policy static
  • Pod 的服务优先级为 Guaranteed
  • Pod 的 CPU 资源为整数

例如,下面的 Nginx Pod

1
2
3
4
5
6
7
8
9
10
11
spec:
containers:
- name: nginx
image: nginx
resources:
limits:
memory: "200Mi"
cpu: "2"
requests:
memory: "200Mi"
cpu: "2"

在运行时可独占两颗 CPU

NUMA 和拓扑管理策略

NUMA 是系统优化的一个常用思路,它是伴随着多处理器出现的一个问题。

非统一内存访问架构(英语:Non-uniform memory access,简称NUMA)是一种为多处理器的电脑设计的内存架构,内存访问时间取决于内存相对于处理器的位置。

常见的硬件有

  • CPU
  • Memory
  • GPU
  • NIC

安装 numactl 后可以通过下面的命令查看主机的 NUMA 相关信息

numactl --harware

通过下面的命令查看 NUMA 的统计信息

numastat

1
2
3
4
5
6
numa_hit       | Number of pages allocated from the node the process wanted.
numa_miss | Number of pages allocated from this node, but the process preferred another node.
numa_foreign | Number of pages allocated another node, but the process preferred this node.
local_node | Number of pages allocated from this node while the process was running locally.
other_node | Number of pages allocated from this node while the process was running remotely (on another node).
interleave_hit | Number of pages allocated successfully with the interleave strategy.

以上指标均可以通过 Telegraf 监控

1
[[inputs.kernel_vmstat]]

--cpu-manager-policy static --topology-manager-scope pod --topology-manager-policy single-numa-node

参考

Istio 请求响应标志

整理了一下 Istio 的 Response Flags

介绍

源码

协议 缩写 备注 备注
HTTP DC DOWNSTREAM_CONNECTION_TERMINATION Downstream connection termination.
HTTP DI DELAY_INJECTED The request processing was delayed for a period specified via fault injection.
HTTP DPE DOWNSTREAM_PROTOCOL_ERROR The downstream request had an HTTP protocol error.
HTTP FI FAULT_INJECTED The request was aborted with a response code specified via fault injection.
HTTP IH INVALID_ENVOY_REQUEST_HEADERS The request was rejected because it set an invalid value for a strictly-checked header in addition to 400 response code.
HTTP LH FAILED_LOCAL_HEALTH_CHECK Local service failed health check request in addition to 503 response code.
HTTP LR LOCAL_RESET Connection local reset in addition to 503 response code.
HTTP/TCP NC NO_CLUSTER_FOUND Upstream cluster not found.
HTTP/TCP NR NO_ROUTE_FOUND No route configured for a given request in addition to 404 response code, or no matching filter chain for a downstream connection.
HTTP RL RATE_LIMITED The request was ratelimited locally by the HTTP rate limit filter in addition to 429 response code.
HTTP RLSE RATELIMIT_SERVICE_ERROR The request was rejected because there was an error in rate limit service.
HTTP SI STREAM_IDLE_TIMEOUT Stream idle timeout in addition to 408 response code.
HTTP UAEX UNAUTHORIZED_EXTERNAL_SERVICE The request was denied by the external authorization service.
HTTP UC UPSTREAM_CONNECTION_TERMINATION Upstream connection termination in addition to 503 response code.
HTTP/TCP UF UPSTREAM_CONNECTION_FAILURE Upstream connection failure in addition to 503 response code.
HTTP/TCP UH NO_HEALTHY_UPSTREAM No healthy upstream hosts in upstream cluster in addition to 503 response code.
HTTP UMSDR UPSTREAM_MAX_STREAM_DURATION_REACHED The upstream request reached to max stream duration.
HTTP/TCP UO UPSTREAM_OVERFLOW Upstream overflow (circuit breaking) in addition to 503 response code.
HTTP UPE UPSTREAM_PROTOCOL_ERROR The upstream response had an HTTP protocol error.
HTTP UR UPSTREAM_REMOTE_RESET Upstream remote reset in addition to 503 response code.
HTTP/TCP URX UPSTREAM_RETRY_LIMIT_EXCEEDED The request was rejected because the upstream retry limit (HTTP) or maximum connect attempts (TCP) was reached.
HTTP UT UPSTREAM_REQUEST_TIMEOUT Upstream request timeout in addition to 504 response code.

JVM 对容器化支持的参数

背景知识

阅读本文你需要了解

  • JVM 的启动参数
  • 容器的启动参数

JVM 启动参数

参数 备注
-Xms MiniumHeapSize
-Xmx MaxHeapSize
-Xss StackSize

在容器化之前一般由运维工程师根据部署环境机器的内存大小来调整,并写在启动脚本里

容器启动参数

在使用 k8s 编排容器的时候通常通过定义 resources 字段来限制容器使用的资源

1
2
3
4
resources:
limit:
cpu: 1000m
memory: 1G

为了方便验证,我们直接用本地 docker 来实验,docker run 命令的一些参数

参数 备注
–cpus Number of CPUs
–cpu-period int Limit CPU CFS (Completely Fair Scheduler) period
–cpu-quota int Limit CPU CFS (Completely Fair Scheduler) quota
–cpu-rt-period int Limit CPU real-time period in microseconds
–cpu-rt-runtime int Limit CPU real-time runtime in microseconds
–cpu-shares int CPU shares (relative weight)
–memory Memory limit

问题

容器化的 JVM 程序会遇到这几个问题

  • 问题 1: 超过了容器的资源限制被 OOM 机制杀掉
  • 问题 2: 在核数比较高的机器上 JVM 频繁 GC

Read More

腾讯 Centos 软件源的使用

1
2
3
wget -O /etc/yum.repos.d/CentOS-Base.repo http://mirrors.cloud.tencent.com/repo/centos7_base.repo
wget -O /etc/yum.repos.d/epel.repo http://mirrors.cloud.tencent.com/repo/epel-7.repo
sed -i 's/cloud\.tencent\.com/tencentyun\.com/1'