系统调用(System Call)是用户模式(User Mode)应用程序访问内核服务的机制。Zephyr 支持用户模式,提供了内存保护和权限隔离。
┌─────────────────────────┐
│ 应用程序 │
├─────────────────────────┤
│ 内核 │ ← 全部运行在特权模式
├─────────────────────────┤
│ 硬件 │
└─────────────────────────┘
问题:
- 应用崩溃可能影响整个系统
- 没有内存保护
- 第三方代码不安全
┌─────────────────────────┐
│ 应用 (用户模式) │ ← 受限访问
├─────────────────────────┤
│ 系统调用接口 │
├─────────────────────────┤
│ 内核 (特权模式) │ ← 完整访问
├─────────────────────────┤
│ 硬件 │
└─────────────────────────┘
优势:
- 应用崩溃不会导致系统崩溃
- 内存隔离,防止越界访问
- 可以安全运行第三方代码
应用程序
│
▼
系统调用宏 (K_SYSCALL_xxx) ────┐
│ │
▼ │
特权提升 (SVC/ECALL) │
│ │
▼ │
内核系统调用处理 │
│ │
▼ │
实际的内核函数实现 ◄─────────────┘
Zephyr 中所有内核服务都有两个版本:
| 名称 | 位置 | 权限 |
|---|---|---|
k_mutex_lock() | 用户模式 | 通过系统调用进入内核 |
z_impl_k_mutex_lock() | 内核模式 | 实际实现 |
CONFIG_ARCH_HAS_USERSPACE=y # 架构支持
CONFIG_USERSPACE=y # 启用用户模式
CONFIG_MPU_STACK_GUARD=y # 栈保护
CONFIG_MAX_THREAD_BYTES=4 # 最大线程数
#include <zephyr/kernel.h>
#include <zephyr/app_memory/app_memdomain.h>
/* 定义用户线程 */
void user_thread_entry(void *p1, void *p2, void *p3)
{
/* 用户模式代码 */
struct k_mutex mutex;
/* 系统调用:初始化互斥锁 */
k_mutex_init(&mutex);
/* 系统调用:锁定 */
k_mutex_lock(&mutex, K_FOREVER);
/* 用户代码 */
printk("In user mode\n");
/* 系统调用:解锁 */
k_mutex_unlock(&mutex);
}
/* 内核线程:创建用户线程 */
void kernel_thread_entry(void)
{
/* 创建用户模式线程 */
k_tid_t tid = k_thread_create(&user_thread, user_stack,
K_THREAD_STACK_SIZEOF(user_stack),
user_thread_entry,
NULL, NULL, NULL,
K_PRIO_PREEMPT(10),
K_USER, /* 用户模式标志 */
K_NO_WAIT);
}
内存域定义了用户线程可以访问的内存区域。每个用户线程都属于一个内存域。
┌─────────────────────────────┐
│ 内存域 A │
│ ┌─────┐ ┌─────┐ ┌─────┐ │
│ │代码 │ │数据 │ │堆栈 │ │ ← 线程1、线程2可访问
│ └─────┘ └─────┘ └─────┘ │
└─────────────────────────────┘
┌─────────────────────────────┐
│ 内存域 B │
│ ┌─────┐ ┌─────┐ ┌─────┐ │
│ │代码 │ │数据 │ │堆栈 │ │ ← 线程3、线程4可访问
│ └─────┘ └─────┘ └─────┘ │
└─────────────────────────────┘
/* 定义内存分区 */
struct k_mem_partition app_partitions[] = {
/* 代码段:只读执行 */
{.start = (uint32_t)&_app_text_start,
.size = (uint32_t)&_app_text_size,
.attr = K_MEM_PARTITION_P_RX_U_RX},
/* 数据段:读写 */
{.start = (uint32_t)&_app_data_start,
.size = (uint32_t)&_app_data_size,
.attr = K_MEM_PARTITION_P_RW_U_RW},
/* 共享内存:只读 */
{.start = (uint32_t)&_shared_region_start,
.size = (uint32_t)&_shared_region_size,
.attr = K_MEM_PARTITION_P_RO_U_RO},
};
/* 初始化内存域 */
struct k_mem_domain app_domain;
int ret = k_mem_domain_init(&app_domain, 3, app_partitions);
/* 添加线程到内存域 */
k_mem_domain_add_thread(&app_domain, user_thread_id);
/* 权限属性 */
K_MEM_PARTITION_P_RWX_U_RWX /* 特权和用户都:读执行写 */
K_MEM_PARTITION_P_RWX_U_RX /* 特权:读写执行,用户:读执行 */
K_MEM_PARTITION_P_RWX_U_RO /* 特权:读写执行,用户:只读 */
K_MEM_PARTITION_P_RWX_U_NA /* 特权:读写执行,用户:无访问 */
K_MEM_PARTITION_P_RX_U_RX /* 特权和用户都:读执行 */
K_MEM_PARTITION_P_RO_U_RO /* 特权和用户都:只读 */
Zephyr 提供宏来自动管理应用内存:
#include <zephyr/app_memory/app_memdomain.h>
/* 定义应用内存域 */
K_APPMEM_PARTITION_DEFINE(my_partition);
/* 在分区中定义变量 */
K_APP_BMEM(my_partition) static uint8_t buffer[256];
K_APP_DMEM(my_partition) static int counter;
/* 初始化应用内存域 */
struct k_mem_domain my_domain;
void init_app_domain(void)
{
struct k_mem_partition *parts[] = {
&my_partition,
};
k_mem_domain_init(&my_domain, 1, parts);
k_mem_domain_add_thread(&my_domain, k_current_get());
}
/* 内核可访问,用户不可直接访问 */
static int kernel_only_var;
/* 用户和内核都可访问 */
K_APP_DMEM(my_partition) int shared_var;
/* 用户模式线程访问 */
void user_thread(void)
{
/* ✅ 允许:访问应用分区变量 */
shared_var = 42;
/* ❌ 错误:访问内核变量会导致崩溃 */
/* kernel_only_var = 42; */ /* 权限错误! */
}
/* 用户传递指针给内核时,内核会验证 */
int z_impl_k_mutex_lock(struct k_mutex *mutex, k_timeout_t timeout)
{
/* 内核自动验证 mutex 是否有效且可访问 */
/* 实现 */
}
/* 从用户空间调用时的包装函数 */
static inline int k_mutex_lock(struct k_mutex *mutex, k_timeout_t timeout)
{
/* 系统调用入口 */
return z_impl_k_mutex_lock(mutex, timeout);
}
/* 内核服务需要验证用户缓冲区 */
int copy_from_user(void *kernel_dst, void *user_src, size_t len)
{
/* 检查用户内存是否可读 */
if (!k_mem_domain_validate(k_current_get(), user_src, len, K_MEM_PERM_RW)) {
return -EACCES;
}
memcpy(kernel_dst, user_src, len);
return 0;
}
int copy_to_user(void *user_dst, void *kernel_src, size_t len)
{
/* 检查用户内存是否可写 */
if (!k_mem_domain_validate(k_current_get(), user_dst, len, K_MEM_PERM_RW)) {
return -EACCES;
}
memcpy(user_dst, kernel_src, len);
return 0;
}
#define K_SYSCALL_CUSTOM_SERVICE 1024
#include <zephyr/syscall.h>
__syscall int custom_service(int arg);
#include <custom_service.h>
/* 实际实现(内核空间) */
int z_impl_custom_service(int arg)
{
/* 参数已验证,安全使用 */
printk("Custom service called with arg=%d\n", arg);
return arg * 2;
}
/* custom_service.h */
#ifndef CUSTOM_SERVICE_H
#define CUSTOM_SERVICE_H
#include <zephyr/kernel.h>
#include <zephyr/syscall.h>
__syscall int configure_device(const char *name, int mode);
#include <syscalls/custom_service.h> /* 自动生成 */
#endif
/* custom_service.c */
#include <custom_service.h>
int z_impl_configure_device(const char *name, int mode)
{
/* 内核自动验证 name 指针 */
printk("Configuring device '%s' with mode %d\n", name, mode);
/* 实际配置逻辑 */
const struct device *dev = device_get_binding(name);
if (!dev) {
return -ENODEV;
}
return device_configure(dev, mode);
}
/* 检查是否在用户模式 */
if (k_is_user_context()) {
printk("Running in user mode\n");
} else {
printk("Running in kernel mode\n");
}
/* 检查地址是否可读 */
if (k_mem_domain_validate(k_current_get(), ptr, len, K_MEM_PERM_RO)) {
/* 安全访问 */
}
/* 检查地址是否可写 */
if (k_mem_domain_validate(k_current_get(), ptr, len, K_MEM_PERM_RW)) {
/* 安全修改 */
}
CONFIG_TRACING=y
CONFIG_TRACING_BACKEND_UART=y
CONFIG_TRACING_SYSCALL=y
/* 加载第三方应用 */
void load_third_party_app(void)
{
/* 创建用户模式线程 */
k_thread_create(&third_party_thread, stack, stack_size,
third_party_entry, NULL, NULL, NULL,
priority, K_USER, K_NO_WAIT);
/* 限制内存访问 */
struct k_mem_partition parts[] = {
/* 只允许访问指定区域 */
{.start = app_start, .size = app_size, .attr = K_MEM_PARTITION_P_RX_U_RX},
{.start = data_start, .size = data_size, .attr = K_MEM_PARTITION_P_RW_U_RW},
};
k_mem_domain_init(&third_party_domain, 2, parts);
k_mem_domain_add_thread(&third_party_domain, third_party_thread_id);
}
/* 应用1域 */
K_APPMEM_PARTITION_DEFINE(app1_partition);
struct k_mem_domain app1_domain;
/* 应用2域 */
K_APPMEM_PARTITION_DEFINE(app2_partition);
struct k_mem_domain app2_domain;
void init_multi_app(void)
{
/* 应用1只能访问自己的内存 */
struct k_mem_partition *app1_parts[] = {
&app1_partition,
&shared_partition, /* 共享内存 */
};
k_mem_domain_init(&app1_domain, 2, app1_parts);
/* 应用2只能访问自己的内存 */
struct k_mem_partition *app2_parts[] = {
&app2_partition,
&shared_partition,
};
k_mem_domain_init(&app2_domain, 2, app2_parts);
}
# 用户模式基础
CONFIG_ARCH_HAS_USERSPACE=y
CONFIG_USERSPACE=y
# 内存保护
CONFIG_MPU_STACK_GUARD=y
CONFIG_STACK_SENTINEL=y
CONFIG_STACK_CANARIES=y
# 内存域
CONFIG_MAX_DOMAIN_PARTITIONS=16
CONFIG_MAX_THREAD_BYTES=4
# 调试
CONFIG_TRACING=y
CONFIG_TRACING_SYSCALL=y
*学习笔记创建时间: 2026-03-19*