修复就绪屏障位被消费致FTP永不监听;新增栈可观测(水位探针+静态调用图报告)并补FTP实测
This commit is contained in:
+107
-1
@@ -52,6 +52,10 @@
|
||||
|
||||
/* Private define ------------------------------------------------------------*/
|
||||
/* USER CODE BEGIN PD */
|
||||
/* 栈水位观测节奏:首次报告须晚于 bring-up 深链(mount/mkfs/探针/cfg/syslog),
|
||||
* 之后每周期只判定"是否出现更深峰值",无新峰值不打印(保持串口静默窗口) */
|
||||
#define STACK_PROBE_FIRST_MS 5000u
|
||||
#define STACK_PROBE_PERIOD_MS 2000u
|
||||
/* USER CODE END PD */
|
||||
|
||||
/* Private macro -------------------------------------------------------------*/
|
||||
@@ -121,6 +125,7 @@ const osEventFlagsAttr_t sys_evt_attributes = {
|
||||
/* Private function prototypes -----------------------------------------------*/
|
||||
/* USER CODE BEGIN FunctionPrototypes */
|
||||
void ui_task_func(void *argument);
|
||||
static void stack_probe_poll(void); /* 栈水位观测(可观测性,实现见文件末尾) */
|
||||
#if !defined(TEST_SUITE_STORAGE)
|
||||
static void default_task_net_cfg_save_notify(void);
|
||||
#endif
|
||||
@@ -372,6 +377,8 @@ void StartDefaultTask(void *argument)
|
||||
/* 排空运行期日志(上电/重启分类与自检故障落盘) */
|
||||
syslog_drain();
|
||||
#endif
|
||||
/* 栈水位观测:首次 5s 后报告,此后每 2s 采样、出现更深峰值才打印 */
|
||||
stack_probe_poll();
|
||||
}
|
||||
/* USER CODE END StartDefaultTask */
|
||||
}
|
||||
@@ -408,9 +415,13 @@ __weak void StartFtpTask(void *argument)
|
||||
osWaitForever:无限期等待,不设超时——任务会挂起(让出 CPU,零占用),直到生产者置位对应位才被唤醒
|
||||
*/
|
||||
if (sys_evtHandle != NULL) {
|
||||
/* osFlagsNoClear 必须带:就绪位是**状态**不是命令,消费型等待会把位清掉,
|
||||
* 导致后面 WaitAll(两位) 的等待者永远等不到(2026-09-21 实测:netTask 先
|
||||
* 消费 STORAGE_READY、uiTask/timeSync 再消费 NET_READY → 本任务永不唤醒、
|
||||
* FTP 21 端口从未监听)。 */
|
||||
(void)osEventFlagsWait(sys_evtHandle,
|
||||
SYS_EVT_NET_READY | SYS_EVT_STORAGE_READY,
|
||||
osFlagsWaitAll, osWaitForever);
|
||||
osFlagsWaitAll | osFlagsNoClear, osWaitForever);
|
||||
}
|
||||
lftpd_t ftp;
|
||||
lftpd_start("/", 21, &ftp);
|
||||
@@ -455,6 +466,101 @@ void StartUiTask(void *argument)
|
||||
|
||||
/* Private application code --------------------------------------------------*/
|
||||
/* USER CODE BEGIN Application */
|
||||
/*
|
||||
* 栈水位观测(可观测性,2026-09-21)
|
||||
* 数据源:任务栈"历史最小剩余"——内核按特征值填充法统计(与
|
||||
* configCHECK_FOR_STACK_OVERFLOW=2 同一机制),osThreadGetStackSpace() 取剩余
|
||||
* 字节,配置字节取各任务 *_attributes.stack_size(唯一定义处,避免常量重复;
|
||||
* 本版 CMSIS 包装只有 GetStackSpace 实现,无 osThreadGetStackSize)。
|
||||
* 峰值占用 = 配置 - 剩余。
|
||||
* 定栈规则(docs/内存管理/RAM使用分析与优化方案.md):新栈 = 峰值*1.5 + 1KB ISR
|
||||
* 余量,并要求 free_min >= 新栈*30% 才算合格。
|
||||
* 节奏:首次 STACK_PROBE_FIRST_MS 后报告(bring-up 深链已跑过),此后每
|
||||
* STACK_PROBE_PERIOD_MS 采样一次,只在出现更深峰值时打印——既不改串口静默
|
||||
* 窗口,也不漏掉之后才走到的更深分支(如卷修复/配置保存路径)。
|
||||
* 输出(纯 ASCII,便于 @test 采集):
|
||||
* [STACK] <task> peak_used=<B>/<cfg> free_min=<B> (<pct>%)
|
||||
* [STACK] heap free=<B> min_ever=<B>
|
||||
* 未覆盖 idle/timer 任务(内核静态小栈、纯循环,未取句柄)。
|
||||
*/
|
||||
typedef struct {
|
||||
const char *p_name;
|
||||
const osThreadId_t *p_id;
|
||||
const osThreadAttr_t *p_attr; /* 配置栈大小来源(stack_size) */
|
||||
} stack_probe_task_t;
|
||||
|
||||
static const stack_probe_task_t s_stack_probe[] = {
|
||||
{ "default", &defaultTaskHandle, &defaultTask_attributes },
|
||||
{ "net", &netTaskHandle, &netTask_attributes },
|
||||
{ "ftp", &ftpTaskHandle, &ftpTask_attributes },
|
||||
{ "test", &testTaskHandle, &testTask_attributes },
|
||||
{ "ui", &uiTaskHandle, &uiTask_attributes },
|
||||
{ "timesync", &timeSyncTaskHandle, &timeSyncTask_attributes },
|
||||
};
|
||||
|
||||
#define STACK_PROBE_COUNT (sizeof(s_stack_probe) / sizeof(s_stack_probe[0]))
|
||||
|
||||
static void stack_probe_poll(void)
|
||||
{
|
||||
static uint32_t s_free_min[STACK_PROBE_COUNT]; /* 历史最小剩余(B),0=未采样 */
|
||||
static uint32_t s_last_tick;
|
||||
static uint8_t s_reported;
|
||||
const TickType_t now = xTaskGetTickCount();
|
||||
uint32_t i;
|
||||
uint8_t deeper = 0u;
|
||||
|
||||
if (s_reported == 0u) {
|
||||
if (now < pdMS_TO_TICKS(STACK_PROBE_FIRST_MS)) {
|
||||
return;
|
||||
}
|
||||
} else if ((uint32_t)(now - s_last_tick) < (uint32_t)pdMS_TO_TICKS(STACK_PROBE_PERIOD_MS)) {
|
||||
return;
|
||||
}
|
||||
s_last_tick = (uint32_t)now;
|
||||
|
||||
for (i = 0u; i < (uint32_t)STACK_PROBE_COUNT; i++) {
|
||||
const osThreadId_t th = *(s_stack_probe[i].p_id);
|
||||
uint32_t free_now;
|
||||
|
||||
if (th == NULL) {
|
||||
continue; /* 任务未创建 */
|
||||
}
|
||||
free_now = osThreadGetStackSpace(th);
|
||||
if (free_now == 0u) {
|
||||
continue; /* 句柄不可用:跳过 */
|
||||
}
|
||||
if ((s_free_min[i] == 0u) || (free_now < s_free_min[i])) {
|
||||
s_free_min[i] = free_now; /* 刷新历史最小(= 最深峰值) */
|
||||
deeper = 1u;
|
||||
}
|
||||
}
|
||||
|
||||
if ((s_reported != 0u) && (deeper == 0u)) {
|
||||
return; /* 无新峰值:静默 */
|
||||
}
|
||||
s_reported = 1u;
|
||||
|
||||
for (i = 0u; i < (uint32_t)STACK_PROBE_COUNT; i++) {
|
||||
const uint32_t free_min = s_free_min[i];
|
||||
const uint32_t cfg = (uint32_t)s_stack_probe[i].p_attr->stack_size;
|
||||
|
||||
if (free_min == 0u) {
|
||||
continue;
|
||||
}
|
||||
if (cfg == 0u) {
|
||||
continue;
|
||||
}
|
||||
DBG_INFO("[STACK] %s peak_used=%lu/%lu free_min=%lu (%lu%%)",
|
||||
s_stack_probe[i].p_name,
|
||||
(unsigned long)(cfg - free_min), (unsigned long)cfg,
|
||||
(unsigned long)free_min,
|
||||
(unsigned long)((free_min * 100u) / cfg));
|
||||
}
|
||||
DBG_INFO("[STACK] heap free=%lu min_ever=%lu",
|
||||
(unsigned long)xPortGetFreeHeapSize(),
|
||||
(unsigned long)xPortGetMinimumEverFreeHeapSize());
|
||||
}
|
||||
|
||||
/*
|
||||
* "保存网络配置"请求通知(注册给 net_cfg 的保存交接):
|
||||
* uiTask 投递保存请求 → 置 SYS_EVT_CFG_SAVE → defaultTask 循环立即唤醒执行写盘
|
||||
|
||||
Reference in New Issue
Block a user