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 問題跟蹤器中報告內核網絡測試出現的任何問題。

記錄提交並添加測試

請如上文所述報告問題;如果可能,請在發生以下情況時上傳更改以修復問題:

  • 測試沒有在通用內核樹上通過
  • 您發現在源代碼注釋中沒有提及某項必要的提交
  • 需要進行重大更改才能在上游內核通過測試
  • 您認為測試是多余指定的,或者未來的內核測試會失敗
  • 您希望添加更多測試或擴大現有測試的覆蓋面。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM