STM32移植FAT文件系统到SD卡:底层驱动与接口适配深入解析
做嵌入式开发难免会遇到“SD 卡里的数据要怎么管理”这类问题。我之前做一套传感器数据记录设备设备每秒产生几十字节的数据看似不多但一天就有 3MB 左右积攒一个月就是上百 MB。拆卡导数据也不是不行但用户嫌麻烦最后只能老老实实把 FAT 文件系统移植到 STM32 上让设备直接在存储卡里生成电脑能读的日志文件。移植的过程虽然不复杂但里面有不少细节和天坑这篇文章把我完整踩过一遍的思路、代码和排查方法都整理出来给同样在折腾 STM32 存储功能的同学做个参考。适合看这篇文章的读者应该是已经会建 STM32 工程、会操作 GPIO 和 SPI 或者 SDIO但对文件系统一头雾水或者想把“裸盘读写”升级成“文件读写”的人。我会尽可能把步骤拆开对小白也友好一些。1. 先把思路理清FAT 文件系统移植到底在移什么很多人一听到“移植文件系统”就觉得很高级觉得要自己从零实现一个成熟的 FAT。实际上99% 的项目都不是这么干的。我们通常做的事情是把一个已经写好的、开源的 FAT 文件系统组件它负责管理文件、目录、分配表接到 STM32 的底层存储驱动它负责写扇区、读扇区上然后在中间的接口层做适配。打个比方文件系统像是一个图书管理员它只知道“把某本书放到第几排第几个格子”但不知道书柜长什么样而底层驱动才是那个真正去开柜子、取书放书的人。移植就是把图书管理员的“抽象的搬书请求”翻译成“实际的柜门操作指令”。这个比喻贯穿整个移植流程你抓住这条线后面所有代码都不难理解。1.1 为什么是 FAT 而不是其他文件系统先说说为什么在 STM32 上面选 FAT。FAT 文件系统最大的优势是和 PC 兼容性最好SD 卡出厂大多是 FAT32 格式插到电脑上就能读写。在工业或者消费类产品里用户经常要拿卡到电脑上看数据或者把写好的配置卡插到设备里去加载这种场景 FAT 几乎是最优解。也有朋友会问那 LittleFS 呢LittleFS 是专门为嵌入式闪存设计的文件系统掉电保护很好、支持磨损均衡但它遇到“PC 直接识别”这个需求就比较尴尬。除非你写一个 PC 端的解析工具否则用户拿到卡只会看到“需要格式化”的提示。所以不做深水区的话FAT 在大多数需要卡交换数据的场景里依然是首选。注意一点FAT32 对单文件 4GB 的限制在很多产品里可能不够用所以如果你有视频录制这种大文件需求要提前想好用 exFAT 还是自己分片存储。开源 FAT 组件通常对 exFAT 的支持比较弱这块配置时容易踩坑。1.2 移植工作的边界划分既然不需要重写文件系统那移植的所有工作就可以按三个层次来分应用层调用文件系统提供的 API比如“挂载”“打开文件”“写一行”“关闭文件”。中间层文件系统组件内部的逻辑管理目录项、文件分配表、碎片整理。底层驱动对 SD 卡的实际初始化、扇区读取和写入。我们真正要写的代码全部集中在“底层驱动”和“中间层与底层之间的适配函数”上。应用层只是测试用。理清这个边界后你就不会在移植时手忙脚乱也不会出现“在底层驱动还没验证通就开始 debug 文件操作”的情况。结合我的经验最稳妥的移植顺序是先写底层驱动单独测试读扇区再适配接口让文件系统挂载并完成格式化最后才做文件读写和性能优化。后面几节我就是按这个顺序展开的。2. 底层 SD 卡驱动文件系统看不见的地基文件系统所有的高层逻辑最后都会落实到“扇区号 数据缓冲 读写方向”这三个要素上。假如 SD 卡底层的初始化、读写都不可靠文件系统再聪明也白搭。所以先把底层选型和驱动框架搞清楚是整个移植最要紧的一步。2.1 选择 SPI 还是 SDIO 作为物理通道STM32 和 SD 卡通信常见有两种方式一是 SPI二是 SDIO。前几年大家还爱争论哪个更好其实它们各有定位选错了就是后面调试地狱。SPI 方式接线少只用 4 根线CS、SCK、MISO、MOSI很多单片机甚至可以用软件模拟 SPI代码逻辑直观适合记录低频数据或者存储配置文件。缺点是速率上限低大量读写时会明显觉得卡顿。SDIO 方式线多一点但一次能传 4 bit还能跑 DMA连续读写速度比 SPI 高好几个档次。如果你要做的设备要保存音频采样、图像连续帧或者需要频繁大批量写数据那就别犹豫直接 SDIO。不过要说明一点对 FAT 文件系统来说它根本不关心 SPI 还是 SDIO。文件系统调用的只是“读扇区 100 号”这样一个函数至于这个扇区是 SPI 读回来的还是 SDIO 读回来的它完全不管。所以切换通道只需要改底层驱动中间层和文件系统代码不用动。这个特性在后期换芯片或者换卡的时候非常爽。2.2 底层驱动需要提供给文件系统的 5 个功能文件系统对底层驱动的需求其实特别朴素归纳下来就是初始化、取状态、读扇区、写扇区、控制命令这 5 个能力。初始化函数要把 SD 卡从“上电”状态带到“识别并进入数据传输模式”。SPI 模式下一般要先让时钟处于低速状态100kHz-400kHz发送 CMD0让卡进入 SPI 模式然后用 CMD8 验证电压之后循环发送 ACMD41 等待卡上电完成最后读取 CID、CSD 寄存器。如果这个流程没走通后边所有操作都会返回“设备不存在”。状态函数用来报告卡是不是正常连接、是否可读写。这个看似多余但在热插拔的场景里很有用文件系统每次操作前会查询状态能避免对一块拔掉的卡反复执行读写。读、写扇区函数是核心。SD 卡数据是按块存储的绝大多数卡一个块就是 512 字节也就是一个扇区。读的时候把逻辑块地址 LBA 和缓冲区指针交给函数函数返回实际读到的字节数和状态。写的时候也一样注意在函数内部保证一个扇区一个扇区完整写入不要把数据发半截就返回。控制命令则要处理一些查询类的请求比如获取扇区大小、获取扇区总数、给底层一个“同步缓存”的通知。这些信息对文件系统计算文件位置和分区边界很重要。一句话总结底层先把裸读写做到“单扇区读写稳定”再谈文件系统。我见过太多人文件系统跑不通最后发现是 SPI 初始化时序不对卡根本没真正进入工作模式白白浪费一整天。3. 与开源 FAT 组件的接口对接底层驱动准备好后下一步就是把文件系统组件“挂上去”。开源 FAT 组件的设计思路基本一致它定义了一组接口函数需要你自己实现并填好底层的函数指针同时留了一个配置头让你决定支持多少字符的文件名、允许多少个卷、是否开启格式化功能等。如果能把这层“胶水”写透后面调应用简直是享受。3.1 配置项是怎么影响后续行为的移植之前必须把配置文件从头到尾过一遍。配置文件里的参数表面上只是几个常量和宏开关但每一个都能把后续行为变得面目全非。第一个要看的是扇区大小。虽然标准 SD 卡是 512B 一个扇区但文件系统组件通常还支持 1024 或 4096如果你在配置里写了 4096而实际驱动底层按 512B 收发数据那文件系统读回来的目录结构就会完全错乱签名、簇号全部对不上。第二是文件名支持。不开长文件名时文件名只有“8.3”格式比如 “TEST_01.TXT”开了长文件名后代码里需要额外空间处理 UTF-16 编码的字符串。这个开关对 RAM 占用影响很大MCU 内存吃紧时要慎开。第三是读写权限。如果产品只要求从 SD 卡读取配置文件那可以关掉写支持甚至去掉格式化功能这样代码更精简也不怕误操作把卡写坏。在量产固件里我一般会保守地关闭可以破坏数据的功能。此外还有卷数量、路径深度等按需配置。原则是能关就关、能小就小毕竟嵌入式设备 RAM 有限开源组件默认配置常常是面向 PC 的“全面支持”直接拿来用往往内存溢出。3.2 disk 接口函数的实现细节我建议文件名不管多花哨先把几个disk_xxx接口落地并测试通过再谈上层。这些接口虽然代码量不大但细节决定成败。disk_initialize(0)初始化 0 号磁盘。返回值 0 表示成功非 0 表示失败。disk_status(0)获取磁盘状态。如果卡拔出或未初始化要返回表明未就绪的状态码。disk_read(0, buffer, sector, count)从第sector扇区开始读count个扇区到buffer。disk_write(0, buffer, sector, count)把buffer内容写到第sector扇区开始的连续区域。disk_ioctl(0, cmd, buffer)处理控制命令。disk_ioctl是最容易出戏的地方。你要正确处理扇区大小和扇区总数查询。扇区总数可以从 SD 卡 CSD 寄存器换算得到也可以用一个全局变量保存初始化时读出的容量。有的组件还会发“强制底层缓存落盘”的命令你不能忽略它否则文件系统关闭文件时数据还停在驱动缓存里拔出卡就丢。读函数要注意count可能大于 1最好用循环每次读一个扇区不要假设驱动支持多扇区 DMA。写函数同理尤其注意必须判断卡的状态忙否因为 SD 卡写入是物理过程写完后卡不会立刻把状态标志置空要轮询判断或者加延时。3.3 时间戳别忽视文件系统给每个文件都标注了创建时间、修改时间。你要是没提供当前时间组件会用一个默认日期往往是 1980 年零点文件在电脑上看起来就像从古代穿越过来的虽然不影响内容读取但非常不专业。解决这个问题的标准做法是增加一个get_fattime函数从 RTC 芯片或者 STM32 内部的备份寄存器和日历外设里读出年月日时分秒然后转换成文件系统要求的位域格式。如果系统确实没有 RTC至少可以用编译时间宏__DATE__和__TIME__生成一个固定时间这样至少文件的创建日期是固件编译时间调试对照时更容易定位版本问题。这个细节不算难但很多项目直到产品出问题才想起补我建议一开始就留好。4. 完整移植实操从空白工程到 SD 卡读写我拿一个常见的 SPI 驱动 SD 卡例子来做完整演示。SPI 驱动代码量少、逻辑直观适合用来讲清楚移植的全部步骤。你用 SDIO 的话思路一模一样只是初始化命令和收发函数不同。4.1 工程文件组织先不要急着写 main把文件分好层。我的工程一般是这样组织的app/存放业务逻辑和测试代码比如生成一个 txt 文件。fs/存放开源 FAT 组件源码和配置文件。bsp/存放 STM32 的板级驱动代码包括 SPI 初始化、SD 卡低级驱动。main.c只做硬件初始化和调用应用层入口。分成文件夹的好处不光是看起来整洁更重要的是隔离问题。文件系统崩了你可以把它单独拿掉测试底层底层驱动崩了你也不用在几百行的文件系统代码里找 bug。尤其是团队协作的时候分层清晰可以明显减少多人改同一个文件的冲突。4.2 一步一步完成移植下面是我自己整理出的可复现步骤。第一步配置 STM32 的 SPI 外设为主机模式引脚、DMA 先不管把最普通的轮询模式跑通。时钟分频先用大分频比如 8 分频或者 16 分频保证初始化时频率低于 400kHz。有些卡对时序敏感初始化太快会直接拉低整个移植进度。第二步实现 SD 卡底层驱动。核心代码如下uint8_t sd_init(void) { uint8_t buf[5]; // 拉低 CS发送至少 74 个时钟脉冲让卡进入 SPI 模式 // 发送 CMD0, 等待返回 0x01 // 发送 CMD8, 检查卡是否支持 SD v2.0 // 循环发送 ACMD41带 HCS bit等待卡返回 ready1 // 读取 CID/CSD 寄存器计算扇区数 return (card_type ! CARD_NONE) ? 0 : 1; } int32_t sd_read_sector(uint32_t sector, uint8_t *buf) { // 发送 CMD17读单扇区 // 等待数据起始标志 0xFE // 读取 512 字节数据 2 字节 CRC return 0; } int32_t sd_write_sector(uint32_t sector, const uint8_t *buf) { // 发送 CMD24写单扇区 // 发送数据起始标志 0xFE 512 字节数据 2 字节伪 CRC // 轮询等待卡不再忙 return 0; }先在 main 里做裸读写测试往某个扇区写一串特征数据再读出来对比。这一步通过才有资格进入文件系统移植。第三步实现给文件系统使用的接口。把所有底层函数封装成统一风格的 5 个函数uint8_t disk_initialize(uint8_t pdrv) { if (pdrv ! 0) return 0x01; return sd_init(); } uint8_t disk_status(uint8_t pdrv) { if (pdrv ! 0) return 0x01; return sd_is_ready() ? 0x00 : 0x01; } int32_t disk_read(uint8_t pdrv, uint8_t *buff, uint32_t sector, uint32_t count) { if (pdrv ! 0) return RES_PARERR; while (count--) { if (sd_read_sector(sector, buff)) return RES_ERROR; buff 512; } return RES_OK; } int32_t disk_write(uint8_t pdrv, const uint8_t *buff, uint32_t sector, uint32_t count) { if (pdrv ! 0) return RES_PARERR; while (count--) { if (sd_write_sector(sector, buff)) return RES_ERROR; buff 512; } return RES_OK; } int32_t disk_ioctl(uint8_t pdrv, uint8_t cmd, void *buff) { if (pdrv ! 0) return RES_PARERR; switch (cmd) { case GET_SECTOR_SIZE: *(uint16_t*)buff 512; break; case GET_SECTOR_COUNT: *(uint32_t*)buff sd_get_sector_count(); break; case CTRL_SYNC: break; default: return RES_PARERR; } return RES_OK; }期间要特别注意buff 512这类指针移位别犯低级错误。如果卡上本来就有一个 FAT 分区这时可以一步到位写个挂载测试。第四步把文件系统组件加入工程后先初始化底层磁盘再挂载disk_initialize(0); res f_mount(fs, , 1); // 空路径表示挂载根目录 if (res FR_NO_FILESYSTEM) { // 说明卡上没有文件系统需要用组件自带的格式化函数创建 f_mkfs(, 0, 0); f_mount(NULL, , 0); // 重新挂载 f_mount(fs, , 1); }注意f_mount的第三个参数如果为 1会在挂载前立即执行初始化如果为 0则延时到第一次访问时才初始化不同模式对调试时序有影响。我在调试时总是显式写 1省得等半天以为死机了。第五步文件读写测试。FIL fp; UINT written, readed; f_open(fp, log.txt, FA_CREATE_ALWAYS | FA_WRITE); f_printf(fp, test line\r\n); f_close(fp); f_open(fp, log.txt, FA_OPEN_EXISTING | FA_READ); f_read(fp, buffer, sizeof(buffer), readed); f_close(fp);如果文件能够在 PC 里正常打开内容和我写入的一模一样移植就算成功了八成。4.3 验证文件读写验证读写这里有几个我总结出来的“靠谱小信号”。写入文件后不要立刻拔卡先关闭文件。很多 FAT 组件是带缓冲的f_write可能只把数据放到内存的 buffer 里要等文件关闭或者缓存满才真正写卡。所以我习惯在大批量写入后调用f_sync或者直接f_close再给用户一个“可以安全拔卡”的提示。然后把卡插到 PC 上看看。文件系统有没有问题用电脑的磁盘检查功能跑一遍也能看出来。如果出现大量“目录错误”或者“文件交叉链接”多半是你的扇区读写函数在某些情况下越界了比如多读或者少读了字节。更专业一点的验证方法是把内容区的原始二进制读出来比对文件系统在文件里写入的“簇号链”。不过这个对新手过于底层建议直接跑一个较大文件比如几 MB的连续读写测试循环几轮不出错就说明驱动基本没问题。5. 常见问题与排查实录移植的过程中你不可能一次跑通。下面这几个问题是我自己在不同项目里都遇到过的每条背后都有一段“明明感觉没问题但就是不通”的辛酸经历。5.1 挂载失败与错误码解读假设你调用挂载函数后返回值不是 0。这个返回值对应的具体错误码在文件系统头文件里都有定义但新手往往不看错误码直接瞪着眼睛看卡有没有反应。实际上错误码已经把问题分类了。如果返回的是“磁盘错误”说明文件系统和驱动之间的通信出了故障可能是底层读写函数返回错误也可能是卡类型无法识别。有个快速定位方法写完底层驱动后先不要挂载文件系统直接在应用里调用disk_read读取扇区 0 的前两个字节0x55AA 签名如果读出来是乱码或者全 0xFF说明底层驱动本身没通问题在 SPI 时序、引脚接线或卡供电上。如果返回的是“未注册文件系统”说明卡上可能没有格式化或者文件系统配置的扇区大小与卡实际不一致。这种情况可以调用格式化函数但格式化前一定要备份数据。返回“逻辑驱动器编号无效”则多半是你没有在配置里启用对应的卷号查一查卷数量配置。5.2 初始化卡时卡在 ACMD41这是 SPI 模式初始化很常见的坑。现象就是程序卡在等待 ACMD41 返回手动复位也不行。我排查过几次原因主要有这几类初始化时钟频率太高。SPI 初始化时如果直接跑在几十兆上很多卡根本反应不过来。把分频系数调大让时钟降到 400kHz 以下等初始化完成后再把频率提上去。卡供电不稳。SD 卡瞬间电流可以到几百毫安如果板子用 3.3V LDO 供电且有余量不足卡会时好时坏。这个时候可以放宽光耦或者换一根粗一点的线或者加个大电容。命令参数错误。ACMD41 的 HCS 位常常被忽略但 SDHC 卡必须置 1否则卡永远不会进入 ready 状态。检查代码里发送的 ACMD41 参数值我这里建议直接用十六进制写0x40000000。另外在新版本内核的卡上有时候需要多试几次 ACMD41。所以我在代码里不会只循环 3 次就放弃而是循环几百次直到超时为止。很多国产卡初始化时会偶尔失败一次重试能解决很大一部分“玄学问题”。5.3 读写乱码与数据错位文件内容乱码是最让人头皮发麻的。有一种很隐蔽的情况是 DMA 缓存对齐。很多 STM32 的 DMA 控制器不能访问部分地址区域比如有的内核要求外设地址按 4 字节对齐而 SPI 的 DMA 模式还会要求 buffer 大小是传输大小的整数倍。如果你把文件系统的读写缓冲区定义成局部变量它的地址是栈分配的并没有保证对齐到 4 字节或 32 字节于是 DMA 搬运时就会出现错位表现为第一个扇区正常后面莫名其妙多出几个空字节或者少一段。解决办法很简单给底层驱动用的缓冲区加上对齐属性或者干脆定义为一个全局静态数组。我通常在 BSP 层定义一个 512 字节的静态 buffer 作为中转__attribute__((aligned(4))) static uint8_t sd_sector_buf[512];文件系统读写时如果需要非对齐 buffer可以先拷贝到这个对齐缓冲区再交给 DMA。这样会多一点内存拷贝开销但稳定压倒一切。另一种乱码原因是 SPI 模式下的“字节交换”。如果你用的是 SPI 硬件外设发送数据时要确认数据位格式是 MSB first。SD 卡规定 SPI 模式是高位在前这个配置不小心被改写成 LSB first那读出来的扇区内容就会左右互换自然无法解析。5.4 文件系统损坏与格式化坑有时候不是代码问题而是卡本身已经乱了。比如你之前拿这张卡在单片机里写了一半就断电FAT 表没有同步电脑会提示“文件损坏”。这时最直接的办法是重新格式化。有些同学喜欢把卡放到电脑上右键格式化但要注意分区表格式。文件系统组件一般只认 MBR 分区如果电脑上用 GPT 分区表格式化插到单片机上可能找不到有效分区。稳妥的办法是用组件自带的格式化函数它能按照你的配置把卡整理成可识别的布局。还有一点经验之谈如果卡容量大于 32GB很多开源的 FAT 组件默认编译配置根本拷贝不到它们不认识 exFAT。不是技术问题是开源库的兼容性问题。建议在产品选型时就限制最大存储卡大小或者确定要自己实现高容量存储那就得换文件系统方案。这个决定最好在架构阶段做不要等到量产前再改。6. 经验总结与优化建议移植 FAT 文件系统的过程真正困难不是抄一堆代码而是理解文件系统和底层驱动的分工以及如何一步步把问题定位出来。经历过几次血泪教训后我自己总结出几条“还没写完代码就觉得很值”的经验。6.1 从实际项目中学到的几个教训第一永远不要相信第一次读写成功就是万事大吉。我现在每次写嵌入式存储代码都会做个压力测试连续往同一张卡上写一万次小文件每写完一轮把数据读回来和源数据比对只有在这种情况下还能全对我才敢把固件交出去。很多第一次成功只是因为恰好没碰到边界情况。第二文件系统的实时性没有直觉中那么好。某些 FAT 操作比如删除文件、更新目录项可能会引起较长的卡忙时间尤其当卡里文件很多、碎片很多时单次操作可能卡住几十毫秒。如果在实时控制任务里直接调用文件 API很容易导致主循环抖动。我的做法是把文件操作放到一个低优先级任务里或者至少保证每次调用的数据量有上限。第三掉电保护永远是 FAT 的软肋。你不能像剪线那样随时拔电否则文件系统可能丢簇、产生大量孤儿簇。如果业务对数据可靠性要求很高可以考虑双写校验、定期回写目录、或者采用先写临时文件再改名的策略。这些方法不能完全避免损坏但能显著降低概率。第四烧录不同批次的 SD 卡前最好先做一次兼容性扫测。我之前在一个项目里换了批国产卡结果很多卡在初始化时表现异常最后调整了供电电容和初始化时序才稳定。不是说国产卡不行而是嵌入式存储的兼容性测试确实是必须的省不得。6.2 后续可扩展方向文件系统移植成功后你可以继续做很多增强功能。最常见的扩展方向是把 RTC 时间写进文件时间戳这样日志文件能按实际时间排序再进一步可以做磁盘管理比如定时清理超过 N 天的旧文件、按容量阈值自动覆盖最老文件。如果你不希望用户拔卡而是想用板载 SPI Flash 保存大量数据那 FAT 不见得是最佳选择。因为 SPI Flash 的擦写次数有限FAT 对 Flash 的磨损控制非常差。这时我更推荐把数据按固定的日志结构顺序追加配合掉电记录和坏块管理。这个方向虽然不叫“FAT 移植”但和 FAT 移植是同一个底层思维都是在“存储介质”和“数据格式”之间做一个翻译层。最后再分享一个小技巧新工程调试的时候我习惯在底层驱动的每个接口函数入口和出口各放一个串口打印开关打印扇区号和返回值。文件系统看起来很难查的问题一旦把底层调用数打开往往一眼就能看出是哪个环节丢了响应。等完全调通了再把打印关掉也不影响性能。这种原始但直接的办法帮我少加了很多班。移植的魅力在于它把“逻辑”和“物理”完美拆开你的价值就是当好这个纽带。希望这篇文章能让你少走一点弯路早日跑通属于你自己的 FAT 文件系统。

相关新闻

SpringBoot+MybatisPlus多模块系统构建实战

SpringBoot+MybatisPlus多模块系统构建实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/15 1:24:00 阅读更多 →
Dijkstra算法详解:从原理到代码实现与工程避坑指南

Dijkstra算法详解:从原理到代码实现与工程避坑指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/15 1:22:58 阅读更多 →
2S电池串联均衡系统设计与实现

2S电池串联均衡系统设计与实现

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/15 1:22:58 阅读更多 →

最新新闻

Linux下C/C++动静态库制作与使用实战指南

Linux下C/C++动静态库制作与使用实战指南

长期以来在Linux下做C/C开发,动静态库始终是绕不开的一个核心技能。无论是自己封装模块、分发二进制产物,还是处理第三方依赖,理解库的制作与使用直接决定了你在编译链接这块能不能少掉头发。我见过不少开发者写业务逻辑很熟练,一…

2026/10/15 4:26:50 阅读更多 →
Flutter鸿蒙开发实战:星座应用跨平台全流程解析

Flutter鸿蒙开发实战:星座应用跨平台全流程解析

1. 项目背景与整体设计思路1.1 为什么选 Flutter 做鸿蒙开发聊到跨平台开发,Flutter 这几年的热度一直在线。原因很简单——一套 Dart 代码,同时跑在 Android、iOS、Windows、macOS、Linux 上,现在还要加上鸿蒙。放在以前,鸿蒙应用…

2026/10/15 4:26:50 阅读更多 →
线上故障排查实战指南:从告警到恢复的黄金步骤

线上故障排查实战指南:从告警到恢复的黄金步骤

半夜两点被电话叫醒,睁开眼看到屏幕上一排红色告警,心跳直接拉满。这种场景,几乎所有做过线上系统的开发者都经历过。告警本身不可怕,可怕的是大脑一片空白,不知道该先点哪里,也不知道哪些命令能救命。这篇…

2026/10/15 4:26:50 阅读更多 →
基于SpringBoot+Vue的个人博客系统开发实战

基于SpringBoot+Vue的个人博客系统开发实战

1. 项目概述与整体技术选型1.1 这个博客系统到底解决了什么问题做个人博客这件事,看起来简单,真做起来坑不少。早期用现成框架,比如WordPress或者各类静态站点生成器,总觉得差了点什么——要么太重,装一堆插件才能用&a…

2026/10/15 4:26:50 阅读更多 →
共享内存IPC实战:从零拷贝原理到信号量同步,提升跨进程通信性能

共享内存IPC实战:从零拷贝原理到信号量同步,提升跨进程通信性能

共享内存(shm)这个老牌IPC方案,我最近在一个数据采集服务里又把它翻了出来。这个服务原本是跨进程传数据,管道和Unix域Socket用得挺顺手,可流量一起来,CPU占用就再也压不下去。换成共享内存之后&#xff0c…

2026/10/15 4:26:50 阅读更多 →
如何判断Ubuntu是否真的关机?三路检查方法与实战探测脚本

如何判断Ubuntu是否真的关机?三路检查方法与实战探测脚本

你有没有遇到过这种情况:在终端里输入了sudo shutdown now,屏幕上日志飞快滚动,最后一切归于黑暗,风扇也慢慢停转,你很自然地认为这台 Ubuntu 机器已经关机了。但我在几年的实践中发现,这个结论经常下得太早…

2026/10/15 4:25:50 阅读更多 →

日新闻

Docker Compose 容器化部署 ChirpStack LoRaWAN 网络服务器实战

Docker Compose 容器化部署 ChirpStack LoRaWAN 网络服务器实战

1. 为什么选择容器化方式部署 LoRa 网络服务LoRa 物联网项目落地时,网络服务器是整个链路的核心枢纽。它负责接收网关上报的数据包、完成去重与解密、再把有效载荷分发给上层应用。早期大家习惯把网络服务器直接装在物理机上,依赖一堆系统库和特定版本的…

2026/10/15 0:00:46 阅读更多 →
共享LLM服务下的跨模型自动扩缩容:Kubernetes实战与避坑指南

共享LLM服务下的跨模型自动扩缩容:Kubernetes实战与避坑指南

1. 共享推理服务里,扩缩容到底难在哪做过大模型推理服务部署的人,大概都经历过这样的场景:一个团队共用一套推理集群,A 业务跑的是 7B 的小模型,B 业务跑的是 72B 的大模型,C 业务白天流量高、晚上几乎归零…

2026/10/15 0:00:46 阅读更多 →
Systemd 启动阶段根文件系统只读之谜:fstab 与 remount-fs 服务深度解析

Systemd 启动阶段根文件系统只读之谜:fstab 与 remount-fs 服务深度解析

1. 引子:一次内核恐慌引发的追查先从一个真实场景说起。某次我给一台老旧的嵌入式设备折腾系统裁减,为了把启动时间压进两秒以内,我手贱地把一串自认为“没用”的 systemd 服务给 disable 了,其中就包括systemd-remount-fs.servic…

2026/10/15 0:00:46 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/14 3:30:28 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/14 16:30:29 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/14 3:29:58 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/15 0:14:36 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/14 22:45:47 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/14 7:53:34 阅读更多 →