完整剖析 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.c 中 DacCheckParamPermission()):
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 文件,编译后打包到镜像中:
步骤:
- 创建
.para文件:
ini
# foundation/window/window_manager/etc/wms.para
persist.window.boot.inited=0
persist.sys.default_display.cutout=false
-
创建对应的
.para.dac权限文件:foundation/window/window_manager/etc/wms.para.dac
persist.window. = system:system:0775
-
在
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"
}
- 编译后文件安装到
/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_parameters、tmp_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 参数服务
通知流程:
- 应用注册 watcher → IPC 到 WatcherManager SA → Socket 消息到 init
- 参数变化 → init 发送
MSG_NOTIFY_PARAM→ WatcherManager - WatcherManager 通过 IPC 回调到应用进程
十一、常用 Shell 命令
所有命令通过 begetctl param 或 param 调用,实现位于 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_prod、chip_prod、vendor 分区安装同前缀参数来覆盖系统默认值:
| 文件 | 安装分区 | 作用 |
|---|---|---|
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.json 或 BUILD.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 |
核心管理器:SystemReadParam、WriteParam、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 树操作:FindTrieNode、AddTrieNode |
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(SystemReadParam、SystemSetParameter 等) |
base/startup/init/interfaces/innerkits/include/syspara/parameter.h |
C 对外 API(GetParameter、SetParameter、WatchParameter) |