物联网Linux网络主机设备系统守护进程
本文分析了Linux系统守护进程的完整架构设计与实现。该系统采用模块化分层架构,主要包含以下核心组件: 核心管理层(sysdemo_main.c) 进程生命周期管理 系统初始化与信号处理 命令行参数解析 配置管理模块(demo_config.c/h) JSON配置文件解析 结构化配置容器设计 动态加载机制 进程监控系统 进程状态检查(PID/堆内存/CPU) 阈值检测与处理策略 死锁检测机制 文件
第一部分 系统守护进程完整架构深度分析
一、整体架构树形分析
核心架构层级
sysdemo 系统守护进程 (主控大脑) ├── 配置管理层 (骨架与神经系统) │ ├── 静态配置 (JSON解析) - demo_config.c/h │ ├── 运行时配置 - sysdemo.h 宏定义 │ └── 阈值策略配置 - 进程监控策略 │ ├── 进程生命周期管理 (心脏与循环系统) │ ├── 进程启动/停止 - run_main_process() │ ├── 状态监控循环 - start_main_processes() │ ├── 健康检查 - get_process_stat()/heap/cpu │ └── 异常处理 - kill_process() │ ├── 文件监控与备份 (免疫系统) │ ├── 实时监控 - inotify/epoll │ ├── 双级备份策略 - 1级(实时)/2级(可靠) │ ├── 完整性校验 - CRC32/MD5 │ └── 分区健康检查 - 读写测试 │ ├── 通信与协调系统 (神经网络) │ ├── 消息系统接口 - sys_demo_msg_* │ ├── 死锁检测机制 - 心跳监控 │ └── 调试接口 - 信号处理 │ └── 工具与服务层 (消化系统) ├── 校验算法 - checkapi.c/h ├── 时间/延迟函数 - 系统调用封装 └── 日志系统 - 进程退出记录
二、主要函数关联树形分析
1. 主控制流函数树
main() ├── 参数解析 (argc/argv) ├── 配置加载 get_demo_config() │ └── JSON解析 get_json_config_file() │ ├── 文件读取/解析 │ ├── boot节点解析 get_boot_cfg() │ ├── 进程节点解析 get_process_cfg() │ └── 备份节点解析 get_backup_cfg() ├── 系统初始化 run_boot_init() │ ├── 首次上电判断 is_first_poweron() │ ├── 数据库初始化 db_env_init() │ ├── 消息模块初始化 module_msg_init() │ └── boot脚本执行 start_boot_config() └── 主任务执行 run_demo_task() ├── 消息模块初始化 sys_demo_msg_init() └── 根据参数分支 ├── start: start_main_processes() ├── factory: start_factory_processes() └── clean: 清理流程
2. 进程监控循环函数树
start_main_processes() ├── 时间基准初始化 (死锁检查/心跳发送) └── 主监控循环 (while(1)) ├── 时间检查 (死锁/心跳周期) ├── 遍历所有进程 for (index < cfg.proc_len) │ ├── 类型过滤 (TYPE_NORMAL/CIE/HLOG) │ ├── 进程管理 run_main_process() │ │ ├── 文件检查 access(path, X_OK) │ │ ├── 依赖检查 is_main_run_ok() │ │ ├── PID检查 get_process_pid() │ │ ├── CIE特殊处理 (生命周期/OOM调整) │ │ ├── 命令构造 get_process_run_cmd() │ │ ├── 进程启动 system(cmd) │ │ └── 状态更新/日志 │ └── 检查周期内处理 │ ├── 死锁检查 check_deadlock() │ │ └── 消息队列分析 is_msg_deadlock() │ ├── 核心转储压缩 (条件编译) │ └── 阈值处理 process_threshold_handler() │ ├── CPU/堆内存获取 get_process_*() │ ├── 阈值比较与处理 │ └── 重启策略 kill_process() + run_main_process() └── 频率控制 delay_s(剩余时间)
3. 备份监控函数树
run_database_backup_thread()
├── epoll/inotify初始化
└── 主监控循环
├── epoll_wait等待文件事件
│ └── inotify事件处理
│ ├── 文件修改 IN_MODIFY → 设置1级备份标志
│ ├── 严重事件 (删除/卸载/移动) → 分区错误退出
│ └── 延迟处理避免过频
├── 定时检查 (2级备份周期)
└── 遍历所有监控文件
├── 错误计数检查与分区健康检查
├── 源文件存在性检查
├── 1级备份处理
│ ├── 条件: 修改标志或备份文件不存在
│ ├── 删除旧备份
│ ├── 直接拷贝 db_backup()
│ └── 结果处理 (错误计数/标志更新)
└── 2级备份处理
├── 条件: 需要2级备份且达到周期
├── 完整性校验 db_integrity_check()
├── 文件压缩与校验 gzip_db()
│ ├── 校验值比较 compare_file()
│ │ ├── 读取旧校验值
│ │ ├── 计算新校验值 (CRC32/MD5)
│ │ └── 比较与返回
│ ├── 文件压缩 system("gzip")
│ └── 校验值保存
└── 结果处理
三、Linux子系统调用逻辑树形分析
1. 进程管理子系统
进程控制 ├── fork/exec 相关 │ ├── system() - 通过shell执行命令 │ ├── (备选) run_exec() - 直接fork+exec │ └── 进程状态获取 │ ├── /proc/[pid]/stat 读取 - get_process_stat() │ ├── /proc/[pid]/maps 解析 - get_process_heap() │ └── /proc/[pid]/comm 读取 - get_process_pid() ├── 信号处理 │ ├── kill() - 发送终止信号 │ ├── signal() - 信号处理器注册 │ └── SIGUSR1 - 调试信号 └── 进程间通信 └── 消息系统 (module_msg_*) - 具体实现隐藏
2. 文件系统子系统
文件操作 ├── 基础I/O │ ├── open/read/close - 配置文件读取 │ ├── access() - 文件存在/权限检查 │ ├── stat() - 文件信息获取 │ └── remove() - 文件删除 ├── 目录操作 │ ├── opendir/readdir/closedir - /proc遍历 │ └── dirent结构分析 ├── 文件监控 │ ├── inotify_init() - 监控初始化 │ ├── inotify_add_watch() - 添加监控 │ └── inotify_rm_watch() - 移除监控 └── 高级I/O ├── epoll_create1() - 事件多路复用 ├── epoll_ctl() - 事件注册 └── epoll_wait() - 事件等待
3. 时间与调度子系统
时间管理 ├── 时间获取 │ ├── gettimeofday() - 高精度时间 │ ├── time() - 日历时间 │ └── sysinfo() - 系统运行时间 └── 延时控制 ├── select() 实现延时 - delay_s()/delay_ms() └── 固定周期巡检 - 主循环频率控制
4. 内存与资源子系统
资源管理 ├── 内存监控 │ └── /proc/[pid]/maps 解析 - 堆内存计算 ├── CPU监控 │ └── (预留) get_process_cpu() └── OOM控制 └── /proc/[pid]/oom_score_adj 设置
四、源码文件设计树形分析
1. sysdemo_main.c - 主控中心
设计思想: 指挥协调中心 ├── 为什么独立文件? │ ├── 主程序逻辑集中,便于理解和维护 │ ├── 与配置文件解析分离,关注业务逻辑 │ └── 函数按调用层次组织,逻辑清晰 ├── 内部结构 │ ├── 顶部: 全局变量和函数声明 │ ├── 中部: main() 入口和主要功能函数 │ └── 底部: 辅助工具函数 └── 设计合理性 ├── 优点: 逻辑层次清晰,职责单一 ├── 缺点: 文件较大(300+行),可考虑进一步拆分 └── 改进建议: 将工具函数拆分到单独文件
/**
* @file sysdemo_main.c
* @brief 系统守护进程主程序文件,负责进程管理和系统监控
*/
#include <stdio.h> /**< 标准输入输出头文件,提供printf等函数 */
#include <stdlib.h> /**< 标准库头文件,提供system、malloc等函数 */
#include <string.h> /**< 字符串处理头文件,提供strcmp、memset等函数 */
#include <sys/statfs.h> /**< 文件系统统计头文件,提供statfs结构体和函数 */
#include <sys/stat.h> /**< 文件状态头文件,提供stat结构体和函数 */
#include <sys/wait.h> /**< 进程等待头文件,提供WIFEXITED等宏 */
#include <fcntl.h> /**< 文件控制头文件,提供文件操作相关常量 */
#include <dirent.h> /**< 目录操作头文件,提供DIR、dirent结构体 */
#include "sysconf_struct_types.h" /**< 系统配置结构体类型定义头文件 */
#include "system_functions.h" /**< 系统函数头文件,提供sys_get_final_iob等函数 */
#include "message.h" /**< 消息模块头文件,提供message_t结构体等 */
#include "db_api.h" /**< 数据库API头文件,提供数据库操作接口 */
#include "checkapi.h" /**< 检查API头文件,提供check_deadlock等函数 */
#include "backup_db.h" /**< 数据库备份头文件,提供run_backup_task函数 */
#include "sysdemo.h" /**< 系统守护进程主头文件,包含宏定义和函数声明 */
static demofcg_t cfg; /**< 全局配置结构体,存储从配置文件读取的配置信息 */
/**
* @brief 声明静态函数原型
* @note 使用静态函数限制作用域在当前文件,提高封装性
*/
static int run_boot_init(void); /**< 系统启动初始化函数 */
static int run_demo_task(int argc, char *argv[]); /**< 执行守护进程任务主函数 */
static void debug_sysdemo_handler(int sig); /**< 调试信号处理函数 */
/**
* @brief 主函数 - 程序入口点
* @param argc 命令行参数个数
* @param argv 命令行参数数组
* @return int 程序退出码,0表示成功,-1表示失败
* @note 设计模式:模板方法模式,根据参数选择不同执行路径
* 性能分析:启动时解析配置文件,有一定I/O开销
*/
int main(int argc, char *argv[])
{
/** 参数检查 */
if (argc < 2) /**< 检查命令行参数数量 */
{
printf("Usage: %s [start|clean|factory]\n", argv[0]); /**< 打印使用说明 */
return -1; /**< 参数不足,返回错误 */
}
/** 初始化配置 */
memset(&cfg, 0, sizeof(demofcg_t)); /**< 清零配置结构体,防止未初始化内存 */
if (get_demo_config(&cfg) < 0) /**< 读取配置文件到cfg结构体 */
{
printf("get demo config error !! \n"); /**< 配置文件读取失败 */
release_demo_config(&cfg); /**< 释放配置相关资源 */
return -1; /**< 返回错误 */
}
run_boot_init(); /**< 执行启动初始化(数据库、消息模块等) */
signal(SIGUSR1, debug_sysdemo_handler); /**< 注册SIGUSR1信号处理函数用于调试 */
/** 根据参数执行相应任务 */
if (strcmp(argv[1], "start") == 0) /**< 如果是start命令 */
run_backup_task(cfg); /**< 执行数据库备份任务 */
run_demo_task(argc, argv); /**< 执行主守护进程任务 */
return 0; /**< 正常退出 */
}
/** ================ 全局变量定义 ================ */
static js_process_t *main_cie = NULL; /**< 指向主CIE进程的指针,设计模式:单例思想 */
static int main_oom_flag = 0; /**< OOM管制设置标志位,0=未设置,1=已设置 */
static long exe_time = 0; /**< 进程存活时间(秒),用于生命周期检测 */
static int exe_count = 0; /**< 进程异常次数计数器,设计模式:状态计数器 */
static int syswd_mod_id = -1; /**< 看门狗消息模块ID,-1表示未注册 */
/** ================ 静态函数声明 ================ */
static int start_boot_config(void); /**< 启动boot配置项(shell脚本) */
static int start_main_processes(void); /**< 启动主进程管理循环 */
static int start_factory_processes(void); /**< 启动工厂测试进程 */
static int stop_boot_config(void); /**< 停止boot配置项 */
static int stop_main_processes(void); /**< 停止主进程 */
#if 0 /**< 条件编译注释掉的函数原型 */
static int run_exec(char *path, char *name, char *arg, int block_flag); /**< 执行外部程序 */
#endif
extern int module_msg_init(); /**< 外部声明消息模块初始化函数 */
static int sys_demo_msg_init(void); /**< 系统守护进程消息模块初始化 */
static void sys_demo_msg_handle(message_t *msg); /**< 消息处理回调函数 */
static int kill_process(char *process, int sig, unsigned int core); /**< 杀死指定进程 */
#ifdef SYSDEMO_COREDUMP_ZIP_ENABLE /**< 条件编译:核心转储压缩使能 */
static int gzip_process_core(char *process); /**< 压缩进程核心转储文件 */
#endif
static int process_threshold_handler(js_process_t *process, int where); /**< 进程阈值处理 */
static int get_process_run_cmd(char cmd[MAX_BUFFER_LEN], js_process_t *process, int mode, int mode_flag); /**< 获取进程运行命令 */
static int get_process_pid(char *process); /**< 获取进程PID */
static char get_process_stat(int pid); /**< 获取进程状态字符 */
static int get_process_cpu(int pid); /**< 获取进程CPU使用率 */
static int get_process_heap(int pid); /**< 获取进程堆内存使用量 */
static bool is_rootfs_mtd(char *mtd); /**< 判断是否为rootfs MTD分区 */
static bool is_main_run_ok(js_process_t *process); /**< 判断主进程是否运行正常 */
static bool is_first_poweron(void); /**< 判断是否为首次上电 */
static int run_main_process(js_process_t *process); /**< 运行主进程管理逻辑 */
static int run_factory_process(js_process_t *process); /**< 运行工厂测试进程 */
static void debug_process_handler(void); /**< 调试进程处理函数 */
/**
* @brief 启动初始化函数
* @return int 总是返回0
* @note 设计模式:初始化钩子,在首次上电时执行一次性初始化
* 性能分析:仅首次上电执行,包含数据库和消息模块初始化
*/
static int run_boot_init(void)
{
if (is_first_poweron() == true) /**< 检查是否为首次上电 */
{
int iob_num; /**< I/O板编号变量 */
sys_get_final_iob(&iob_num); /**< 获取最终I/O板编号 */
db_env_init(iob_num); /**< 初始化数据库环境,性能:可能有磁盘I/O */
module_msg_init(); /**< 初始化消息模块,性能:进程间通信初始化 */
start_boot_config(); /**< 执行boot配置脚本 */
}
return 0; /**< 总是成功返回 */
}
/**
* @brief 执行守护进程的主任务
* @param argc 命令行参数个数
* @param argv 命令行参数数组
* @return int 成功返回0,失败返回-1
* @note 设计模式:命令模式,根据参数执行不同操作
* 性能分析:长时间运行,包含消息循环和进程监控
*/
static int run_demo_task(int argc, char *argv[])
{
sys_demo_msg_init(); /**< 初始化系统守护进程消息模块 */
if (strcmp(argv[1], "start") == 0) /**< start命令:启动主进程 */
{
start_main_processes(); /**< 进入主进程管理循环(不会返回) */
}
else if (strcmp(argv[1], "factory") == 0) /**< factory命令:启动工厂测试 */
{
start_factory_processes(); /**< 进入工厂测试进程循环 */
}
else if (strcmp(argv[1], "clean") == 0) /**< clean命令:清理停止 */
{
stop_main_processes(); /**< 停止主进程 */
stop_boot_config(); /**< 停止boot配置 */
module_msg_init(); /**< 重新初始化消息模块 */
release_demo_config(&cfg); /**< 释放配置资源 */
}
else /**< 无效参数处理 */
{
printf("invalid param !!\n"); /**< 打印错误信息 */
release_demo_config(&cfg); /**< 释放配置资源 */
return -1; /**< 返回错误 */
}
return 0; /**< 成功返回 */
}
/**
* @brief 执行boot节点的start配置脚本
* @return int 总是返回0
* @note 设计模式:命令执行器,依次执行配置的shell脚本
* 性能分析:顺序执行,阻塞等待每个脚本完成
*/
static int start_boot_config(void)
{
int index; /**< 循环索引 */
for (index = 0; index < cfg.boot.shell_len; index++) /**< 遍历所有boot脚本 */
{
if (cfg.boot.shell[index].valid != true || cfg.boot.shell[index].type != TYPE_SCRIPT_S) /**< 检查脚本有效性和类型 */
continue; /**< 跳过无效或非启动脚本 */
int status = system(cfg.boot.shell[index].cmd); /**< 执行shell命令,性能:fork+exec开销 */
if (-1 == status || WIFEXITED(status) != true || 0 != WEXITSTATUS(status)) /**< 检查执行结果 */
{
printf("run shell error, cmd = \n%s\n", cfg.boot.shell[index].cmd); /**< 打印错误信息 */
}
}
return 0; /**< 总是成功返回 */
}
/**
* @brief 启动主进程管理循环
* @return int 不会返回,除非发生致命错误
* @note 设计模式:轮询模式,定期检查所有进程状态
* 性能分析:循环频率由SYSDEMO_PROCESS_INSPECTION控制,需平衡响应性和CPU使用
*/
static int start_main_processes(void)
{
long check_freq = get_sysuptime(); /**< 死锁检查时间基准点 */
long baet_freq = get_sysuptime(); /**< 心跳消息发送时间基准点 */
while(1) /**< 主监控循环,永不退出 */
{
int index, check_flag = 0; /**< 进程索引和检查标志 */
long start = get_sysuptime(); /**< 记录循环开始时间 */
/** 检查是否到达死锁检查时间 */
if (start-check_freq >= SYSDEMO_DEADLOCK_CHECK_FREQ) /**< 死锁检查频率判断 */
{
check_freq = start; /**< 更新检查时间基准点 */
check_flag = 1; /**< 设置检查标志 */
}
/** 检查是否到达心跳发送时间 */
if (start-baet_freq >= SYSDEMO_SEND_BAET_MSG_FREQ) /**< 心跳发送频率判断 */
{
baet_freq = start; /**< 更新心跳时间基准点 */
sys_demo_msg_send(NULL, MNTLOC_BAET_MSG, NULL, 0); /**< 发送心跳消息 */
}
/** 遍历所有进程 */
for (index = 0; index < cfg.proc_len; index++) /**< 遍历进程配置数组 */
{
/** 过滤非监控类型的进程 */
if (cfg.process[index].type != TYPE_NORMAL && \
cfg.process[index].type != TYPE_CIE && \
cfg.process[index].type != TYPE_HLOG) /**< 只监控普通、CIE和HLOG进程 */
continue; /**< 跳过非监控类型进程 */
/** 运行主进程管理逻辑 */
if (run_main_process(&cfg.process[index]) < 0) /**< 启动/重启进程 */
continue; /**< 运行失败则跳过后续检查 */
if (check_flag == 0) /**< 非检查周期 */
continue; /**< 跳过死锁和阈值检查 */
/** 1. 死锁监测 */
char stat = get_process_stat(cfg.process[index].pid); /**< 获取进程状态字符 */
if ((stat != 'T' && stat != 't') && check_deadlock(cfg.process[index].pid) == true) /**< 非停止状态且检测到死锁 */
{
kill_process(cfg.process[index].name, SIGABRT, SYSDEMO_KILLCORE_DEADLOCK_FROM_MSG); /**< 发送SIGABRT终止进程 */
printf("[pid = %d] %s/%s run deadlock !!\n", cfg.process[index].pid, cfg.process[index].path, cfg.process[index].name); /**< 打印死锁信息 */
printf("[restart] # %s/%s\n", cfg.process[index].path, cfg.process[index].name); /**< 打印重启信息 */
--index; /**< 回退索引以便立即重启该进程 */
continue; /**< 继续循环 */
}
/** 2. core文件压缩(条件编译) */
#ifdef SYSDEMO_COREDUMP_ZIP_ENABLE /**< 如果启用核心转储压缩 */
gzip_process_core(cfg.process[index].name); /**< 压缩core文件 */
#endif
/** 3. 阈值处理 */
process_threshold_handler(&cfg.process[index], THRESHOLD_WHERE_INSPECTION); /**< 检查CPU/内存阈值 */
}
/** 控制循环频率 */
long end = get_sysuptime(); /**< 记录循环结束时间 */
int use_time = end - start; /**< 计算循环耗时 */
int inspection = SYSDEMO_PROCESS_INSPECTION - use_time; /**< 计算需要睡眠的时间 */
if (inspection >= 0) /**< 如果还有剩余时间 */
delay_s(inspection); /**< 精确睡眠剩余时间,性能:保持固定巡检周期 */
}
return 0; /**< 理论上不会执行到这里 */
}
/**
* @brief 启动工厂测试进程
* @return int 不会返回,除非发生致命错误
* @note 设计模式:简单轮询,仅管理工厂测试进程
* 性能分析:相比主进程管理更简单,仅负责重启
*/
static int start_factory_processes(void)
{
while(1) /**< 工厂测试监控循环 */
{
int index; /**< 循环索引 */
for (index = 0; index < cfg.proc_len; index++) /**< 遍历所有进程配置 */
{
if (cfg.process[index].type != TYPE_FAC_NORMAL && cfg.process[index].type != TYPE_FAC) /**< 只处理工厂测试进程 */
continue; /**< 跳过非工厂测试进程 */
run_factory_process(&cfg.process[index]); /**< 运行工厂测试进程管理逻辑 */
}
delay_s(SYSDEMO_PROCESS_INSPECTION); /**< 固定周期睡眠,性能:简单定时 */
}
return 0; /**< 理论上不会执行到这里 */
}
/**
* @brief 执行boot节点的stop配置脚本
* @return int 总是返回0
* @note 设计模式:与start_boot_config对称,执行停止脚本
* 性能分析:通常在系统关闭时执行
*/
static int stop_boot_config(void)
{
int index; /**< 循环索引 */
for (index = 0; index < cfg.boot.shell_len; index++) /**< 遍历所有boot脚本 */
{
if (cfg.boot.shell[index].valid != true || cfg.boot.shell[index].type != TYPE_SCRIPT_K) /**< 检查脚本有效性和类型 */
continue; /**< 跳过无效或非停止脚本 */
int status = system(cfg.boot.shell[index].cmd); /**< 执行shell命令 */
if (-1 == status || WIFEXITED(status) != true || 0 != WEXITSTATUS(status)) /**< 检查执行结果 */
{
printf("run shell error, cmd = \n%s\n", cfg.boot.shell[index].cmd); /**< 打印错误信息 */
}
}
return 0; /**< 总是成功返回 */
}
/**
* @brief 杀死指定进程
* @param process 进程名
* @param sig 终止信号(如SIGKILL、SIGABRT)
* @param core 终止原因代码(见sysdemo.h中的宏)
* @return int 成功返回0,失败返回-1
* @note 设计模式:进程生命周期管理
* 性能分析:发送信号+等待回收,有超时机制
* 数据流结构:使用固定超时计数target_cnt
*/
static int kill_process(char *process, int sig, unsigned int core)
{
if (process == NULL) /**< 参数检查 */
return -1; /**< 进程名为空 */
int pid = get_process_pid(process); /**< 通过进程名获取PID */
if (pid <= 0) /**< PID无效检查 */
return -1; /**< 进程不存在 */
if (kill(pid, sig) < 0) /**< 发送终止信号 */
{
printf("kill %s error\n", process); /**< 打印错误信息 */
return -1; /**< 发送信号失败 */
}
int loop_cnt = 0; /**< 等待循环计数器 */
int each_wait = 200; /**< 每次等待200ms,数据流:控制等待粒度 */
int target_cnt = SYSDEMO_PROCESS_FINISH_RECYCLE_WAIT*5; /**< 最大等待次数 = 2分钟*5=600次 */
delay_ms(each_wait); /**< 首次等待 */
/** 等待进程完全退出 */
while(get_process_pid(process) > 0) /**< 检查进程是否还存在 */
{
if ((++loop_cnt) >= target_cnt) /**< 超时检查 */
{
printf("kill %s timeout, loop = %d\n", process, loop_cnt); /**< 打印超时信息 */
return -1; /**< 进程回收超时 */
}
delay_ms(each_wait); /**< 继续等待 */
}
process_exit_info(process, pid, sig, core); /**< 记录进程退出信息到日志 */
printf("\033[35m[%d] %s kill sucess !! \n\033[0m", pid, process); /**< 彩色打印成功信息 */
return 0; /**< 成功返回 */
}
#ifdef SYSDEMO_COREDUMP_ZIP_ENABLE /**< 条件编译:核心转储压缩功能 */
/**
* @brief 压缩进程的核心转储文件
* @param process 进程名
* @return int 成功返回0,失败返回-1
* @note 设计模式:异步压缩(使用&后台执行)
* 性能分析:gzip压缩可能消耗CPU,后台执行减少阻塞
* 数据流结构:使用MAX_BUFFER_LEN(256)字节的缓冲区
*/
static int gzip_process_core(char *process)
{
if (process == NULL) /**< 参数检查 */
return -1; /**< 进程名为空 */
char path[MAX_BUFFER_LEN] = {0}; /**< 文件路径缓冲区 */
snprintf(path, MAX_BUFFER_LEN, "%s/%s.core", SYSDEMO_CORE_FILE_PATH, process); /**< 构造core文件路径 */
if (access(path, F_OK) < 0) /**< 检查文件是否存在 */
return -1; /**< core文件不存在 */
char cmd[MAX_BUFFER_LEN] = {0}; /**< 命令缓冲区 */
snprintf(cmd, MAX_BUFFER_LEN, "gzip %s&", path); /**< 构造gzip命令,&表示后台执行 */
snprintf(path, MAX_BUFFER_LEN, "%s/%s.core.gz", SYSDEMO_CORE_FILE_PATH, process); /**< 构造压缩后文件路径 */
if (access(path, F_OK) == 0) /**< 检查是否已存在压缩文件 */
remove(path); /**< 删除已存在的压缩文件,避免冲突 */
system(cmd); /**< 执行压缩命令,性能:异步执行不阻塞 */
return 0; /**< 成功返回 */
}
#endif /**< 结束条件编译块 */
/**
* @brief 存储进程杀死的原因到文件中
* @param process 进程名称字符串
* @param pid 进程ID
* @param sig 导致进程终止的信号值
* @param core 终止原因代码(位掩码,见sysdemo.h中的宏定义)
* @return int 成功返回0,失败返回-1,正常退出返回0(不记录)
* @note 设计模式:日志记录器模式,提供统一的进程终止记录
* 性能分析:每次调用都涉及文件I/O,建议异步或批量处理
* 数据流结构:info缓冲区大小MAX_BUFFER_LEN(256字节)
*/
int process_exit_info(char *process, int pid, int sig, unsigned int core)
{
/** 参数有效性检查 */
if (process == NULL || pid < 0 || (core & SYSDEMO_KILLCORE_NORMAL)) /**< 检查空指针、无效PID和正常退出标志 */
return 0; /**< 正常退出不记录日志 */
char info[MAX_BUFFER_LEN] = {0}; /**< 日志信息缓冲区,清零初始化 */
time_t timep; /**< 时间戳变量,存储秒数 */
time(&timep); /**< 获取当前系统时间,性能:系统调用开销 */
struct tm *ptime = gmtime(&timep); /**< 转换为UTC时间结构体,线程不安全但此处为局部变量 */
/** 格式化日志字符串 */
snprintf(info, MAX_BUFFER_LEN, "[%d-%02d-%02d %02d:%02d:%02d]\t%s\t%d-%d\t0x%08x\n",
(1900+ptime->tm_year), (1+ptime->tm_mon), ptime->tm_mday, /**< 年、月、日(tm_year从1900开始,tm_mon从0开始) */
ptime->tm_hour, ptime->tm_min, ptime->tm_sec, /**< 时、分、秒 */
process, pid, sig, core /**< 进程名、PID、信号、原因代码 */
); /**< 数据流:格式化为固定格式的字符串,便于解析 */
/** 打开日志文件(追加模式) */
FILE *fp = fopen(SYSDEMO_KILL_COREINFO_PATH, "a+"); /**< 以追加读写模式打开文件 */
if (fp == NULL) /**< 文件打开失败处理 */
{
printf("request_info_path %s error !!\n", SYSDEMO_KILL_COREINFO_PATH); /**< 打印错误信息 */
return -1; /**< 返回失败 */
}
/** 写入日志信息 */
int len = strlen(info); /**< 计算字符串长度(不含结尾空字符) */
int ret = fwrite(info, 1, len, fp); /**< 写入文件,fwrite返回实际写入字节数 */
if (ret < len) /**< 写入字节数不足处理 */
{
printf("write_to_info_path %s error: %d !!\n", SYSDEMO_KILL_COREINFO_PATH, ret); /**< 打印错误信息 */
fclose(fp); /**< 关闭文件 */
return -1; /**< 返回失败 */
}
fclose(fp); /**< 成功写入后关闭文件 */
/** 日志文件轮询管理(限制文件大小) */
struct stat fstat; /**< 文件状态结构体 */
int linsize = 80; /**< 估算每行日志大小(字节),基于格式化字符串长度 */
stat(SYSDEMO_KILL_COREINFO_PATH, &fstat); /**< 获取文件状态,性能:系统调用开销 */
if (fstat.st_size/linsize > SYSDEMO_KILL_COREINFO_MAX_LINES) /**< 计算行数并检查是否超过最大限制 */
{
char cmd[MAX_BUFFER_LEN] = {0}; /**< 命令缓冲区 */
/** 构造sed命令删除前N行 */
snprintf(cmd, MAX_BUFFER_LEN, "busybox sed -i \"1,%dd\" %s", SYSDEMO_KILL_COREINFO_DELETE_LINES, SYSDEMO_KILL_COREINFO_PATH);
system(cmd); /**< 执行sed命令删除旧日志,性能:fork+exec开销较大 */
/** 设计模式:使用外部工具实现日志轮转,简单但效率较低 */
}
printf("%s", info); /**< 同时在控制台输出日志信息 */
return 0; /**< 成功返回 */
}
/**
* @brief 终止所有主程序节点进程
* @return int 总是返回0
* @note 设计模式:反向遍历终止,避免进程依赖问题
* 性能分析:顺序终止,可能耗时较长
*/
static int stop_main_processes(void)
{
int index; /**< 循环索引,从后往前遍历 */
for (index = cfg.proc_len-1; index >= 0; index--) /**< 反向遍历进程配置数组 */
{
if (cfg.process[index].type == TYPE_NORMAL || cfg.process[index].type == TYPE_CIE) /**< 只处理普通和CIE类型进程 */
{
kill_process(cfg.process[index].name, SIGKILL, SYSDEMO_KILLCORE_NORMAL); /**< 发送SIGKILL终止进程 */
/** 设计模式:使用SYSDEMO_KILLCORE_NORMAL表示正常终止,不记录日志 */
}
}
return 0; /**< 总是成功返回 */
}
/**
* @brief 根据进程名获取进程ID
* @param process 进程名称字符串
* @return int >0表示进程ID,<0表示获取失败
* @note 设计模式:遍历/proc文件系统,查找comm文件匹配的进程
* 性能分析:每次调用都遍历/proc,O(n)复杂度,可优化为缓存机制
* 数据流结构:使用MAX_BUFFER_LEN(256)字节的缓冲区
*/
static int get_process_pid(char *process)
{
DIR *dir; /**< 目录流指针 */
struct dirent *ptr; /**< 目录项结构体指针 */
int fd, pid = -1; /**< 文件描述符和进程ID(初始化为-1) */
char filepath[MAX_BUFFER_LEN] = {0}; /**< 文件路径缓冲区 */
char buf[MAX_BUFFER_LEN] = {0}; /**< 读取缓冲区 */
int len = strlen(process); /**< 进程名长度 */
dir = opendir("/proc"); /**< 打开/proc目录,性能:目录遍历开始 */
if (dir == NULL) /**< 目录打开失败处理 */
{
printf("directory /proc not exist!\n"); /**< 打印错误信息(实际上/proc总是存在) */
return -1; /**< 返回失败 */
}
/** 遍历/proc目录下所有子目录 */
while ((ptr = readdir(dir)) != NULL) /**< 读取下一个目录项 */
{
/** 跳过"."和".."目录 */
if ((strcmp(ptr->d_name, ".") == 0) || (strcmp(ptr->d_name, "..") == 0))
continue; /**< 跳过当前目录和父目录 */
/** 只处理目录类型 */
if (DT_DIR != ptr->d_type) /**< 检查是否为目录(d_type可能不可靠) */
continue; /**< 跳过非目录项 */
/** 只处理数字命名的目录(进程ID目录) */
if (ptr->d_name[0] < '0' || ptr->d_name[0] > '9') /**< 检查目录名首字符是否为数字 */
continue; /**< 跳过非进程ID目录 */
/** 构造comm文件路径:/proc/[pid]/comm */
snprintf(filepath, MAX_BUFFER_LEN, "/proc/%s/comm", ptr->d_name); /**< 格式化文件路径 */
fd = open(filepath, O_RDONLY); /**< 以只读方式打开comm文件 */
if (fd < 0) /**< 文件打开失败(进程可能已退出) */
continue; /**< 跳过该进程 */
memset(buf, 0, len+1); /**< 清空缓冲区(只清空需要比较的部分+1) */
int ret = read(fd, buf, len); /**< 读取comm文件内容,最大读取进程名长度 */
if (ret < 0) /**< 读取失败 */
{
close(fd); /**< 关闭文件描述符 */
continue; /**< 跳过该进程 */
}
close(fd); /**< 读取成功后关闭文件描述符 */
/** 比较进程名(注意:comm文件包含换行符,但strcmp比较到第一个不同字符) */
if (strcmp(process, buf) == 0) /**< 进程名匹配成功 */
{
pid = atoi(ptr->d_name); /**< 将目录名转换为整数PID */
break; /**< 找到后跳出循环 */
}
}
closedir(dir); /**< 关闭目录流 */
// printf("%s's pid = %d\n", process, pid); /**< 调试信息(已注释) */
return pid; /**< 返回PID(未找到返回-1) */
}
/**
* @brief 获取进程的运行状态字符
* @param pid 进程ID
* @return char 进程状态字符,错误返回-1
* @note 设计模式:解析/proc/[pid]/stat文件第三字段
* 性能分析:每次调用打开文件,可缓存提高性能
* 数据流结构:linebuff缓冲区32字节足够存放状态字符
*/
static char get_process_stat(int pid)
{
char file_name[MAX_BUFFER_LEN]={0}; /**< 文件路径缓冲区 */
int index = 0; /**< 字符索引 */
int target = 3 - 1; /**< 目标字段索引(stat文件中第3个字段为状态) */
FILE *fp = NULL; /**< 文件指针 */
char linebuff[32]={0}; /**< 行缓冲区,大小32字节 */
snprintf(file_name, MAX_BUFFER_LEN, "/proc/%d/stat", pid); /**< 构造stat文件路径 */
fp = fopen(file_name, "r"); /**< 以只读方式打开stat文件 */
if (NULL == fp) /**< 文件打开失败处理 */
{
printf("open %s error !!\n", file_name); /**< 打印错误信息(进程可能已退出) */
return -1; /**< 返回错误 */
}
fgets(linebuff, 32-1, fp); /**< 读取一行(stat文件只有一行),保留一个字节给结尾空字符 */
fclose(fp); /**< 关闭文件 */
/** 查找第三个空格分隔的字段 */
for (index = 0; index < 32; index++) /**< 遍历缓冲区 */
{
if (' ' != linebuff[index]) /**< 不是空格则继续 */
continue; /**< 跳过非空格字符 */
if((--target) == 0) /**< 找到目标字段前的空格 */
{
index++; /**< 移动到状态字符位置 */
break; /**< 跳出循环 */
}
}
if (index >= 32) /**< 索引越界处理 */
return 0; /**< 返回0表示未找到 */
// printf("pid[%d] stat = %c\n", pid, linebuff[index]); /**< 调试信息(已注释) */
return linebuff[index]; /**< 返回状态字符 */
}
/**
* @brief 获取进程的CPU占用率(暂未实现)
* @param pid 进程ID
* @return int 失败返回-1
* @note 预留接口,待需求明确后实现
*/
static int get_process_cpu(int pid)
{
return -1; /**< 暂未实现,返回-1 */
}
/**
* @brief 获取进程的堆内存大小(单位:KB)
* @param pid 进程ID
* @return int 堆大小(KB),失败返回-1,无堆返回0
* @note 设计模式:解析/proc/[pid]/maps文件查找[heap]段
* 性能分析:线性搜索maps文件,最坏情况遍历所有内存段
* 数据流结构:buf缓冲区128字节,maps行通常较短
*/
static int get_process_heap(int pid)
{
char buf[MAX_BUFFER_LEN] = {0}; /**< 行读取缓冲区 */
char path[MAX_BUFFER_LEN] = {0}; /**< 文件路径缓冲区 */
char *delim = "-: \t\n"; /**< 分隔符字符串:减号、冒号、空格、制表符、换行符 */
char *p0 = NULL; /**< 字符串分割结果指针1 */
char *p1 = NULL; /**< 字符串分割结果指针2 */
int find_flag = 0; /**< 查找标志:0=未找到,1=找到 */
int i, heap_start, heap_end, heap_size = 0; /**< 循环变量、堆起始地址、结束地址、堆大小 */
snprintf(path, MAX_BUFFER_LEN, "/proc/%d/maps", pid); /**< 构造maps文件路径 */
FILE *fp = fopen(path,"r"); /**< 以只读方式打开maps文件 */
if (fp == NULL) /**< 文件打开失败处理 */
{
printf("open %s fail !!\n", path); /**< 打印错误信息 */
return -1; /**< 返回失败 */
}
/** 逐行读取maps文件 */
while (fgets(buf,128,fp) != NULL) /**< 读取一行,最多127字符 */
{
int len = strlen(buf); /**< 获取行长度 */
/** 查找包含"[heap]"的行 */
for (i = 0; i < len; i++) /**< 遍历行中每个字符 */
{
if (buf[i] == '[') /**< 找到左方括号 */
{
if (strncmp(&buf[i+1], "heap", strlen("heap")) == 0) /**< 检查是否为"[heap]" */
{
find_flag = 1; /**< 设置找到标志 */
break; /**< 跳出字符循环 */
}
}
}
if (find_flag == 0) /**< 未找到[heap]标签 */
continue; /**< 继续下一行 */
/** 解析地址范围(格式:start-end) */
p0 = strtok(buf, delim); /**< 第一次分割,获取起始地址十六进制字符串 */
if (p0 == NULL) /**< 分割失败处理 */
{
fclose(fp); /**< 关闭文件 */
return -1; /**< 返回失败 */
}
p1 = strtok(NULL, delim); /**< 第二次分割,获取结束地址十六进制字符串 */
if (p1 == NULL) /**< 分割失败处理 */
{
fclose(fp); /**< 关闭文件 */
return -1; /**< 返回失败 */
}
/** 将十六进制字符串转换为整数并计算堆大小 */
heap_start = strtol(p0, NULL, 16); /**< 转换起始地址 */
heap_end = strtol(p1, NULL, 16); /**< 转换结束地址 */
heap_size = (heap_end - heap_start)/1024; /**< 计算堆大小(KB) */
// printf("Heap: %d KB\n", heap_size); /**< 调试信息(已注释) */
fclose(fp); /**< 关闭文件 */
return heap_size; /**< 返回堆大小 */
}
fclose(fp); /**< 关闭文件(未找到[heap]段) */
return 0; /**< 返回0表示无堆段 */
}
/**
* @brief 进程阈值处理接口
* @param process 进程配置结构体指针
* @param where 阈值检查位置标识(如巡检时、启动时等)
* @return int 总是返回0
* @note 设计模式:策略模式,根据阈值配置采取不同处理方式
* 性能分析:每次调用可能涉及CPU和堆内存检测,I/O开销
*/
static int process_threshold_handler(js_process_t *process, int where)
{
/** 参数有效性检查 */
if (process == NULL || process->threshold.where == 0 || (process->threshold.where & where) == 0) /**< 检查空指针和阈值使能位 */
return 0; /**< 无需处理则直接返回 */
int handler_flag = 0; /**< 处理标志:0=未触发,1=已触发 */
int cpu = get_process_cpu(process->pid); /**< 获取CPU使用率(当前返回-1未实现) */
int heap = get_process_heap(process->pid); /**< 获取堆内存使用量(KB) */
unsigned int mode = 0; /**< 终止原因位掩码,初始为0 */
/** CPU阈值检查 */
if (process->threshold.cpu > 0 && process->threshold.cpu <= cpu) /**< 配置了CPU阈值且当前值超过阈值 */
{
handler_flag = 1; /**< 设置处理标志 */
mode |= SYSDEMO_KILLCORE_THRESHOLD_FROM_CPU; /**< 设置CPU超限位 */
printf("\033[35m%s's cpu too high: %d%% \n\033[0m", process->name, cpu); /**< 彩色打印警告信息 */
}
/** 堆内存阈值检查 */
if (process->threshold.heap > 0 && process->threshold.heap <= heap) /**< 配置了堆内存阈值且当前值超过阈值 */
{
handler_flag = 1; /**< 设置处理标志 */
mode |= SYSDEMO_KILLCORE_THRESHOLD_FROM_HEAP; /**< 设置堆内存超限位 */
printf("\033[35m%s's heap too high: %d KB \n\033[0m", process->name, heap); /**< 彩色打印警告信息 */
}
if (handler_flag != 1) /**< 未触发任何阈值 */
return 0; /**< 直接返回 */
/** 根据配置的处理方式执行相应操作 */
switch (process->threshold.handler) /**< 检查阈值处理策略 */
{
case THRESHOLD_HANDLER_RESTART: /**< 重启策略 */
kill_process(process->name, SIGKILL, mode); /**< 杀死进程 */
run_main_process(process); /**< 重新启动进程 */
break;
default: /**< 其他策略(暂未实现) */
break;
}
return 0; /**< 总是成功返回 */
}
/**
* @brief 判断当前rootfs是否挂载在指定的MTD分区
* @param mtd MTD分区名称字符串
* @return bool true=是,false=否
* @note 设计模式:文件解析器,解析/proc/mounts文件
* 性能分析:线性搜索,最坏情况遍历所有挂载点
* 数据流结构:buf缓冲区128字节足够
*/
static bool is_rootfs_mtd(char *mtd)
{
char buf[128] = {0}; /**< 行读取缓冲区 */
char *delim = "/ \t\n"; /**< 分隔符:斜杠、空格、制表符、换行符 */
FILE *fp = fopen("/proc/mounts","r"); /**< 打开挂载信息文件 */
if (fp == NULL) /**< 文件打开失败处理 */
{
printf("open /proc/mounts fail !!\n"); /**< 打印错误信息 */
return false; /**< 返回false */
}
/** 逐行读取/proc/mounts文件 */
while (fgets(buf,128,fp) != NULL) /**< 读取一行 */
{
char *ptk = strtok(buf, delim); /**< 第一次分割,获取设备名 */
while (ptk != NULL) /**< 遍历该行的所有字段 */
{
if (strncmp(ptk, mtd, strlen(mtd)) == 0) /**< 比较字段是否匹配目标MTD */
{
fclose(fp); /**< 关闭文件 */
return true; /**< 找到匹配,返回true */
}
ptk = strtok(NULL, delim); /**< 继续分割下一个字段 */
}
}
fclose(fp); /**< 关闭文件 */
return false; /**< 未找到匹配,返回false */
}
/**
* @brief 判断CIE主进程是否已经正常运行
* @param process 当前待检查的进程配置
* @return bool true=CIE运行正常可启动其他进程,false=CIE未运行或运行异常
* @note 设计模式:依赖检查器,确保进程启动顺序
* 性能分析:最多等待SYSDEMO_PROCESS_WAIT_MAIN_RUN秒,每秒检查一次
*/
static bool is_main_run_ok(js_process_t *process)
{
/** CIE和HLOG进程自身不需要等待 */
if (process->type == TYPE_CIE || process->type == TYPE_HLOG)
return true; /**< 直接返回true */
/** 防止空指针(main_cie可能未初始化) */
if (main_cie == NULL) /**< 主CIE指针为空 */
return false; /**< 返回false */
/** 等待CIE主进程启动 */
int cnt = SYSDEMO_PROCESS_WAIT_MAIN_RUN; /**< 最大等待次数(秒) */
while(cnt--) /**< 循环等待 */
{
if (get_process_pid(main_cie->name) > 0) /**< 检查CIE进程是否存在 */
return true; /**< CIE已启动,返回true */
delay_s(1); /**< 等待1秒,性能:每秒检查一次 */
}
/** 最终检查 */
if (get_process_pid(main_cie->name) < 0) /**< 等待超时后再次检查 */
return false; /**< CIE仍未启动,返回false */
return true; /**< CIE已启动,返回true */
}
/**
* @brief 判断当前是否为系统刚上电的状态
* @return bool true=首次上电,false=非首次上电
* @note 设计模式:状态检测器,通过检查所有配置进程是否存在判断
* 性能分析:遍历所有进程配置,每个进程调用get_process_pid(O(n)复杂度)
* 注释说明:经测试,该接口耗时约为进程数*5ms
*/
static bool is_first_poweron(void)
{
int index; /**< 循环索引 */
for (index = 0; index < cfg.proc_len; index++) /**< 遍历所有进程配置 */
{
int pid = get_process_pid(cfg.process[index].name); /**< 获取进程PID */
if (pid > 0) /**< 进程已存在 */
return false; /**< 非首次上电 */
}
return true; /**< 所有进程都不存在,首次上电 */
}
/**
* @brief 获取程序执行的命令行字符串
* @param cmd 存放命令的缓冲区
* @param process 程序配置信息
* @param mode 运行模式参数
* @param mode_flag 是否启用mode参数(1=启用,0=不启用)
* @return int 总是返回0
* @note 设计模式:命令构建器,根据配置动态构建执行命令
* 性能分析:字符串拼接操作,开销较小
* 数据流结构:cmd缓冲区大小MAX_BUFFER_LEN(256字节)
*/
static int get_process_run_cmd(char cmd[MAX_BUFFER_LEN], js_process_t *process, int mode, int mode_flag)
{
char *path = process->path; /**< 进程路径 */
char *name = process->name; /**< 进程名称 */
char *arg = process->arg[0]; /**< 默认参数(第一个参数) */
/** 处理多参数配置(arg_len为偶数时有效) */
if (process->arg_len > 1 && process->arg_len%2 == 0) /**< 参数成对出现:条件-参数 */
{
int index; /**< 循环索引 */
for (index = 0; index < process->arg_len; index+=2) /**< 每次跳两个元素 */
{
if (strcmp("default", process->arg[index]) == 0) /**< 找到"default"条件 */
break; /**< 使用默认参数 */
if (is_rootfs_mtd(process->arg[index]) == true) /**< 检查MTD条件是否满足 */
break; /**< 使用对应参数 */
}
arg = process->arg[index+1]; /**< 获取对应参数值 */
}
/** 根据mode_flag构造完整命令 */
if (mode_flag == 1) /**< 启用mode参数 */
snprintf(cmd, MAX_BUFFER_LEN, "%s/%s %d %s", path, name, mode, arg); /**< 包含mode参数 */
else /**< 不启用mode参数 */
snprintf(cmd, MAX_BUFFER_LEN, "%s/%s %s", path, name, arg); /**< 不包含mode参数 */
return 0; /**< 总是成功返回 */
}
/**
* @brief 执行主进程管理逻辑
* @param process 进程配置结构体指针
* @return int 成功返回进程PID,进程不存在返回-2,执行失败返回-1
* @note 设计模式:进程生命周期管理器,包含启动、监控、重启逻辑
* 性能分析:涉及文件检查、进程状态获取、系统命令执行
* 数据流结构:使用MAX_BUFFER_LEN(256字节)的缓冲区
*/
static int run_main_process(js_process_t *process)
{
/** 参数有效性检查 */
if (process == NULL) /**< 空指针检查 */
return -2; /**< 返回进程不存在错误码 */
/** 检查可执行文件是否存在且可执行 */
char path[MAX_BUFFER_LEN] = {0}; /**< 可执行文件完整路径缓冲区 */
snprintf(path, MAX_BUFFER_LEN, "%s/%s", process->path, process->name); /**< 拼接路径和文件名 */
if (access(path, X_OK) < 0) /**< 检查文件是否存在且有执行权限 */
return -2; /**< 文件不可执行,返回进程不存在 */
/** 检查依赖的主进程是否已正常运行 */
if (is_main_run_ok(process) == false) /**< 对于非CIE进程,需要等待CIE启动 */
return -1; /**< 依赖进程未就绪,返回失败 */
/** 检查进程是否已经在运行 */
int pid = get_process_pid(process->name); /**< 通过进程名获取PID */
if (pid > 0) /**< 进程已存在 */
{
process->pid = pid; /**< 更新配置结构体中的PID */
if (process->type == TYPE_CIE) /**< 如果是CIE主进程 */
main_cie = process; /**< 更新全局主进程指针 */
return pid; /**< 返回已有PID */
}
/** 进程不存在,需要启动 */
char cmd[MAX_BUFFER_LEN] = {0}; /**< 命令缓冲区 */
/** CIE主进程的特殊处理:生命周期检测 */
if (process->type == TYPE_CIE) /**< 仅对CIE主进程进行生命周期管理 */
{
long date = get_sysuptime(); /**< 获取当前系统运行时间 */
/** 检查进程存活时间是否过短 */
if (date < exe_time + SYSDEMO_PROCESS_MIN_LIFETIME) /**< 如果距离上次启动时间小于最小生命周期 */
{
++exe_count; /**< 增加异常次数计数器 */
if (exe_count > 1) /**< 第一次异常不打印kill计数 */
printf("kill num = %d\n", exe_count-1); /**< 打印异常重启次数 */
/** 检查是否超过最大异常次数 */
if (exe_count > SYSDEMO_PROCESS_LIFETIME_MAX) /**< 超过最大允许异常次数 */
{
/** 打印致命错误信息并退出 */
printf("%s/%s cannot normal runing, exit 0x%X\n", process->path, process->name, SYSDEMO_EXIT_CORE_FROM_LIFETIME);
process_exit_info("sysdemo", getpid(), 0, SYSDEMO_EXIT_CORE_FROM_LIFETIME); /**< 记录守护进程自身退出信息 */
exit(SYSDEMO_EXIT_CORE_FROM_LIFETIME); /**< 守护进程自身退出 */
}
}
else /**< 进程存活时间正常 */
{
exe_count = 0; /**< 重置异常计数器 */
}
/** CIE启动前停止所有其他进程 */
stop_main_processes(); /**< 停止所有主进程 */
main_oom_flag = 0; /**< 重置OOM调整标志 */
}
/** 构造执行命令 */
get_process_run_cmd(cmd, process, 0, 0); /**< 获取进程执行命令,不传递mode参数 */
// printf("cmd = %s\n", cmd); /**< 调试信息(已注释) */
/** 执行进程(使用system()函数) */
#if 0 /**< 条件编译:备用的run_exec函数(已注释) */
int status = run_exec(process->path, process->name, process->arg, 0);
#endif
int status = system(cmd); /**< 执行shell命令启动进程,性能:fork+exec开销 */
if (-1 == status || WIFEXITED(status) != true || 0 != WEXITSTATUS(status)) /**< 检查执行结果 */
{
printf("run process error: %s\n", cmd); /**< 打印错误信息 */
return -1; /**< 返回执行失败 */
}
/** 等待进程稳定(如果配置了等待时间) */
if (process->wait) /**< 配置了等待时间 */
delay_s(process->wait); /**< 等待指定秒数 */
/** 获取新启动进程的PID */
process->pid = get_process_pid(process->name); /**< 重新获取PID */
if (process->type == TYPE_CIE) /**< CIE主进程的特殊处理 */
{
main_cie = process; /**< 更新全局主进程指针 */
/** 设置OOM调整分数(防止CIE被OOM killer杀死) */
if (main_oom_flag == 0 && main_cie->pid > 0) /**< 未设置过且PID有效 */
{
//#echo -17 > /proc/`busybox pgrep cie_app`/oom_adj 已弃用 /**< 旧方法注释 */
snprintf(cmd, MAX_BUFFER_LEN, "echo -1000 > /proc/%d/oom_score_adj", main_cie->pid); /**< 新方法:设置OOM分数调整 */
system(cmd); /**< 执行echo命令设置OOM调整 */
main_oom_flag = 1; /**< 设置标志防止重复执行 */
}
exe_time = get_sysuptime(); /**< 记录CIE启动时间 */
}
/** 打印成功启动信息 */
if (process->pid > 0) /**< PID有效 */
printf("\033[35m[%d] %s init sucess !! \n\033[0m", process->pid, process->name); /**< 彩色打印成功信息 */
return process->pid; /**< 返回新进程PID */
}
/**
* @brief 执行工厂测试节点进程
* @param process 进程配置结构体指针
* @return int 成功返回进程PID,进程不存在返回-2,执行失败返回-1,进程已运行返回1
* @note 设计模式:简单进程管理器,只负责启动和状态维护
* 性能分析:相比主进程逻辑更简单,无生命周期检测
*/
static int run_factory_process(js_process_t *process)
{
/** 参数有效性检查 */
if (process == NULL) /**< 空指针检查 */
return -2; /**< 返回进程不存在错误码 */
/** 检查可执行文件是否存在且可执行 */
char path[MAX_BUFFER_LEN] = {0}; /**< 可执行文件完整路径缓冲区 */
snprintf(path, MAX_BUFFER_LEN, "%s/%s", process->path, process->name); /**< 拼接路径和文件名 */
if (access(path, X_OK) < 0) /**< 检查文件是否存在且有执行权限 */
return -2; /**< 文件不可执行,返回进程不存在 */
/** 检查进程是否已经在运行 */
int pid = get_process_pid(process->name); /**< 通过进程名获取PID */
if (pid > 0) /**< 进程已存在 */
{
process->pid = pid; /**< 更新配置结构体中的PID */
return 1; /**< 返回1表示进程已运行(区别于PID) */
}
/** 进程不存在,需要启动 */
char cmd[MAX_BUFFER_LEN] = {0}; /**< 命令缓冲区 */
get_process_run_cmd(cmd, process, 0, 0); /**< 获取进程执行命令 */
// printf("cmd = %s\n", cmd); /**< 调试信息(已注释) */
/** 执行进程 */
int status = system(cmd); /**< 执行shell命令启动进程 */
if (-1 == status || WIFEXITED(status) != true || 0 != WEXITSTATUS(status)) /**< 检查执行结果 */
{
printf("run process error: %s\n", cmd); /**< 打印错误信息 */
return -1; /**< 返回执行失败 */
}
/** 等待进程稳定 */
delay_s(process->wait); /**< 等待配置的时间 */
/** 获取新启动进程的PID */
process->pid = get_process_pid(process->name); /**< 重新获取PID */
/** 打印成功启动信息 */
if (process->pid > 0) /**< PID有效 */
printf("[%d] %s init sucess !! \n", process->pid, process->name); /**< 打印成功信息 */
return process->pid; /**< 返回新进程PID */
}
/**
* @brief 备用的进程执行函数(使用fork+exec,当前未使用)
* @param path 可执行文件路径
* @param name 进程名称
* @param arg 命令行参数字符串
* @param block_flag 是否阻塞等待
* @return int 成功返回0,失败返回-1
* @note 设计模式:直接进程创建,相比system()更高效但更复杂
* 性能分析:减少shell解释器开销,但需手动处理参数解析
*/
#if 0 /**< 条件编译:当前未使用此函数 */
static int run_exec(char *path, char *name, char *arg, int block_flag)
{
pid_t pid = fork(); /**< 创建子进程 */
if (pid < 0) /**< fork失败 */
return -1; /**< 返回失败 */
if (pid == 0) /**< 子进程代码 */
{
if (arg == NULL) /**< 无参数情况 */
{
execlp(path, name, NULL); /**< 执行程序,自动搜索PATH */
}
else /**< 有参数情况 */
{
char *argv[128]; /**< 参数指针数组,最大128个参数 */
char tmp[128]; /**< 参数字符串临时缓冲区 */
int cnt = 0; /**< 参数计数器 */
char *delim = " "; /**< 参数分隔符(空格) */
strcpy(tmp, arg); /**< 复制参数字符串到临时缓冲区 */
argv[cnt++] = name; /**< 第一个参数为程序名 */
argv[cnt] = strtok(tmp, delim); /**< 分割第一个参数 */
while (argv[cnt] != NULL) /**< 继续分割所有参数 */
{
argv[++cnt] = strtok(NULL, delim); /**< 分割下一个参数 */
}
execvp(path, argv); /**< 执行程序,自动搜索PATH */
}
}
int status; /**< 子进程退出状态 */
wait(&status); /**< 等待子进程退出 */
return 0; /**< 成功返回 */
}
#endif /**< 结束条件编译块 */
/**
* @brief 初始化系统守护进程消息模块
* @return int 总是返回0
* @note 设计模式:模块初始化器,负责消息系统的初始化和注册
* 性能分析:涉及消息队列/共享内存等IPC机制初始化
*/
static int sys_demo_msg_init(void)
{
module_logout(SYS_WATCHDOG_MODULE); /**< 先登出(确保不会重复登录) */
init_beat_msg_list(); /**< 初始化心跳消息列表 */
syswd_mod_id = module_login(SYS_WATCHDOG_MODULE, 32, sys_demo_msg_handle); /**< 登录消息模块,注册处理函数 */
return 0; /**< 总是成功返回 */
}
/**
* @brief 进程管理模块的消息发送接口
* @param dest 目标模块名称,NULL表示广播
* @param msgtype 消息类型
* @param message 消息数据指针
* @param len 消息数据长度
* @return int 成功返回消息发送结果,失败返回-1
* @note 设计模式:门面模式,封装底层消息发送细节
* 性能分析:消息发送性能取决于底层IPC机制
*/
int sys_demo_msg_send(char *dest, int msgtype, void *message, unsigned int len)
{
if(syswd_mod_id <= 0) /**< 检查消息模块是否已初始化 */
return -1; /**< 模块未初始化,返回失败 */
if(dest != NULL) /**< 指定目标模块 */
return module_msg_send(syswd_mod_id, dest, MSG_SEND_P2P, msgtype, message, len); /**< 点对点发送 */
else /**< 目标为空 */
return module_msg_send(syswd_mod_id, NULL, MSG_SEND_BROADCAST, msgtype, message, len); /**< 广播发送 */
}
/**
* @brief 进程管理模块的消息处理回调函数
* @param msg 消息结构体指针
* @note 设计模式:观察者模式,响应系统事件消息
* 性能分析:消息处理应快速完成,避免阻塞消息队列
*/
static void sys_demo_msg_handle(message_t *msg)
{
if (msg == NULL) /**< 空消息检查 */
return ; /**< 直接返回 */
int index; /**< 循环索引 */
switch (msg->msg_type) /**< 根据消息类型处理 */
{
case MNTALL_RESET_BEGIN_MSG: /**< 系统重置开始消息 */
for (index = 0; index < cfg.proc_len; index++) /**< 遍历所有进程 */
{
process_threshold_handler(&cfg.process[index], THRESHOLD_WHERE_RESET_BEGIN); /**< 在重置开始处检查阈值 */
}
break; /**< 结束case */
case MNTALL_RESET_END_MSG: /**< 系统重置结束消息 */
for (index = 0; index < cfg.proc_len; index++) /**< 遍历所有进程 */
{
process_threshold_handler(&cfg.process[index], THRESHOLD_WHERE_RESET_END); /**< 在重置结束处检查阈值 */
}
break; /**< 结束case */
default: /**< 其他未处理的消息类型 */
break; /**< 忽略 */
}
}
/**
* @brief 打印进程管理模块的调试信息
* @note 设计模式:调试信息聚合器,集中展示系统状态
* 性能分析:遍历所有进程配置,可能有较多输出
*/
static void debug_process_handler(void)
{
int index; /**< 循环索引 */
/** 打印模块头部信息 */
printf("--------------- debug_process_handler --------------\n"); /**< 分隔线 */
printf("| syswd module id = %08x\n", syswd_mod_id); /**< 消息模块ID */
printf("| linux runtime = %ld s\n", get_sysuptime()); /**< 系统运行时间 */
printf("| cie runtime = %ld s\n", exe_time); /**< CIE进程运行时间 */
printf("| cie run error cnt = %d\n", exe_count); /**< CIE异常次数 */
printf("----------------------------------------------------\n"); /**< 分隔线 */
/** 遍历并打印每个进程的详细信息 */
for (index = 0; index < cfg.proc_len; index++) /**< 遍历所有进程配置 */
{
printf("| path: %s/%s\n", cfg.process[index].path, cfg.process[index].name); /**< 进程路径和名称 */
printf("| arg: \n"); /**< 参数信息标题 */
/** 打印参数配置 */
if (cfg.process[index].arg_len == 1) /**< 单个参数(默认配置) */
{
printf("| default partition: %s\n", cfg.process[index].arg[0]); /**< 默认分区参数 */
}
else /**< 多个参数对 */
{
int i; /**< 内部循环索引 */
for (i = 0; i < cfg.process[index].arg_len; i += 2) /**< 每次跳两个元素(条件-参数对) */
printf("| %s partition: %s\n", cfg.process[index].arg[i], cfg.process[index].arg[i+1]); /**< 打印条件参数对 */
}
printf("| wait: %d s\n", cfg.process[index].wait); /**< 启动等待时间 */
printf("| pid: %d\n", cfg.process[index].pid); /**< 当前进程PID */
printf("| threshold: \n"); /**< 阈值配置标题 */
/** 打印阈值配置详情 */
printf("| max cpu: %d%s\n", cfg.process[index].threshold.cpu,
cfg.process[index].threshold.cpu?"":"(not set)"); /**< CPU阈值,未设置时标注 */
printf("| max heap: %d%s\n", cfg.process[index].threshold.heap,
cfg.process[index].threshold.heap?"":"(not set)"); /**< 堆内存阈值,未设置时标注 */
printf("| handler: %d%s\n", cfg.process[index].threshold.handler,
cfg.process[index].threshold.handler?"":"(nothing to do)"); /**< 处理方式,未设置时标注 */
printf("| where: %08x%s\n", cfg.process[index].threshold.where,
cfg.process[index].threshold.where?"":"(not set)"); /**< 检查位置位掩码,未设置时标注 */
printf("----------------------------------------------------\n"); /**< 每个进程信息后的分隔线 */
}
}
/**
* @brief SIGUSR1信号处理函数,用于调试
* @param sig 信号值(应为SIGUSR1)
* @note 设计模式:信号处理器,响应调试请求
* 性能分析:信号处理函数中应避免复杂操作,此处仅打印信息
*/
static void debug_sysdemo_handler(int sig)
{
debug_process_handler(); /**< 打印进程管理调试信息 */
debug_backup_handler(); /**< 打印备份模块调试信息(外部函数) */
}
2. demo_config.c/h - 配置管理
设计思想: 数据与逻辑分离 ├── 为什么使用JSON配置? │ ├── 人类可读/可编辑 │ ├── 结构化数据,支持复杂配置 │ ├── 动态加载,无需重新编译 │ └── 易于扩展,添加新字段 ├── cJSON库集成 │ ├── 轻量级,适合嵌入式 │ ├── 内存管理可控 │ └── API简洁 └── 结构体设计层次 ├── demofcg_t (顶层容器) ├── js_bootcfg_t (启动配置) ├── js_process_t (进程配置,最复杂) ├── js_backup_t (备份配置) └── js_threshold_t (阈值配置,内嵌)
/**
* @file demo_config.c
* @brief 配置文件解析实现文件,负责JSON配置文件的读取和解析
*/
#include <stdio.h> /**< 标准输入输出头文件,提供printf等函数 */
#include <stdlib.h> /**< 标准库头文件,提供malloc、free等函数 */
#include <string.h> /**< 字符串处理头文件,提供memset、strlen等函数 */
#include <sys/stat.h> /**< 文件状态头文件,提供stat结构体和函数 */
#include <fcntl.h> /**< 文件控制头文件,提供open、O_RDONLY等常量 */
#include <unistd.h> /**< UNIX标准函数头文件,提供access、read、close等函数 */
#include "cJSON.h" /**< cJSON库头文件,提供JSON解析功能 */
#include "demo_config.h" /**< 配置文件头文件,包含结构体定义和常量 */
/**
* @defgroup JSON_NODE_NAMES JSON节点名称常量
* @brief JSON配置文件中各节点的名称定义
* @{
*/
#define JSNODE_BOOT_NAME "boot" /**< 启动配置的json节点名称 */
#define JSNODE_MAIN_NAME "main process" /**< 主应用的json节点名称 */
#define JSNODE_FACT_NAME "factory process" /**< 产测应用的json节点名称 */
#define JSNODE_BACKUP_NAME "backup files" /**< 文件备份的json节点名称 */
/** @} */ /* JSON_NODE_NAMES */
/** 全局cJSON对象指针,存储解析后的JSON树 */
static cJSON *root = NULL; /**< JSON根节点指针,设计模式:单例模式思想 */
static cJSON *boot_root = NULL; /**< boot节点指针 */
static cJSON *main_root = NULL; /**< main process节点指针 */
static cJSON *fact_root = NULL; /**< factory process节点指针 */
static cJSON *back_root = NULL; /**< backup files节点指针 */
/** 静态函数声明 */
static int get_json_config_file(void); /**< 读取并解析JSON配置文件 */
static int get_boot_cfg(js_bootcfg_t *boot); /**< 解析boot节点配置 */
static int get_process_cfg(js_process_t **process, int *proc_len); /**< 解析进程节点配置 */
static int get_backup_cfg(js_backup_t **backup, int *bak_len); /**< 解析备份节点配置 */
static int release_json_config_file(void); /**< 释放JSON解析树内存 */
static int release_boot_cfg(js_bootcfg_t *boot); /**< 释放boot配置内存 */
static int release_process_cfg(js_process_t **process); /**< 释放进程配置内存 */
static int release_backup_cfg(js_backup_t **bakup); /**< 释放备份配置内存 */
static int get_process_nodes(cJSON *array, js_process_t *process, int len); /**< 解析进程数组节点 */
static int get_shell_nodes(cJSON *array, js_shell_t *shell, int len); /**< 解析shell命令数组节点 */
static int get_backup_nodes(cJSON *array, js_backup_t *bakup, int len); /**< 解析备份数组节点 */
/**
* @brief 获取演示配置(主入口函数)
* @param cfg 配置结构体指针,用于存储解析后的配置
* @return int 成功返回0,失败返回-1
* @note 设计模式:外观模式,提供简化的配置获取接口
* 性能分析:总耗时约3.76ms(如测试代码所示),包含文件I/O和JSON解析
*/
int get_demo_config(demofcg_t *cfg)
{
/** 读取并解析JSON配置文件 */
if (get_json_config_file() < 0) /**< 读取配置文件失败 */
{
printf("get json config file error !!\n"); /**< 打印错误信息 */
release_json_config_file(); /**< 清理可能已分配的资源 */
return -1; /**< 返回错误 */
}
/** 解析boot节点配置(允许失败,非关键) */
if (get_boot_cfg(&(cfg->boot)) < 0) /**< 解析boot配置 */
{
printf("get boot config error !!\n"); /**< 打印警告信息 */
}
/** 解析备份节点配置(允许失败,非关键) */
if (get_backup_cfg(&(cfg->backup), &(cfg->bak_len)) < 0) /**< 解析备份配置 */
{
printf("get backup config error !!\n"); /**< 打印警告信息 */
}
/** 解析进程节点配置(关键,失败需清理) */
if (get_process_cfg(&(cfg->process), &(cfg->proc_len)) < 0) /**< 解析进程配置 */
{
printf("get process config error !!\n"); /**< 打印错误信息 */
release_boot_cfg(&(cfg->boot)); /**< 释放已分配的boot配置内存 */
release_backup_cfg(&(cfg->backup)); /**< 释放已分配的备份配置内存 */
release_json_config_file(); /**< 释放JSON解析树 */
return -1; /**< 返回错误 */
}
return 0; /**< 成功返回 */
}
/**
* @brief 释放演示配置占用的内存
* @param cfg 配置结构体指针
* @return int 总是返回0
* @note 设计模式:资源清理器,对称于get_demo_config
* 性能分析:释放所有动态分配的内存,防止内存泄漏
*/
int release_demo_config(demofcg_t *cfg)
{
release_boot_cfg(&(cfg->boot)); /**< 释放boot配置内存 */
release_process_cfg(&(cfg->process)); /**< 释放进程配置内存 */
release_backup_cfg(&(cfg->backup)); /**< 释放备份配置内存 */
release_json_config_file(); /**< 释放JSON解析树 */
memset(cfg, 0, sizeof(demofcg_t)); /**< 清零配置结构体,数据流:安全清理 */
return 0; /**< 总是成功返回 */
}
/**
* @brief 获取配置文件内容并解析为cJSON树
* @return int 成功返回0,失败返回-1
* @note 设计模式:单例模式,全局只加载一次配置文件
* 性能分析:涉及文件I/O和JSON解析,是主要耗时操作
*/
static int get_json_config_file(void)
{
if (root != NULL) /**< 已经加载过,直接返回(单例模式) */
return 0; /**< 已初始化,直接成功返回 */
char *filepath = DEMO_CONFIG_PATH_BAK; /**< 默认使用备用路径 */
#ifdef ENABLE_PATCH_PATH /**< 如果启用了补丁路径功能 */
if (access(DEMO_CONFIG_PATH, R_OK) >= 0) /**< 检查首选路径是否存在且可读 */
{
filepath = DEMO_CONFIG_PATH; /**< 使用首选路径 */
}
#endif
/** 打开配置文件 */
int fd = open(filepath, O_RDONLY); /**< 以只读方式打开文件 */
if (fd < 0) /**< 打开文件失败 */
{
printf("open %s error !!\n", filepath); /**< 打印错误信息 */
return -1; /**< 返回错误 */
}
printf("get jsconfig from %s !!\n", filepath); /**< 打印加载的配置文件路径 */
/** 获取文件大小 */
struct stat statbuff; /**< 文件状态结构体 */
if(stat(filepath, &statbuff) < 0) /**< 获取文件状态失败 */
{
printf("get %s file stat error !!\n", filepath); /**< 打印错误信息 */
return -1; /**< 返回错误 */
}
/** 分配文件内容缓冲区 */
char *filebuff = (char *)malloc(statbuff.st_size); /**< 根据文件大小分配内存 */
if (filebuff == NULL) /**< 内存分配失败 */
{
printf("file buffer malloc error !!\n"); /**< 打印错误信息 */
return -1; /**< 返回错误 */
}
/** 读取文件内容 */
lseek(fd, 0, SEEK_SET); /**< 确保文件指针在开头 */
if (read(fd, filebuff, statbuff.st_size) < 0) /**< 读取整个文件 */
{
printf("read %s error!! \n", filepath); /**< 读取失败 */
free(filebuff); /**< 释放缓冲区 */
filebuff = NULL; /**< 指针置空 */
return -1; /**< 返回错误 */
}
close(fd); /**< 关闭文件描述符 */
/** 解析JSON内容 */
root = cJSON_Parse(filebuff); /**< 解析JSON字符串为cJSON树 */
if (root == NULL) /**< 解析失败 */
{
printf("parse config file error!\n"); /**< 打印错误信息 */
free(filebuff); /**< 释放缓冲区 */
filebuff = NULL; /**< 指针置空 */
return -1; /**< 返回错误 */
}
/** 删除注释性节点(减少内存占用) */
cJSON_DeleteItemFromObject(root, "copyright"); /**< 删除版权信息节点 */
cJSON_DeleteItemFromObject(root, "version"); /**< 删除版本信息节点 */
cJSON_DeleteItemFromObject(root, "boot description"); /**< 删除boot描述节点 */
cJSON_DeleteItemFromObject(root, "process description"); /**< 删除进程描述节点 */
/** 获取各功能节点 */
boot_root = cJSON_GetObjectItem(root, JSNODE_BOOT_NAME); /**< 获取boot节点 */
if (boot_root == NULL) /**< boot节点不存在 */
{
printf("get boot config json node error !!\n"); /**< 打印警告信息 */
}
main_root = cJSON_GetObjectItem(root, JSNODE_MAIN_NAME); /**< 获取main process节点 */
if (main_root == NULL) /**< main节点不存在 */
{
printf("get main config json node error !!\n"); /**< 打印警告信息 */
}
fact_root = cJSON_GetObjectItem(root, JSNODE_FACT_NAME); /**< 获取factory process节点 */
if (fact_root == NULL) /**< factory节点不存在 */
{
printf("get factory config json node error !!\n"); /**< 打印警告信息 */
}
back_root = cJSON_GetObjectItem(root, JSNODE_BACKUP_NAME); /**< 获取backup files节点 */
if (back_root == NULL) /**< backup节点不存在 */
{
printf("get backup config json node error !!\n"); /**< 打印警告信息 */
}
/** 调试信息(已注释) */
// printf("root = %s\n", cJSON_Print(root));
// printf("boot_root = %s\n", cJSON_Print(boot_root));
// printf("main_root = %s\n", cJSON_Print(main_root));
// printf("fact_root = %s\n", cJSON_Print(fact_root));
// printf("root_len = %lu\n", strlen(cJSON_Print(root)));
free(filebuff); /**< 释放原始文件内容缓冲区 */
return 0; /**< 成功返回 */
}
/**
* @brief 释放JSON解析树内存
* @return int 总是返回0
* @note cJSON_Delete会自动递归释放所有子节点
*/
static int release_json_config_file()
{
cJSON_Delete(root); /**< 递归删除整个JSON树 */
root = NULL; /**< 根指针置空 */
boot_root = NULL; /**< boot节点指针置空 */
main_root = NULL; /**< main节点指针置空 */
fact_root = NULL; /**< factory节点指针置空 */
back_root = NULL; /**< backup节点指针置空 */
return 0; /**< 总是成功返回 */
}
/**
* @brief 解析boot节点配置
* @param boot boot配置结构体指针
* @return int 成功返回0,失败返回-1
* @note 设计模式:建造者模式,逐步构建配置对象
*/
static int get_boot_cfg(js_bootcfg_t *boot)
{
if (boot_root == NULL) /**< boot节点不存在 */
return -1; /**< 返回错误 */
/** 获取shell命令数组 */
cJSON *shell = cJSON_GetObjectItem(boot_root, "shell"); /**< 获取shell数组节点 */
cJSON *check_freq = cJSON_GetObjectItem(boot_root, "check freq"); /**< 获取检查频率节点(已弃用) */
if (shell != NULL) /**< shell节点存在 */
{
if (boot->shell != NULL) /**< 如果之前已分配内存(安全处理) */
{
free(boot->shell); /**< 先释放旧内存 */
}
boot->shell_len = cJSON_GetArraySize(shell); /**< 获取数组大小 */
if (boot->shell_len > 0) /**< 数组非空 */
{
/** 分配shell配置结构体数组内存 */
boot->shell = (js_shell_t *)malloc(boot->shell_len * sizeof(js_shell_t)); /**< 动态分配内存 */
if (boot->shell != NULL) /**< 分配成功 */
{
/** 解析shell节点数组 */
boot->shell_len = get_shell_nodes(shell, boot->shell, boot->shell_len); /**< 解析实际有效项数 */
}
}
}
if (check_freq != NULL) /**< check freq节点存在(向后兼容) */
{
boot->check_freq = check_freq->valueint; /**< 设置检查频率值 */
}
return 0; /**< 成功返回 */
}
/**
* @brief 释放boot节点配置内存
* @param boot boot配置结构体指针
* @return int 总是返回0
*/
static int release_boot_cfg(js_bootcfg_t *boot)
{
if (boot->shell == NULL) /**< 内存已释放或未分配 */
return 0; /**< 直接返回 */
free(boot->shell); /**< 释放shell数组内存 */
boot->shell = NULL; /**< 指针置空 */
boot->shell_len = 0; /**< 长度清零 */
return 0; /**< 成功返回 */
}
/**
* @brief 解析进程节点配置(包括main和factory)
* @param process 进程配置数组指针的指针(二级指针,用于修改指针值)
* @param proc_len 进程数量的指针
* @return int 成功返回0,失败返回-1
* @note 设计模式:工厂方法,创建进程配置对象数组
* 数据流结构:分配连续内存存储所有进程配置
*/
static int get_process_cfg(js_process_t **process, int *proc_len)
{
/** 计算总进程数 */
int main_len = cJSON_GetArraySize(main_root); /**< main进程数组大小 */
int fact_len = cJSON_GetArraySize(fact_root); /**< factory进程数组大小 */
*proc_len = main_len + fact_len; /**< 总进程数 */
if (*proc_len == 0) /**< 没有配置任何进程 */
return 0; /**< 直接成功返回(空配置) */
/** 分配进程配置结构体数组内存 */
js_process_t *proc = (js_process_t *)malloc(*proc_len * sizeof(js_process_t)); /**< 动态分配内存 */
if (proc == NULL) /**< 内存分配失败 */
{
*proc_len = 0; /**< 长度清零 */
*process = NULL; /**< 指针置空 */
printf("malloc process buffer error !!\n"); /**< 打印错误信息 */
return -1; /**< 返回错误 */
}
/** 分别解析main和factory进程 */
if (main_len > 0) /**< 存在main进程配置 */
{
main_len = get_process_nodes(main_root, proc, main_len); /**< 解析main进程,返回实际有效数 */
}
if (fact_len > 0) /**< 存在factory进程配置 */
{
fact_len = get_process_nodes(fact_root, &proc[main_len], fact_len); /**< 解析factory进程,从数组中间开始 */
}
*process = proc; /**< 设置输出指针 */
*proc_len = main_len + fact_len; /**< 更新实际总进程数 */
/** 调试信息(已注释) */
// int index = 0;
// for (index = 0; index < *proc_len; index++)
// {
// printf("path = %s \n", (*process)[index].path );
// printf("name = %s \n", (*process)[index].name );
// printf("arg = %s \n", (*process)[index].arg );
// printf("wait = %d \n", (*process)[index].wait );
// printf("type = 0x%X \n", (*process)[index].type );
// }
return 0; /**< 成功返回 */
}
/**
* @brief 释放进程节点配置内存
* @param process 进程配置数组指针的指针
* @return int 总是返回0
*/
static int release_process_cfg(js_process_t **process)
{
free(*process); /**< 释放进程数组内存 */
*process = NULL; /**< 指针置空 */
return 0; /**< 成功返回 */
}
/**
* @brief 解析备份节点配置
* @param backup 备份配置数组指针的指针
* @param bak_len 备份数量的指针
* @return int 成功返回0,失败返回-1
*/
static int get_backup_cfg(js_backup_t **backup, int *bak_len)
{
if (back_root == NULL) /**< backup节点不存在 */
return -1; /**< 返回错误 */
*bak_len = cJSON_GetArraySize(back_root); /**< 获取备份数组大小 */
if (*bak_len == 0) /**< 没有备份配置 */
return 0; /**< 直接成功返回(空配置) */
/** 分配备份配置结构体数组内存 */
*backup = (js_backup_t *)malloc(*bak_len * sizeof(js_backup_t)); /**< 动态分配内存 */
memset(*backup, 0, *bak_len * sizeof(js_backup_t)); /**< 清零初始化,数据流:安全初始化 */
if (*backup == NULL) /**< 内存分配失败 */
{
printf("malloc backup buffer error !!\n"); /**< 打印错误信息 */
return -1; /**< 返回错误 */
}
/** 解析备份节点数组 */
*bak_len = get_backup_nodes(back_root, *backup, *bak_len); /**< 解析实际有效项数 */
return 0; /**< 成功返回 */
}
/**
* @brief 释放备份节点配置内存
* @param bakup 备份配置数组指针的指针
* @return int 总是返回0
*/
static int release_backup_cfg(js_backup_t **bakup)
{
if (*bakup == NULL) /**< 内存已释放或未分配 */
return 0; /**< 直接返回 */
free(*bakup); /**< 释放备份数组内存 */
*bakup = NULL; /**< 指针置空 */
return 0; /**< 成功返回 */
}
/**
* @brief 解析阈值配置节点
* @param node 阈值JSON节点指针
* @param threshold 阈值配置结构体指针
* @return int 总是返回0
* @note 设计模式:数据转换器,将JSON数据转换为C结构体
*/
static int get_threshold_node(cJSON *node, js_threshold_t *threshold)
{
if (node == NULL) /**< 阈值节点不存在 */
{
memset(threshold, 0, sizeof(js_threshold_t)); /**< 清零初始化阈值配置 */
return 0; /**< 返回成功(使用默认值) */
}
/** 从JSON节点读取各字段值 */
threshold->cpu = cJSON_GetObjectItem(node, "cpu")->valueint; /**< CPU阈值(百分比) */
threshold->heap = cJSON_GetObjectItem(node, "heap")->valueint; /**< 堆内存阈值(KB) */
threshold->handler = cJSON_GetObjectItem(node, "handler")->valueint; /**< 处理方式 */
threshold->where = (int)strtol(cJSON_GetObjectItem(node, "where")->valuestring, NULL, 16); /**< 监控位置(十六进制字符串转整数) */
/** 调试信息(已注释) */
// printf("threshold->cpu = %d \n", threshold->cpu );
// printf("threshold->heap = %d \n", threshold->heap );
// printf("threshold->handler = %d \n", threshold->handler);
// printf("threshold->where = %d \n", threshold->where );
return 0; /**< 成功返回 */
}
/**
* @brief 解析进程数组节点
* @param array 进程数组JSON节点指针
* @param process 进程配置结构体数组指针
* @param len 预期解析的进程数量
* @return int 实际解析的有效进程数量
* @note 设计模式:迭代器模式,遍历JSON数组
* 性能分析:对每个进程进行文件可执行性检查,可能涉及磁盘I/O
*/
static int get_process_nodes(cJSON *array, js_process_t *process, int len)
{
int index = 0; /**< 实际有效进程计数器 */
int i; /**< 循环索引 */
for (i = 0; i < len; i++) /**< 遍历JSON数组 */
{
cJSON *node = cJSON_GetArrayItem(array, i); /**< 获取第i个数组元素 */
/** 读取基本字段(注意:valuestring指向JSON字符串,不复制) */
process[index].path = cJSON_GetObjectItem(node, "path")->valuestring; /**< 路径字符串 */
process[index].name = cJSON_GetObjectItem(node, "name")->valuestring; /**< 程序名字符串 */
/** 检查可执行文件是否存在且可执行 */
char path[128] = {0}; /**< 完整路径缓冲区,数据流:固定大小128字节 */
sprintf(path, "%s/%s", process[index].path, process[index].name); /**< 拼接完整路径 */
if (access(path, X_OK) < 0) /**< 检查文件可执行权限 */
continue; /**< 跳过不可执行的文件 */
/** 读取其他字段 */
process[index].wait = cJSON_GetObjectItem(node, "wait")->valueint; /**< 等待时间 */
process[index].type = (int)strtol(cJSON_GetObjectItem(node, "type")->valuestring, NULL, 16); /**< 进程类型(十六进制字符串转整数) */
/** 解析参数数组 */
cJSON *arg = cJSON_GetObjectItem(node, "arg"); /**< 获取arg节点 */
if (arg->type == cJSON_Array) /**< arg是数组类型 */
{
process[index].arg_len = cJSON_GetArraySize(arg); /**< 获取数组大小 */
int arg_index; /**< 参数数组索引 */
for (arg_index = 0; arg_index < process[index].arg_len; arg_index++) /**< 遍历参数数组 */
process[index].arg[arg_index] = cJSON_GetArrayItem(arg, arg_index)->valuestring; /**< 获取参数值 */
}
else /**< arg是单个字符串 */
{
process[index].arg_len = 1; /**< 参数长度为1 */
process[index].arg[0] = cJSON_GetObjectItem(node, "arg")->valuestring; /**< 获取参数字符串 */
}
/** 解析阈值配置 */
get_threshold_node(cJSON_GetObjectItem(node, "threshold"), &process[index].threshold); /**< 解析阈值节点 */
/** 调试信息(已注释) */
// printf("path = %s \n", process[index].path );
// printf("name = %s \n", process[index].name );
// int arg_index;
// for (arg_index = 0; arg_index < process[index].arg_len; arg_index++)
// printf("arg[%d] = %s \n", arg_index, process[index].arg[arg_index]);
// printf("wait = %d \n", process[index].wait );
// printf("type = 0x%X \n", process[index].type );
index++; /**< 增加有效进程计数 */
}
return index; /**< 返回实际解析的有效进程数 */
}
/**
* @brief 解析shell命令数组节点
* @param array shell数组JSON节点指针
* @param shell shell配置结构体数组指针
* @param len 预期解析的shell命令数量
* @return int 实际解析的有效shell命令数量
*/
static int get_shell_nodes(cJSON *array, js_shell_t *shell, int len)
{
int index = 0; /**< 实际有效shell命令计数器 */
int i; /**< 循环索引 */
for (i = 0; i < len; i++) /**< 遍历JSON数组 */
{
cJSON *node = cJSON_GetArrayItem(array, i); /**< 获取第i个数组元素 */
shell[index].valid = cJSON_GetObjectItem(node, "valid")->valueint; /**< 有效性标志 */
if (shell[index].valid == false) /**< 无效的shell命令 */
continue; /**< 跳过 */
/** 根据命令类型字符串'S'或'K'设置类型常量 */
shell[index].type = cJSON_GetObjectItem(node, "type")->valuestring[0] == 'S'? TYPE_SCRIPT_S:TYPE_SCRIPT_K;
shell[index].cmd = cJSON_GetObjectItem(node, "command")->valuestring; /**< 命令字符串 */
index++; /**< 增加有效命令计数 */
// printf("[%c-%X] %s \n", shell[index].valid?'V':'X', shell[index].type, shell[index].cmd);
}
return index; /**< 返回实际解析的有效shell命令数 */
}
/**
* @brief 解析备份数组节点
* @param array 备份数组JSON节点指针
* @param bakup 备份配置结构体数组指针
* @param len 预期解析的备份项数量
* @return int 实际解析的有效备份项数量
*/
static int get_backup_nodes(cJSON *array, js_backup_t *bakup, int len)
{
int index = 0; /**< 实际有效备份项计数器 */
int i; /**< 循环索引 */
for (i = 0; i < len; i++) /**< 遍历JSON数组 */
{
cJSON *node = cJSON_GetArrayItem(array, i); /**< 获取第i个数组元素 */
bakup[index].path = cJSON_GetObjectItem(node, "path")->valuestring; /**< 源文件路径 */
bakup[index].file = cJSON_GetObjectItem(node, "file")->valuestring; /**< 源文件名 */
bakup[index].backup_path = cJSON_GetObjectItem(node, "backup-path")->valuestring; /**< 备份路径 */
index++; /**< 增加有效备份项计数 */
}
return index; /**< 返回实际解析的有效备份项数 */
}
/**
* @brief 性能测试主函数(条件编译,实际未使用)
* @note 设计模式:性能测试桩,用于评估配置文件解析性能
* 性能分析:测试结果显示解析耗时约3.76ms(平均值)
*/
#if 0 /**< 条件编译:性能测试代码,实际未使用 */
#include <sys/time.h> /**< 时间头文件,用于性能测试 */
#define TEST_CNT 10000 /**< 测试循环次数 */
int main()
{
int cnt0 = TEST_CNT; /**< 测试计数器1 */
int cnt1 = TEST_CNT; /**< 测试计数器2(用于计算平均值) */
struct timeval start, end; /**< 时间戳结构体 */
gettimeofday(&start, NULL); /**< 记录开始时间 */
/** 性能测试循环 */
while(cnt0--) /**< 重复测试TEST_CNT次 */
{
demofcg_t cfg; /**< 配置结构体 */
memset(&cfg, 0, sizeof(demofcg_t)); /**< 清零初始化 */
get_demo_config(&cfg); /**< 解析配置文件 */
release_demo_config(&cfg); /**< 释放配置内存 */
}
gettimeofday(&end, NULL); /**< 记录结束时间 */
/** 计算并打印平均耗时 */
printf("useTime = %.2lf ms\n",
((end.tv_sec - start.tv_sec)*1000.0+(end.tv_usec - start.tv_usec)/1000.0)/cnt1); /**< 计算平均毫秒数 */
return 0; /**< 测试完成 */
}
#endif /**< 结束条件编译块 */
3. checkapi.c/h - 校验算法
设计思想: 算法插件化 ├── 为什么条件编译? │ ├── MD5 vs CRC32 选择 │ ├── 性能vs安全性权衡 │ └── 编译时决策,运行时无开销 ├── 内联算法实现 │ ├── 避免外部依赖 │ ├── 控制代码大小 │ └── 性能优化(查表法) └── 文件流处理模式 ├── 固定缓冲区(4KB) ├── 流式读取/计算 └── 内存效率高
/**
* @file checkapi.c
* @brief 文件校验API实现文件,提供CRC32和MD5文件校验功能
*/
#include <stdio.h> /**< 标准输入输出头文件,提供fprintf、fread等函数 */
#include <stdlib.h> /**< 标准库头文件,提供exit等函数 */
#include <string.h> /**< 字符串处理头文件,提供memcpy、memset等函数 */
#include "checkapi.h" /**< 校验API头文件,包含函数声明 */
/** 类型定义别名,提高代码可读性 */
typedef unsigned int u32; /**< 32位无符号整数类型别名 */
typedef unsigned char uint8_t; /**< 8位无符号整数类型别名 */
typedef unsigned short int uint16_t; /**< 16位无符号整数类型别名 */
typedef unsigned int uint32_t; /**< 32位无符号整数类型别名 */
/**
* @defgroup MD5_IMPLEMENTATION MD5实现
* @brief MD5哈希算法完整实现(条件编译)
* @note 设计模式:模板方法模式,将算法分解为初始化、更新、结束步骤
* @{
*/
#ifdef USE_MD5 /**< 条件编译:如果定义了USE_MD5宏 */
/**
* @def 循环左移宏
* @param x 要移位的32位值
* @param n 移位位数
* @return 循环左移结果
*/
#define rol(x,n) ( ((x) << (n)) | ((x) >> (32-(n))) ) /**< 32位循环左移宏定义 */
/**
* @brief MD5上下文结构体
* @note 设计模式:状态保持器,维护MD5计算中间状态
* 数据流结构:92字节(4*4 + 4 + 64 + 4 = 92)
*/
typedef struct {
u32 A,B,C,D; /**< 链式变量(chaining variables),MD5的四个状态寄存器 */
u32 nblocks; /**< 已处理的完整块数(每个块64字节) */
unsigned char buf[64]; /**< 缓冲区,用于存储不足64字节的数据 */
int count; /**< 缓冲区中当前字节数 */
} MD5_CONTEXT;
/**
* @brief 初始化MD5上下文
* @param ctx MD5上下文结构体指针
* @note MD5初始向量(IV)定义在RFC 1321中
*/
static void md5_init( MD5_CONTEXT *ctx )
{
ctx->A = 0x67452301; /**< 初始化A寄存器 */
ctx->B = 0xefcdab89; /**< 初始化B寄存器 */
ctx->C = 0x98badcfe; /**< 初始化C寄存器 */
ctx->D = 0x10325476; /**< 初始化D寄存器 */
ctx->nblocks = 0; /**< 已处理块数清零 */
ctx->count = 0; /**< 缓冲区计数器清零 */
}
/** MD5逻辑函数定义(来自RFC 1321) */
#define FF(b, c, d) (d ^ (b & (c ^ d))) /**< 第一轮逻辑函数F */
#define FG(b, c, d) FF (d, b, c) /**< 第二轮逻辑函数G(实际上是F的重排) */
#define FH(b, c, d) (b ^ c ^ d) /**< 第三轮逻辑函数H */
#define FI(b, c, d) (c ^ (b | ~d)) /**< 第四轮逻辑函数I */
/**
* @brief MD5核心变换函数
* @param ctx MD5上下文结构体指针
* @param data 64字节数据块指针
* @note 实现MD5算法的64轮变换,每轮16次操作
*/
static void transform( MD5_CONTEXT *ctx, unsigned char *data )
{
u32 correct_words[16]; /**< 16个32位字的工作数组 */
u32 A = ctx->A; /**< 加载A寄存器到局部变量 */
u32 B = ctx->B; /**< 加载B寄存器到局部变量 */
u32 C = ctx->C; /**< 加载C寄存器到局部变量 */
u32 D = ctx->D; /**< 加载D寄存器到局部变量 */
u32 *cwp = correct_words; /**< 工作数组指针 */
memcpy( correct_words, data, 64 ); /**< 将64字节数据复制到32位字数组 */
/**
* @def 第一轮操作宏(简化版)
* @note 用于MD5第一轮(共16次操作)的宏定义
*/
#define OP(a, b, c, d, s, T) \
do \
{ \
a += FF (b, c, d) + (*cwp++) + T; /**< a += F(b,c,d) + M[i] + T[i] */ \
a = rol(a, s); /**< 循环左移s位 */ \
a += b; /**< a += b */ \
} \
while (0)
/* Before we start, one word about the strange constants.
They are defined in RFC 1321 as
T[i] = (int) (4294967296.0 * fabs (sin (i))), i=1..64
这些常量在RFC 1321中定义为 T[i] = (int) (4294967296.0 * fabs (sin (i))), i=1..64
*/
/* Round 1. 第一轮(16次操作) */
OP (A, B, C, D, 7, 0xd76aa478); /**< 操作1 */
OP (D, A, B, C, 12, 0xe8c7b756); /**< 操作2 */
OP (C, D, A, B, 17, 0x242070db); /**< 操作3 */
OP (B, C, D, A, 22, 0xc1bdceee); /**< 操作4 */
OP (A, B, C, D, 7, 0xf57c0faf); /**< 操作5 */
OP (D, A, B, C, 12, 0x4787c62a); /**< 操作6 */
OP (C, D, A, B, 17, 0xa8304613); /**< 操作7 */
OP (B, C, D, A, 22, 0xfd469501); /**< 操作8 */
OP (A, B, C, D, 7, 0x698098d8); /**< 操作9 */
OP (D, A, B, C, 12, 0x8b44f7af); /**< 操作10 */
OP (C, D, A, B, 17, 0xffff5bb1); /**< 操作11 */
OP (B, C, D, A, 22, 0x895cd7be); /**< 操作12 */
OP (A, B, C, D, 7, 0x6b901122); /**< 操作13 */
OP (D, A, B, C, 12, 0xfd987193); /**< 操作14 */
OP (C, D, A, B, 17, 0xa679438e); /**< 操作15 */
OP (B, C, D, A, 22, 0x49b40821); /**< 操作16 */
#undef OP /**< 取消第一轮操作宏定义 */
/**
* @def 通用操作宏(用于第2-4轮)
* @note 支持不同逻辑函数的通用操作宏
*/
#define OP(f, a, b, c, d, k, s, T) \
do \
{ \
a += f (b, c, d) + correct_words[k] + T; /**< a += f(b,c,d) + M[k] + T[i] */ \
a = rol(a, s); /**< 循环左移s位 */ \
a += b; /**< a += b */ \
} \
while (0)
/* Round 2. 第二轮(16次操作) */
OP (FG, A, B, C, D, 1, 5, 0xf61e2562); /**< 操作17 */
OP (FG, D, A, B, C, 6, 9, 0xc040b340); /**< 操作18 */
OP (FG, C, D, A, B, 11, 14, 0x265e5a51); /**< 操作19 */
OP (FG, B, C, D, A, 0, 20, 0xe9b6c7aa); /**< 操作20 */
OP (FG, A, B, C, D, 5, 5, 0xd62f105d); /**< 操作21 */
OP (FG, D, A, B, C, 10, 9, 0x02441453); /**< 操作22 */
OP (FG, C, D, A, B, 15, 14, 0xd8a1e681); /**< 操作23 */
OP (FG, B, C, D, A, 4, 20, 0xe7d3fbc8); /**< 操作24 */
OP (FG, A, B, C, D, 9, 5, 0x21e1cde6); /**< 操作25 */
OP (FG, D, A, B, C, 14, 9, 0xc33707d6); /**< 操作26 */
OP (FG, C, D, A, B, 3, 14, 0xf4d50d87); /**< 操作27 */
OP (FG, B, C, D, A, 8, 20, 0x455a14ed); /**< 操作28 */
OP (FG, A, B, C, D, 13, 5, 0xa9e3e905); /**< 操作29 */
OP (FG, D, A, B, C, 2, 9, 0xfcefa3f8); /**< 操作30 */
OP (FG, C, D, A, B, 7, 14, 0x676f02d9); /**< 操作31 */
OP (FG, B, C, D, A, 12, 20, 0x8d2a4c8a); /**< 操作32 */
/* Round 3. 第三轮(16次操作) */
OP (FH, A, B, C, D, 5, 4, 0xfffa3942); /**< 操作33 */
OP (FH, D, A, B, C, 8, 11, 0x8771f681); /**< 操作34 */
OP (FH, C, D, A, B, 11, 16, 0x6d9d6122); /**< 操作35 */
OP (FH, B, C, D, A, 14, 23, 0xfde5380c); /**< 操作36 */
OP (FH, A, B, C, D, 1, 4, 0xa4beea44); /**< 操作37 */
OP (FH, D, A, B, C, 4, 11, 0x4bdecfa9); /**< 操作38 */
OP (FH, C, D, A, B, 7, 16, 0xf6bb4b60); /**< 操作39 */
OP (FH, B, C, D, A, 10, 23, 0xbebfbc70); /**< 操作40 */
OP (FH, A, B, C, D, 13, 4, 0x289b7ec6); /**< 操作41 */
OP (FH, D, A, B, C, 0, 11, 0xeaa127fa); /**< 操作42 */
OP (FH, C, D, A, B, 3, 16, 0xd4ef3085); /**< 操作43 */
OP (FH, B, C, D, A, 6, 23, 0x04881d05); /**< 操作44 */
OP (FH, A, B, C, D, 9, 4, 0xd9d4d039); /**< 操作45 */
OP (FH, D, A, B, C, 12, 11, 0xe6db99e5); /**< 操作46 */
OP (FH, C, D, A, B, 15, 16, 0x1fa27cf8); /**< 操作47 */
OP (FH, B, C, D, A, 2, 23, 0xc4ac5665); /**< 操作48 */
/* Round 4. 第四轮(16次操作) */
OP (FI, A, B, C, D, 0, 6, 0xf4292244); /**< 操作49 */
OP (FI, D, A, B, C, 7, 10, 0x432aff97); /**< 操作50 */
OP (FI, C, D, A, B, 14, 15, 0xab9423a7); /**< 操作51 */
OP (FI, B, C, D, A, 5, 21, 0xfc93a039); /**< 操作52 */
OP (FI, A, B, C, D, 12, 6, 0x655b59c3); /**< 操作53 */
OP (FI, D, A, B, C, 3, 10, 0x8f0ccc92); /**< 操作54 */
OP (FI, C, D, A, B, 10, 15, 0xffeff47d); /**< 操作55 */
OP (FI, B, C, D, A, 1, 21, 0x85845dd1); /**< 操作56 */
OP (FI, A, B, C, D, 8, 6, 0x6fa87e4f); /**< 操作57 */
OP (FI, D, A, B, C, 15, 10, 0xfe2ce6e0); /**< 操作58 */
OP (FI, C, D, A, B, 6, 15, 0xa3014314); /**< 操作59 */
OP (FI, B, C, D, A, 13, 21, 0x4e0811a1); /**< 操作60 */
OP (FI, A, B, C, D, 4, 6, 0xf7537e82); /**< 操作61 */
OP (FI, D, A, B, C, 11, 10, 0xbd3af235); /**< 操作62 */
OP (FI, C, D, A, B, 2, 15, 0x2ad7d2bb); /**< 操作63 */
OP (FI, B, C, D, A, 9, 21, 0xeb86d391); /**< 操作64 */
/* Put checksum in context given as argument. 将校验和存入上下文 */
ctx->A += A; /**< 更新上下文A寄存器 */
ctx->B += B; /**< 更新上下文B寄存器 */
ctx->C += C; /**< 更新上下文C寄存器 */
ctx->D += D; /**< 更新上下文D寄存器 */
}
/**
* @brief MD5数据更新函数
* @param hd MD5上下文指针
* @param inbuf 输入数据缓冲区
* @param inlen 输入数据长度
* @note 处理任意长度的数据,以64字节为单位进行变换
*/
static void md5_write( MD5_CONTEXT *hd, unsigned char *inbuf, size_t inlen)
{
/** 如果缓冲区已满(64字节),则执行变换 */
if( hd->count == 64 ) { /* flush the buffer 刷新缓冲区 */
transform( hd, hd->buf ); /**< 对缓冲区数据执行MD5变换 */
hd->count = 0; /**< 重置缓冲区计数器 */
hd->nblocks++; /**< 增加已处理块数 */
}
if( !inbuf ) /**< 输入缓冲区为空(用于刷新) */
return;
/** 如果缓冲区有部分数据,先填满缓冲区 */
if( hd->count ) {
for( ; inlen && hd->count < 64; inlen-- )
hd->buf[hd->count++] = *inbuf++; /**< 复制数据到缓冲区 */
md5_write( hd, NULL, 0 ); /**< 递归调用以处理填满的缓冲区 */
if( !inlen ) /**< 所有数据已处理 */
return;
}
/** 处理完整的64字节块 */
while( inlen >= 64 ) {
transform( hd, inbuf ); /**< 直接对输入数据执行变换 */
hd->count = 0; /**< 重置缓冲区计数器 */
hd->nblocks++; /**< 增加已处理块数 */
inlen -= 64; /**< 减少剩余数据长度 */
inbuf += 64; /**< 移动输入缓冲区指针 */
}
/** 处理剩余的不完整块 */
for( ; inlen && hd->count < 64; inlen-- )
hd->buf[hd->count++] = *inbuf++; /**< 复制剩余数据到缓冲区 */
}
/**
* @brief MD5最终处理函数
* @param hd MD5上下文指针
* @note 添加填充位和长度信息,生成最终的128位MD5值
*/
static void md5_final( MD5_CONTEXT *hd )
{
u32 t, msb, lsb; /**< 临时变量用于位操作 */
unsigned char *p; /**< 缓冲区指针 */
md5_write(hd, NULL, 0); /* flush 刷新缓冲区 */;
/** 计算原始消息的总位数(乘以8) */
t = hd->nblocks; /**< 获取已处理的完整块数 */
lsb = t << 6; /**< 乘以64转换为字节数(左移6位) */
msb = t >> 26; /**< 高位部分(用于处理溢出) */
t = lsb; /**< 保存低位 */
if( (lsb += hd->count) < t ) /**< 加上缓冲区字节数,检查是否溢出 */
msb++; /**< 溢出则高位加1 */
t = lsb; /**< 保存新的低位 */
lsb <<= 3; /**< 乘以8转换为位数(左移3位) */
msb <<= 3; /**< 高位也乘以8 */
msb |= t >> 29; /**< 将低位的溢出位合并到高位 */
/** 添加填充位(根据RFC 1321规范) */
if( hd->count < 56 ) { /* enough room 有足够空间 */
hd->buf[hd->count++] = 0x80; /* pad 填充0x80 */
while( hd->count < 56 )
hd->buf[hd->count++] = 0; /* pad 填充0 */
}
else { /* need one extra block 需要额外一个块 */
hd->buf[hd->count++] = 0x80; /* pad character 填充字符0x80 */
while( hd->count < 64 )
hd->buf[hd->count++] = 0; /**< 填充0直到块满 */
md5_write(hd, NULL, 0); /* flush 刷新 */;
memset(hd->buf, 0, 56 ); /* fill next block with zeroes 用零填充下一个块 */
}
/** 附加64位消息长度(小端序) */
hd->buf[56] = lsb ; /**< 长度低位字节0 */
hd->buf[57] = lsb >> 8; /**< 长度低位字节1 */
hd->buf[58] = lsb >> 16; /**< 长度低位字节2 */
hd->buf[59] = lsb >> 24; /**< 长度低位字节3 */
hd->buf[60] = msb ; /**< 长度高位字节0 */
hd->buf[61] = msb >> 8; /**< 长度高位字节1 */
hd->buf[62] = msb >> 16; /**< 长度高位字节2 */
hd->buf[63] = msb >> 24; /**< 长度高位字节3 */
transform( hd, hd->buf ); /**< 对填充后的块执行最终变换 */
/** 将结果从寄存器复制到缓冲区(小端序) */
p = hd->buf;
#ifdef BIG_ENDIAN_HOST /**< 大端系统处理 */
#define X(a) do { *p++ = hd-> a ; *p++ = hd-> a >> 8; \
*p++ = hd-> a >> 16; *p++ = hd-> a >> 24; } while(0)
#else /* little endian 小端系统 */
#define X(a) do { *(u32*)p = hd-> a ; p += 4; } while(0) /**< 直接按32位复制 */
#endif
X(A); /**< 复制A寄存器 */
X(B); /**< 复制B寄存器 */
X(C); /**< 复制C寄存器 */
X(D); /**< 复制D寄存器 */
#undef X /**< 取消宏定义 */
}
#endif /**< 结束条件编译块 */
/** @} */ /* MD5_IMPLEMENTATION */
/**
* @brief CRC32查找表(预计算好的256个值)
* @note 数据流结构:256*4=1024字节,使用标准CRC32多项式0xEDB88320
*/
static uint32_t crc32Table[256] ={
0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3,
0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91,
0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5,
0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b,
0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f,
0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d,
0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01,
0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457,
0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb,
0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9,
0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad,
0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683,
0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7,
0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5,
0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79,
0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f,
0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713,
0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21,
0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45,
0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db,
0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf,
0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d
};
/**
* @defgroup PERFORMANCE_TEST 性能测试相关
* @brief 用于性能测试的代码(条件编译)
* @{
*/
#ifdef ENABLE_CHECK_TEST /**< 条件编译:性能测试使能 */
#include <sys/time.h> /**< 时间头文件,用于性能测试 */
/**
* @brief 计算函数接口所耗费的时间(单位:ms)
* @param tag 时间戳标签
* @note 与sysdemo.h中的函数类似,但此处用于性能测试
*/
static inline void cal_timestamp_ms(char *tag)
{
static struct timeval start = {0}; /**< 静态变量保存开始时间 */
static struct timeval end = {0}; /**< 静态变量保存结束时间 */
gettimeofday(&end, NULL); /**< 获取当前时间 */
if (start.tv_sec != 0 && start.tv_usec != 0) /**< 不是第一次调用 */
{
unsigned long useTime = (end.tv_sec-start.tv_sec)*1000+(end.tv_usec-start.tv_usec)/1000; /**< 计算时间差(毫秒) */
printf("%s use time: %lu ms\n", tag, useTime); /**< 打印耗时 */
}
start.tv_sec = end.tv_sec; start.tv_usec = end.tv_usec; /**< 更新开始时间 */
}
#endif
/** @} */ /* PERFORMANCE_TEST */
/**
* @brief 对文件进行CRC32校验
* @param filename 需要校验的文件名
* @param crcval 返回CRC32校验值
* @return int 返回0表示成功,非0表示失败
* @note 设计模式:流处理器,逐块读取文件并计算CRC32
* 性能分析:使用4KB缓冲区,平衡内存使用和I/O效率
* 参考:http://www.ip33.com/crc.html CRC-32
*/
int crc32(char *filename, unsigned int *crcval)
{
FILE *fp; /**< 文件指针 */
char buffer[4096]; /**< 4KB读取缓冲区,数据流:固定大小,平衡性能和内存 */
size_t n; /**< 实际读取的字节数 */
/** 打开文件 */
fp = fopen (filename, "rb"); /**< 以二进制只读模式打开文件 */
if (!fp) /**< 文件打开失败 */
{
fprintf (stderr, "can't open %s\n", filename); /**< 打印错误信息到标准错误 */
return -1; /**< 返回错误 */
}
/** 性能测试(条件编译) */
#ifdef ENABLE_CHECK_TEST
int j; /**< 循环计数器 */
cal_timestamp_ms(NULL); /**< 记录开始时间 */
for (j = 0; j < 1000; j++) /**< 重复测试1000次 */
{
#endif
/** CRC32计算(初始值为0xffffffff) */
uint32_t crc = 0xffffffff; /**< CRC32初始值 */
while ((n = fread (buffer, 1, sizeof buffer, fp))) /**< 循环读取文件,直到文件结束 */
{
int i; /**< 字节索引 */
for (i = 0; i < n; i++) /**< 处理每个字节 */
{
/** CRC32查表算法:crc = (crc >> 8) ^ table[(crc ^ byte) & 0xff] */
crc = (crc >> 8)^(crc32Table[(crc ^ buffer[i])&0xff]); /**< 查表计算CRC32 */
}
}
crc = crc^0xffffffff; /**< 最终异或值(取反) */
*crcval = crc; /**< 将结果存入输出参数 */
#ifdef ENABLE_CHECK_TEST
fseek(fp, 0, SEEK_SET); /**< 重置文件指针到开头,用于下一次循环 */
}
printf ("crc32: %08x %s", *crcval, filename); /**< 打印CRC32结果和文件名 */
cal_timestamp_ms(" "); /**< 记录结束时间并打印耗时 */
#endif
fclose (fp); /**< 关闭文件 */
return 0; /**< 成功返回 */
}
#ifdef USE_MD5 /**< 条件编译:MD5功能 */
/**
* @brief 对文件进行MD5SUM校验
* @param filename 需要校验的文件名
* @param md5val 返回MD5SUM校验值(16字节数组)
* @return int 返回0表示成功,非0表示失败
* @note 设计模式:流处理器,逐块读取文件并计算MD5
* 性能分析:使用4KB缓冲区,与CRC32相同的I/O模式
*/
int md5sum(char *filename, unsigned char md5val[16])
{
FILE *fp; /**< 文件指针 */
unsigned char buffer[4096]; /**< 4KB读取缓冲区 */
size_t n; /**< 实际读取的字节数 */
MD5_CONTEXT ctx; /**< MD5上下文结构体 */
/** 打开文件 */
fp = fopen (filename, "rb"); /**< 以二进制只读模式打开文件 */
if (!fp) /**< 文件打开失败 */
{
fprintf (stderr, "can't open %s\n", filename); /**< 打印错误信息 */
return -1; /**< 返回错误 */
}
/** 性能测试(条件编译) */
#ifdef ENABLE_CHECK_TEST
int i; /**< 循环计数器 */
cal_timestamp_ms(NULL); /**< 记录开始时间 */
for (i = 0; i < 1000; i++) /**< 重复测试1000次 */
{
#endif
/** MD5计算 */
md5_init (&ctx); /**< 初始化MD5上下文 */
while ((n = fread(buffer, 1, sizeof(buffer), fp))) /**< 循环读取文件 */
{
md5_write (&ctx, buffer, n); /**< 更新MD5计算 */
}
md5_final (&ctx); /**< 完成MD5计算 */
#ifdef ENABLE_CHECK_TEST
fseek(fp, 0, SEEK_SET); /**< 重置文件指针到开头 */
}
printf ("md5sum: "); /**< 打印MD5结果 */
for (i=0; i < 16; i++)
printf ("%02x", ctx.buf[i]); /**< 以十六进制打印每个字节 */
printf (" "); /**< 打印分隔符 */
cal_timestamp_ms(filename); /**< 记录结束时间并打印耗时 */
#endif
memcpy(md5val, ctx.buf, 16); /**< 将MD5结果复制到输出参数 */
fclose (fp); /**< 关闭文件 */
return 0; /**< 成功返回 */
}
#endif /**< 结束条件编译块 */
#ifdef ENABLE_CHECK_TEST /**< 条件编译:性能测试主函数 */
/**
* @brief 性能测试主函数
* @param argc 命令行参数个数
* @param argv 命令行参数数组
* @return int 总是返回0
* @note 设计模式:测试桩,用于评估CRC32和MD5的性能差异
* 性能分析结论:128KB文件,相同CPU占用率79%情况下,
* MD5耗时28.8秒,CRC32耗时29秒,两者性能相当
*/
int main (int argc, char **argv)
{
if (argc < 2) /**< 参数检查 */
{
fprintf (stderr, "usage: ./checkapi filenames\n"); /**< 打印使用说明 */
exit (1); /**< 参数错误退出 */
}
int tmpc = argc; /**< 参数计数器副本 */
char **tmpv = argv; /**< 参数数组副本 */
/** 测试MD5(如果启用) */
#ifdef USE_MD5
for (tmpc--, tmpv++; tmpc; tmpv++, tmpc--) /**< 遍历所有文件名参数 */
{
char md5val[16]; /**< MD5结果缓冲区 */
md5sum(*tmpv, md5val); /**< 计算MD5 */
}
tmpc = argc; /**< 重置参数计数器 */
tmpv = argv; /**< 重置参数数组指针 */
#endif
/** 测试CRC32 */
for (tmpc--, tmpv++; tmpc; tmpv++, tmpc--) /**< 遍历所有文件名参数 */
{
int crc; /**< CRC32结果变量 */
crc32(*tmpv, &crc); /**< 计算CRC32 */
}
return 0; /**< 测试完成 */
}
#endif /**< 结束条件编译块 */
4. file_inotify.c + backup_db.h - 文件监控
设计思想: 事件驱动架构 ├── 线程分离设计 │ ├── 主线程专注进程管理 │ ├── 备份线程专注文件监控 │ └── 通过全局变量通信 ├── inotify + epoll 组合 │ ├── inotify: 文件变化通知 │ ├── epoll: 高效事件等待 │ └── 水平触发(LT)模式,简单可靠 └── 双级备份策略 ├── 1级: 快速响应,保持最新 ├── 2级: 可靠存储,完整性验证 └── 压缩存储,节省空间
/**
* @file file_inotify.c
* @brief 文件监控与备份实现文件,使用inotify实时监控文件变化并进行备份
*/
#include <stdio.h> /**< 标准输入输出头文件,提供printf等函数 */
#include <stdlib.h> /**< 标准库头文件,提供free等函数 */
#include <string.h> /**< 字符串处理头文件,提供memset、snprintf等函数 */
#include <sys/inotify.h> /**< inotify文件监控头文件,提供inotify相关结构体和函数 */
#include <sys/types.h> /**< 系统类型定义头文件,提供基本数据类型定义 */
#include <sys/epoll.h> /**< epoll I/O多路复用头文件,提供epoll相关函数 */
#include <pthread.h> /**< 线程头文件,提供pthread_create等线程函数 */
#include <sys/stat.h> /**< 文件状态头文件,提供stat结构体和函数 */
#include <sys/wait.h> /**< 进程等待头文件,提供wait相关函数 */
#include "db_api.h" /**< 数据库API头文件,提供数据库备份和完整性检查函数 */
#include "sysdemo.h" /**< 系统演示主头文件,包含系统函数和宏定义 */
#include "checkapi.h" /**< 检查API头文件,提供CRC32校验函数 */
#include "backup_db.h" /**< 数据库备份头文件,包含备份任务接口和常量 */
/**
* @brief 文件监控结构体
* @note 设计模式:状态保持器,维护每个被监控文件的状态信息
* 数据流结构:大小约32字节(假设指针8字节,int 4字节,6个int=24字节,3个指针=24字节,实际更大)
*/
typedef struct
{
int id; /**< inotify监测标识(watch descriptor),由inotify_add_watch返回 */
int flag[2]; /**< 文件监测标志位数组:flag[0]=1级备份标志,flag[1]=2级备份标志 */
int cnt[2]; /**< 文件备份次数记录数组:cnt[0]=1级备份次数,cnt[1]=2级备份次数 */
char *path; /**< 监测的文件所在目录路径 */
char *file; /**< 监测的文件名 */
char *bakup_path;/**< 备份文件存储路径 */
int err_cnt; /**< 拷贝失败的错误次数计数器,低8位为1级错误,高8位为2级错误 */
}file_watch_t;
/**
* @brief 文件监控管理器结构体
* @note 设计模式:管理器模式,集中管理所有文件监控资源
* 数据流结构:大小约24字节(假设指针8字节)
*/
typedef struct {
int ntfd; /**< inotify 文件描述符 */
int epfd; /**< epoll 文件描述符 */
file_watch_t *wd; /**< 监测信息数组指针 */
int wd_len; /**< 监测的文件数量 */
}file_inotify_t;
/** 函数声明 */
void *run_database_backup_thread(void *arg); /**< 数据库备份线程函数 */
static int add_files_to_inotify(demofcg_t cfg, file_inotify_t *fitfy); /**< 初始化inotify监控 */
static int release_inotify(file_inotify_t *fitfy); /**< 释放inotify资源 */
static struct epoll_event *get_epoll_event(struct epoll_event events[MAX_EPOLL_EVENT_LEN], int event_num, int efd); /**< 获取epoll事件 */
static int compare_file(char *file, char *check_file, char val[16]); /**< 文件比较函数 */
static int gzip_db(char *file, char gzip_file[MAX_BUFFER_LEN]); /**< 数据库压缩函数 */
static void partition_error_exit(char *path); /**< 分区错误退出函数 */
static bool is_partition_ok(char *path); /**< 分区状态检查函数 */
/** 全局变量 */
static pthread_t bak_tid = 0; /**< 备份线程ID,0表示线程未创建 */
static file_inotify_t fitfy; /**< 文件监控管理器实例 */
/**
* @brief 执行备份任务线程(主入口函数)
* @param cfg 配置结构体,包含备份文件列表
* @return int 成功返回0,失败返回-1
* @note 设计模式:工厂方法,创建并启动备份监控线程
* 性能分析:初始化inotify和epoll,创建独立线程进行备份监控
*/
int run_backup_task(demofcg_t cfg)
{
/** 初始化inotify文件监控 */
if (add_files_to_inotify(cfg, &fitfy) != 0) /**< 将配置中的文件添加到inotify监控 */
{
printf("init backup_file_task error !!\n"); /**< 初始化失败打印错误 */
partition_error_exit("init-backup_file_task"); /**< 分区错误退出 */
}
printf("init backup_file_task ok !!\n"); /**< 初始化成功打印信息 */
/** 创建备份线程 */
if(pthread_create(&bak_tid, NULL, run_database_backup_thread, NULL)) /**< 创建数据库备份线程 */
{
printf("sysdemo creat backup_file_thread error !!\n"); /**< 线程创建失败打印错误 */
partition_error_exit("start-backup_file_task"); /**< 分区错误退出 */
}
printf("sysdemo creat backup_file_thread ok !!\n"); /**< 线程创建成功打印信息 */
return 0; /**< 成功返回 */
}
/**
* @brief 数据库文件轮询备份线程(核心业务逻辑)
* @param arg 线程参数(未使用)
* @return void* 线程返回值(总是NULL)
* @note 备份流程说明:
* (1)方式:双备份机制
* (2)1级备份:主要是为了尽可能保持与源文件处于同步状态,(copy current->newbakup)
* (3)2级备份:主要是为了保存相对于1级备份更为完整可靠的文件;
* 先将1级保留下的文件newbakup进行完整性校验,如果完整性校验不通过,则删除1级所备份的文件;(check newbakup)
* 再与2级当前的文件oldbakup进行md5/crc32校验,(compare newbakup ?= oldbakup)
* 校验值相同则不拷贝文件,校验值不相同则将文件拷贝(copy newbakup->oldbakup)
* (4)分区检测:当拷贝失败的次数达到一定值时,则检测分区:
* 检测通过,则继续重新备份
* 检测不通过,则退出系统
* (5)备份结果:备份分区会存在 xxx 和 xxx.gz 文件;
* (6)还原备份:先尝试还原1级保存下来的文件,因为实时性较优;
* 1级还原失败,则还原2级保存下来的数据库,须先解压,再检验还原;
* 2级也还原失败,则使用默认的数据库文件。
* 设计模式:状态机模式,根据文件状态和错误计数决定备份策略
* 性能分析:使用epoll非阻塞等待,减少CPU占用,双级备份平衡实时性和可靠性
*/
void *run_database_backup_thread(void *arg)
{
struct epoll_event evt[MAX_EPOLL_EVENT_LEN]; /**< epoll事件数组,最大MAX_EPOLL_EVENT_LEN(10)个事件 */
long oldbakup_time = get_sysuptime(); /**< 2级备份时间基准点,初始为当前系统运行时间 */
int timeout_ms = BACKUP_INSPECTION_LEVEL_1*1000; /**< epoll等待超时时间,初始为1级检查间隔15秒 */
/** 主监控循环 */
while (1) /**< 无限循环,直到系统退出 */
{
int index; /**< 循环索引变量 */
memset(evt, 0, sizeof(struct epoll_event)*MAX_EPOLL_EVENT_LEN); /**< 清零事件数组 */
/** 等待epoll事件 */
int ret = epoll_wait(fitfy.epfd, evt, MAX_EPOLL_EVENT_LEN, timeout_ms); /**< 等待文件变化事件 */
if (ret < 0) /**< epoll错误 */
{
delay_s(BACKUP_INSPECTION_LEVEL_1); /**< 等待1级检查间隔时间 */
continue; /**< 继续循环 */
}
/** 处理inotify文件变化事件 */
if (ret > 0 && get_epoll_event(evt, ret, fitfy.ntfd) != NULL) /**< 有事件且是inotify文件描述符的事件 */
{
char recvbuf[MAX_INOTIFY_BUFFER_LEN] = {0}; /**< inotify事件接收缓冲区,大小1024字节 */
int recvlen = read(fitfy.ntfd, recvbuf, MAX_INOTIFY_BUFFER_LEN); /**< 读取inotify事件 */
if (recvlen <= 0) /**< 读取失败或没有数据 */
continue; /**< 继续循环 */
struct inotify_event *event = (struct inotify_event *)recvbuf; /**< 事件结构体指针 */
int loop = 0; /**< 缓冲区偏移量 */
/** 遍历所有事件 */
while (loop < recvlen) /**< 循环处理缓冲区中的所有事件 */
{
for (index = 0; index < fitfy.wd_len; index++) /**< 遍历所有监控的文件 */
{
if (event->wd != fitfy.wd[index].id) /**< 事件ID与监控ID不匹配 */
continue; /**< 跳过非目标文件 */
/** 文件修改事件 */
if (event->mask & IN_MODIFY) /**< 文件被修改 */
{
fitfy.wd[index].flag[0] = 1; /**< 设置1级备份标志位,可以执行1级备份 */
}
/** 严重文件系统事件:源文件被删除、宿主目录被卸载、被移动 */
else if (event->mask & (IN_DELETE_SELF | IN_UNMOUNT | IN_MOVE_SELF)) /**< 文件被删除、卸载或移动 */
{
char result[MAX_BUFFER_LEN] = {0}; /**< 错误信息缓冲区 */
char *ptr = "delete"; /**< 默认错误类型为删除 */
if (event->mask & IN_UNMOUNT) /**< 目录卸载事件 */
ptr = "unmount"; /**< 错误类型改为卸载 */
if (event->mask & IN_MOVE_SELF) /**< 文件移动事件 */
ptr = "moved"; /**< 错误类型改为移动 */
snprintf(result, MAX_BUFFER_LEN, "%s/%s-%s", fitfy.wd[index].path, fitfy.wd[index].file, ptr); /**< 构造错误信息 */
partition_error_exit(result); /**< 分区错误退出系统 */
}
}
loop += sizeof(struct inotify_event) + event->len; /**< 移动到下一个事件位置 */
event = (struct inotify_event *)(recvbuf + loop); /**< 更新事件指针 */
}
delay_s(BACKUP_WAIT_EVENT_FINISH); /**< 事件发生后等待5秒,避免过于频繁的监听事件 */
timeout_ms = BACKUP_WAIT_EVENT_FINISH * 1000; /**< 更新超时时间为等待时间 */
continue; /**< 继续循环(跳过本次的备份检查) */
}
timeout_ms = BACKUP_INSPECTION_LEVEL_1 * 1000; /**< 重置超时时间为1级检查间隔 */
/** 检查是否需要进行2级备份 */
int oldbakup_flag=0; /**< 2级备份标志,0=不需要,1=需要 */
long start = get_sysuptime(); /**< 获取当前系统运行时间 */
if (start-oldbakup_time >= BACKUP_INSPECTION_LEVEL_2) /**< 距离上次2级备份超过60秒 */
{
oldbakup_time = start; /**< 更新2级备份时间基准点 */
oldbakup_flag = 1; /**< 设置2级备份标志 */
}
/** 遍历所有监控文件进行备份处理 */
for (index = 0; index < fitfy.wd_len; index++) /**< 遍历每个监控文件 */
{
char curfile[MAX_BUFFER_LEN] = {0}; /**< 当前源文件完整路径缓冲区 */
char newbakup[MAX_BUFFER_LEN] = {0}; /**< 1级备份文件完整路径缓冲区 */
snprintf(curfile, MAX_BUFFER_LEN, "%s/%s", fitfy.wd[index].path, fitfy.wd[index].file); /**< 构造源文件路径 */
snprintf(newbakup, MAX_BUFFER_LEN, "%s/%s", fitfy.wd[index].bakup_path, fitfy.wd[index].file); /**< 构造1级备份文件路径 */
/** 错误处理:1级备份连续失败达到阈值 */
if ((fitfy.wd[index].err_cnt&0xFF) >= BACKUP_ERROR_CNT_LEVEL_1) /**< 低8位错误计数>=5(1级错误) */
{
if (access(curfile, F_OK) != 0) /**< 检查源文件是否存在 */
partition_error_exit(curfile); /**< 源文件丢失,分区错误退出 */
if (is_partition_ok(fitfy.wd[index].path) == false) /**< 检测数据分区是否正常 */
partition_error_exit(fitfy.wd[index].path); /**< 数据分区异常退出 */
// if (is_partition_ok(fitfy.wd[index].bakup_path) == false) // 检测备份分区是否正常(注释掉)
// partition_error_exit(fitfy.wd[index].bakup_path);
fitfy.wd[index].err_cnt &= 0x0000; /**< 清空错误计数器(通过分区检查后重置) */
}
// else if (((fitfy.wd[index].err_cnt>>8)&0xFF) >= BACKUP_ERROR_CNT_LEVEL_2) // 高8位错误计数>=5(2级错误,注释掉)
// {
// if (is_partition_ok(fitfy.wd[index].bakup_path) == false) // 检测备份分区是否正常
// partition_error_exit(fitfy.wd[index].bakup_path);
// fitfy.wd[index].err_cnt &= 0x00FF; /**< 只清空高8位错误计数 */
// }
/** 检查源文件是否存在 */
if (access(curfile, F_OK) != 0) /**< 源文件丢失 */
{
fitfy.wd[index].err_cnt += 0x01; /**< 增加1级错误计数 */
continue; /**< 跳过该文件的备份处理 */
}
/** 执行1级备份 */
if (fitfy.wd[index].flag[0] == 1 || access(newbakup, F_OK) != 0) /**< 需要1级备份或1级备份文件不存在 */
{
remove(newbakup); /**< 删除旧的1级备份文件 */
/** 执行1级备份,直接拷贝,不进行校验 */
if (db_backup(curfile, newbakup) < 0) /**< 1.1 直接拷贝 cur->new(调用数据库备份API) */
{
fitfy.wd[index].err_cnt += 0x1; /**< 1.2 拷贝失败则 err_cnt 低8位加1 */
fitfy.wd[index].flag[1] = 0; /**< 2级备份标志清零 */
}
else
{
fitfy.wd[index].flag[0] = 0; /**< 1.3 拷贝成功则清空1级备份标志位 */
fitfy.wd[index].flag[1] = 1; /**< 设置2级备份标志位(需要进一步校验) */
fitfy.wd[index].cnt[0]++; /**< 增加1级备份成功计数 */
fitfy.wd[index].err_cnt &= 0xFF00; /**< 清空低8位错误计数(保留高8位) */
sys_demo_msg_send(MNT_MODULE, SWDMNT_BACKUP_CONF_MSG, NULL, 0); /**< 发送备份完成消息 */
}
}
/** 执行2级备份 */
if (fitfy.wd[index].flag[1] == 1 && oldbakup_flag == 1) /**< 需要2级备份且达到2级备份时间 */
{
char oldbakup[MAX_BUFFER_LEN] = {0}; /**< 2级备份文件(压缩文件)路径缓冲区 */
/** 执行2级备份,完整性校验,crc32校验,最后进行压缩 */
if (db_integrity_check(newbakup) != 0) /**< 2.1 校验new database文件的完整性 */
{
fitfy.wd[index].flag[0] = 1; /**< 完整性校验失败,重新触发1级备份 */
continue; /**< 跳过2级备份 */
}
snprintf(oldbakup, MAX_BUFFER_LEN, "%s/%s", fitfy.wd[index].bakup_path, fitfy.wd[index].file); /**< 构造2级备份文件路径 */
int gzret = gzip_db(newbakup, oldbakup); /**< 2.2 比较new和old的md5/crc32校验值,相同不压缩,不相同则压缩 */
if (gzret < 0) /**< 压缩失败 */
{
fitfy.wd[index].err_cnt += 0x100; /**< 2.3 压缩失败则 err_cnt 高8位加1 */
}
else /**< 压缩成功 */
{
fitfy.wd[index].flag[1] = 0; /**< 2.4 清空2级备份标志位 */
fitfy.wd[index].cnt[1]++; /**< 增加2级备份成功计数 */
fitfy.wd[index].err_cnt &= 0x00FF; /**< 清空高8位错误计数(保留低8位) */
sys_demo_msg_send(MNT_MODULE, SWDMNT_BACKUP_CONF_MSG, NULL, 0); /**< 发送备份完成消息 */
}
}
}
}
release_inotify(&fitfy); /**< 理论上不会执行到这里,但保持资源释放的完整性 */
return NULL; /**< 线程正常退出返回NULL */
}
/**
* @brief 添加需要监测的文件到inotify监控系统
* @param cfg json配置文件结构体信息
* @param fitfy 文件监测结构体信息指针
* @return int 返回1表示无需监测,返回0表示添加监测事件成功,返回-1表示监测失败
* @note 设计模式:建造者模式,逐步构建文件监控系统
* 性能分析:初始化inotify和epoll,涉及系统调用开销,但仅启动时执行一次
*/
static int add_files_to_inotify(demofcg_t cfg, file_inotify_t *fitfy)
{
int index; /**< 循环索引变量 */
memset(fitfy, 0, sizeof(file_inotify_t)); /**< 清零文件监控结构体,防止未初始化内存 */
/** 检查是否需要监控文件 */
if (cfg.bak_len == 0 || cfg.backup == NULL) /**< 备份配置为空或备份数组指针为空 */
return 1; /**< 返回1表示无需监测任何文件 */
/** 1. 初始化 inotify 文件描述符 */
fitfy->ntfd = inotify_init(); /**< 创建inotify实例,返回文件描述符 */
if(fitfy->ntfd == -1){ /**< inotify初始化失败 */
perror("inotify_init()-error\n"); /**< 打印系统错误信息 */
return -1; /**< 返回失败 */
}
/** 分配监控信息数组内存 */
fitfy->wd = (file_watch_t *)malloc(cfg.bak_len * sizeof(file_watch_t)); /**< 根据备份配置数量分配内存 */
if (fitfy->wd == NULL) /**< 内存分配失败 */
{
close(fitfy->ntfd); /**< 关闭inotify文件描述符 */
return -1; /**< 返回失败 */
}
memset(fitfy->wd, 0, cfg.bak_len * sizeof(file_watch_t)); /**< 清零监控信息数组,数据流:按实际大小清零 */
/** 2. 注册需要监测的文件以及监测类型 */
uint32_t mask = IN_DELETE_SELF | IN_MOVE_SELF | IN_MODIFY | IN_UNMOUNT; /**< 监控事件掩码:删除自身、移动自身、修改、卸载 */
for (index = 0; index < cfg.bak_len; index++) /**< 遍历所有备份配置 */
{
char file[MAX_BUFFER_LEN] = {0}; /**< 文件完整路径缓冲区 */
/** 构造源文件完整路径 */
snprintf(file, MAX_BUFFER_LEN, "%s/%s", cfg.backup[index].path, cfg.backup[index].file); /**< 拼接路径和文件名 */
/** 添加文件到inotify监控 */
fitfy->wd[fitfy->wd_len].id = inotify_add_watch(fitfy->ntfd, file, mask); /**< 注册监控,返回watch descriptor */
if (fitfy->wd[fitfy->wd_len].id == -1) /**< 监控添加失败(文件可能不存在) */
{
printf("inotify_add_watch(%s)-error\n", file); /**< 打印错误信息 */
partition_error_exit(file); /**< 分区错误退出程序(文件系统异常) */
}
/** 保存监控信息 */
fitfy->wd[fitfy->wd_len].path = cfg.backup[index].path; /**< 源文件路径 */
fitfy->wd[fitfy->wd_len].file = cfg.backup[index].file; /**< 源文件名 */
fitfy->wd[fitfy->wd_len].bakup_path = cfg.backup[index].backup_path; /**< 备份路径 */
fitfy->wd_len++; /**< 增加监控文件计数 */
}
/** 检查是否有成功添加的监控 */
if (fitfy->wd_len == 0) /**< 没有成功添加任何监控 */
{
close(fitfy->ntfd); /**< 关闭inotify文件描述符 */
free(fitfy->wd); /**< 释放监控信息数组内存 */
return -1; /**< 返回失败 */
}
/** 3. 初始化epoll文件描述符 */
fitfy->epfd = epoll_create1(EPOLL_CLOEXEC); /**< 创建epoll实例,设置close-on-exec标志 */
if(fitfy->epfd == -1){ /**< epoll创建失败 */
perror("epoll_create1()-error\n"); /**< 打印系统错误信息 */
release_inotify(fitfy); /**< 释放已分配的资源 */
return -1; /**< 返回失败 */
}
/** 4. 添加epoll监听事件:监听inotify的可读事件 */
struct epoll_event evt; /**< epoll事件结构体 */
memset(&evt, 0, sizeof(evt)); /**< 清零事件结构体 */
evt.events = EPOLLIN; /**< 监听可读事件,默认是水平触发(LT模式) */
evt.data.fd = fitfy->ntfd; /**< 关联inotify文件描述符 */
if (epoll_ctl(fitfy->epfd, EPOLL_CTL_ADD, fitfy->ntfd, &evt) == -1) /**< 将inotify fd添加到epoll监控 */
{
perror("epoll_ctl()-error\n"); /**< 打印系统错误信息 */
release_inotify(fitfy); /**< 释放已分配的资源 */
return -1; /**< 返回失败 */
}
return 0; /**< 成功返回 */
}
/**
* @brief 关闭并释放文件监测结构体信息
* @param fitfy 文件监控结构体指针
* @return int 总是返回0
* @note 设计模式:资源清理器,对称于add_files_to_inotify
* 性能分析:释放所有系统资源,防止资源泄漏
*/
static int release_inotify(file_inotify_t *fitfy)
{
/** 关闭epoll文件描述符 */
if (fitfy->epfd > 0) /**< epoll文件描述符有效 */
close(fitfy->epfd); /**< 关闭epoll文件描述符 */
/** 释放监控信息数组 */
if (fitfy->wd != NULL) /**< 监控信息数组指针非空 */
{
int index; /**< 循环索引 */
for (index = 0; index < fitfy->wd_len; index++) /**< 遍历所有监控项 */
{
inotify_rm_watch(fitfy->ntfd, fitfy->wd[index].id); /**< 移除inotify监控 */
}
free(fitfy->wd); /**< 释放监控信息数组内存 */
}
/** 关闭inotify文件描述符 */
if (fitfy->ntfd > 0) /**< inotify文件描述符有效 */
close(fitfy->ntfd); /**< 关闭inotify文件描述符 */
memset(fitfy, 0, sizeof(file_inotify_t)); /**< 清零整个结构体,数据流:安全清理 */
return 0; /**< 成功返回 */
}
/**
* @brief 获取特定的事件(从epoll事件数组中查找inotify事件)
* @param events 事件列表数组
* @param event_num 事件数量
* @param efd 监听的文件描述符(实际上未使用,使用全局fitfy.ntfd)
* @return struct epoll_event* 找到的事件指针,未找到返回NULL
* @note 设计模式:事件过滤器,从多个epoll事件中筛选目标事件
*/
static struct epoll_event *get_epoll_event(struct epoll_event events[MAX_EPOLL_EVENT_LEN], int event_num, int efd)
{
int index; /**< 循环索引 */
for (index = 0; index < event_num; index++) /**< 遍历所有epoll事件 */
{
if (events[index].data.fd == fitfy.ntfd) /**< 找到inotify文件描述符对应的事件 */
{
if (events[index].events & EPOLLIN) /**< 检查是否为可读事件 */
return &events[index]; /**< 返回事件指针 */
}
}
return NULL; /**< 未找到目标事件 */
}
/**
* @brief 数据库文件压缩函数(带校验值比较)
* @param file 源文件路径
* @param gzip_file 压缩文件输出路径缓冲区
* @return int 成功返回0,失败返回-1,文件相同返回-2
* @note 设计模式:策略模式,根据校验值决定是否压缩
* 性能分析:涉及文件校验和外部gzip命令执行,开销较大
* 数据流结构:gzip_file缓冲区大小MAX_BUFFER_LEN(256字节)
*/
static int gzip_db(char *file, char gzip_file[MAX_BUFFER_LEN])
{
int len = strlen(gzip_file); /**< 获取输出路径当前长度 */
char val[16] = {0}; /**< 校验值存储缓冲区,16字节足够存储MD5或CRC32 */
/** 构造校验文件路径(在原路径后追加.crc32) */
snprintf(gzip_file+len, MAX_BUFFER_LEN-len, ".crc32"); /**< 添加.crc32扩展名 */
int ret = compare_file(file, gzip_file, val); /**< 比较文件校验值 */
if (ret <= 0) /**< 文件相同(ret=0)或比较失败(ret<0) */
return ret; /**< 返回比较结果 */
/** 执行压缩 */
char cmd[MAX_BUFFER_LEN] = {0}; /**< 命令缓冲区 */
snprintf(gzip_file+len, MAX_BUFFER_LEN-len, ".gz"); /**< 修改扩展名为.gz */
snprintf(cmd, MAX_BUFFER_LEN, "busybox nice -n 19 gzip -c %s > %s", file, gzip_file); /**< 构造gzip命令,设置低优先级(nice值19) */
int status = system(cmd); /**< 执行压缩命令 */
if (-1 == status || WIFEXITED(status) != true || 0 != WEXITSTATUS(status)) /**< 检查命令执行结果 */
{
/** 压缩失败,清理临时文件 */
snprintf(gzip_file+len, MAX_BUFFER_LEN-len, ".gz"); /**< 确保扩展名为.gz */
remove(gzip_file); /**< 删除压缩失败的文件 */
snprintf(gzip_file+len, MAX_BUFFER_LEN-len, ".crc32"); /**< 修改扩展名为.crc32 */
remove(gzip_file); /**< 删除校验文件 */
return -1; /**< 返回失败 */
}
/** 保存校验值到文件 */
snprintf(gzip_file+len, MAX_BUFFER_LEN-len, ".crc32"); /**< 修改扩展名为.crc32 */
FILE *fp = fopen(gzip_file, "w"); /**< 以写入模式打开校验文件 */
if (fp == NULL) /**< 文件打开失败 */
return -1; /**< 返回失败 */
fwrite(val, 1, ret, fp); /**< 写入校验值(ret为校验值长度) */
fclose(fp); /**< 关闭文件 */
return 0; /**< 成功返回 */
}
/**
* @brief 文件校验值比较(MD5或CRC32)
* @param file 源文件路径
* @param check_file 校验文件路径(存储之前计算的校验值)
* @param val 校验值输出缓冲区
* @return int 文件相同返回0,文件不同返回校验值长度,失败返回-1
* @note 设计模式:模板方法模式,根据USE_MD5宏选择不同校验算法
* 性能分析:MD5计算比CRC32更耗时但更安全
*/
static int compare_file(char *file, char *check_file, char val[16])
{
int ret = -1; /**< 返回值,默认失败 */
int wt_sz = 0; /**< 校验值字节大小 */
unsigned char *wt_ptr = NULL; /**< 校验值数据指针 */
/** 读取已保存的校验值(如果存在) */
if (access(check_file, R_OK|W_OK) == 0) /**< 校验文件存在且可读写 */
{
FILE *fp = fopen(check_file, "rb"); /**< 以二进制读取模式打开校验文件 */
if (fp != NULL) /**< 文件打开成功 */
{
fread(val, 1, 16, fp); /**< 读取校验值(最多16字节) */
fclose(fp); /**< 关闭文件 */
}
}
/** 根据编译选项选择校验算法 */
#ifdef USE_MD5 /**< 如果启用MD5校验 */
unsigned char md5val[16] = {0}; /**< MD5校验值缓冲区(16字节) */
memset(md5val, 0xFF, 16); /**< 初始化为全0xFF(实际md5sum函数会覆盖) */
md5sum(file, md5val); /**< 计算MD5校验值 */
ret = memcmp(md5val, val, 16); /**< 比较新旧MD5值 */
wt_sz = 16; /**< MD5校验值长度为16字节 */
wt_ptr = md5val; /**< 指向MD5校验值 */
#else /**< 使用CRC32校验(默认) */
unsigned int crcval = 0xFFFFFFFF; /**< CRC32校验值变量 */
crc32(file, &crcval); /**< 计算CRC32校验值 */
ret = memcmp(&crcval, val, sizeof(unsigned int)); /**< 比较新旧CRC32值 */
wt_sz = sizeof(unsigned int); /**< CRC32校验值长度为4字节 */
wt_ptr = (unsigned char *)(&crcval); /**< 指向CRC32校验值 */
#endif
if (ret == 0) /**< 校验值相同 */
return 0; /**< 返回0表示文件未改变 */
memcpy(val, wt_ptr, wt_sz); /**< 将新校验值复制到输出缓冲区 */
return wt_sz; /**< 返回校验值长度表示文件已改变 */
}
/**
* @brief 由于文件所在的分区异常导致的系统退出处理
* @param path 异常文件路径
* @note 设计模式:错误处理器,处理文件系统异常情况
* 性能分析:记录日志并发送消息,不实际退出系统(当前版本)
*/
static void partition_error_exit(char *path)
{
/** 记录进程退出信息 */
process_exit_info(path, getpid(), 0, SYSDEMO_EXIT_CORE_FROM_PARTITION); /**< 记录分区异常退出信息 */
printf("%s exception !! core = 0x%02X\n", path, SYSDEMO_EXIT_CORE_FROM_PARTITION); /**< 打印异常信息 */
/** 条件编译:是否实际退出系统(当前版本不退出) */
#if 0 /**< 分区异常暂时不重启(注释掉退出代码) */
exit(SYSDEMO_EXIT_CORE_FROM_PARTITION); /**< 退出程序并返回错误码 */
#endif
/** 发送备份异常消息 */
sys_demo_msg_send(NULL, SWDALL_BACKUP_CONF_ABNORMAL_MSG, NULL, 0); /**< 广播备份配置异常消息 */
}
/**
* @brief 校验路径所在的分区是否正常(读写测试)
* @param path 文件路径
* @return true 分区正常
* @return false 分区异常
* @note 设计模式:健康检查器,通过实际读写测试验证分区状态
* 性能分析:创建4KB测试文件,涉及磁盘I/O,但仅错误恢复时执行
*/
static bool is_partition_ok(char *path)
{
if (path == NULL) /**< 路径指针为空 */
return false; /**< 返回异常 */
char file[MAX_BUFFER_LEN] = {0}; /**< 测试文件路径缓冲区 */
snprintf(file, MAX_BUFFER_LEN, "%s/.partitiontmpfile", path); /**< 构造测试文件路径(隐藏文件) */
/** 1. 检测该分区是否可写 */
FILE *fp = fopen(file, "w"); /**< 以写入模式打开测试文件 */
if (fp == NULL) /**< 文件打开失败(权限不足或分区只读) */
return false; /**< 返回异常 */
/** 写入测试数据(全0xFF) */
char buffer[4096]; /**< 4KB测试缓冲区,数据流:固定大小 */
int n = 0; /**< 已写入字节数 */
int bufsz = 4096; /**< 缓冲区大小 */
memset(buffer, 0xFF, bufsz); /**< 填充缓冲区为全0xFF */
while (n < bufsz) /**< 循环写入直到写满缓冲区 */
{
int ret = fwrite(buffer+n, 1, bufsz-n, fp); /**< 写入数据 */
if (ret == 0) /**< 写入失败 */
break; /**< 跳出循环 */
n += ret; /**< 更新已写入字节数 */
}
fclose(fp); /**< 关闭文件 */
if (n != bufsz) /**< 写入字节数不足 */
{
remove(file); /**< 删除测试文件 */
return false; /**< 返回异常 */
}
/** 2. 检测该分区是否可读 */
fp = fopen(file, "rb"); /**< 以二进制读取模式重新打开文件 */
if (fp == NULL) /**< 文件打开失败 */
return false; /**< 返回异常 */
/** 读取并验证数据 */
n = 0; /**< 重置已读取字节数 */
memset(buffer, 0, bufsz); /**< 清空缓冲区 */
while (n < bufsz) /**< 循环读取直到读满缓冲区 */
{
int ret = fread(buffer+n, 1, bufsz-n, fp); /**< 读取数据 */
if (ret == 0) /**< 读取失败或到达文件末尾 */
break; /**< 跳出循环 */
n += ret; /**< 更新已读取字节数 */
}
fclose(fp); /**< 关闭文件 */
remove(file); /**< 删除测试文件 */
if (n != bufsz) /**< 读取字节数不足 */
return false; /**< 返回异常 */
/** 验证读取的数据是否与写入的一致 */
for (n = 0; n < bufsz; n++) /**< 遍历缓冲区每个字节 */
{
if (buffer[n] != 0xFF) /**< 数据不一致 */
return false; /**< 返回异常 */
}
printf("current partition = %s is ok !!\n", path); /**< 打印分区正常信息 */
return true; /**< 返回正常 */
}
/**
* @brief 打印backup线程的全局信息(调试接口)
* @note 设计模式:调试信息聚合器,集中展示备份模块状态
* 性能分析:仅调试时使用,遍历所有监控项,可能有较多输出
*/
void debug_backup_handler(void)
{
int index; /**< 循环索引 */
/** 打印头部信息 */
printf("--------------- debug_backup_handler ---------------\n"); /**< 分隔线 */
printf("| epoll fd = %d\n", fitfy.epfd); /**< epoll文件描述符 */
printf("| inotify fd = %d\n", fitfy.ntfd); /**< inotify文件描述符 */
printf("| watch nums = %d\n", fitfy.wd_len); /**< 监控文件数量 */
printf("----------------------------------------------------\n"); /**< 分隔线 */
/** 遍历打印每个监控文件的详细信息 */
for (index = 0; index < fitfy.wd_len; index++) /**< 遍历所有监控项 */
{
printf("| source path: %s\n", fitfy.wd[index].path); /**< 源文件路径 */
printf("| bakup path: %s\n", fitfy.wd[index].bakup_path); /**< 备份路径 */
printf("| file name: %s\n", fitfy.wd[index].file); /**< 文件名 */
printf("| id = %d\n", fitfy.wd[index].id); /**< inotify监控ID */
printf("| flag[0] = %d, flag[1] = %d\n", fitfy.wd[index].flag[0], fitfy.wd[index].flag[1]); /**< 备份标志位 */
printf("| cnt[0] = %d, cnt[1] = %d\n", fitfy.wd[index].cnt[0], fitfy.wd[index].cnt[1]); /**< 备份次数 */
printf("| err_cnt = %04x\n", fitfy.wd[index].err_cnt); /**< 错误计数器(十六进制显示) */
printf("----------------------------------------------------\n"); /**< 每个监控项后的分隔线 */
}
}
5. deadlock_check.c/h - 死锁检测
设计思想: 基于消息的监控 ├── 为什么基于消息队列? │ ├── 无侵入式监控 │ ├── 反映实际业务阻塞 │ └── 与系统架构匹配 ├── 状态跟踪机制 │ ├── 连续计数,避免误报 │ ├── 定时检测,平衡开销 │ └── 模块化设计,易于扩展 └── 设计局限性 ├── 只检测消息线程死锁 ├── 预留其他检测接口 └── 依赖消息系统实现
/**
* @file deadlock_check.c
* @brief 死锁检测模块实现文件,通过消息队列状态检测进程死锁
*/
#include <stdio.h> /**< 标准输入输出头文件,提供printf等函数 */
#include <stdlib.h> /**< 标准库头文件,提供malloc、free等函数 */
#include <string.h> /**< 字符串处理头文件,提供memset、strlen等函数 */
#include "sysconf_struct_types.h" /**< 系统配置结构体类型定义头文件 */
#include "message.h" /**< 消息模块头文件,提供消息相关结构体和函数 */
#include "deadlock_check.h" /**< 死锁检测模块头文件,包含本模块的函数声明和常量定义 */
/**
* @defgroup MODULE_IDENTIFICATION 模块标识常量
* @brief 用于模块识别的键值和掩码
* @{
*/
#define MODULE_KEYCODE (0x7a9ec500) /**< 模块键值常量,用于模块识别 */
#define KEYCODE_MASK (0xff) /**< 键值掩码,用于提取特定比特位 */
/** @} */ /* MODULE_IDENTIFICATION */
/**
* @brief 系统模块名称数组
* @note 数据流结构:字符串指针数组,以NULL结尾表示数组结束
* 设计模式:注册表模式,集中管理系统所有模块信息
*/
static char * module_list[] = /**< 模块名称字符串指针数组 */
{
MNT_MODULE, /**< 管理模块名称 */
GUI_MODULE, /**< 界面模块名称 */
CANNETHOST_MODULE, /**< 主机联网模块名称 */
POWER_MODULE, /**< 电源管理模块名称 */
DECHIP_MODULE, /**< 解码芯片模块名称 */
PRINTER_MODULE, /**< 打印机模块名称 */
FACTORY_MODULE, /**< 产测模块名称 */
HAL_MODULE, /**< HAL模块名称 */
MACHINE_BUS_MODULE, /**< 机内总线模块名称 */
LINKAGE_MODULE, /**< 联动模块名称 */
IOB_MODULE, /**< 接口板模块名称 */
HLOG_MODULE, /**< 日志管理模块名称 */
NULL, /**< 数组结束标志,用于遍历时判断边界 */
};
/**
* @brief 心跳消息结构体
* @note 设计模式:状态保持器,跟踪每个模块的消息队列状态
* 数据流结构:大小约为32字节(假设指针8字节,int 4字节,time_t 8字节)
*/
typedef struct{
module_infos_t* msg; /**< 模块信息结构体指针,指向目标模块的消息信息 */
int deadlock_cnt; /**< 死锁疑似事件计数器,连续疑似死锁次数 */
int read_index; /**< 用于记录首帧时间的读索引 */
time_t read_index_time; /**< 用于记录首帧的时间戳 */
}beat_msg;
/**
* @brief 心跳消息列表数组,每个元素对应一个模块的状态跟踪
* @note 数据流结构:静态数组,大小MODULE_NUM_MAX由系统定义
* 性能分析:数组大小固定,内存占用可预测,访问效率高
*/
static beat_msg beat_msg_list[MODULE_NUM_MAX]; /**< 心跳消息状态跟踪数组,初始全零 */
/** 静态函数声明 */
static bool is_msg_deadlock(int pid); /**< 检查消息线程是否死锁 */
static bool is_other_deadlock(int pid); /**< 检查其他原因导致的死锁(预留接口) */
/**
* @brief 初始化心跳消息计数器的模块列表
* @return int 总是返回0
* @note 设计模式:初始化器模式,清空状态跟踪数组
* 性能分析:O(1)复杂度,仅执行一次memset操作
*/
int init_beat_msg_list(void)
{
memset(beat_msg_list, 0, sizeof(beat_msg_list)); /**< 清零整个状态跟踪数组 */
return 0; /**< 总是成功返回 */
}
/**
* @brief 检查指定进程是否发生死锁(主入口函数)
* @param pid 进程ID
* @return true 检测到死锁
* @return false 未检测到死锁
* @note 设计模式:策略模式,组合多种死锁检测方法
* 性能分析:先检查消息死锁,再检查其他死锁,顺序执行
*/
bool check_deadlock(int pid)
{
/** 优先检查消息线程死锁(主要检测方法) */
if (is_msg_deadlock(pid) == true) /**< 调用消息死锁检测 */
{
return true; /**< 检测到死锁,立即返回true */
}
/** 预留:检查其他原因导致的死锁 */
if (is_other_deadlock(pid) == true) /**< 调用其他死锁检测(当前总是返回false) */
{
return true; /**< 检测到死锁,返回true */
}
return false; /**< 未检测到任何死锁,返回false */
}
/**
* @brief 判断消息线程是否死锁
* @param pid 进程ID
* @return true 消息线程死锁
* @return false 消息线程正常
* @note 设计模式:状态机检测,基于消息队列的读写状态
* 性能分析:遍历所有模块,对匹配的模块进行状态跟踪,可能有等待延迟
*/
static bool is_msg_deadlock(int pid)
{
int msg_id; /**< 模块索引,用于遍历module_list数组 */
for (msg_id = 0; module_list[msg_id] != NULL; msg_id++) /**< 遍历所有已注册模块 */
{
/** 根据模块名称获取模块信息 */
module_infos_t* msg = get_module_infos_byname(module_list[msg_id]); /**< 获取模块信息结构体 */
if (msg == NULL) /**< 模块信息获取失败(模块可能未注册) */
continue; /**< 跳过该模块 */
if (msg->pid != pid) /**< 模块的PID与目标PID不匹配 */
continue; /**< 跳过非目标进程的模块 */
/** 更新状态跟踪结构体 */
beat_msg_list[msg_id].msg = msg; /**< 保存模块信息指针 */
/** 检查消息队列状态 */
if (msg->msg.msg_num == 0) /**< 消息队列为空,表示消息线程正常处理消息 */
{
beat_msg_list[msg_id].deadlock_cnt = 0; /**< 重置死锁计数器 */
continue; /**< 继续检查下一个模块 */
}
/** 消息队列非空,可能存在死锁,开始跟踪检测 */
/** 1. 记录队列中的首帧消息状态 */
int read_index = msg->msg.read_index; /**< 记录当前读指针位置 */
time_t read_index_time = msg->msg.message[read_index].msg_time; /**< 记录当前读指针处消息的时间戳 */
/** 如果是首次检测或计数器已清零,则初始化跟踪状态 */
if (beat_msg_list[msg_id].deadlock_cnt > 0) /**< 已经处于跟踪状态 */
{
/** 保持之前记录的读指针和时间戳(持续跟踪同一帧消息) */
read_index = beat_msg_list[msg_id].read_index;
read_index_time = beat_msg_list[msg_id].read_index_time;
}
else /**< 首次发现消息队列非空 */
{
/** 初始化跟踪状态 */
beat_msg_list[msg_id].read_index = read_index; /**< 保存读指针 */
beat_msg_list[msg_id].read_index_time = read_index_time; /**< 保存时间戳 */
}
/** 2. 等待500毫秒:判断队列的第一帧可读消息是否被处理 */
struct timeval timeout = {.tv_sec = 0, .tv_usec = 500*1000}; /**< 设置500ms超时 */
select(0, NULL, NULL, NULL, &timeout); /**< 阻塞等待500ms,性能:系统调用开销 */
/** 3. 检查消息是否被处理 */
if (msg->msg.msg_num > 0 && /**< 消息队列仍然非空 */
read_index == msg->msg.read_index && /**< 读指针未移动 */
read_index_time == msg->msg.message[msg->msg.read_index].msg_time) /**< 时间戳未变(同一帧消息) */
{
/** 消息未被处理,增加死锁疑似事件计数 */
beat_msg_list[msg_id].deadlock_cnt++;
/** 检查是否达到死锁判定阈值 */
if (beat_msg_list[msg_id].deadlock_cnt >= DEADLOCK_SUSPECT_EVENT_MAX) /**< 连续达到最大疑似次数 */
{
/** 判定为死锁,打印告警信息 */
printf("\033[35mpid = %d is deadlock, md_name = %s !!\n\033[0m", pid, msg->name); /**< 彩色输出死锁信息 */
beat_msg_list[msg_id].deadlock_cnt = 0; /**< 重置计数器(可选) */
return true; /**< 返回true表示检测到死锁 */
}
}
else /**< 消息已被处理,说明线程正常 */
{
beat_msg_list[msg_id].deadlock_cnt = 0; /**< 清空死锁疑似事件次数 */
}
}
return false; /**< 遍历所有模块均未检测到死锁 */
}
/**
* @brief 判断是否是其他原因导致的死锁(预留接口)
* @param pid 进程ID
* @return true 其他原因导致死锁
* @return false 无其他原因死锁
* @note 设计模式:预留扩展点,为未来的死锁检测方法提供接口
* 当前实现总是返回false,表示尚未实现其他检测方法
*/
static bool is_other_deadlock(int pid)
{
return false; /**< 预留接口,当前总是返回false */
}
五、数据结构设计树形分析
1. 核心数据结构评估
demofcg_t (配置总容器) ├── 优点 │ ├── 层次清晰,逻辑分组 │ ├── 指针+长度,动态大小 │ └── 内存连续,访问高效 ├── 缺点 │ ├── 多层间接访问,cache不友好 │ ├── 字符串指针,需要额外内存管理 │ └── 没有版本控制字段 └── 改进建议 ├── 添加配置版本字段 ├── 考虑内存池管理字符串 └── 添加配置校验标志
2. 进程配置结构体 js_process_t
字段分析 ├── 必要字段 (8个) │ ├── path/name/type - 进程标识 │ ├── pid - 运行时状态 │ ├── arg[]/arg_len - 启动参数 │ ├── wait - 启动等待 │ └── threshold - 监控阈值 ├── 设计合理性 │ ├── 参数数组大小固定(DEMO_CONFIG_MAX_ARGUMENT=10) │ ├── 阈值结构体内嵌,逻辑关联 │ └── 类型字段使用位掩码,支持组合 └── 潜在问题 ├── arg数组可能浪费内存 ├── 没有进程重启次数限制 └── 缺乏优先级或CPU亲和性设置
3. 文件监控结构体 file_watch_t
位字段设计分析 ├── err_cnt 双用途设计 │ ├── 低8位: 1级备份错误计数 │ ├── 高8位: 2级备份错误计数 │ └── 节省内存,但可读性差 ├── flag[2] 标志位数组 │ ├── flag[0]: 1级备份需要 │ ├── flag[1]: 2级备份需要 │ └:: 清晰表达状态机 └:: cnt[2] 计数器数组 ├── cnt[0]: 1级备份成功次数 ├── cnt[1]: 2级备份成功次数 └── 用于统计和调试
4. 内存使用优化分析
静态数据内存 ├── CRC32表: 1024字节 (256*4) ├── 模块列表: 约13*8=104字节 ├── 心跳消息数组: MODULE_NUM_MAX * sizeof(beat_msg) └── 文件监控数组: cfg.bak_len * sizeof(file_watch_t) 运行时内存 ├── 配置文件缓冲: 文件大小 ├── JSON解析树: 可变大小 ├── 进程配置数组: cfg.proc_len * sizeof(js_process_t) └── 备份配置数组: cfg.bak_len * sizeof(js_backup_t) 内存管理策略 ├── 启动时一次性分配 ├── 运行期间少量动态分配 ├── 字符串使用指针引用(不复制) └── 适合嵌入式环境
六、架构设计综合评价
优点总结
-
模块化清晰:每个文件职责单一,接口明确
-
层次结构合理:从底层系统调用到高层业务逻辑分层清晰
-
错误处理全面:从分区错误到进程异常都有相应处理
-
资源管理严谨:对称的分配/释放模式,避免内存泄漏
-
可扩展性好:预留接口和条件编译支持功能扩展
-
性能考虑周全:epoll非阻塞、批量处理、查表算法等优化
可改进之处
-
配置热重载:当前需要重启才能加载新配置
-
更细粒度监控:只监控堆内存,缺少栈内存和文件描述符监控
-
日志系统增强:当前日志较为简单,缺少日志级别和轮转策略
-
测试覆盖率:缺乏单元测试和集成测试框架
-
配置文件验证:缺少JSON schema验证,配置错误可能运行时才发现
设计模式应用评估
-
状态模式:进程生命周期管理实现良好
-
观察者模式:文件监控系统是典型实现
-
策略模式:阈值处理和校验算法选择
-
工厂模式:配置对象创建
-
单例模式:全局配置管理
适合的应用场景
-
嵌入式Linux系统:资源受限,需要可靠守护进程
-
工业控制设备:需要长时间稳定运行
-
物联网网关:需要管理多个子进程
-
网络设备:需要进程监控和自动恢复
设计一个良好、实现稳健的系统守护进程框架,需要丰富的Linux系统编程经验和对嵌入式环境的深入理解。必须架构简洁性、可靠性和功能性之间取得了良好平衡。
第二部分 Makefile
# ================================================================
# sysdemo 模块 Makefile 配置文件
# 用于编译系统守护进程应用程序
# ================================================================
# ==================== 路径配置区域 ====================
# 根文件系统路径(相对路径,指向项目根目录)
ROOTFS_PATH = ../../
# 包含头文件路径,如果未定义则使用默认路径
INC_PATH ?= $(ROOTFS_PATH)include/
# 可执行文件安装路径,如果未定义则使用默认路径
BIN_PATH ?= $(ROOTFS_PATH)image/user_rootfs/bin
# 库文件路径,如果未定义则使用默认路径
LIBS_PATH ?= $(ROOTFS_PATH)image/user_rootfs/lib
# ==================== 目标文件配置 ====================
# 最终生成的可执行文件名
EXECUTABLE := sysdemo
# 库文件目录
LIBDIR:= $(LIBS_PATH)
# 需要链接的库列表:cjson(JSON解析)、message(消息模块)、pthread(线程)、db(数据库)、sysfunc(系统函数)
LIBS := cjson message pthread db sysfunc
# ==================== 源文件配置 ====================
# 当前本地路径
LOCAL_PATH := .
# 头文件包含路径列表:
# 1. 当前目录(用于包含本模块的头文件)
# 2. 项目公共include目录(用于包含系统公共头文件)
INCLUDEH := \
$(LOCAL_PATH)/ \
$(LOCAL_PATH)/../../include
# 自定义编译标志,可由外部Makefile传入(如调试标志、优化级别等)
DEFIND_FLAG ?=
# ==================== 自动文件发现 ====================
# 使用wildcard函数自动查找当前目录下所有.c源文件
SRCDIR := $(wildcard *.c)
# 包含路径变量赋值
INCLUDES := $(INCLUDEH)
# BIN_PATH := ~/nfs # 注释掉的备用安装路径
# ==================== 编译器配置 ====================
# 交叉编译工具链:arm-linux-gcc(针对ARM架构的Linux系统)
CC:=arm-linux-gcc
# 编译标志:-g(包含调试信息) -Wall(显示所有警告) -lm(链接数学库)
CFLAGS:=-g -Wall -lm
# 预处理器标志(包含编译器标志)
CPPFLAGS := $(CFLAGS)
# 添加包含路径:将INCLUDES中的每个路径前加上-I前缀
CPPFLAGS += $(addprefix -I,$(INCLUDES)/)
# 自动生成依赖文件(.d文件),用于增量编译
CPPFLAGS += -MMD
# 添加自定义编译标志
CPPFLAGS += $(DEFIND_FLAG)
# 删除命令别名
RM-F := rm -f
# ==================== 文件列表生成 ====================
# 源文件列表
SRCS := $(SRCDIR)
# 对象文件列表:将.c文件替换为.o文件(模式替换)
OBJS := $(patsubst %.c,%.o,$(SRCS))
# 依赖文件列表:将.o文件替换为.d文件
DEPS := $(patsubst %.o,%.d,$(OBJS))
# 查找缺失的依赖文件(实际不存在的.d文件)
MISSING_DEPS := $(filter-out $(wildcard $(DEPS)),$(DEPS))
# 根据缺失的依赖文件找到对应的源文件
MISSING_DEPS_SOURCES := $(wildcard $(patsubst %.d,%.c,$(MISSING_DEPS)))
# ==================== 构建目标 ====================
# 默认目标:构建可执行文件
all: $(EXECUTABLE)
# 安装目标:将编译好的可执行文件复制到目标路径
install :
@cp $(EXECUTABLE) $(BIN_PATH)
@echo " [target] $(BIN_PATH)/$(EXECUTABLE) "
# 清理目标:删除所有中间文件和目标文件
clean :
@$(RM-F) *.o *.d
@$(RM-F) $(OBJS_DIR)
@$(RM-F) $(EXECUTABLE)
@$(RM-F) $(BIN_PATH)/$(EXECUTABLE)
@echo " [clean] sucess !!"
# ==================== 依赖处理 ====================
# 如果存在缺失的依赖文件,则删除对应的.o文件(强制重新编译)
ifneq ($(MISSING_DEPS),)
$(MISSING_DEPS) :
@$(RM-F) $(patsubst %.d,%.o,$@)
endif
# 包含所有依赖文件(如果存在)
# -include表示如果文件不存在也不报错
-include $(DEPS)
# ==================== 链接规则 ====================
# 主链接规则:将所有的.o文件链接成可执行文件
# 1. 设置输出可执行文件名
# 2. 链接所有对象文件
# 3. 添加库搜索路径
# 4. 添加需要链接的库
# 5. 设置运行时库搜索路径
$(EXECUTABLE) : $(OBJS)
$(CC) $(CPPFLAGS) -o $(EXECUTABLE) $(OBJS) $(addprefix -L,$(LIBDIR)) $(addprefix -l,$(LIBS)) -Wl,-rpath=$(LIBDIR)
# ==================== 编译规则 ====================
# 通用编译规则:将.c文件编译成.o文件
# $@ 表示目标文件(.o文件)
# $< 表示第一个依赖文件(.c文件)
%.o:%.c
$(CC) $(CPPFLAGS) -o $@ -c $<
程序/模块架构思想树形结构分析
整体架构层次
sysdemo系统守护进程 ├── 核心管理层 (sysdemo_main.c) │ ├── 进程生命周期管理 │ ├── 系统初始化 │ ├── 命令行参数处理 │ └── 信号处理 │ ├── 配置管理模块 (demo_config.c/h) │ ├── JSON配置文件解析 │ ├── 结构体定义 │ │ ├── demofcg_t (总配置) │ │ ├── js_process_t (进程配置) │ │ ├── js_bootcfg_t (启动配置) │ │ ├── js_backup_t (备份配置) │ │ └── js_threshold_t (阈值配置) │ └── 配置加载/释放接口 │ ├── 进程监控模块 (集成在main中) │ ├── 进程状态检查 │ │ ├── PID获取 │ │ ├── 进程状态获取 │ │ ├── 堆内存监控 │ │ └── CPU使用率监控(预留) │ ├── 阈值管理 │ │ ├── CPU阈值检测 │ │ ├── 内存阈值检测 │ │ └── 处理策略(重启等) │ └── 死锁检测模块 (deadlock_check.c/h) │ ├── 心跳消息监控 │ ├── 消息队列状态检查 │ └── 死锁判定逻辑 │ ├── 备份管理模块 (file_inotify.c/backup_db.h) │ ├── 文件监控子系统 │ │ ├── inotify初始化 │ │ ├── epoll事件循环 │ │ └── 文件变化检测 │ ├── 双级备份策略 │ │ ├── 1级备份(实时同步) │ │ ├── 2级备份(可靠存档) │ │ └── 完整性校验 │ └── 分区健康检查 │ ├── 读写测试 │ └── 错误恢复 │ ├── 工具模块 │ ├── 校验模块 (checkapi.c/h) │ │ ├── CRC32校验 │ │ └── MD5校验(条件编译) │ └── 系统函数封装 (sysdemo.h) │ ├── 时间相关函数 │ ├── 延时函数 │ └── 系统信息获取 │ └── 通信模块 ├── 消息系统接口 (集成) ├── 进程间通信 └── 系统状态报告
软件设计系统源码树形结构
项目根目录/ ├── 系统守护进程模块 (sysdemo/) │ ├── Makefile # 本文件 - 构建配置 │ ├── sysdemo_main.c # 主程序入口 │ ├── sysdemo.h # 主头文件(宏定义、接口声明) │ ├── demo_config.c # 配置文件解析实现 │ ├── demo_config.h # 配置结构体定义 │ ├── deadlock_check.c # 死锁检测实现 │ ├── deadlock_check.h # 死锁检测接口 │ ├── checkapi.c # 文件校验实现 │ ├── checkapi.h # 校验接口 │ ├── file_inotify.c # 文件监控和备份实现 │ └── backup_db.h # 备份模块接口 │ ├── 公共头文件目录 (include/) │ ├── sysconf_struct_types.h # 系统配置结构体类型 │ ├── system_functions.h # 系统函数声明 │ ├── message.h # 消息系统接口 │ └── db_api.h # 数据库API接口 │ ├── 第三方库依赖 (image/user_rootfs/lib/) │ ├── libcjson.so # JSON解析库 │ ├── libmessage.so # 消息模块库 │ ├── libdb.so # 数据库操作库 │ └── libsysfunc.so # 系统函数库 │ ├── 配置文件目录 (由代码定义) │ ├── /mnt/user_data/config/democfg.json # 主配置文件(首选) │ └── /mnt/user_rootfs/bin/democfg.json # 默认配置文件(备选) │ └── 运行时目录 ├── /mnt/user_data/log/ # 日志和core文件目录 │ ├── core.log # 进程终止记录 │ └── *.core # 核心转储文件 └── 备份文件目录 (由配置指定)
架构设计思想分析
1. 模块化设计
-
高内聚低耦合:每个.c文件负责一个明确的功能模块
-
接口清晰:通过.h文件定义模块间的接口契约
-
条件编译:通过宏定义控制功能开关(如USE_MD5、ENABLE_PATCH_PATH)
2. 分层架构
-
应用层:sysdemo_main.c - 业务逻辑协调
-
服务层:配置管理、进程监控、文件备份
-
工具层:校验算法、系统函数封装
-
驱动层:inotify/epoll系统调用封装
3. 设计模式应用
-
单例模式:全局配置结构体、JSON解析树
-
观察者模式:inotify文件监控
-
状态模式:进程生命周期管理
-
策略模式:阈值处理策略、校验算法选择
-
工厂模式:配置对象创建
4. 错误处理与容错
-
分级错误处理:错误计数器、错误升级机制
-
资源管理:对称的资源分配/释放模式
-
健康检查:分区读写测试、进程状态验证
-
优雅降级:主备配置文件、双级备份策略
5. 性能优化考虑
-
增量编译:通过.d依赖文件实现
-
事件驱动:epoll非阻塞I/O减少CPU占用
-
延迟加载:配置文件按需解析
-
资源复用:静态局部变量减少重复计算
6. 可维护性设计
-
清晰的目录结构:源码、头文件、库文件分离
-
自动化构建:Makefile自动处理依赖关系
-
调试支持:调试信息输出、信号处理
-
版本兼容:弃用功能的向后兼容
7. 安全考虑
-
权限检查:文件可执行性验证
-
输入验证:配置参数范围检查
-
资源限制:日志文件轮转、最大行数限制
-
进程隔离:独立线程处理备份任务
构建系统特点
-
交叉编译支持:针对ARM架构的交叉编译工具链
-
自动化依赖管理:通过-MMD自动生成和包含依赖关系
-
灵活的路径配置:通过变量覆盖支持不同部署环境
-
增量编译优化:缺失依赖检测避免不必要的重新编译
-
安装目标:支持一键部署到目标文件系统
这个架构体现了典型的Linux系统守护进程设计模式,结合了现代软件工程的模块化思想和传统Unix的简洁实用主义。
更多推荐



所有评论(0)