OpenHarmony北向开发基础之系统参数Param机制

完整剖析 OpenHarmony 系统参数的设计目标、存储原理、安全机制、使用方法及与 Linux 的对比。


一、概述:系统参数是什么

系统参数(System Parameter)是 OpenHarmony 的全局键值对配置系统 。每个参数是一个 name=value 字符串对,用于在进程间共享配置信息。

典型用法

bash 复制代码
# 在设备 shell 中查看参数
param get                                    # 列出所有参数
param get const.product.model                # 查单个参数
param get | grep persist.sys.usb             # 筛选参数
param set persist.sys.usb.config hdc         # 设置参数

核心设计目标

目标 实现方式
极速读取 mmap 共享内存 + Trie 前缀树,读取零系统调用
安全可控 DAC + SELinux 双重权限控制
启动配置 init 进程读取 .para 文件初始化系统配置
持久化 persist.* 前缀参数自动保存到磁盘,重启不丢失
变更通知 参数变化可触发 init 任务或应用回调

二、与 Linux 的关系

2.1 Linux 有 Param 系统吗?

标准 Linux 内核没有用户态的 param 键值对系统。 Linux 内核提供的参数机制是 sysctl (通过 /proc/sys/ 虚拟文件系统暴露),它的作用域是内核态------控制 TCP 窗口大小、内存回收策略、进程调度参数等内核行为,普通应用无法用它来存储业务配置。

OpenHarmony 的 param 系统运行在用户态 ,是由 init 进程实现的一套独立的进程间配置共享机制,底层使用了 Linux 的 mmap 和 Unix Domain Socket,但概念上与 sysctl 完全不同。

2.2 与 Linux sysctl 的差异

维度 Linux sysctl OpenHarmony Param
层级 内核态 用户态
用途 控制内核行为(网络、内存、调度) 控制系统服务和应用行为(产品信息、开关、配置)
访问接口 读写 /proc/sys/ 文件 或 sysctl 命令 param get/set 命令 或 SystemGetParameter() API
存储方式 内核内存,通过 VFS 暴露 mmap 共享内存 + Trie 前缀树
持久化 无自动持久化,需手动写 /etc/sysctl.conf persist.* 前缀自动持久化到磁盘
安全控制 root 或 CAP_SYS_ADMIN DAC(.para.dac)+ SELinux 细粒度控制
变更通知 无(需轮询) 支持 WatchParameter() 回调 + init 触发器
读取开销 VFS 读文件(需系统调用) 直接读共享内存(零系统调用)

2.3 两者如何配合

虽然是不同层面的机制,但 OpenHarmony 通过 init 触发器将两者桥接起来------用户态设置 param,自动写入内核 sysctl:

json 复制代码
// base/startup/init/services/etc/init.cfg
{
    "name": "param:sys.sysctl.extra_free_kbytes=*",
    "cmds": ["write /proc/sys/vm/extra_free_kbytes ${sys.sysctl.extra_free_kbytes}"]
}

当应用调用 SetParameter("sys.sysctl.extra_free_kbytes", "32768") 时,init 自动将值写入内核 /proc/sys/vm/extra_free_kbytes。这样应用无需 root 权限即可间接调整内核参数(前提是 DAC/SELinux 允许写入该 param)。

类似的桥接还有 TCP 初始窗口、perf_harden 安全策略等:

json 复制代码
{
    "name": "param:security.perf_harden=1",
    "cmds": ["write /proc/sys/kernel/perf_event_paranoid 3"]
}

三、参数前缀与行为规则

参数名的前缀决定了参数的读写规则和生命周期:

前缀 行为 值长度上限 典型示例
const.* 只读 ,只能写入一次,再次写入返回 PARAM_CODE_READ_ONLY 4096 字节 const.product.model=RK3588
ro.* 只读 ,行为同 const.* 4096 字节 ro.build.display.id
ohos.boot.* 只读,从内核 cmdline 解析 4096 字节 ohos.boot.sn=xxx
persist.* 可读写 + 持久化,修改后自动保存到磁盘 96 字节 persist.sys.usb.config=hdc
sys.* / debug.* / 其他 可读写,重启后丢失 96 字节 sys.usb.state=configured

源码定义

c 复制代码
// base/startup/init/services/param/include/param_utils.h
#define IS_READY_ONLY(name) \
    ((strncmp((name), "const.", strlen("const.")) == 0) ||  \
    (strncmp((name), "ro.", strlen("ro.")) == 0) || \
    (strncmp((name), "ohos.boot.", strlen("ohos.boot.")) == 0))

#define PARAM_PERSIST_PREFIX "persist."

四、底层技术原理:mmap 共享内存演示

OpenHarmony 的 param 系统本质上就是 mmap 共享内存 ------ 一个进程写入数据,其他进程无需任何 IPC 即可直接读取。下面用一个完整的 C 语言 demo 演示这个核心机制:自己实现一个简化版的"param 系统",支持写入和读取 JSON 字符串。

4.1 原理说明

复制代码
          /dev/shm/__my_params__/param_store(tmpfs 上的普通文件,数据在内存中)
                              │
        ┌─────────────────────┼─────────────────────┐
        │                     │                     │
   写入进程                                     读取进程(可以有多个)
   mmap(PROT_READ|WRITE)                      mmap(PROT_READ)
        │                     │                     │
        ▼                     ▼                     ▼
   ┌─────────┐         ┌─────────┐           ┌─────────┐
   │ 虚拟地址 │         │ 物理页面 │           │ 虚拟地址 │
   │ 0x7f... │ ──────► │  (共享)  │ ◄──────── │ 0x7a... │
   └─────────┘         └─────────┘           └─────────┘
        │                                         │
   直接写内存                                  直接读内存
   (零系统调用)                              (零系统调用)

关键点:

  • 写入进程和读取进程 mmap 同一个文件,内核将它们映射到同一块物理内存
  • 写入方只需要往自己的虚拟地址写数据,读取方立刻就能从自己的虚拟地址看到------不需要 socket、pipe、消息队列等任何 IPC 机制
  • 这就是为什么 param get 能做到零系统调用、极速读取

4.2 前置条件:共享内存文件从哪来

