JasonWang's Blog

进程调度中的PELT与WALT

对于任务调度器来说,在发生调度时需要决定选择某个进程调度到哪个CPU执行,同时还需要基于系统当前的负载,实时的调整CPU运行的频率。Linux进程调度器为了适配各种不同场景的设备,如服务器,嵌入式设备,通常需要在多个性能目标上进行权衡取舍:

  • 需要确保调度尽可能公平,保证每个任务都有机会得到执行
  • 快速响应用户请求,比如对于交互式的用户任务,需要降低调度延迟,快速调度任务执行
  • 实现更高的系统吞吐量,可以满足更多的任务并发执行
  • 同时尽可能降低系统功耗,在系统空闲或者负载降低时尽可能减小工作频率,减少能耗
深入Linux容器LXC之二-LXC源码分析

在上一篇文章中深入Linux容器LXC之一-LXC的实现原理着重介绍了LXC容器的实现原理,我们对LXC容器的基本原理有了一定的了解,但其中有一个问题,启动一个容器后,容器系统中存在两个名为init的进程。为什么会有两个init进程?为了解开这个疑问,需要对LXC的源码做一个深入的分析。本篇文章主要是围绕这个问题展开,大致分为两个大的部分:

  • lxc-create的实现: LXC容器是如何创建的
  • lxc-start的实现: LXC容器是如何启动的
深入Linux容器LXC之一-LXC的实现原理

容器(Containers)是一种创建轻量级虚拟的应用执行环境的技术;基于容器技术,我们可以轻松的在同一个操作系统中构建出多个隔离、虚拟的运行环境,不同于基于虚拟化技术(hypervisor)的硬件级别的隔离方案,容器通过Linux内核中的命名空间(Namespace)以及控制分组(Cgroups)来实现进程级资源如CPU、内存、IO、网络等隔离与管理。目前常见的容器方案有Linux Containers(LXC)DockerLXC可以用于进程执行也可以用于启动一个系统镜像(包含rootfs的完整系统执行环境),而Docker一般用于云计算中的应用程序的打包运行。

深入Linux容器文章系列准备分为上下两篇来写,第一篇主要围绕LXC容器的基本实现原理以及如何在ubuntu系统中创建自己的容器;下篇主要从源码的角度分析下LXC是如何实现的。这篇文章,我们着重了解下LXC的实现原理,主要从如下两个方面进行介绍:

  • 首先从namespacecgroups两个基本的概念介绍LXC的基本原理
  • 基于Ubuntu系统搭建、启动一个完整的LXC容器
Linux内核模块签名那些事

最近有同事反馈一个系统启动失败的问题,根因是系统的驱动模块加载失败导致system_server无法正常启动。lsmod查看,没有有任何的驱动加载,尝试insmod /vendor/lib/modules/cnss2.ko会提示:

1
2
3

insmod: failed to to load cnss2.ko : Key was rejected by service

说明对应模块的签名与内核不一致,导致安装失败了。这里我们就来看看内核模块具体是怎么签名的,模块签名又是如何验证的,以及如何通过工具进行模块的签名检验。

Linux实时调度踩到的那些坑

早期Linux内核的调度更多考虑的是系统调度的公平与吞吐量,对于实时性的支持并不友好。为了改善系统的响应时间,降低某些场景下实时任务的调度延迟,从2.6版本开始支持了实时调度与抢占功能,开发人员为此专门建立了一个实时Linux的网站,上面提供了实时内核的一些历史状态与补丁信息。实时调度对于音视频、UI渲染等对时间非常敏感的任务来说,非常必要。比如对于Android平台,会将音频、渲染相关的一些核心任务的调度策略设置为实时调度,这样可以减少系统调度延迟与任务抢占带来的延时。Linux内核中的实时调度主要有两种调度策略:

  • SCHED_FIFO: 先入先出,即优先级高的任务优先执行,不会被其他任务抢占,直到对应的任务阻塞或者主动释放CPU
  • SCHED_RR: 轮询(也称随机轮盘)调度,相同优先级的任务轮流执行相同的时间片,时间片用完后会调度其他的任务

本文基于Linux内核5.10版本分析

如何通过QEMU启动Linux系统

看Linux驱动相关的代码, 却没有一个好的调试环境可以跟踪内核相关的调用流程。于是,想着用QEMU虚拟机来搭建一个Linux系统。花了大半天时间,终于能够启动一个简单的Linux系统了。中间踩了不少坑,找了不少资料,这里简单总结下整个过程。

以下操作都是基于Ubuntu 18.04 x86_64平台

最开始参考了如何使用QEMU跑内核,使用系统自带的QEMU工具,结果提示如下错误:

1
2
3

