4. PFS 性能调优:IO大小与对齐、并发访问控制、缓存策略、碎片整理与磨损均衡
好,咱们接着聊PFS。前面几章把PFS的架构和基本操作讲透了,这一章我打算聊聊实战中最头疼的问题——性能调优。
说实话,PFS这玩意儿,用好了是真香,用不好那就是灾难。我在QNX车机项目上见过太多因为PFS配置不当导致的性能翻车事故。嗯,咱们一个一个来说。
4.1 IO大小与对齐:别小看这个细节
先问个问题:你写文件的时候,是一次写4KB快,还是一次写64KB快?
很多人想都不想就说64KB。其实不一定。PFS底层操作的是flash块,而flash块的大小通常是4KB、8KB或者16KB。如果你写的IO大小刚好是块大小的整数倍,而且地址对齐到块边界,那性能会好很多。
我遇到过这样一个案例:有个同事做日志系统,每次写一条日志就调一次write(),每条日志才128字节。结果呢?PFS每写一次都要做读-修改-写操作,性能惨不忍睹。后来改成攒够4KB再写,吞吐量直接翻了5倍。
核心原则:
- IO大小最好是PFS块大小的整数倍(常见4KB、8KB)
- 起始地址要对齐到块边界
- 避免小于块大小的随机写
代码里怎么控制?其实很简单:
/* 推荐:对齐的、整块大小的IO */
#define PFS_BLOCK_SIZE 4096
char buffer[PFS_BLOCK_SIZE] __attribute__((aligned(PFS_BLOCK_SIZE)));
/* 坏例子:不对齐、非整块 */
char small_buf[128];
lseek(fd, 100, SEEK_SET); /* 偏移不对齐 */
write(fd, small_buf, 128); /* 大小不是块整数倍 */
我的小技巧:在PFS上做数据库或者日志系统时,我习惯把记录大小设计成块大小的约数,比如4KB块就用512字节一条记录。这样既能避免跨块操作,又能充分利用空间。
4.2 并发访问控制:别让多线程打架
车机里多线程是常态。导航线程写地图缓存,媒体线程写播放列表,日志线程写诊断信息……大家都往PFS上写,冲突怎么办?
PFS本身是支持多线程并发读写的,但有个坑——写操作是互斥的。同一时刻只能有一个线程在写,其他写线程得排队。
为什么会这样?因为PFS要保证元数据的一致性。你想想看,两个线程同时写,一个在改文件大小,一个在改目录项,那不乱套了?
我在项目里遇到过这种情况:四个线程同时写日志,结果发现写延迟从2ms飙到了50ms。一查,原来是写锁竞争太激烈了。
避坑指南:我曾经在一个项目中,多个线程频繁写入小文件,导致PFS的写锁几乎被持续占用,读操作也被拖慢。后来我做了两件事:
- 把多个小写合并成一个大写(攒批写入)
- 把日志线程的优先级降低,别让它抢了关键任务的资源
并发控制的最佳实践:
- 读多写少:PFS读操作不阻塞,放心并发读
- 写操作:尽量合并,减少写锁竞争
- 关键路径:别在中断上下文或者高优先级线程里做PFS写操作
- 使用O_SYNC:慎用,每次写都刷盘,性能影响很大
4.3 缓存策略:读写性能的加速器
PFS有缓存,这是它比裸flash快的重要原因。但缓存怎么配,学问大了。
PFS的缓存分两种:数据缓存和元数据缓存。数据缓存缓存文件内容,元数据缓存缓存目录项、inode这些。
我个人的习惯是:
- 如果系统内存充裕(比如2GB以上),缓存设大点,128MB起步
- 如果内存紧张(比如512MB),缓存设小点,但别低于16MB
- 元数据缓存至少留4MB,不然目录操作会慢得让你怀疑人生
配置方法:
/* 在PFS挂载时指定缓存大小 */
mount -t pfs -o cache_size=64m,meta_cache=8m /dev/flash0 /mnt/pfs
缓存策略的取舍:
| 场景 | 推荐缓存策略 | 原因 |
|---|---|---|
| 频繁读小文件 | 大缓存 + 预读 | 减少flash访问次数 |
| 频繁写日志 | 写缓存 + 延迟刷盘 | 合并小写,提升吞吐 |
| 关键数据(如配置) | 直写 + O_SYNC | 保证数据不丢 |
| 多媒体流 | 大缓存 + 顺序预读 | 减少卡顿 |
嗯,这里要注意:缓存不是越大越好。缓存太大,刷盘的时候会卡一下,影响实时性。车机里最怕的就是这种「突然卡顿」。我建议你根据实际场景做压力测试,找到那个平衡点。
4.4 碎片整理:PFS也需要「瘦身」
很多人以为PFS是flash文件系统,没有磁盘碎片问题。其实不然。PFS长时间使用后,文件频繁创建删除,会导致逻辑碎片和物理碎片。
逻辑碎片是指文件的数据块在flash上不连续,读文件时要跳来跳去。物理碎片是指flash上有大量空闲但分散的小块,没法分配大文件。
我在一个车载记录仪项目上遇到过:连续录了三个月视频,PFS上全是碎片,写一个新文件要等好几秒——因为PFS在拼命找连续的空闲块。
碎片整理的策略:
- 定期整理:车机休眠或者空闲时,触发碎片整理
- 预留空间:保留10%-20%的空闲空间,能显著减少碎片
- 大文件优先:大文件更容易产生碎片,优先整理
- 使用PFS自带的defrag工具:QNX提供了pfs_defrag命令
/* 手动触发碎片整理 */
pfs_defrag /mnt/pfs
/* 查看碎片率 */
pfs_stat /mnt/pfs | grep fragmentation
注意:碎片整理时PFS性能会下降,千万别在行车过程中做。我一般选择在车机进入休眠模式后触发,或者用定时任务在凌晨3点做。
4.5 磨损均衡:让flash活得更久
flash有写入寿命,这是常识。PFS内置了磨损均衡算法,但你要是不注意,它照样会提前报废。
磨损均衡的核心思想是:把写入操作均匀分布到所有flash块上,别逮着一个块猛写。
我见过最典型的反面教材:有人把日志文件固定写在一个位置,结果那个块一个月就挂了。PFS虽然会做磨损均衡,但如果你频繁改写同一个文件,它也没办法——因为文件的数据块是固定的。
怎么优化?
- 日志轮转:别老写同一个文件,用logrotate定期切分
- 预留备用块:PFS默认会预留一部分块做备用,别改这个参数
- 减少写放大:小写操作会导致写放大,尽量合并成大写
- 使用TRIM:删除文件后通知flash回收空间,减少无效磨损
磨损均衡的监控:
/* 查看flash的擦除次数分布 */
pfs_stat /mnt/pfs -v | grep erase
/* 如果发现某些块擦除次数远高于平均值,说明磨损均衡有问题 */
说实话,磨损均衡这东西,你平时感觉不到它的存在。但一旦flash提前挂了,数据全丢,那才叫欲哭无泪。我建议你在项目初期就做好磨损均衡的规划,别等到量产了再改。
好了,PFS性能调优就聊到这儿。总结一下:IO对齐是基本功,并发控制要谨慎,缓存策略看场景,碎片整理定期做,磨损均衡别忽视。这五点做好了,PFS在车机上跑个三五年没问题。
下一章咱们聊聊PFS的可靠性设计,包括掉电保护、数据校验和恢复机制。那个更刺激,到时候见。