eBPF

使用 eBPF 扩展内核

Android 包含一个 eBPF 加载程序和库,它会在 Android 启动时加载 eBPF 程序以扩展内核功能,这可用于从内核收集统计信息,进行监控或调试。

关于 eBPF

扩展型伯克利包过滤器 (eBPF) 是一个内核内部的虚拟机,可运行用户提供的 eBPF 程序。这些程序可以通过 hook 接入内核中的探测点或事件、收集有用的统计信息,并将结果存储在多个数据结构中。程序通过 bpf(2) 系统调用加载到内核中,并作为 eBPF 机器指令的二进制 blob 由用户提供。Android 编译系统支持使用下文所述的简单编译文件语法将 C 程序编译到 eBPF。

要详细了解 eBPF 内部架构,请参阅 Brendan Gregg 的 eBPF 页面

Android BPF 加载程序

在 Android 启动期间,系统会加载位于 /system/etc/bpf/ 的所有 eBPF 程序。这些程序是 Android 编译系统根据 C 程序和 Android 源代码树中的 Android.bp 文件编译而成的二进制对象。编译系统将生成的对象存储在 /system/etc/bpf,这些对象将成为系统映像的一部分。

Android eBPF C 程序的格式

在 Android 设备上加载的 eBPF C 程序必须具有以下格式:

#include <bpf_helpers.h>

/* Define one or more maps in the maps section, for example
 * define a map of type array int -> uint32_t, with 10 entries
 */
DEFINE_BPF_MAP(name_of_my_map, ARRAY, int, uint32_t, 10);

/* this will also define type-safe accessors:
 *   value * bpf_name_of_my_map_lookup_elem(&key);
 *   int bpf_name_of_my_map_update_elem(&key, &value, flags);
 *   int bpf_name_of_my_map_delete_elem(&key);
 * as such it is heavily suggested to use lowercase *_map names.
 * Also note that due to compiler deficiencies you cannot use a type
 * of 'struct foo' but must instead use just 'foo'.  As such structs
 * must not be defined as 'struct foo {}' and must instead be
 * 'typedef struct {} foo'.
 */

SEC("PROGTYPE/PROGNAME")
int PROGFUNC(..args..) {
   <body-of-code
    ... read or write to MY_MAPNAME
    ... do other things
   >
}

char _license[] SEC("license") = "GPL"; // or other license
 

在这里,name_of_my_map 是您映射变量的名称,用于指示 BPF 加载程序要使用哪些参数来创建哪种类型的映射。此结构体的定义见 C 程序所包含的 bpf_helpers.h 头文件。运行以上代码会创建一个由 10 个条目构成的数组映射。

接下来,程序会定义函数 PROGFUNC。编译时,此函数会放置在一个 section 中。此 section 的名称必须为以下格式:PROGTYPE/PROGNAMEPROGTYPE 可以是下列任一项。更多类型可以在加载程序源代码中找到。

kprobe 使用 kprobe 基础架构将 PROGFUNC 通过 hook 接入某个内核指令。PROGNAME 必须是 kprobe 目标内核函数的名称。要详细了解 kprobe,请参阅 kprobe 内核文档
tracepoint 通过 hook 将 PROGFUNC 接入某个跟踪点。PROGNAME 的格式必须为 SUBSYSTEM/EVENT。例如,用于将函数附加到调度程序上下文切换事件的跟踪点 section 将是 SEC("tracepoint/sched/sched_switch"),其中 sched 是跟踪子系统的名称,sched_switch 是跟踪事件的名称。要详细了解跟踪点,请参阅跟踪事件内核文档
skfilter 程序将用作网络套接字过滤器。
schedcls 程序将用作网络流量分类器。
cgroupskb、cgroupsock 只要 CGroup 中的进程创建了 AF_INET 或 AF_INET6 套接字,程序就会运行。

下面是一个完整的 C 程序示例,它创建了一个映射并定义了函数 tp_sched_switch,该函数可以附加到 sched:sched_switch trace 事件中(要了解如何附加,请参阅此部分内容)。该程序添加了与曾在特定 CPU 上运行的最新任务 PID 相关的信息,命名为 myschedtp.c。我们将在本文档的后面部分说到此文件。

#include <linux/bpf.h>
#include <stdbool.h>
#include <stdint.h>
#include <bpf_helpers.h>