demo 中的共享内存文件不是凭空就存在的,需要写入端主动创建。不同系统上的 tmpfs 挂载点不同,下面分别说明。

OpenHarmony 与 Linux 的 /dev 根本不同

对比项 OpenHarmony 设备 标准 Linux(Ubuntu 等)
/dev 文件系统类型 tmpfs(内存文件系统) udev / devtmpfs(设备管理器管理)
/dev 能否随意创建文件 可以(它就是一块内存) 不可以(由 udev 管理,不应放自定义文件)
/dev/shm 是否存在 不存在 存在,是专门给用户用的 tmpfs
/tmp 文件系统类型 tmpfs 取决于发行版,可能是磁盘分区,也可能是 tmpfs

通过 df -h 可以直观看到差异:

bash 复制代码
# === OpenHarmony 设备 ===
# df -h
tmpfs    3.6G  432K  3.6G   1% /dev           ← /dev 是 tmpfs,param 用这里
# (没有 /dev/shm 这个挂载点)

# === Linux 服务器(Ubuntu) ===
$ df -h
udev      16G     0   16G   0% /dev           ← /dev 是 udev,不是 tmpfs!
tmpfs     16G     0   16G   0% /dev/shm       ← /dev/shm 才是 tmpfs

因此:

  • OpenHarmony 的 param 直接在 /dev/ 下创建 /dev/__parameters__/ 目录
  • Linux PC 上要做 mmap 共享内存 demo,应该用 /dev/shm/(保证是 tmpfs,纯内存)

demo 代码默认使用 /dev/shm/__my_params__/ 路径,这样在 Linux PC 上无需 root 即可运行,且保证数据在内存中。如需在 OpenHarmony 设备上运行,改为 /dev/__my_params__/ 即可。

创建文件的完整步骤

复制代码
第一步:mkdir 创建目录(目录不会自动存在)
第二步:open(O_CREAT) 在目录下创建文件
第三步:ftruncate 扩展文件到目标大小(新文件为 0 字节,不扩展 mmap 会 SIGBUS)
第四步:mmap 映射为共享内存

对应到代码:

c 复制代码
// Linux PC 上(/dev/shm 是 tmpfs,普通用户即可)
mkdir("/dev/shm/__my_params__", 0755);
int fd = open("/dev/shm/__my_params__/param_store", O_RDWR | O_CREAT | O_TRUNC, 0644);

// OpenHarmony 设备上(/dev 本身就是 tmpfs,需要 root)
mkdir("/dev/__my_params__", 0755);
int fd = open("/dev/__my_params__/param_store", O_RDWR | O_CREAT | O_TRUNC, 0644);

// 两者后续操作完全一样------mmap 不关心文件在哪个目录,只要底层是 tmpfs
ftruncate(fd, 4096);
void *addr = mmap(NULL, 4096, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);

完整的文件生命周期

复制代码
writer 启动 → mkdir 创建目录 → open(O_CREAT) 创建文件 → ftruncate 扩展到 4KB
            → mmap 映射为共享内存 → 写入数据

reader 启动 → open(O_RDONLY) 打开同一个文件(此时文件已存在)
            → mmap 只读映射 → 直接从内存读取

系统重启 → tmpfs 被重新挂载,所有文件全部消失

与 OpenHarmony 的对应 :真实系统中,init 进程在启动早期调用 InitParamWorkSpace()OpenWorkSpace()mkdir("/dev/__parameters__") + open(O_CREAT) + ftruncate() + mmap() 创建各个 workspace 文件。其他进程启动时,这些文件已经存在,只需 open(O_RDONLY) + mmap(PROT_READ) 即可。

4.3 写入端 demo(param_writer.c)

c 复制代码
/**
 * param_writer.c --- 模拟 OpenHarmony init 进程的参数写入端
 *
 * 编译: gcc param_writer.c -o param_writer
 * 运行: ./param_writer
 *
 * 默认路径 /dev/shm/(Linux PC 的 tmpfs),可直接运行。
 * 若在 OpenHarmony 设备上测试,将 PARAM_DIR 改为 "/dev/__my_params__"
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <errno.h>

/* Linux PC: /dev/shm/__my_params__    OpenHarmony: /dev/__my_params__ */
#define PARAM_DIR  "/dev/shm/__my_params__"
#define PARAM_FILE PARAM_DIR "/param_store"
#define PARAM_SIZE 4096

/* 共享内存头部,类比 OpenHarmony 的 ParamTrieHeader */
typedef struct {
    int count;                  /* 当前键值对数量 */
    int data_offset;            /* 数据写入偏移 */
    char data[0];               /* 柔性数组,存储实际数据 */
} ParamHeader;

