当前位置: 首页 > ds >正文

STM32的 syscalls.c 和 sysmem.c

syscalls.c 是 STM32CubeIDE 自动生成的标准系统调用适配文件,用于裸机环境下支持 newlib 标准库(如 printf, scanf, malloc)的运行。这份文件提供了标准库运行所需的最小系统调用实现。现在我来逐段解析其作用,并补充你可能需要修改或关注的地方。


🧩 主要用途

该文件让 标准C库函数 在没有操作系统的 STM32 环境中可以正常工作。特别是:

  • printf() 调用 _write()(需要重定向到串口)
  • scanf() 调用 _read()(需要接收串口输入)
  • malloc() 依赖 _sbrk()(位于另一个文件 sysmem.c 中)

📄 文件结构解析

🔹 引入的头文件

#include <sys/stat.h>
#include <stdlib.h>
#include <errno.h>
#include <stdio.h>
#include <signal.h>
#include <time.h>
#include <sys/time.h>
#include <sys/times.h>

用于提供标准类型定义和错误码,支持不同系统调用的原型和实现。


🔹 环境变量(通常忽略)

char *__env[1] = { 0 };
char **environ = __env;

🔹 核心系统调用函数(重点)

1. _write(): 用于 printf
__attribute__((weak)) int _write(int file, char *ptr, int len)
{(void)file;for (int i = 0; i < len; i++)__io_putchar(*ptr++);return len;
}

⚠️ __io_putchar()弱函数(weak),你需要在项目中自己实现这个函数,常用于串口重定向:

int __io_putchar(int ch)
{HAL_UART_Transmit(&huart2, (uint8_t *)&ch, 1, HAL_MAX_DELAY);return ch;
}
2. _read(): 用于 scanf
__attribute__((weak)) int _read(int file, char *ptr, int len)
{for (int i = 0; i < len; i++)*ptr++ = __io_getchar();return len;
}

你也需实现 __io_getchar(),如:

int __io_getchar(void)
{uint8_t ch;HAL_UART_Receive(&huart2, &ch, 1, HAL_MAX_DELAY);return ch;
}

🔹 其他系统调用(通常不使用,但需要实现以避免链接错误)

这些函数都是 dummy 实现(返回错误码),用于满足编译要求:

函数用途/说明
_exit()程序退出,死循环防止退出
_kill()假设无信号机制,返回错误
_getpid()返回 PID 1,占位
_close()文件关闭,占位
_fstat()标识为字符设备
_isatty()判断文件是否为终端,返回1
_lseek()文件定位,占位
_open()打开文件,占位
_unlink()删除文件,占位
_fork()无进程管理,占位
_execve()不支持执行程序
_wait()无子进程支持,占位
_times()占位
_link()占位
_stat()占位

✅ 如何启用 UART 输出功能?

  1. 确保开启 UART(比如 USART2)并启用 HAL_UART_Transmit
  2. 添加以下函数实现(在 main.c 或独立 retarget.c 文件)
int __io_putchar(int ch)
{HAL_UART_Transmit(&huart2, (uint8_t *)&ch, 1, HAL_MAX_DELAY);return ch;
}int __io_getchar(void)
{uint8_t ch;HAL_UART_Receive(&huart2, &ch, 1, HAL_MAX_DELAY);return ch;
}

📝 如果你想改为 fputc() 重定向,也可以用以下方式:

int fputc(int ch, FILE *f)
{HAL_UART_Transmit(&huart2, (uint8_t *)&ch, 1, HAL_MAX_DELAY);return ch;
}

不过 __io_putchar 更适合与 CubeIDE 生成的 syscalls.c 搭配使用。


🔚 总结

方面内容
是否需要改动?如果你要使用 printf/scanf,是的!
推荐修改点实现 __io_putchar__io_getchar
使用 HAL_UART 示例?是,可用于 STM32 串口输出

你提供的 sysmem.c 是 STM32CubeIDE 自动生成的内存分配支持文件,主要目的是实现标准 C 库 malloc 系列函数所依赖的 _sbrk() 系统调用。

这是嵌入式裸机编程中用于支持动态内存分配的 标准方法


