返回首页

Zephyr 系统调用学习笔记

概述

系统调用(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);
}

内存域 (Memory Domains)

概念

内存域定义了用户线程可以访问的内存区域。每个用户线程都属于一个内存域。

┌─────────────────────────────┐
│      内存域 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;
}

创建自定义系统调用

步骤

  1. 定义系统调用 ID (syscall_list.h)
#define K_SYSCALL_CUSTOM_SERVICE 1024
  1. 声明系统调用 (custom_service.h)
#include <zephyr/syscall.h>

__syscall int custom_service(int arg);
  1. 实现内核函数 (custom_service.c)
#include <custom_service.h>

/* 实际实现(内核空间) */
int z_impl_custom_service(int arg)
{
    /* 参数已验证,安全使用 */
    printk("Custom service called with arg=%d\n", arg);
    return arg * 2;
}
  1. 生成系统调用表(构建时自动处理)

完整示例

/* 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

性能考虑

系统调用开销

优化建议

  1. 批量操作:减少系统调用次数
  2. 共享内存:使用内存映射减少拷贝
  3. 预分配:用户空间预分配缓冲区

实际应用场景

场景1:安全运行第三方代码

/* 加载第三方应用 */
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);
}

场景2:多应用隔离

/* 应用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*