int main()
{
    /*
     * 第一步:在 tmpfs 上创建存储目录
     *
     * /dev/shm 在 Linux 上是 tmpfs(内存文件系统),
     * 我们在其下创建自己的目录,类比 OpenHarmony init 创建 /dev/__parameters__/
     *
     * mkdir 如果目录已存在会返回 -1 + EEXIST,这是正常的
     */
    if (mkdir(PARAM_DIR, 0755) != 0 && errno != EEXIST) {
        perror("mkdir");
        return 1;
    }
    printf("[writer] 第一步: 目录 %s 已就绪\n", PARAM_DIR);

    /*
     * 第二步:在目录下创建共享内存文件
     *
     * O_CREAT  --- 文件不存在则创建(这是文件诞生的地方)
     * O_TRUNC  --- 文件已存在则清空
     * 0644     --- 权限:owner 读写,其他进程只读
     *
     * 类比 OpenHarmony init 创建 /dev/__parameters__/default_param
     */
    int fd = open(PARAM_FILE, O_RDWR | O_CREAT | O_TRUNC, 0644);
    if (fd < 0) { perror("open"); return 1; }
    printf("[writer] 第二步: 文件 %s 已创建\n", PARAM_FILE);

    /*
     * 第三步:扩展文件到指定大小
     *
     * 新创建的文件大小为 0,必须先 ftruncate 扩展,
     * 否则 mmap 映射后访问会触发 SIGBUS(越界访问)
     */
    ftruncate(fd, PARAM_SIZE);
    printf("[writer] 第三步: 文件已扩展到 %d 字节\n", PARAM_SIZE);

    /* 第四步:mmap 映射为可读写共享内存 */
    void *addr = mmap(NULL, PARAM_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    close(fd);  /* 关闭 fd 后 mmap 映射仍然有效 */
    if (addr == MAP_FAILED) { perror("mmap"); return 1; }
    printf("[writer] 第四步: mmap 映射成功,地址 %p\n", addr);

    /* 第五步:初始化头部 */
    ParamHeader *header = (ParamHeader *)addr;
    header->count = 0;
    header->data_offset = 0;

    /* 第六步:写入 JSON 键值对(模拟 param set) */
    const char *params[] = {
        "const.product.model",
        "{\"name\":\"RK3588\",\"arch\":\"aarch64\",\"cores\":8}",

        "persist.sys.usb.config",
        "{\"mode\":\"hdc\",\"functions\":[\"ffs\",\"adb\"]}",

        "sys.boot.completed",
        "{\"status\":true,\"timestamp\":1719000000}",
        NULL
    };

    char *write_ptr = header->data;
    for (int i = 0; params[i] != NULL; i += 2) {
        const char *key = params[i];
        const char *value = params[i + 1];

        /* 写入格式:key\0value\0(类比 ParamNode 的 data 字段) */
        int key_len = strlen(key);
        int val_len = strlen(value);
        memcpy(write_ptr, key, key_len + 1);
        write_ptr += key_len + 1;
        memcpy(write_ptr, value, val_len + 1);
        write_ptr += val_len + 1;

        header->count++;
        header->data_offset = write_ptr - header->data;

        printf("[writer] 写入: %s = %s\n", key, value);
    }

    printf("[writer] 共写入 %d 个参数,数据区 %d 字节\n",
           header->count, header->data_offset);
    printf("[writer] 共享内存文件: %s\n", PARAM_FILE);
    printf("[writer] 按回车退出(退出前可运行 reader 读取)...\n");
    getchar();

    munmap(addr, PARAM_SIZE);
    return 0;
}

4.4 读取端 demo(param_reader.c)

c 复制代码
/**
 * param_reader.c --- 模拟其他进程的参数读取端
 *
 * 编译: gcc param_reader.c -o param_reader
 * 运行: ./param_reader                       (列出所有参数)
 *       ./param_reader persist.sys.usb.config (查询单个参数)
 *
 * 注意:PARAM_FILE 必须与 writer 使用同一路径
 * 关键点:读取过程完全在用户态完成,不需要任何 IPC
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>

/* 路径必须和 writer 一致(Linux PC 用 /dev/shm/,OpenHarmony 用 /dev/) */
#define PARAM_FILE "/dev/shm/__my_params__/param_store"
#define PARAM_SIZE 4096

typedef struct {
    int count;
    int data_offset;
    char data[0];
} ParamHeader;

/* 在共享内存中查找参数(类比 FindTrieNode + ReadParamValue) */
const char *param_get(const ParamHeader *header, const char *key)
{
    const char *ptr = header->data;
    const char *end = header->data + header->data_offset;
    while (ptr < end) {
        const char *k = ptr;
        ptr += strlen(k) + 1;
        const char *v = ptr;
        ptr += strlen(v) + 1;
        if (strcmp(k, key) == 0) return v;
    }
    return NULL;
}

int main(int argc, char *argv[])
{
    /* 以只读方式打开同一个文件 */
    int fd = open(PARAM_FILE, O_RDONLY);
    if (fd < 0) {
        fprintf(stderr, "打开失败,请先运行 param_writer\n");
        return 1;
    }

    /* 只读 mmap(其他进程不能修改,类比 OpenHarmony 非 init 进程) */
    void *addr = mmap(NULL, PARAM_SIZE, PROT_READ, MAP_SHARED, fd, 0);
    close(fd);
    if (addr == MAP_FAILED) { perror("mmap"); return 1; }

    const ParamHeader *header = (const ParamHeader *)addr;

    if (argc > 1) {
        /* 查询单个参数(类比 param get xxx) */
        const char *value = param_get(header, argv[1]);
        if (value)
            printf("%s = %s\n", argv[1], value);
        else
            printf("参数 \"%s\" 不存在\n", argv[1]);
    } else {
        /* 列出所有参数(类比 param get) */
        printf("共 %d 个参数:\n\n", header->count);
        const char *ptr = header->data;
        const char *end = header->data + header->data_offset;
        while (ptr < end) {
            const char *k = ptr;
            ptr += strlen(k) + 1;
            const char *v = ptr;
            ptr += strlen(v) + 1;
            printf("  %s = %s\n", k, v);
        }
    }

    munmap(addr, PARAM_SIZE);
    return 0;
}

4.5 运行效果

bash 复制代码
# 终端1:启动写入端(模拟 init 进程)
$ gcc param_writer.c -o param_writer && ./param_writer
[writer] 第一步: 目录 /dev/shm/__my_params__ 已就绪
[writer] 第二步: 文件 /dev/shm/__my_params__/param_store 已创建
[writer] 第三步: 文件已扩展到 4096 字节
[writer] 第四步: mmap 映射成功,地址 0x7f8a3c000000
[writer] 写入: const.product.model = {"name":"RK3588","arch":"aarch64","cores":8}
[writer] 写入: persist.sys.usb.config = {"mode":"hdc","functions":["ffs","adb"]}
[writer] 写入: sys.boot.completed = {"status":true,"timestamp":1719000000}
[writer] 共写入 3 个参数,数据区 218 字节
[writer] 共享内存文件: /dev/shm/__my_params__/param_store
[writer] 按回车退出(退出前可运行 reader 读取)...

# 可以用 ls 验证文件确实被创建在 tmpfs 上:
$ ls -la /dev/shm/__my_params__/
-rw-r--r-- 1 root root 4096 Jun 22 11:00 param_store
$ df -h /dev/shm/__my_params__/param_store
tmpfs     16G     0   16G   0% /dev/shm     ← 确认在 tmpfs 上,数据在内存中

# 终端2:启动读取端(模拟其他进程)
$ gcc param_reader.c -o param_reader

$ ./param_reader
共 3 个参数:

  const.product.model = {"name":"RK3588","arch":"aarch64","cores":8}
  persist.sys.usb.config = {"mode":"hdc","functions":["ffs","adb"]}
  sys.boot.completed = {"status":true,"timestamp":1719000000}

$ ./param_reader persist.sys.usb.config
persist.sys.usb.config = {"mode":"hdc","functions":["ffs","adb"]}

$ ./param_reader not.exist
参数 "not.exist" 不存在

在 OpenHarmony 设备上运行 :将 writer 的 PARAM_DIR 和 reader 的 PARAM_FILE 路径改为 /dev/__my_params__(因为 OpenHarmony 上 /dev 就是 tmpfs,没有 /dev/shm),效果完全一致。

4.6 demo 与真实 param 系统的对应关系

demo 中的操作 OpenHarmony 真实实现
mkdir("/dev/shm/__my_params__") mkdir("/dev/__parameters__")(OpenHarmony 的 /dev 是 tmpfs)
open("/dev/shm/__my_params__/param_store") open("/dev/__parameters__/default_param")
`mmap(PROT_READ WRITE, MAP_SHARED)`
mmap(PROT_READ, MAP_SHARED) 其他进程(只读)映射 workspace
memcpy 写入 key\0value\0 WriteParam()AddParam() 写入 Trie 节点
strcmp 遍历查找 key FindTrieNode() 在 Trie 前缀树中按 . 分层查找(更高效)
ParamHeader.count ParamTrieHeader.paramNodeCount
无锁直接读写 commitId + 原子操作实现 lock-free 并发读取

真实系统的优化

  • demo 用线性遍历查找(O(n)),真实系统用 Trie 前缀树 + BST(O(log n))
  • demo 没有并发控制,真实系统用 原子 commitId 实现 lock-free 读取
  • demo 没有权限检查,真实系统有 DAC + SELinux 双重校验
  • demo 没有持久化,真实系统对 persist.* 参数有 1 秒防抖自动落盘
  • demo 没有写入保护,真实系统非 init 进程必须通过 Unix Socket 请求 init 代写

五、存储架构:共享内存 + Trie 前缀树

5.1 整体架构

复制代码
┌──────────────────────────────────────────────────────────────┐
│                    init 进程(参数服务端)                      │
│                                                               │
│  启动 → 读取 .para 文件 → 写入 mmap 共享内存                    │
│       → 创建 Unix Socket 监听写入请求                           │
│       → 加载 persist 参数                                      │
├──────────────────────────────────────────────────────────────┤
│              /dev/__parameters__/ (共享内存文件)               │
│                                                               │
│  ┌─────────────┐  ┌─────────────┐  ┌─────────────────────┐  │
│  │ DAC trie    │  │ default     │  │ persist_param       │  │
│  │ (index 0)   │  │ (index 1)   │  │ (index N)           │  │
│  └─────────────┘  └─────────────┘  └─────────────────────┘  │
│                                                               │
│  每个 workspace 是独立的 mmap 文件,按 SELinux label 分区      │
├──────────────────────────────────────────────────────────────┤
│                    其他进程(参数客户端)                        │
│                                                               │
│  读取 → mmap 只读映射 → Trie 树查找(零系统调用)               │
│  写入 → Unix Socket → init 校验权限后写入共享内存               │
└──────────────────────────────────────────────────────────────┘

5.2 Trie 前缀树结构

参数名按 . 分隔为多级节点,每级使用 child 指针 + 左右 BST 组织同级兄弟:

c 复制代码
// base/startup/init/services/param/include/param_common.h
typedef struct {
    uint32_t left, right, child;   // 同级 BST + 下级 child
    uint32_t labelIndex, dataIndex;
    uint16_t length;
    char key[0];                   // 当前层级的名称段(如 "persist"、"sys")
} ParamTrieNode;

typedef struct {
    ATOMIC_UINT32 commitId;        // 版本号(lock-free 并发控制)
    uint8_t keyLength;
    uint16_t valueLength;
    char data[0];                  // "完整name\0value\0"
} ParamNode;

查找 persist.sys.usb.config 的过程:

复制代码
根节点 → BST 查找 "persist" → child → BST 查找 "sys"
       → child → BST 查找 "usb" → child → BST 查找 "config"
       → dataIndex → ParamNode → 读取 value

5.3 共享内存创建

c 复制代码
// base/startup/init/services/param/linux/param_osadp.c
void *areaAddr = (void *)mmap(NULL, spaceSize, prot, MAP_SHARED, fd, 0);
  • init 进程以读写方式创建:PROT_READ | PROT_WRITE
  • 其他进程以只读方式映射:PROT_READ
  • 文件位于 /dev/__parameters__/<workspace_name>
  • 各 workspace 大小由 ohos.para.size 配置

5.4 读写流程

操作 调用者 路径 是否需要系统调用
读取 任意进程 SystemReadParam()FindTrieNode()ReadParamValue() (纯共享内存)
写入(init) init 进程 SystemWriteParam()WriteParam() → 直接写 trie
写入(其他进程) 非 init 进程 SystemSetParameter() → Unix Socket → init HandleParamSet() (Socket IPC)

六、安全机制:DAC + SELinux 双重控制

6.1 DAC 权限控制

每个参数前缀可在 .para.dac 文件中定义访问权限:

格式参数前缀 = 用户:组:八进制权限

复制代码
// base/startup/init/services/etc/param/ohos.para.dac
ohos.servicectrl.           = system:servicectrl:0775
persist.init.               = root:root:0775
const.product.              = root:root:0775
const.SystemCapability.     = root:root:0775
ohos.boot.sn                = root:deviceprivate:0750

权限位含义 (八进制,注意不是 Unix 的 rwx,而是读/写/watch):

c 复制代码
// base/startup/init/services/param/include/param_security.h
#define DAC_READ  0x0100  // 八进制中的 4 位(r)
#define DAC_WRITE 0x0080  // 八进制中的 2 位(w)
#define DAC_WATCH 0x0040  // 八进制中的 1 位(不是 execute,是 watch)
含义 示例
高位(owner) 属主 uid 的读(4)/写(2)/watch(1) 7 = 读+写+watch
中位(group) 属组 gid 的读(4)/写(2)/watch(1) 7 = 读+写+watch
低位(other) 其他进程的读(4)/写(2)/watch(1) 5 = 读+watch

检查流程param_base.cDacCheckParamPermission()):