DEFINE_BPF_MAP(cpu_pid_map, ARRAY, int, uint32_t, 1024);

struct switch_args {
    unsigned long long ignore;
    char prev_comm[16];
    int prev_pid;
    int prev_prio;
    long long prev_state;
    char next_comm[16];
    int next_pid;
    int next_prio;
};

SEC("tracepoint/sched/sched_switch")
int tp_sched_switch(struct switch_args* args) {
    int key;
    uint32_t val;

    key = bpf_get_smp_processor_id();
    val = args->next_pid;

    bpf_cpu_pid_map_update_elem(&key, &val, BPF_ANY);
    return 0;
}

char _license[] SEC("license") = "GPL";
 

当该程序使用内核提供的 BPF 辅助函数时,内核会使用许可证 section 验证程序是否与内核许可证兼容。请将 _license 设为您项目的许可证。

Android.bp 文件的格式

为了使 Android 编译系统能编译 eBPF .c 程序,必须在项目的 Android.bp 文件中输入内容。

例如,要编译一个名为 bpf_test.c 的 eBPF C 程序,请在项目的 Android.bp 文件中输入以下内容:

bpf {
    name: "bpf_test.o",
    srcs: ["bpf_test.c"],
    cflags: [
        "-Wall",
        "-Werror",
    ],
}
 

 

这样就会编译该 C 程序并生成对象 /system/etc/bpf/bpf_test.o。在启动时,Android 系统会自动将 bpf_test.o 程序加载到内核中。

sysfs 中的可用文件

在启动过程中,Android 系统会自动从 /system/etc/bpf/ 加载所有 eBPF 对象、创建程序所需的映射,并将加载的程序及其映射固定到 bpf 文件系统。这些文件之后可用于与 eBPF 程序进一步交互或读取映射。本部分介绍了这些文件的命名规范及它们在 sysfs 中的位置。

系统会创建并固定以下文件:

  • 对于任何已加载的程序,假设 PROGNAME 是程序的名称,而 FILENAME 是 eBPF C 文件的名称,则 Android 加载程序会创建每个程序并将其固定到 /sys/fs/bpf/prog_FILENAME_PROGTYPE_PROGNAME

    例如,对于上述 myschedtp.c 中的 sched_switch 跟踪点示例,系统会创建一个程序文件并将其固定到 /sys/fs/bpf/prog_myschedtp_tracepoint_sched_sched_switch

  • 对于任何已创建的映射,假设 MAPNAME 是映射的名称,而 FILENAME 是 eBPF C 文件的名称,则 Android 加载程序会将其创建的每个映射固定到 /sys/fs/bpf/map_FILENAME_MAPNAME

    例如,对于上述 myschedtp.c 中的 sched_switch 跟踪点示例,系统会创建一个映射文件并将其固定到 /sys/fs/bpf/map_myschedtp_cpu_pid_map

  • Android BPF 库中的 bpf_obj_get() 可用于从这些固定的 /sys/fs/bpf 文件中获取文件描述符。此函数会返回文件描述符,该描述符可用于进一步执行操作(例如读取映射或将程序附加到跟踪点)。

Android BPF 库

Android BPF 库名为 libbpf_android.so,属于系统映像的一部分。该库向用户提供了执行以下操作所需的低级 eBPF 功能:创建和读取映射,以及创建探测点、跟踪点、性能缓冲区等。

将程序附加到跟踪点和 kprobe

跟踪点和 kprobe 程序加载完成后(如前所述,会在启动时自动完成),需要激活。要激活它们,请首先使用 bpf_obj_get() API 从固定文件的位置获取程序 fd(请参阅 sysfs 中的可用文件部分)。接下来,调用 BPF 库中的 bpf_attach_tracepoint() API,将程序 fd 和跟踪点名称传递给该 API。

例如,要附加在上述示例的 myschedtp.c 源文件中定义的 sched_switch 跟踪点,请执行以下操作(没显示错误检查):

  char *tp_prog_path = "/sys/fs/bpf/prog_myschedtp_tracepoint_sched_sched_switch";
  char *tp_map_path = "/sys/fs/bpf/map_myschedtp_cpu_pid";

  // Attach tracepoint and wait for 4 seconds
  int mProgFd = bpf_obj_get(tp_prog_path);
  int mMapFd = bpf_obj_get(tp_map_path);
  int ret = bpf_attach_tracepoint(mProgFd, "sched", "sched_switch");
  sleep(4);

  // Read the map to find the last PID that ran on CPU 0
  android::bpf::BpfMap myMap(mMapFd);
  printf("last PID running on CPU %d is %d\n", 0, myMap.readValue(0));
 

从映射中读取数据

BPF 映射支持任意复杂的键和值结构或类型。Android BPF 库包含一个 android::BpfMap 类,该类利用 C++ 模板根据相关映射的键和值类型来实例化 BpfMap。上述代码示例演示了键和值为整数的 BpfMap。整数也可以是任意结构。

因此,使用模板化的 BpfMap 类可让您轻松定义适合特定映射的自定义 BpfMap 对象。之后可以使用生成的类型感知型自定义函数来访问该映射,从而生成更清晰的代码。

要详细了解 BpfMap,请参阅 Android 源代码

调试问题

在启动期间,系统将记录与 BPF 加载相关的多条消息。如果加载进程因任何原因失败,logcat 中会提供详细的日志消息。按照“bpf”过滤 logcat 日志时,会输出加载过程中的所有消息和错误详情,例如 eBPF 验证程序错误。

Android 中的 eBPF 用户

目前 Android 中有两个 eBPF C 程序,您可以作为示例来参考。

netd eBPF C 程序可供 Android 中的网络守护进程 (netd) 用于多种用途,例如过滤套接字和收集统计信息。要了解此程序的使用方式,请查阅 eBPF 流量监控源代码。

time_in_state eBPF C 程序会计算一款 Android 应用在不同 CPU 频率下运行所花费的时间,该时间可用于计算功率。此程序目前正在开发中。

许可注意事项

如果您想贡献 eBPF C 程序,则应根据其许可证将该程序贡献给合适的项目。获得 GPL 许可证的 eBPF C 程序应该贡献给 system/bpfprogs AOSP 项目,获得 Apache 许可证的程序则应该贡献给 system/bpf AOSP 项目。

 

Ion ABI Changes

Devices shipping kernel 4.14 and higher are affected by a major refactoring of the Ion kernel module, which many vendor graphics memory allocator (gralloc) hardware abstraction layer (HAL) implementations call to allocate shared memory buffers. This article provides guidance on migrating legacy vendor code over to the new version of Ion and discusses possible future application binary interface (ABI) breaks.

About Ion

Ion is part of the upstream kernel's work-in-progress staging tree. While in staging, Ion's userspace-to-kernel ABI may break between major kernel versions. While Ion ABI breaks don't directly affect either ordinary applications or already-launched devices, vendors migrating to new major kernel versions may encounter changes that affect vendor code calling into Ion. Additionally, future ABI breaks may occur as the Android systems team works with upstream to move Ion out of the staging tree.

Changes in android-4.14

Kernel 4.12 heavily refactored the Ion kernel code, cleaning up and removing parts of Ion that overlapped with other kernel frameworks. As a result, many legacy Ion ioctls are no longer relevant and have been removed.

Removal of Ion clients and handles

Before kernel 4.12, opening /dev/ion allocated an Ion client. The IOC_ION_ALLOC ioctl allocated a new buffer and returned it to userspace as an Ion handle (an opaque integer meaningful only to the Ion client that allocated it). To map buffers into userspace or share them with other processes, Ion handles were re-exported as dma-buf fds using the IOC_ION_SHARE ioctl.

In kernel 4.12, the IOC_ION_ALLOC ioctl directly outputs dma-buf fds. The intermediate Ion handle state has been removed, along with all ioctls that consume or produce Ion handles. Because dma-buf fds aren't tied to specific Ion clients, the IOC_ION_SHARE ioctl is no longer needed, and all Ion client infrastructure has been removed.

Addition of cache-coherency ioctls

Before kernel 4.12, Ion provided an ION_IOC_SYNC ioctl to synchronize the file descriptor with memory. This ioctl was poorly documented and inflexible. As a result, many vendors implemented custom ioctls to perform cache maintenance.

Kernel 4.12 replaced ION_IOC_SYNC with the DMA_BUF_IOCTL_SYNC ioctl defined in linux/dma-buf.h. Call DMA_BUF_IOCTL_SYNC at the start and end of every CPU access, with flags specifying whether these accesses are reads and/or writes. Although DMA_BUF_IOCTL_SYNC is more verbose than ION_IOC_SYNC, it gives userspace more control over the underlying cache maintenance operations.

