USB SPOR工具开发问题集
USB SPOR 工具 MFC 开发问题集
1 Sector = 0.5 K B= 512Byte
2 Sector = 1 KB
128 Sector = 64 KB
1. SPOR的整体架构
主线程:循环写 → 被打断 → 校验 → 再循环写
掉电线程:随机时刻打断写 → 掉电 → 保持掉电2秒 → 上电 → 交回主线程进行校验
1.1 整体逻辑
上电 → 主线程连续写入
掉电线程 1~5 秒内随机到断电
不管主线程写到哪,直接断
上电 → 主线程校验上一次写入段
校验成功 → 继续连续写入
日志输出:
[通道0] 第1轮 | 写入起始LBA: 0 | 长度: 156
[通道0] 执行掉电
[通道0] 执行上电 ⚡
[通道0] 开始校验 | 起始LBA: 0 | 长度: 156
[通道0] 第1轮 校验成功 ✅
[通道0] 第2轮 | 写入起始LBA: 156 | 长度: 273
[通道0] 执行掉电
[通道0] 执行上电 ⚡
[通道0] 开始校验 | 起始LBA: 156 | 长度: 273
[通道0] 第2轮 校验成功 ✅
1.2 写入的数据模型
每个扇区的数据都是 测试次数+LBA
每一个扇区(512Byte)结构:
【0~3字节】 SPOR 轮次
【4~511字节】 全是当前这个扇区的 LBA 地址(重复填充)
✅ 扇区 0:SPOR 次数 + LBA0
✅ 扇区 1:SPOR 次数 + LBA1
✅ 扇区 2:SPOR 次数 + LBA2
2. SPOR测试过程打开日志文件导致异常中断
原因:文件没有共享权限,当文件被占用 or 多线程写日志没有同步就会出现这个问题
流程:文件独占 + 错误写入 + 无锁 + 错误判断 → 打开log就触发异常
- 文件被记事本占用
- 你用独占模式打开 → 失败
- 失败后逻辑继续执行
- 多线程同时写 → 放大问题,出现异常中断
2.1 创建文件时没有设置共享模式
// 未设置共享模式:默认情况下是独占访问(不允许其他程序打开)
MyFile.Open(pszlogName, CFile::modeReadWrite)
// 设置共享模式:
CFile file;
file.Open(pszlogName,CFile::modeWrite | CFile::modeCreate | CFile::modeNoTruncate)
MFC CFile 默认规则:
- 只要用了CFile::modeWrite 或 CFile::modeCreate系统会自动加上 FILE_SHARE_READ | FILE_SHARE_WRITE 共享权限!
- 只用 CFile::modeReadWrite 没有共享权限
| 代码 | 共享权限 | 多线程安全 | 是否适合 SPOR 测试 |
|---|---|---|---|
| modeReadWrite | ❌ 无 | 不安全,会锁文件 | ❌ 不推荐 |
| modeWrite / modeCreate | ✅ 有 | 安全 | ✅ 推荐 |
2.2 多线程写入日志文件时无锁 SPOR 容易出问题
一般来说主线程 + 掉电线程 → 同时写log,如果没有任何锁容易出现文件指针竞争、写入交错甚至崩溃
static CRITICAL_SECTION logLock;
EnterCriticalSection(&logLock);
LeaveCriticalSection(&logLock);
2.3 Windows 记事本打开日志输出乱码问题
问题:日志输出使用 SublimeText 能正常打开不会乱码,使用Window11系统自带的记事本打开出现乱码
SublimeText:宽容 → 自动猜编码 → 正常显示
记事本:严格 → 解析失败 → 乱码
| 编辑器 | 行为 |
|---|---|
| Sublime | 自动猜编码(容错强) |
| Notepad | 严格按 BOM + 编码解析 |
// 只需要在调用File.Open之后增加
if (file.GetLength() == 0)
{
WORD bom = 0xFEFF; // UTF-16 LE BOM
file.Write(&bom, sizeof(bom));
}
3. 使用 GDI/DC 日志输出至 UI 界面问题
如果使用 GDI/DC 进行日志输出至UI界面,很容易犯的两个典型坑:
- GetDC 没有 ReleaseDC(会耗尽GDI资源)
- 递归 + 高频调用
3.1 最直接的问题:GetDC 没有 ReleaseDC(会耗尽GDI资源)
CDC *pDC = pListBox->GetDC();
...
pDC->SelectObject(pOldFont);
// 但没有释放
pListBox->ReleaseDC(pDC);
❗结果:SPOR长时间跑:
每次调用 → GetDC → 不释放
→ GDI Handle 持续增长
→ 达到上限(~10k)
→ SelectObject / GetTextExtent 崩
3.2 第二个隐患:递归 + 高频调用
if(len != strLen)
{
ListBoxShowInfo(pListBox, infoStr.Right(strLen - len));
}
👉 问题:
长字符串 → 多次递归
每一层都:
GetDC
SelectObject
在SPOR日志高频输出下
👉 GDI压力被放大
3.3 ListBox本身也有上限
Win32 ListBox:
- 默认最多 ~32767 条(早期)
- 实际受内存/GDI影响
4. 了解什么是 GDI(顺便复习一下)
GDI 是 Graphics Device Interface(图形设备接口),是 Windows 提供的一套底层绘图系统,负责把“画图操作”真正画到屏幕、打印机等设备上。
GDI = Windows 负责画界面的底层引擎
CDC *pDC = pListBox->GetDC();
CFont *pOldFont = pDC->SelectObject(pFont);
CSize sz = pDC->GetTextExtent(infoStr, len);
// 👉 这些全部都是 GDI操作
| API | 本质 |
|---|---|
| GetDC | 获取绘图上下文(画布) |
| SelectObject | 选择字体/画笔 |
| GetTextExtent | 计算文本尺寸 |
| ReleaseDC | 释放资源 |
4.1 GDI 的核心概念(必须理解的)
1️⃣ DC(Device Context)
可以理解为:画布 + 画图工具集合
👉 你要画任何东西,都必须先拿到 DC:
CDC *pDC = GetDC();
2️⃣ GDI对象
比如:
- 字体(CFont)
- 画笔(Pen)
- 画刷(Brush)
- 位图(Bitmap)
👉 使用前必须:
SelectObject
3️⃣ GDI资源是有限的(重点)
Windows 每个进程:
GDI对象上限 ≈ 10,000(典型值)
4.2 典型错误模式(刚好踩了)
❌ 错误1:GetDC 不释放
pListBox->GetDC();
// 没有 ReleaseDC
❌ 错误2:频繁GDI操作
循环里疯狂 GetTextExtent
❌ 错误3:多线程用GDI
👉 GDI本身不是线程安全的
4.3 正确使用方式(标准写法)
CDC *pDC = pListBox->GetDC();
if (pDC)
{
CFont *pOldFont = pDC->SelectObject(pFont);
CSize sz = pDC->GetTextExtent(infoStr);
pDC->SelectObject(pOldFont);
pListBox->ReleaseDC(pDC); // ★必须
}
5. Windows 的设备插拔通知导致 PC 卡顿
当进行 SPOR 测试时:
掉电 → USB断开 → 上电 → 重新枚举
Windows 会触发:
Shell Hardware Detection (ShellHWDetection)
它会做:
- 自动播放检测
- 设备变化广播
- Explorer刷新
- 弹提示
👉 SPOR 高频触发 → 系统消息风暴 → UI卡顿
5.1 手动禁用 Shell Hardware Detection (ShellHWDetection)
❗注意:👉 测试前停 → 测试后恢复
桌面我的电脑 → 右键找到“管理”→ 服务 → Shell Hardware Detection (ShellHWDetection)
5.2 程序里代码控制 ShellHWDetection
启动SPOR工具时停止 ShellHWDetection,退出SPOR工具时恢复启动 ShellHWDetection
必须要用管理员模式运行 SPOR 工具才能控制 ShellHWDetection,不然权限不够
#include <windows.h>
#include <winsvc.h> // ★关键:服务控制API定义在这里
#pragma comment(lib, "Advapi32.lib")
👉 加在 .cpp 顶部即可
BOOL CDeviceOpreationDlg::StopShellHWDetection()
{
SC_HANDLE hSCM = OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT);
if (!hSCM) return FALSE;
SC_HANDLE hService = OpenService(
hSCM,
_T("ShellHWDetection"),
SERVICE_STOP | SERVICE_QUERY_STATUS
);
if (!hService)
{
CloseServiceHandle(hSCM);
return FALSE;
}
SERVICE_STATUS status;
ControlService(hService, SERVICE_CONTROL_STOP, &status);
// 等待停止
for (int i = 0; i < 20; i++)
{
QueryServiceStatus(hService, &status);
if (status.dwCurrentState == SERVICE_STOPPED)
break;
Sleep(200);
}
CloseServiceHandle(hService);
CloseServiceHandle(hSCM);
return TRUE;
}
BOOL CDeviceOpreationDlg::StartShellHWDetection()
{
SC_HANDLE hSCM = OpenSCManager(NULL, NULL, SC_MANAGER_CONNECT);
if (!hSCM) return FALSE;
SC_HANDLE hService = OpenService(
hSCM,
_T("ShellHWDetection"),
SERVICE_START | SERVICE_QUERY_STATUS
);
if (!hService)
{
CloseServiceHandle(hSCM);
return FALSE;
}
StartService(hService, 0, NULL);
SERVICE_STATUS status;
for (int i = 0; i < 20; i++)
{
QueryServiceStatus(hService, &status);
if (status.dwCurrentState == SERVICE_RUNNING)
break;
Sleep(200);
}
CloseServiceHandle(hService);
CloseServiceHandle(hSCM);
return TRUE;
}
6. 多设备并发 SPOR 触发系统层竞争
6.1 原因
6.1.1 为什么单盘OK,双盘就不行
当两个设备同时:
掉电 → 上电 → 枚举 → 挂载 → 打开句柄
Windows 内部发生:
1. USB Hub 复位(可能是同一个Hub)
2. PnP 设备枚举(串行/锁)
3. 磁盘类驱动初始化
4. 卷管理器挂载(MountMgr)
5. 文件系统锁
👉 两个盘一起,会出现:
✔ 枚举延迟
✔ 盘符暂时不存在
✔ 设备 busy
✔ 句柄被系统占用
✔ CreateFile 返回失败
6.1.2 为什么“线程独立”仍然会冲突
假设并发是这样:
线程A:USB1 掉电 → 上电 → CreateFile
线程B:USB2 掉电 → 上电 → CreateFile
但 Windows 内部实际是:
USB Hub(共享)
↓
USB Host Controller(共享)
↓
PnP Manager(全局锁/串行化)
↓
Disk.sys / ClassPnP(共享队列)
↓
MountMgr(盘符分配,强串行)
👉 关键点:
这些路径不是 per-device 独立的,而是“全局共享 + 有锁”
所以会出现:
- 一个设备正在枚举,另一个设备的 I/O 被阻塞
- MountMgr 正在处理盘符,另一个设备 CreateFile 失败
- 设备节点还没 ready,但已经访问了
典型竞态如下:
T0:USB1 上电 → 开始枚举
T1:USB2 上电 → 也开始枚举
T2:系统正在处理 USB1(锁住)
T3:线程B 去 CreateFile(USB2) → 设备还没 ready → FAIL
6.1.3 容易忽略的点
❗盘符不是稳定资源,盘符分配是动态的
可能发生:
- 上电瞬间没有盘符
- 盘符被延迟挂载
- 两个设备竞争 MountMgr
👉 这也会导致:
ERROR_FILE_NOT_FOUND / ERROR_PATH_NOT_FOUND
6.1.4 正确的解决思路(不是简单加锁)
✅ 1. CreateFile 必须变成“状态等待”
不是:
失败 → return
而是:
失败 → 等待设备ready → 重试
但多设备要更激进一点:
// 建议:5~10秒窗口
for (int i = 0; i < 100; i++)
{
...
Sleep(100);
}
但笔者测试过似乎并没有完全解决这个问题,只是降低了发生竞争的概率,应该还有其他解决方案,暂时未探讨!
6.2 CreateFile 失败不直接返回 NULL 或错误
❗核心思想:CreateFile失败 ≠ 错误,而是“设备暂时不可用”
HANDLE CDeviceOpreationDlg::GetDiskHandle(CString diskName)
{
CString path;
path.Format(_T("\\\\.\\%s"), diskName);
HANDLE hDev = INVALID_HANDLE_VALUE;
DWORD err = 0;
// 等待最长 5 秒(多设备必须加长)
for (int i = 0; i < 50; i++)
{
hDev = CreateFile(
path,
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL);
if (hDev != INVALID_HANDLE_VALUE)
{
return hDev; // 成功
}
err = GetLastError();
// 关键:这些错误是“正常的”
if (err == ERROR_ACCESS_DENIED ||
err == ERROR_NOT_READY ||
err == ERROR_FILE_NOT_FOUND ||
err == ERROR_PATH_NOT_FOUND ||
err == ERROR_SHARING_VIOLATION)
{
Sleep(100); // 等设备稳定
continue;
}
// 其他异常才认为是真失败
break;
}
CString str;
str.Format(_T("GetDiskHandle fail! err=%d disk=%s"), err, diskName);
Infomation_Deal(ONLY_SAVE, str);
return INVALID_HANDLE_VALUE;
}
6.3 GPT 给出的方案
这个问题没有“彻底消除冲突”的办法,只能工程化地规避系统瓶颈。你现在的冲突点在三层:
USB Host / Hub(硬件共享)
→ PnP / 枚举(内核串行)
→ MountMgr / 文件系统(全局资源)
所以思路不是“让它们完全并发”,而是:
❗把“破坏性操作(掉电/上电/枚举)串行化”,把“数据IO”并行化
下面给你一套可落地的分层方案(按收益排序)。
6.3.1 核心方案:掉电调度器(必须做)
❗问题本质
多个设备 → 同时掉电 → 同时上电 → 同时枚举
👉 这一步直接把:
USB + PnP + MountMgr 全部打爆
✅ 解决:全局掉电仲裁(推荐)
CRITICAL_SECTION g_SporPowerLock;
掉电线程:
EnterCriticalSection(&g_SporPowerLock);
// 掉电
AXxxxDev.ControlDevicePower(cmdHdev, FALSE);
Sleep(...);
// 上电
AXxxxDev.ControlDevicePower(cmdHdev, TRUE);
Sleep(...);
LeaveCriticalSection(&g_SporPowerLock);
✔ 效果
✔ 掉电不会同时发生
✔ 枚举不会冲突
✔ CreateFile 成功率大幅提升
⚠️ 注意
锁住的是“掉电过程”,不是整个测试
IO仍然可以并发。
6.3.2 进阶方案:时间片调度(比锁更优雅)
比起“硬锁”,更推荐:
✅ 分时调度(工业常用)
设备0:0~5秒窗口掉电
设备1:5~10秒窗口掉电
设备2:10~15秒窗口掉电
实现:
int base = channel * 5000;
Sleep(base + rand() % 5000);
✔ 优势
✔ 无锁
✔ 不会完全串行
✔ 更接近真实随机SPOR
6.3.3 设备Ready检测(必须补)
✅ 正确做法:以“设备可打开”为准
HANDLE hDev;
for (int i = 0; i < 100; i++)
{
hDev = CreateFile(...);
if (hDev != INVALID_HANDLE_VALUE)
break;
Sleep(100);
}
✔ 本质
CreateFile成功 = 设备ready(唯一可信信号)
6.3.4 避免盘符竞争(强烈建议)
问题:
盘符由 MountMgr 分配 → 强串行资源
✅ 改成:
\\.\PhysicalDriveX
✔ 优势
✔ 不依赖文件系统
✔ 枚举更早可访问
✔ 多设备更稳定
6.3.5 降低系统干扰
✅ 必做
测试前:
关闭 ShellHWDetection 服务
否则:
每次上电 → 系统扫描 → 抢占设备
6.3.6 I/O层容错(必须做)
✅ 正确:
if (!ReadSectorDataFromUDisk(...))
{
// 重试几次
for (int retry = 0; retry < 5; retry++)
{
Sleep(50);
if (ReadSectorDataFromUDisk(...))
break;
}
}
✔ 原因
设备刚ready → 短时间IO不稳定(正常现象)
6.3.7 硬件层优化(很多人忽略)
如果你真要跑多设备:
❗不要用同一个 USB Hub
笔记本/前面板USB口 → 很可能同一个Hub ❌
✅ 推荐
✔ 不同Root Hub(主板后置口)
✔ PCIe USB扩展卡
✔ 工业USB HUB(带独立控制器)
6.3.8 终极方案(量产级)
引入一个SPOR调度状态机:
状态:
IDLE
WRITE
WAIT_POWER_SLOT
POWER_DOWN
POWER_UP
WAIT_READY
VERIFY
统一调度:
任何时刻:
只允许1个设备处于 POWER_DOWN / POWER_UP
6.3.9 一句话解决方案
掉电串行化 + IO并行化 + CreateFile等待机制
6.3.10、最小可落地组合(建议先做)
先改这三点,就能明显稳定:
① 掉电加全局锁(或错峰)
② CreateFile失败重试(5~10秒)
③ Read/Write失败重试
如果你后面要扩到:
4路 / 8路 / 16路 SPOR
那就必须上:
调度器 + 状态机 + 设备管理层
否则不是“偶现问题”,而是“必然不稳定”。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)