复制代码
1. 检查 other 位 → 匹配则放行
2. 检查调用者 uid 是否等于参数 owner uid
3. 检查调用者 gid 是否等于参数 owner gid
4. 检查调用者是否在参数 owner group 的成员列表中

写入请求通过 Socket 到达 init 时,init 使用 SO_PEERCRED 获取调用方的真实 uid/gid 进行校验。

6.2 SELinux 策略控制

在 DAC 之上,还有 SELinux 层面的强制访问控制:

参数上下文映射parameter_contexts):

复制代码
// base/security/selinux_adapter/sepolicy/base/public/parameter_contexts
ohos.servicectrl.           u:object_r:servicectrl_param:s0
const.build.                u:object_r:devinfo_public_param:s0
persist.                    u:object_r:persist_param:s0
sys.                        u:object_r:sys_param:s0
debug.                      u:object_r:debug_param:s0

写入时校验 :init 从 Socket 获取调用方的 SELinux 上下文,调用 selinux_check_access() 校验是否有 parameter_service { set } 权限。

Workspace 隔离 :不同 SELinux label 的参数存储在不同的 mmap 文件中,通过 setfilecon() 设置文件 SELinux 标签。


七、参数设置的三种场景

7.1 场景一:源码中定义 .para 文件(编译时)