DMA_BUF_IOCTL_SYNC is part of the kernel's stable ABI and is usable with all dma-buf fds, whether or not they were allocated by Ion.

Migrating vendor code to android-4.12+

For userspace clients, the Android systems team strongly encourages using libion rather than open-coding ioctl() calls. As of Android 9, libion automatically detects the Ion ABI at runtime and attempts to mask any differences between kernels. However, any libion functions that produced or consumed ion_user_handle_thandles no longer work after kernel 4.12. You can replace these functions with the following equivalent operations on dma-buf fds, which work on all versions of the kernel to date.

Legacy ion_user_handle_t call
ion_alloc(ion_fd, …, &buf_handle) ion_alloc_fd(ion_fd, ..., &buf_fd)
ion_share(ion_fd, buf_handle, &buf_fd) N/A (this call isn't needed with dma-buf fds)
ion_map(ion_fd, buf_handle, ...) mmap(buf_fd, ...)
ion_free(ion_fd, buf_handle) close(buf_fd)
ion_import(ion_fd, buf_fd, &buf_handle) N/A (this call isn't needed with dma-buf fds)
ion_sync_fd(ion_fd, buf_fd) If (ion_is_legacy(ion_fd))

ion_sync_fd(ion_fd, buf_fd);

else

ioctl(buf_fd, DMA_BUF_IOCTL_SYNC, ...);

For in-kernel clients, because Ion no longer exports any kernel-facing APIs, drivers that previously used the in-kernel Ion kernel API with ion_import_dma_buf_fd() must be converted to use the in-kernel dma-buf API with dma_buf_get().

Future Ion ABI breaks

Before Ion can be moved out of the staging tree, future kernel releases may need to break the Ion ABI again. The Android systems team doesn't expect these changes to affect devices launching with the next Android version, but such changes may affect devices launching with subsequent Android versions.

For example, the upstream community has proposed splitting the single /dev/ion node into multiple, per-heap nodes (e.g., /dev/ion/heap0) to enable devices to apply different SELinux policies to each heap. If this change is implemented in a future kernel release, it would break Ion ABI.

 

内核配置

您可以将以下配置设置用作 Android 内核配置的基础。设置会整理到 android-baseandroid-base-ARCH 和 android-recommended 的 .cfg 文件中:

  • android-base 选项可实现核心 Android 功能,并且应配置为所有设备指定的选项。
  • android-base-ARCH 选项可实现核心 Android 功能,并且应配置为架构 ARCH 的所有设备指定的选项。并非所有架构都具有相应的特定于架构的必需选项文件。如果您的架构没有相应文件,则它没有额外特定于架构的 Android 内核配置要求。
  • android-recommended。这些选项可实现高级 Android 功能,设备可选择性启用。

这些配置文件位于 kernel/configs repo 中。使用一组对应您正在使用的内核版本的配置文件。

如需详细了解已用于加强设备内核的控件,请参阅系统和内核安全。如需详细了解必需的设置,请参阅 Android 兼容性定义文档 (CDD)

生成内核配置

对于具有极简 defconfig 格式的设备,您可以在内核树中使用 merge_config.sh 脚本来启用选项:

ARCH=ARCH scripts/kconfig/merge_config.sh <...>/device_defconfig <...>/android-base.cfg <...>/android-base-ARCH.cfg <...>/android-recommended.cfg
 

这会生成一个 .config 文件,您可以使用该文件保存新的 defconfig 文件或编译启用 Android 功能的新内核。

其他内核配置要求

在某些情况下,平台维护人员可以从多项内核功能中进行选择以满足 Android 依赖项的要求。这类依赖项不能在内核配置片段文件(如上所述)中表示,因为这些文件的格式不支持逻辑表达式。在 Android 9 及更高版本中,兼容性测试套件 (CTS) 和供应商测试套件 (VTS) 会验证是否满足以下要求:

  • CONFIG_OF=y 或 CONFIG_ACPI=y
  • 4.4 和 4.9 内核具有 CONFIG_ANDROID_LOW_MEMORY_KILLER=y,或同时具有 CONFIG_MEMCG=y 和 CONFIG_MEMCG_SWAP=y
  • CONFIG_DEBUG_RODATA=y 或 CONFIG_STRICT_KERNEL_RWX=y
  • CONFIG_DEBUG_SET_MODULE_RONX=y 或 CONFIG_STRICT_MODULE_RWX=y
  • 仅适用于 ARM64:CONFIG_ARM64_SW_TTBR0_PAN=y 或 CONFIG_ARM64_PAN=y

此外,对于 Android 9 及更高版本中的 4.9 内核,必须将 CONFIG_INET_UDP_DIAG 选项设为 y

启用 USB 主机模式选项

对于 USB 主机模式音频,请启用以下选项:

CONFIG_SND_USB=y
CONFIG_SND_USB_AUDIO=y
# CONFIG_USB_AUDIO is for a peripheral mode (gadget) driver
 

对于 USB 主机模式 MIDI,请启用以下选项:

CONFIG_SND_USB_MIDI=y
 

Seccomp BPF 与 TSYNC

安全计算柏克莱封包过滤器 (Seccomp BPF) 是一种内核安全技术,它支持创建沙盒来定义进程可进行系统调用的上下文。线程同步 (TSYNC) 功能可以实现从多线程程序中使用 Seccomp BPF。这种能力仅限由上游提供 Seccomp 支持的架构(ARM、ARM64、x86 和 x86_64)。

Android Live-Lock 守护进程

Android 10 包含 Android Live-Lock 守护进程 (llkd),它旨在捕获和减少内核死锁问题。如需详细了解如何使用 llkd,请参阅 README.md

在 ARM64 上使用 vDSO32

虚拟动态共享对象 (vDSO) 是系统调用的替代选项,如果正确使用和配置,可以降低周期费用。Android 10 增加了对在 64 位内核上使用 vDSO32 的支持(Android 已经支持在 64 位内核上使用 vDSO64 以及在 32 位内核上使用 vDSO32)。在 ARM64 架构上使用 vDSO32 (CONFIG_VDSO_COMPAT) 可以使电池续航时间提升 0.4%,并有助于改进其他方面的性能。

Linux 社区致力于跨架构统一 vDSO。您可以在 Linux 内核中设置 vDSO,方法是通过 arm32 编译器三元组启用具有 CONFIG_COMPAT 和 CONFIG_CROSS_COMPILE_COMPAT_VDSO 的 vDSO32。Android 内核团队已将旧版 vDSO 补丁程序系列向后移植至 Pixel 设备,因此您可以在 Pixel 内核版本(LINUX_FCC_CROSS_COMPILE_ARM32_PREBUILTS_BIN 路径、CROSS_COMPILE_ARM32 参考和 CONFIG_CROSS_COMPILE_ARM32 配置)中找到相关示例。

内核加固

Android 8.0 增添了内核加固功能,以帮助减少内核漏洞并发现内核驱动程序中的错误。这些功能位于分支 android-3.18、android-4.4 和 android-4.9 的 kernel/common 中。

实现

要获得这些功能,设备制造商和 SOC 应该将来自 kernel/common 的所有加固补丁程序合并到其内核树并启用以下内核配置选项:

  • 加固后的用户复制功能:CONFIG_HARDENED_USERCOPY=y
  • PAN 模拟 - arm64:CONFIG_ARM64_SW_TTBR0_PAN=y
  • PAN 模拟 - arm:CONFIG_CPU_SW_DOMAIN_PAN=y
  • KASLR - 4.4 及更高版本的内核:CONFIG_RANDOMIZE_BASE=y

KASLR 还需要引导加载程序支持以通过设备树节点 /chosen/kaslr-seed 或通过实现 EFI_RNG_PROTOCOL 来传递硬件熵。

此外,还要确保启用现有的加固功能:

  • 堆栈缓冲区溢出缓解:CONFIG_CC_STACKPROTECTOR_STRONG=y
  • 内存储器保护:CONFIG_DEBUG_RODATA=y 或 CONFIG_STRICT_KERNEL_RWX=y
  • 限制内核对用户空间的访问 - x86(默认已启用):CONFIG_X86_SMAP=y

测试

要测试您的实现,请将 CONFIG_LKDTM=y 添加到内核配置,并确认以下每个命令都会导致内核崩溃:

echo ACCESS_USERSPACE > /sys/kernel/debug/provoke-crash/DIRECT
echo EXEC_USERSPACE > /sys/kernel/debug/provoke-crash/DIRECT
echo WRITE_RO > /sys/kernel/debug/provoke-crash/DIRECT
echo WRITE_RO_AFTER_INIT > /sys/kernel/debug/provoke-crash/DIRECT
echo WRITE_KERN > /sys/kernel/debug/provoke-crash/DIRECT
echo EXEC_STACK > /sys/kernel/debug/provoke-crash/DIRECT
echo EXEC_RODATA > /sys/kernel/debug/provoke-crash/DIRECT
echo EXEC_KMALLOC > /sys/kernel/debug/provoke-crash/DIRECT
echo EXEC_VMALLOC > /sys/kernel/debug/provoke-crash/DIRECT
echo CORRUPT_STACK > /sys/kernel/debug/provoke-crash/DIRECT
 

对于 android-4.9:

echo USERCOPY_HEAP_SIZE_TO > /sys/kernel/debug/provoke-crash/DIRECT
echo USERCOPY_HEAP_SIZE_FROM > /sys/kernel/debug/provoke-crash/DIRECT
 

常见问题

这些更改可能会暴露内核驱动程序中的错误,这些错误则需要由设备制造商或内核驱动程序所有者修复。

  • 将数据复制到用户空间/从用户空间复制数据时,加固用户复制功能会发生错误的边界检查。应该像修复任何其他存储器损坏错误一样,对这些错误进行修复。
  • PAN 模拟会导致内核直接访问用户空间,而这是不允许的。相反,尝试访问用户空间存储器的驱动程序需要改为使用标准的 copy_to_user()/copy_from_user() 函数。

在内核级别优化 SquashFS

SquashFS 是 Linux 的只读压缩文件系统。该文件系统设计为只读,因此适合在系统分区上使用。很多 Android 设备都可以通过对其系统分区使用此文件系统来获益;例如,以下设备:

  • 存储容量小的设备,例如 Android Watch。
  • 闪存缓慢的设备(压缩可减少块 I/O 的数量)。

遗憾的是,SquashFS 的性能落后于 ext4。

优化

为提高 SquashFS 的性能,已经实现下列优化。

减少内存使用量和 memcpy 调用次数

读取块(默认为 128K)时,SquashFS 会尝试抓取包含此块的所有页面。

如果某个页面是最新页面或已被锁定,则 SquashFS 会转而分配一个完整块,提交读取请求,然后将其内容复制到这些页面。

这种方法效果极其低效;一段时间后,页面缓存可能包含最新页面,即使相邻页面并非最新页面也是如此。

代码现在能够处理有孔(即缺少页面)的块。这通过以下方式来提高性能:

  • 减少 memcpy 调用次数
  • 减少内存分配

异步读取

SquashFS 仍使用已弃用的 ll_rw_block() 函数。使用这种方法存在两个问题:

  • 顾名思义,该函数会等待读取完成之后再返回结果。这是多余的,因为 .readpage() 已在页面锁上等待。此外,我们需要一个异步机制来高效实现 .readpages()
  • 合并读取请求完全取决于 I/O 调度程序。 ll_rw_block() 只会为每个缓冲区创建一个请求。在应该合并哪些信息方面,SquashFS 包含的信息比 I/O 调度程序多。此外,合并请求意味着我们对 I/O 调度程序的依赖会有所减少。

因此,ll_rw_block() 函数已被 submit_bio() 替换。

Readpages(预先抓取)

SquashFS 不会实现 .readpages(),因此内核会反复调用 .readpage()

由于我们的读取请求是异步的,因此内核可以使用其异步预读机制真正预先抓取页面。

优化未压缩块的读取操作

Android 之类的现代系统包含大量经过压缩的文件。因此,映像包含大量无法压缩的块。

SquashFS 使用相同的逻辑处理压缩和未压缩的块:当 SquashFS 需要读取一个页面时,它实际上读取的是一个完整块(默认为 128k)。 虽然对于压缩块,这是必需的;但对于未压缩的块,这只是在浪费资源。

SquashFS 现在只读取预读算法建议的内容,而不会读取一个完整块。

这极大地提高了随机读取的性能。

代码

AOSP 中提供 SquashFS 代码优化:

用于 LLDB/C++ 调试的内核增强功能

Android 8.0 版本包含一些内核增强功能,可通过改善开发者的调试体验来帮助开发者打造出更好的应用。

arm64 Android 内核支持在非 4/8 字节对齐的存储器地址上设置观察点,并报告对这些地址的所有访问。

实现

该功能可在任何 ARM 64 位设备上运行。可以选择添加对 32 位硬件/内核的支持。所有必要的内核修改都已完成。

4.4 及更高版本的通用内核包含此功能。要将此功能添加到尚未包含它的内核中,请择优挑选必要的 CL 并将其加入到您的内核版本中。由于内核代码库会不断发展完善,补丁程序也会随之进行一些必要的调整,因此请根据您的内核所基于的版本选择相应的补丁程序集:

内核网络单元测试

从 Android 5.0 开始,要使 Android 网络堆栈在 Linux 内核中正常运行,开发者需要集成近期才提交给上游或尚未提交给上游的多个补丁程序。手动验证需要的内核功能或跟踪缺失的补丁程序并不容易,因此,Android 团队打算共享他们使用的测试方案,以确保内核按预期运行。

为何要运行测试?

运行这些测试的原因主要有 3 个:

  1. 设备上使用的 Linux 内核的确切版本通常是特定于设备的,如果不运行测试,就很难了解所有内核是否都能够正常工作。
  2. 将内核补丁程序向前移植和向后移植到不同的内核版本或不同的设备树时,可能会导致出现一些细微的问题;如果不运行测试,则很难发现这些问题。
  3. 新的网络功能可能需要新的内核功能,或需要修复内核错误。

如果未通过测试,设备的网络堆栈将无法正常运作,从而导致出现用户可见的连接错误,例如 WLAN 网络断开连接。设备还可能会无法通过 Android 兼容性测试套件 (CTS) 测试。

使用测试

测试会使用 User-Mode Linux 来启动内核,如同 Linux 主机上的一个进程。请参阅构建编译环境,查看合适的操作系统版本。单元测试框架会使用适当的磁盘映像启动内核,并从主机文件系统运行测试。测试使用 Python 2.x 进行编写,并使用 TAP 接口来测试内核行为和套接字 API。

针对 ARCH=um 编译内核

要运行测试,内核必须针对 ARCH=um SUBARCH=x86_64 进行编译。此架构在上游和通用 Android 内核树(例如 android-4.4)中均受支持。不过,有时设备内核不能在此模式下编译,因为设备树的公共文件(例如 sys/exit.c)中包含特定于设备或特定于硬件的代码。

在很多情况下,只要确保特定于硬件的代码位于 #ifdef 之后,就足够了。通常,这应该是配置选项中的 #ifdef,用于控制与代码相关的特定功能。如果没有这样的配置选项,则将特定于硬件的代码放在 #ifndef CONFIG_UML 块中。

一般情况下,此项修复应该由内核树提供方(例如,芯片组供应商或 SoC 供应商)负责。我们正在与原始设备制造商 (OEM) 和供应商合作,确保当前和未来的内核将针对 ARCH=um SUBARCH=x86_64 进行编译,而无需进行任何更改。

运行测试

测试位于 kernel/tests/net/test 下。建议您从 AOSP master 运行测试,因为它们是最新的;在某些情况下,指定的 Android 版本正常运行所必需的内核功能尚未在指定版本中进行全面测试。有关如何运行测试的信息,请参阅内核网络测试自述文件。总而言之,从您的内核树顶部运行:

ANDROID_TREE/kernel/tests/net/test/run_net_test.sh all_tests.sh
 

通过测试

内核网络测试 Python 源文件包含注释,这些注释会指定通过测试所必需的已知内核提交。测试应该在 AOSP 的 kernel/common 项目中传入通用内核树(所有通用内核分支 android-4.4 及更高版本)。因此,要在内核上通过测试,只需从相应的通用内核分支不断进行合并。

贡献

报告问题

请使用组件网络标签在 Android 问题跟踪器中报告内核网络测试出现的任何问题。

记录提交并添加测试

请如上文所述报告问题;如果可能,请在发生以下情况时上传更改以修复问题:

  • 测试没有在通用内核树上通过
  • 您发现在源代码注释中没有提及某项必要的提交
  • 需要进行重大更改才能在上游内核通过测试
  • 您认为测试是多余指定的,或者未来的内核测试会失败
  • 您希望添加更多测试或扩大现有测试的覆盖面。
posted @ 2019-11-12 16:51  anonmous  阅读(1666)  评论(0编辑  收藏  举报