qemu-system-aarch64 rom check and register reset failed

怀疑是QEMU的版本太低导致,于是只好又重新编译QEMU源码,最后总算大功告成。总的说来,大致要做的事情有这么几个:

  • 编译Linux内核,利用busybox来生成一个小的rootfs(关于什么是rootfs,可以参考The rootfs FS)
  • 编译QEMU,确保正常配置ARM64架构的虚拟环境
  • 一切就绪,通过qemu-system-aarch64跑起来虚拟机来
如何移植openssh到ARM开发板

最近因为公司安全的需求, 要修改openssh的源码, 将其移植到一个ARM的嵌入式系统上, 替换原有的预编译的版本. 参考了网上的一些移植openssh的资料, 如openssl官网编译安装说明; 移植openssh到arm-linux, 但是由于目标平台不一样, 实践起来并不能完全参考, 会有细微的差异. 这里把整个流程写下来, 总结一下, 方便后面移植相关开发工具.

理解Android eBPF

Android从9.0版本开始全面支持eBPF(extended Berkeley Packet Filters), 其主要用在流量统计上, 也可以用来监控CPU/IO/内存等模块的状态.简单来说, eBPF可以与内核的kprobe/tracepoints/skfilter等模块相结合, 将eBPF的函数hook到内核事件从而监控相应的系统状态.

Android为eBPF提供了许多封装的库, 并提供了eBPF加载器bpfloader:

  • bpfloader: 位于/system/bpf/bpfloader, 系统启动时负责加载位于/system/etc/bpf 中的eBPF目标文件
  • libbpf_android: 位于/system/bpf/libbpf_android提供创建bpf容器/加载bpf目标文件的接口
  • libbpf: 位于/external/bcc, 封装了bpf的系统调用, 提供如attach/dettach程序的接口
  • libnetdbpf: 位于/system/netd/libnetdbpf, 实现了netd流量统计功能的函数

目前在Android(Q)上有两处eBPF的代码: 一个是/system/netd/bpf_progs/netd.c, 主要是用于流量统计;一个是/system/bpfprogs/time_in_state.c用于监控CPU运行频率以及上下文切换的耗时.

接下来我们就从三个部分来深入理解下Android是如何利用eBPF的:

  • eBPF程序与目标文件格式
  • Android eBPF加载与执行流程
  • Android如何基于eBPF实现流量统计
一个SMMU内存访问异常的问题

最近碰到棘手的问题: 以太网进行iperf测试时,发生了SMMU (System Memory Management Unit)访问异常导致内核崩溃。原本只是内部测试发现, 后面在试验车上也概率性的出现. 问题发生的概率还不小,很严重。 只能先从头把一些基本概念与流程梳理清楚。好在最后还是找到了原因并解决了,趁着有时间把整个问题的来龙去脉细细的总结下, 算是一个SMMU相关问题的案例。

首先来看看问题的发生的背景.

问题背景

问题发生在利用iperf做网络性能测试的时候, 测试系统(采用高通8155平台, 内置一个EMAC芯片, 最高支持1Gbps速率)作为客户端:

1
2
3

iperf -c 172.20.2.33 -p 8989 -f m -R

这里加-R参数表示客户端作为数据接收方(奇怪的是, 测试不加-R参数就不会有问题, 这也说明只有在接收数据的过程才会出现问题), 而服务端是发送方:

1
2
3

iperf -s -p 8989 -f m

这么测试几十个小时就很快出现了, 抓取到的问题堆栈如下. 前面的日志是SMMU相关的寄存器状态打印, 后面是内核调用堆栈.

Linux内核中的锁

在看Linux内核代码时,经常会遇到各种锁(lock)的使用。对于像spin_lock_irq/spin_lock_irqsave的区别感到困惑,每次都要重新查一下资料。遂决定写一篇文章记录下内核中使用到的锁,以及使用的场景。

与应用中的锁类似,内核中的锁也只是为了保护某个内核数据结构或者内存区域在多个并发执行路径时不被破坏,确保数据的一致性。Linux内核作为应用层服务的提供者,一方面要为应用提供系统调用接口(system call),代表用户进程执行任务,即process context, 在进程上下文中可以休眠,执行调度;同时与硬件直接交互,要响应硬件中断的请求,处理诸如网卡数据/串口数据等请求,即Interrupt Context,在中断上下文内核不能休眠,无法重新调度. 内核就是在进程上下文/中断上下文直接来回切换,执行相应的任务请求。这就自然产生了数据的并发访问,产生了竞争条件(race condition)。另一方面,目前大多数的系统都是多核CPU、支持多进程,多个CPU、多个进程同时访问内核数据也同样会产生竞争条件。