这是最常用的方式,在源码中创建 .para 文件,编译后打包到镜像中:

步骤

  1. 创建 .para 文件:
ini 复制代码
# foundation/window/window_manager/etc/wms.para
persist.window.boot.inited=0
persist.sys.default_display.cutout=false
  1. 创建对应的 .para.dac 权限文件:

    foundation/window/window_manager/etc/wms.para.dac

    persist.window. = system:system:0775

  2. BUILD.gn 中声明安装规则:

gn 复制代码
ohos_prebuilt_etc("wms.para") {
  source = "wms.para"
  relative_install_dir = "param"
  part_name = "window_manager"
}

ohos_prebuilt_etc("wms.para.dac") {
  source = "wms.para.dac"
  relative_install_dir = "param"
  part_name = "window_manager"
}
  1. 编译后文件安装到 /system/etc/param/wms.para

产品仓覆盖 :vendor 可以通过同名文件覆盖系统默认值。例如 vendor/kaihong/xxx/etc/param/ohos.para 安装到 sys_prod/etc/param/ohos.para,由于加载顺序,产品仓的值会覆盖系统默认值。

7.2 场景二:init.cfg 中通过 setparam 设置(启动时)

在 init 配置文件中使用 setparam 命令:

json 复制代码
// base/startup/init/services/etc/init.cfg
{
    "name": "boot",
    "cmds": [
        "setparam net.tcp.default_init_rwnd 60",
        "setparam sys.use_memfd false"
    ]
}

也可以使用参数触发器------当某个参数变化时执行命令:

json 复制代码
// base/startup/init/services/etc/init.usb.cfg
{
    "name": "boot && param:persist.sys.usb.config=*",
    "cmds": [
        "setparam sys.usb.config ${persist.sys.usb.config}"
    ]
}
json 复制代码
{
    "name": "param:sys.usb.config=none && param:sys.usb.configfs=0",
    "cmds": ["stop hdcd"]
}

注意 :OpenHarmony 使用 param:key=value 语法(JSON 格式的 init.cfg),触发器写在 "name" 字段中。

7.3 场景三:在运行中的设备上设置(运行时)

通过 Shell 命令

bash 复制代码
# 查看参数
param get persist.sys.usb.config
# 输出: persist.sys.usb.config = hdc

# 设置参数(需要权限)
param set persist.sys.usb.config adb

# 等待参数变为某个值(超时 30s)
param wait sys.boot.completed 1

# 导出所有参数
param dump

# 强制保存 persist 参数到磁盘
param save

通过 C/C++ API

c 复制代码
#include "parameter.h"

// 读取参数
char value[128] = {0};
int ret = GetParameter("persist.sys.usb.config", "default", value, sizeof(value));

// 设置参数
ret = SetParameter("persist.sys.usb.config", "hdc");

// 监听参数变化
ret = WatchParameter("persist.sys.usb.config", OnParamChanged, NULL);

通过 ArkTS/JS API

typescript 复制代码
import systemparameter from '@ohos.systemParameterEnhance';

// 读取
let value = systemparameter.getSync("const.product.model", "unknown");

// 设置(需要系统权限)
systemparameter.setSync("persist.custom.key", "value");

// 监听
systemparameter.on("persist.custom.key", (key: string, value: string) => {
    console.log(`${key} changed to ${value}`);
});

八、启动时参数加载顺序

init 进程启动时按以下顺序加载参数,后加载的同名参数不覆盖先加载的LOAD_PARAM_ONLY_ADD 模式),ohos_const 除外:

复制代码
┌─ 1. LoadSpecialParam()
│   ├── LoadParamAreaSize()         ← ohos.para.size(workspace 内存大小)
│   ├── LoadSelinuxLabel("init")    ← parameter_contexts(SELinux 标签)
│   ├── LoadParamFromCmdLine()      ← /proc/cmdline → ohos.boot.* 参数
│   └── LoadParamFromBuild()        ← 编译时注入(build_type, build_time 等)
│
├─ 2. InitLoadParamFiles()
│   ├── LoadDefaultParams("/system/etc/param/ohos_const", NORMAL)  ← 不可变常量(最高优先级)
│   └── GetCfgFiles("etc/param") **倒序遍历**(LOAD_PARAM_ONLY_ADD,先到先得):
│       ├── /chip_prod/etc/param/*.para       ← 芯片产品参数(最先加载,优先级最高)
│       ├── /sys_prod/etc/param/*.para        ← 系统产品参数(产品仓覆盖)
│       ├── /chipset/etc/param/*.para         ← 芯片厂商参数
│       └── /system/etc/param/*.para          ← 系统默认参数(最后加载,优先级最低)
│
├─ 3. LoadPersistParams()           ← /data/service/el1/startup/parameters/
│   └── 从磁盘读取上次保存的 persist.* 参数
│
└─ 4. StartParamService()           ← 开始监听 Unix Socket 写入请求

Config-policy 覆盖规则

config-policy 默认目录定义(config_policy_impl.h):

c 复制代码
#define DEFAULT_LAYER "/system:/chipset:/sys_prod:/chip_prod"
// paths[0]=/system  paths[1]=/chipset  paths[2]=/sys_prod  paths[3]=/chip_prod

init 加载时倒序遍历for (i = MAX-1; i >= 0; i--)),配合 LOAD_PARAM_ONLY_ADD(先到先得,同名参数不覆盖),所以:

分区 加载顺序 优先级 安装来源
/system/etc/param/ohos_const/ 第 1 步 最高 不可变常量(LOAD_PARAM_NORMAL,const 值写入后不可改)
/chip_prod/etc/param/ 第 2 步 芯片产品定制
/sys_prod/etc/param/ 第 3 步 系统产品定制(vendor 仓)
/chipset/etc/param/ 第 4 步 芯片厂商
/system/etc/param/ 第 5 步 系统默认

注意ohos_const 是独立加载的(使用 LOAD_PARAM_NORMAL 模式),它在 config-policy 遍历之前执行,且 const.* 参数写入后不可被修改。其余目录使用 LOAD_PARAM_ONLY_ADD 模式------先到先得,同名参数只保留第一次写入的值。


九、持久化机制

9.1 哪些参数会持久化

只有 persist.* 前缀的参数会自动保存到磁盘:

c 复制代码
// base/startup/init/services/param/manager/param_persist.c
static int IsNeedToSave(const char *name)
{
    return (strncmp(name, PARAM_PERSIST_PREFIX, strlen(PARAM_PERSIST_PREFIX)) == 0) ? 1 : 0;
}

9.2 持久化存储路径

文件 设备路径
公共持久参数 /data/service/el1/startup/parameters/public_persist_parameters
私有持久参数 /data/service/el1/public/startup/parameters/private_persist_parameters
临时文件 tmp_public_persist_parameterstmp_private_persist_parameters

文件格式为纯文本 name=value 逐行存储。

9.3 保存时机

采用 1 秒防抖 策略,避免频繁磁盘写入:

复制代码
persist.* 参数被修改
    ↓
距上次保存 > 1秒?
    ├── 是 → 立即批量保存(遍历所有 persist.* 参数,写入临时文件,原子 rename)
    └── 否 → 标记 WORKSPACE_FLAGS_UPDATE,启动 1 秒定时器
              → 定时器到期 → 批量保存

原子写入 :先写到 tmp_* 文件 → fsync()rename() 替换正式文件,保证断电安全。


十、参数变更通知机制

10.1 init 触发器(param trigger)

在 init.cfg 中使用 param:key=value 语法注册触发器,支持 &&||* 通配符:

json 复制代码
{
    "name": "param:sys.usb.config=hdc",
    "cmds": ["start hdcd"]
}

sys.usb.config 被设为 hdc 时,init 自动启动 hdcd 服务。

实现原理

复制代码
参数写入 → CheckAndSendTrigger()
    → PostParamTrigger(EVENT_TRIGGER_PARAM)
    → CheckTrigger(TRIGGER_PARAM) 匹配条件表达式
    → DoTriggerExecute_() 执行 init 命令

10.2 应用层参数监听(WatchParameter)

应用和系统服务可通过 WatchParameter() API 监听参数变化:

复制代码
架构:应用进程 → WatcherManagerKits(SA 代理)→ WatcherManager 系统服务
                                                    ↕ Unix Socket
                                               init 参数服务

通知流程

  1. 应用注册 watcher → IPC 到 WatcherManager SA → Socket 消息到 init
  2. 参数变化 → init 发送 MSG_NOTIFY_PARAM → WatcherManager
  3. WatcherManager 通过 IPC 回调到应用进程

十一、常用 Shell 命令

所有命令通过 begetctl paramparam 调用,实现位于 base/startup/init/services/begetctl/param_cmd.c

命令 功能 底层 API 示例
param get [name] 读取参数 SystemGetParameter() / SystemTraversalParameter() param get const.product.model
param set name value 设置参数 SystemSetParameter() param set persist.sys.usb.config hdc
param wait name [value] [timeout] 等待参数达到期望值 SystemWaitParameter() param wait sys.boot.completed 1 30
param dump [verbose] 导出所有参数详细信息 SystemDumpParameters() param dump
param save 强制保存 persist 参数 SystemSaveParameters() param save
param ls 列出参数及 DAC 权限信息 traversal + DAC audit param ls

常用筛选技巧

bash 复制代码
# 查看所有产品相关参数
param get | grep const.product

# 查看所有持久化参数
param get | grep persist.

# 查看 USB 相关配置
param get | grep usb

# 查看 SysCap 参数数量
param get | grep SystemCapability | wc -l

# 查看系统版本
param get const.ohos.fullname
param get const.product.software.version

十二、实际 .para 文件分布

12.1 系统核心参数文件

源码位置 安装路径 内容
base/startup/init/services/etc/param/ohos.para /system/etc/param/ohos.para 产品基础信息、安全配置、sandbox 开关等
base/startup/init/services/etc/param/ohos_const/ohos.para /system/etc/param/ohos_const/ohos.para 不可变常量:API 版本、安全补丁日期、发布类型
base/startup/init/services/etc/param/ohos.startup.para /system/etc/param/ohos.startup.para 启动默认值:USB 配置、bootevent 开关
base/startup/init/services/etc/param/ohos.para.dac /system/etc/param/ohos.para.dac 核心 DAC 权限规则
base/startup/init/services/etc/param/ohos.para.size /system/etc/param/ohos.para.size 各 workspace 共享内存大小配置

12.2 子系统参数文件(部分)

子系统 .para 文件 典型参数
窗口管理 foundation/window/window_manager/etc/wms.para persist.window.boot.inited
图形 foundation/graphic/graphic_2d/etc/graphic.para persist.sys.graphic.animationscale
音频 foundation/multimedia/audio_framework/.../audio_config.para 音频策略配置
WiFi foundation/communication/wifi/.../wifi.para WiFi 运行时参数
电源 base/powermgr/power_manager/etc/para/powermgr.para 电源管理策略
日志 base/hiviewdfx/hilog/services/hilogd/etc/hilog.para hilog 等级配置
包管理 foundation/bundlemanager/bundle_framework/etc/bms.para 包管理运行参数
USB base/usb/usb_manager/etc/param/usb_service.para USB 服务配置
电话 base/telephony/core_service/.../telephony.para 电话服务参数
位置 base/location/services/etc/param/location.para 定位服务参数
输入法 base/inputmethod/imf/etc/para/inputmethod.para 输入法配置

12.3 产品仓覆盖文件

产品仓通过在 sys_prodchip_prodvendor 分区安装同前缀参数来覆盖系统默认值:

文件 安装分区 作用
vendor/kaihong/xxx/etc/param/ohos.para sys_prod 覆盖产品品牌、制造商等
vendor/kaihong/xxx/etc/param/product_rk3588.para sys_prod 产品标识
vendor/kaihong/xxx/etc/param/hardware_rk3588.para chip_prod 硬件版本
vendor/kaihong/xxx/powermgr/display_manager/display.para sys_prod 亮度范围覆盖
vendor/kaihong/xxx/ril/etc/ic801_modem.para vendor Modem GPIO 配置
vendor/kaihong/xxx/display/mipi/composer_host.para vendor 显示接口配置

十三、完整范例:如何新增一个自定义参数

以新增参数 persist.myservice.debug_level(可读写、持久化)和 const.myservice.version(只读常量)为例,覆盖构建时和运行时两种场景。

13.1 构建时新增(随镜像编译)

适用于需要预置默认值、随系统发布的参数。

第一步:创建 .para 文件

假设你的部件位于 foundation/mysubsystem/myservice/,创建参数文件:

ini 复制代码
# foundation/mysubsystem/myservice/etc/myservice.para
const.myservice.version=1.0.3
persist.myservice.debug_level=0
persist.myservice.enable_log=true
sys.myservice.status=stopped

参数前缀决定行为

  • const.myservice.version --- 只能写入一次,开机后不可修改
  • persist.myservice.* --- 可读写,修改后自动保存到磁盘,重启保留
  • sys.myservice.status --- 可读写,重启后恢复为 .para 文件中的默认值
第二步:创建 .para.dac 权限文件
复制代码
# foundation/mysubsystem/myservice/etc/myservice.para.dac
const.myservice.             = root:root:0755
persist.myservice.           = system:system:0775
sys.myservice.               = system:system:0775

权限说明(注意:param 的 DAC 权限位不是 Unix 的 rwx,而是 读/写/watch):

c 复制代码
// base/startup/init/services/param/include/param_security.h
#define DAC_READ  0x0100  // 八进制中的 4 位
#define DAC_WRITE 0x0080  // 八进制中的 2 位
#define DAC_WATCH 0x0040  // 八进制中的 1 位
  • 0755 --- owner 读+写+watch(7),group 读+watch(5),other 读+watch(5)
  • 0775 --- owner 读+写+watch(7),group 读+写+watch(7),other 读+watch(5)
第三步:在 BUILD.gn 中声明安装
gn 复制代码
# foundation/mysubsystem/myservice/etc/BUILD.gn
import("//build/ohos.gni")

ohos_prebuilt_etc("myservice.para") {
  source = "myservice.para"
  relative_install_dir = "param"       # 安装到 /system/etc/param/myservice.para
  part_name = "myservice"
  subsystem_name = "mysubsystem"
}

ohos_prebuilt_etc("myservice.para.dac") {
  source = "myservice.para.dac"
  relative_install_dir = "param"       # 安装到 /system/etc/param/myservice.para.dac
  part_name = "myservice"
  subsystem_name = "mysubsystem"
}

确保在部件的 bundle.jsonBUILD.gn 的 deps 中引用这两个 target。

第四步:(可选)产品仓覆盖默认值

如果产品需要不同的默认值,在产品仓创建同名参数:

ini 复制代码
# vendor/kaihong/xxx/etc/param/myservice.para
persist.myservice.debug_level=3
persist.myservice.enable_log=false
gn 复制代码
# vendor/kaihong/xxx/etc/BUILD.gn
ohos_prebuilt_etc("myservice.para") {
  source = "./param/myservice.para"
  relative_install_dir = "param"
  install_images = [ sys_prod_base_dir ]   # 安装到 sys_prod 分区,覆盖 system 默认值
  part_name = "product_kaihong"
  subsystem_name = "product_kaihong"
}

由于 init 倒序遍历 config-policy 目录,sys_prod 先于 system 被加载(LOAD_PARAM_ONLY_ADD 模式,先到先得),产品仓的值会生效。

第五步:编译验证
bash 复制代码
./build.sh --product-name rk3588j_isdt-2

# 编译后检查镜像中的文件
ls out/rk3588j_isdt-2/packages/phone/system/etc/param/myservice.para
ls out/rk3588j_isdt-2/packages/phone/system/etc/param/myservice.para.dac
第六步:设备上验证
bash 复制代码
# 烧录镜像后
param get const.myservice.version
# 输出: const.myservice.version = 1.0.3

param get persist.myservice.debug_level
# 输出: persist.myservice.debug_level = 0

# 尝试修改 const 参数(会失败)
param set const.myservice.version 2.0.0
# 输出: Set parameter "const.myservice.version" fail

# 修改 persist 参数(成功,重启保留)
param set persist.myservice.debug_level 3
param get persist.myservice.debug_level
# 输出: persist.myservice.debug_level = 3

13.2 运行时新增(设备上动态设置)

不需要编译,直接在运行中的设备上通过 Shell 或代码设置参数。

方式一:Shell 命令
bash 复制代码
# ---- 永久保存(重启保留)----
# 使用 persist.* 前缀,设置后自动保存到磁盘
param set persist.myapp.config.theme dark
param set persist.myapp.config.language zh-CN

# 验证
param get persist.myapp.config.theme
# 输出: persist.myapp.config.theme = dark

# 重启设备后
reboot
# ... 重启完成后
param get persist.myapp.config.theme
# 输出: persist.myapp.config.theme = dark    ← 仍然存在

# ---- 临时保存(重启丢失)----
# 使用 sys.* 或 debug.* 等非 persist 前缀
param set sys.myapp.debug_mode 1
param set debug.myapp.trace_enabled true

# 验证
param get sys.myapp.debug_mode
# 输出: sys.myapp.debug_mode = 1

# 重启设备后
reboot
# ... 重启完成后
param get sys.myapp.debug_mode
# 输出: Get parameter "sys.myapp.debug_mode" fail    ← 已丢失

注意 :运行时通过 Shell 设置参数可能受 DAC/SELinux 限制。如果没有对应的 .para.dac 规则,写入会被 default_param 的 SELinux 策略拦截。Shell(uid=2000, shell 组)通常只能写入 DAC 规则中 other 位有写权限的参数。

方式二:C/C++ API
c 复制代码
#include "parameter.h"  // base/startup/init/interfaces/innerkits/include/syspara/parameter.h

// =========================================
// 永久保存:使用 persist.* 前缀
// =========================================
int ret = SetParameter("persist.myapp.config.volume", "80");
if (ret == 0) {
    // 设置成功,值已写入共享内存,并在 1 秒内自动保存到磁盘
    // 重启后仍然存在
}

// 读取
char value[128] = {0};
ret = GetParameter("persist.myapp.config.volume", "50", value, sizeof(value));
// value = "80"(如果存在),否则 value = "50"(默认值)

// =========================================
// 临时保存:使用 sys.* / debug.* 等前缀
// =========================================
ret = SetParameter("sys.myapp.runtime.state", "running");
// 写入共享内存,重启后丢失

// =========================================
// 只读常量:const.* 前缀只能设置一次
// =========================================
ret = SetParameter("const.myapp.build_id", "20260622");
// 第一次:成功
ret = SetParameter("const.myapp.build_id", "20260623");
// 第二次:失败,返回错误码(PARAM_CODE_READ_ONLY)

// =========================================
// 监听参数变化
// =========================================
void OnVolumeChanged(const char *key, const char *value, void *context)
{
    printf("参数变化: %s = %s\n", key, value);
}

ret = WatchParameter("persist.myapp.config.volume", OnVolumeChanged, NULL);
方式三:ArkTS API(应用层)
typescript 复制代码
import systemparameter from '@ohos.systemParameterEnhance';

// =========================================
// 永久保存
// =========================================
try {
    systemparameter.setSync("persist.myapp.config.theme", "dark");
    // 成功:值写入共享内存 + 自动持久化到磁盘
} catch (err) {
    console.error(`设置失败: ${err.code} - ${err.message}`);
    // 常见错误:权限不足(需要 ohos.permission.SET_SENSITIVE_PARAMETERS)
}

// 读取(带默认值)
let theme = systemparameter.getSync("persist.myapp.config.theme", "light");

// =========================================
// 临时保存
// =========================================
systemparameter.setSync("sys.myapp.debug", "true");
// 重启后丢失

// =========================================
// 异步读取 + 监听
// =========================================
systemparameter.get("persist.myapp.config.theme", (err, value) => {
    if (!err) {
        console.log(`当前主题: ${value}`);
    }
});

systemparameter.on("persist.myapp.config.theme", (key: string, value: string) => {
    console.log(`主题变更为: ${value}`);
});

13.3 三种保存方式总结

场景 前缀 操作方式 生命周期 存储位置
构建时预置 任意 .para 文件 + 编译 随镜像永久存在 /system/etc/param/*.para
运行时永久 persist.* param set / SetParameter() 重启保留,恢复出厂清除 /data/service/el1/startup/parameters/
运行时临时 sys.* / debug.* param set / SetParameter() 重启丢失 仅在共享内存中
运行时常量 const.* SetParameter()(仅一次) 本次开机内不可变 仅在共享内存中

十四、关键源码文件索引

核心框架

文件 角色
base/startup/init/services/param/linux/param_service.c 参数服务端:Socket 监听、写入处理、触发器分发
base/startup/init/services/param/linux/param_request.c 参数客户端:Socket 连接、mmap 初始化
base/startup/init/services/param/linux/param_osadp.c OS 适配层:mmap 创建/映射
base/startup/init/services/param/manager/param_manager.c 核心管理器:SystemReadParamWriteParam、DAC 检查
base/startup/init/services/param/manager/param_server.c 服务端加载:LoadDefaultParams、解析 .para 文件
base/startup/init/services/param/manager/param_persist.c 持久化管理:定时保存、批量写入
base/startup/init/services/param/base/param_trie.c Trie 树操作:FindTrieNodeAddTrieNode
base/startup/init/services/param/base/param_base.c DAC 权限检查:DacCheckParamPermission

安全控制

文件 角色
base/startup/init/services/param/adapter/param_dac.c DAC 标签加载(解析 .para.dac
base/startup/init/services/param/adapter/param_selinux.c SELinux 集成:workspace 标签、写入校验
base/security/selinux_adapter/sepolicy/base/public/parameter_contexts SELinux 参数上下文映射

监听与触发

文件 角色
base/startup/init/services/param/trigger/trigger_processor.c 参数触发器:条件匹配、命令执行
base/startup/init/services/param/watcher/agent/watcher_manager_kits.cpp 客户端 WatchParameter() 实现
base/startup/init/services/param/watcher/proxy/watcher_manager.cpp WatcherManager SA 服务端

命令行工具

文件 角色
base/startup/init/services/begetctl/param_cmd.c param get/set/wait/dump/save/ls 实现

API 头文件

文件 角色
base/startup/init/interfaces/innerkits/include/param/init_param.h C 内部 API(SystemReadParamSystemSetParameter 等)
base/startup/init/interfaces/innerkits/include/syspara/parameter.h C 对外 API(GetParameterSetParameterWatchParameter