🔍 核心作用:实现 _sbrk()

_sbrk(ptrdiff_t incr) 说明

该函数负责从 RAM 中划出堆空间。

void *_sbrk(ptrdiff_t incr)
  • incr: 要增加的堆大小(单位:字节)
  • 返回值: 成功时返回旧的堆尾地址,失败返回 (void *) -1 并设置 errno = ENOMEM

🧠 工作原理

它通过链接脚本中的符号 _end, _estack, _Min_Stack_Size 来判断内存边界:

 RAM 起始地址 → [.data][.bss][heap → → →] ... [stack] ← ← ← ← ← _estack(RAM末端)↑       ↑_end   _estack
  • __sbrk_heap_end:当前堆的“末尾指针”
  • _end:链接脚本中定义的 静态数据段结束地址(堆的起始)
  • _estack:RAM 顶部(栈的起点)
  • _Min_Stack_Size:为主堆栈指针(MSP)保留的空间,避免栈和堆冲突

⚠️ 如果你用的是默认 STM32CubeIDE 生成的 .ld 链接脚本,这些符号是自动生成的,你无需手动添加。


🧩 链接脚本中通常有以下内容(例如):

  ._user_heap_stack :{. = ALIGN(8);PROVIDE ( end = . );PROVIDE ( _end = . );. = . + _Min_Heap_Size;. = . + _Min_Stack_Size;. = ALIGN(8);} >RAM

⚠️ 如果你使用 malloc() 报错或崩溃…

  1. 确保链接器脚本中定义了 _end, _estack, _Min_Stack_Size

  2. 确保在 sysmem.c_sbrk() 没有超出 max_heap

  3. 调整堆栈大小:STM32CubeIDE 中修改:

    Project -> Properties -> C/C++ Build -> Settings -> Tool Settings -> MCU Settings
    
    • 增大 Heap size
    • 调整 Stack size

✅ 示例:在 STM32 中使用 malloc()

#include <stdlib.h>char *buf = malloc(100);
if (!buf) {// 分配失败,内存不足
}

该调用会通过 malloc()sbrk() 来从 RAM 中申请空间


📝 总结

项目内容
文件作用支持 malloc 的底层实现
关键函数_sbrk()
内存边界判断依据_end, _estack, _Min_Stack_Size
错误处理堆超出栈保留区时返回 ENOMEM

Makefile 中用于链接(linking)的部分配置,主要用来指导 GCC 如何链接目标文件、库文件和链接脚本。

我们来逐项解释这几行内容。


🔍 内容逐行解析

# libraries
LIBS = -lc -lm -lnosys 
  • -lc:链接标准 C 库(libc)
  • -lm:链接数学库(libm),例如支持 sin, cos, sqrt
  • -lnosys:链接 libnosys.a,这是一个提供最小系统调用桩函数的库(避免链接错误)
LIBDIR = 
  • 没设置额外的库搜索目录
LDFLAGS = $(MCU) -specs=nano.specs -T$(LDSCRIPT) $(LIBDIR) $(LIBS) \-Wl,-Map=$(BUILD_DIR)/$(TARGET).map,--cref \-Wl,--gc-sections

这是构造链接参数的完整语句,分解如下:

参数说明
$(MCU)MCU 相关的 GCC 参数(如 -mcpu=cortex-m4 -mthumb
-specs=nano.specs使用轻量级 nano newlib(newlib-nano),适合嵌入式系统
-T$(LDSCRIPT)指定链接脚本路径,例如 stm32f4xx.ld
$(LIBDIR)库目录参数(未定义时留空)
$(LIBS)链接所需的库,如 -lc, -lm, -lnosys
-Wl,-Map=...ld 生成 .map 文件,帮助查看内存布局和符号
--cref.map 文件中启用交叉引用
--gc-sections丢弃未使用的段,减小最终二进制体积

🔧 举例:使用这些参数生成固件

假设你运行 arm-none-eabi-gcc 手动链接:

arm-none-eabi-gcc main.o startup_stm32f4xx.o \-mcpu=cortex-m4 -mthumb \-specs=nano.specs \-Tstm32f4xx.ld \-lc -lm -lnosys \-Wl,-Map=output.map,--cref \-Wl,--gc-sections \-o firmware.elf

✅ 关于 -lnosys 的补充

  • libnosys.anewlib 提供的最小系统调用实现库。
  • 它中断了 exit(), _kill(), _getpid() 等函数的调用链。
  • 通常你会通过 syscalls.c 或自己实现 _write(), _sbrk() 来覆盖它。

✅ 关于 -specs=nano.specs

  • 它会让 gcc 使用 libc_nano.alibg_nano.a
  • 这是为嵌入式优化的版本,更小但功能有限,适用于 STM32、ARM Cortex-M 等资源受限环境。

🧠 提示

如果你想使用 printf() 的浮点功能(在 nano.specs 下默认关闭),你需要额外加:

-Wl,-u,_printf_float

或在 LDFLAGS 中加:

LDFLAGS += -Wl,-u,_printf_float

✅ 总结

用途
-lnosys提供默认的空系统调用,避免链接错误(可被用户定义的 _write 等函数替代)
-specs=nano.specs使用轻量 newlib 版本,适合嵌入式
-T$(LDSCRIPT)指定 .ld 链接脚本路径
-Wl,--gc-sections去掉未用段,减小大小
-Map=...生成 map 文件,调试内存和符号使用情况

USE_NEWLIB_REENTRANT(或在 FreeRTOSConfig.h 中叫 configUSE_NEWLIB_REENTRANT)是一个和 FreeRTOS + newlib 标准 C 库 集成相关的重要配置项,确实需要谨慎使用。


🧠 什么是 USE_NEWLIB_REENTRANT

简单说:

它表示是否为每个 FreeRTOS 任务分配一个 独立的 newlib 重入结构体 struct _reent,以支持 线程安全的标准 C 函数(如 printf, malloc 等)


🔧 背景:newlib 是单线程库

标准的 newlib(ARM GCC 默认使用的 C 标准库)不是线程安全的。比如:

  • 全局变量 errno
  • 全局堆指针
  • malloc() / free()

如果多任务同时调用这些函数,可能发生内存损坏或数据错乱


✅ 设置 configUSE_NEWLIB_REENTRANT = 1 的作用

启用后:

  • FreeRTOS 会为每个任务分配一个独立的 struct _reent
  • 标准库中的函数会使用当前任务的 reent 结构
  • 保证 malloc()errnoprintf() 等的线程安全性

⚠️ 警告与注意事项

❗ 1. 你必须使用 newlib-nanonewlib

启用 configUSE_NEWLIB_REENTRANT = 1 的前提是你项目确实在使用 newlib,否则会报错:

cannot open source file "reent.h"

如果你没有用 -specs=nano.specs-lc 之类的 newlib 库,启用这个配置毫无意义还会出错。


❗ 2. 你必须提供必要的系统调用(_sbrk, _write, 等)

CubeMX 会生成 syscalls.c/sysmem.c,你必须保留它们。

否则 malloc()printf() 就无法工作。


❗ 3. 你可能还需要实现 __malloc_lock()__malloc_unlock()

如果你使用的是 full newlib(不是 nano 版),malloc 默认是非线程安全的,除非你实现:

void __malloc_lock(struct _reent *r) {taskENTER_CRITICAL(); // 或者使用 mutex
}void __malloc_unlock(struct _reent *r) {taskEXIT_CRITICAL();
}

🔍 启用方式

FreeRTOSConfig.h 中加上:

#define configUSE_NEWLIB_REENTRANT 1

然后确保:

  • 编译器链接了 newlib/newlib-nano(一般 -lc -lrdimon -lnosys
  • syscalls.csysmem.c
  • 编译器路径中包含了 reent.h(通常自动包含)

❌ 不建议随意启用的原因

官方警告你很清楚:

⚠️ “FreeRTOS does not use newlib itself. If you enable reentrancy, you are responsible for making it work safely.”

启用后:

  • 系统内存占用增加(每个任务增加 struct _reent
  • 锁管理交给你自己实现
  • 如果你不理解 mallocerrno 的底层行为,很容易出错

✅ 总结

配置项作用
configUSE_NEWLIB_REENTRANT = 1每个任务都有独立 reent 结构,支持线程安全 newlib
启用前需满足说明
使用 newlib 库链接 -lc(默认是)或 -specs=nano.specs
包含 reent.h 头文件通常自动包含于 newlib
提供系统调用(如 _sbrk()用于 malloc
了解线程安全需求特别是 __malloc_lock()

当你在 STM32CubeMX 中启用了 FreeRTOS,它可能会在“Project Report”或生成代码时提示你:

提示:configUSE_NEWLIB_REENTRANT is enabled. You must ensure newlib is thread-safe and syscalls are implemented properly.


这是一个 重要但非致命 的提示,意思是你需要为启用 configUSE_NEWLIB_REENTRANT = 1额外准备工作。下面是详细解释和应对方法:


🔍 提示含义详解

当你在 CubeMX 中启用

Use Newlib reentrant

它就会在生成的 FreeRTOSConfig.h 中添加:

#define configUSE_NEWLIB_REENTRANT 1

并提示你:你必须确保系统环境满足 Newlib 多线程安全运行的条件,否则可能引发运行时错误。


✅ 要做的准备(否则会踩坑)

条件说明
✅ 使用 newlib 标准版 而非 nanonano 是精简版,不完整支持线程安全结构
✅ 正确实现了 syscalls,如 _sbrk(), _write()否则 malloc()printf() 等会失效
✅ 每个任务堆栈空间足够(通常 ≥ 256 字节)否则 newlib 的 _reent 结构体无法分配
heap_3.c 要求线程锁保护 malloc()推荐改用 heap_useNewlib.cheap_4.c

🛠 关键实现步骤

① 项目设置中使用 full newlib

在 STM32CubeIDE 中:

  • Project > Properties > C/C++ Build > Settings
  • 进入 Tool Settings > MCU GCC Linker > Libraries
  • ❌ 取消勾选 Use newlib-nano (--specs=nano.specs)
  • ✔️ 保留或添加 --specs=nosys.specs

② 实现系统调用 syscalls.c(如果还没有)

你需要提供如下函数,至少要包括:

void *_sbrk(ptrdiff_t incr);     // 用于 malloc 内存扩展
int _write(int file, char *ptr, int len);  // 用于 printf 输出

✅ 示例 _sbrk() 实现:

extern char _end; // Defined in linker script
static char *heap_end;void *_sbrk(ptrdiff_t incr) {char *prev_heap_end;if (heap_end == 0)heap_end = &_end;prev_heap_end = heap_end;heap_end += incr;return (void *) prev_heap_end;
}

③ 使用线程安全的 malloc() 方案(可选)

如果你使用 heap_3.c(基于标准 malloc),你需要启用锁支持,否则多个任务调用会冲突。

✅ 更好的方式:

  • 使用 heap_useNewlib.c(基于 Dave Nadler 的线程安全 malloc)
  • 或使用 heap_4.c + pvPortMalloc() 替代系统 malloc

✅ 示例配置(FreeRTOSConfig.h)

#define configUSE_NEWLIB_REENTRANT 1
#define configTOTAL_HEAP_SIZE     (10 * 1024)

✅ 示例任务(带 printf)

void TaskPrint(void *pvParameters) {while (1) {printf("Hello from task %s\n", pcTaskGetName(NULL));vTaskDelay(pdMS_TO_TICKS(1000));}
}

✅ 如何验证配置正确

测试正常现象
printf() 在多个任务中输出没有乱码、不会崩溃
malloc() 在任务中使用分配正常、无崩溃
errno 在任务间隔离每个任务 errno 不冲突

📌 总结

这条提示的真正含义是:

启用了线程安全支持后,你必须确保 newlib 运行环境完整、堆/栈足够、syscalls 正确、malloc 线程安全,否则系统将不可预期。


当启用configUSE_NEWLIB_REENTRANT后,每个任务的栈空间需求会显著增加,因为需要为每个任务分配独立的C库上下文。

栈空间需求

基本推荐值

// 最小栈大小建议
#define configMINIMAL_STACK_SIZE    ((unsigned short)512)  // 2KB (512 * 4字节)// 实际任务栈大小建议
xTaskCreate(TaskFunction, "TaskName", 1024,        // 4KB栈空间NULL, Priority, &TaskHandle);

不同使用场景的栈大小

1. 简单任务(只有基本操作)

#define SIMPLE_TASK_STACK_SIZE    512   // 2KB

2. 使用printf/sprintf的任务

#define PRINTF_TASK_STACK_SIZE    1024  // 4KB

3. 使用malloc/free的任务

#define MALLOC_TASK_STACK_SIZE    1024  // 4KB

4. 使用文件系统操作的任务

#define FILE_TASK_STACK_SIZE      2048  // 8KB

栈空间增加的原因

启用configUSE_NEWLIB_REENTRANT后增加的开销包括:

  1. struct _reent结构体:约400-600字节
  2. I/O缓冲区:printf等函数的缓冲区
  3. malloc堆管理:每个任务的堆状态信息
  4. 错误处理:errno等错误状态

实际测试建议

// 栈使用情况检查
void vTaskStackCheck(void)
{UBaseType_t uxHighWaterMark;// 获取任务剩余栈空间uxHighWaterMark = uxTaskGetStackHighWaterMark(NULL);printf("Stack remaining: %d words\n", uxHighWaterMark);
}

优化建议

1. 根据实际需求调整

// 不同任务使用不同栈大小
xTaskCreate(SimpleTask, "Simple", 512, NULL, 1, NULL);    // 2KB
xTaskCreate(PrintfTask, "Printf", 1024, NULL, 1, NULL);   // 4KB
xTaskCreate(FileTask, "File", 2048, NULL, 1, NULL);       // 8KB

2. 使用栈监控

// 在FreeRTOSConfig.h中启用
#define configCHECK_FOR_STACK_OVERFLOW  2

3. 考虑关闭可重入支持

如果RAM紧张,可以考虑:

#define configUSE_NEWLIB_REENTRANT 0

然后使用互斥锁保护共享资源。

总结

  • 最小推荐:512 words (2KB)
  • 安全推荐:1024 words (4KB)
  • 重度使用:2048 words (8KB)

建议从4KB开始,然后根据实际的栈使用情况进行调整。记住要定期检查栈的高水位标记,确保没有栈溢出的风险。

http://www.xdnf.cn/news/15000.html

相关文章:

  • 《C++初阶之类和对象》【经典案例:日期类】
  • RESTful API 安装使用教程
  • 黑马python(二十五)
  • Spring Boot + 本地部署大模型实现:优化与性能提升
  • 基于Linux的Spark本地模式环境搭建实验指南
  • 【github】想fork的项目变为私有副本
  • 项目介绍:LangGPT
  • Android View的绘制原理详解
  • 使用reactor-rabbitmq库监听Rabbitmq
  • Python 量化交易安装使用教程
  • opencv的颜色通道问题 rgb bgr
  • 如何查看自己电脑的CUDA版本?
  • 【深度解析】Seedance 1.0:重新定义 AI 视频生成的工业级标准
  • 《Java修仙传:从凡胎到码帝》第三章:缩进之劫与函数峰试炼
  • python脚本编程:使用BeautifulSoup爬虫库获取热门单机游戏排行榜
  • PHP从字符串到数值的类型转换
  • 三、jenkins使用tomcat部署项目
  • 服务器间接口安全问题的全面分析
  • 模拟热血三国内城安置建筑物
  • 【wps】 excel 删除重复项
  • 【Spring Boot】HikariCP 连接池 YAML 配置详解
  • Tomcat镜像实战:掌握Dockerfile的编写以及发布项目
  • day47-tomcat
  • 《Spring 中上下文传递的那些事儿》Part 4:分布式链路追踪 —— Sleuth + Zipkin 实践
  • Python 闭包(Closure)实战总结
  • 【PyCharm 2025.1.2配置debug】
  • 分类树查询性能优化:从 2 秒到 0.1 秒的技术蜕变之路
  • 低代码实战训练营教学大纲 (10天)
  • [特殊字符] 电子机械制动(EMB)产业全景分析:从技术演进到千亿市场爆发
  • 网络编程学习路线图