本章实验 · 三个实验
本章三个实验一览
第六章主实验要接 MQTT、风扇与三线程产品骨架。为了不一把抓,拆成三个递进实验:实验一、实验二纯软件、无接线、无 Broker,先看清「丢更新」和「成对快照不一致」;实验三再接到真硬件与 MQTT。
| 实验 | 练什么 | 器材 | 源码目录 |
|---|---|---|---|
| 实验一 race-demo + TSan |
无锁计数丢更新;TSan 指出抢同一变量的两行 | 无 | code/race-demo/ |
| 实验二 成对快照 + 互斥锁 |
写/读 temp_a/temp_b;无锁必现 [RACE],加锁后 race_hits=0 |
无 | code/snapshot-lock/ |
| 实验三 三线程协同 |
采集∥控制∥MQTT;把实验二的成对加锁接到产品路径;Ctrl+C 关风扇 | 风扇/继电器(SIMULATE 可先不接 DHT) | code/tri-thread/ |
USE_LOCK 1 为准。
实验一 · race-demo + TSan
目的:用「很多线程同时给同一计数器加一」亲眼看到最终计数偏小,再用 TSan 指出具体是哪两行在抢。主机或板端均可;下面摘录来自板端。
$ cd chapters/ch06/code/race-demo $ make && ./race-demo expect=400000 got=215946 RACE(lost updates)
主机跑 TSan 若报 FATAL: ThreadSanitizer: unexpected memory mapping,用下面任一种方式规避 ASLR:sudo sysctl -w vm.mmap_rnd_bits=28,或直接用 setarch $(uname -m) -R ./race-demo(无需 root;板端可直接跑,不用处理)。
$ make tsan && ./race-demo
WARNING: ThreadSanitizer: data race
Read of size 8 ... by thread T2:
#0 bump_racy .../main.c:24
Previous write of size 8 ... by thread T1:
#0 bump_racy .../main.c:26
SUMMARY: ThreadSanitizer: data race .../main.c:24 in bump_racy
本实验成功判据:有「计数偏小」记录;有完整 TSan 报告;能指出抢变量的两行。
RACE
make tsan 后 ./race-demo 给出 WARNING: ThreadSanitizer: data race,指出 bump_racy 里读/写同一全局计数的两行实验二 · 成对快照 + 互斥锁(snapshot-lock)
实验三卡人的往往是「同一次温度拆成 temp_a/temp_b」:无锁时控制线程读到半截快照就打 [RACE]。本实验把这对写入/读出剥成纯软件:写线程 pair_write,读线程 pair_read。源码 chapters/ch06/code/snapshot-lock/,只实现这两个函数(脚手架已故意留空窗)。
# 主机,或先 scp 到板子再进目录 $ cd chapters/ch06/code/snapshot-lock # 板上:cd ~/snapshot-lock $ make && ./build/snapshot-lock # 未加锁 → 大量 [RACE],race_hits 很多(预期) $ make sol && ./build/snapshot-lock-sol # 参考:race_hits=0
打开 src/pair.c:用同一把 pthread_mutex 包住成对写与成对读;加锁后不要在中间 usleep。对照 pair-sol.c。
本实验成功判据:脚手架能打出 [RACE];make sol 后 race_hits=0;能口头说这套成对加锁平移到实验三就是 USE_LOCK 1。
[RACE];加锁版 race_hits=0
race_hits=0
race_hits实验三 · 三线程协同(主实验)
打开 chapters/ch06/code/tri-thread/。Broker 用 BROKER_HOST(主机先 hostname -I)。脚手架默认 USE_LOCK 0:采集把同一次温度拆成 temp_a/temp_b 两步写并留空窗(与实验二同一招),控制线程读到不一致就打印 [RACE]。
-
先
USE_LOCK 0,跑 10–20 秒,应反复出现:[RACE] inconsistent snapshot temp_a=275 temp_b=270
退出时race_hits远大于 0。 -
改为
USE_LOCK 1重编再跑——改宏后必须clean,否则目标文件不会重编:$ make clean && make USE_LOCK=1 $ BROKER_HOST=<主机局域网IP> ./tri-thread
无[RACE],race_hits=0;风扇按阈值动作。 -
编译运行(板端原生或主机交叉):
$ cd chapters/ch06/code/tri-thread $ make clean && make $ BROKER_HOST=<主机局域网IP> ./tri-thread # 或:make CROSS_COMPILE=riscv64-ruyisdk-linux-gnu- && scp …
-
MQTT / 干净停:主机
mosquitto_pub … -t course/thermo/cmd -m 'status'(还有fan on/fan off/fan auto、set high 30),板端见[MQTT] cmd payload=...;Ctrl+C 后风扇关并exited cleanly。强制开关不会被自动控制覆盖:下发fan off后即使温度高于上限,状态仍是fan=off mode=force-off。
实机摘录(USE_LOCK=1):
[INFO] USE_LOCK=1 SIMULATE_SENSOR=1 [MQTT] subscribed course/thermo/cmd [INFO] fan ON [MQTT] cmd payload=set high 30 (len=11) [INFO] race_hits=0 (USE_LOCK=1;期望 0→很多,1→0) [INFO] exited cleanly
MQTT 命令与并发的实机转录(USE_LOCK=1,主机侧发命令)。注意 fan off 那次温度已高于上限,自动控制没有把它翻回 ON——读、判、写在同一把锁内完成;最后一条 fan on 长度是 11 而不是 6,那是带内嵌 NUL 的伪造载荷 fan on\0junk,被拒且不改状态:
[MQTT] cmd payload=fan on (len=6) [INFO] fan ON [MQTT] status temp=30.5 hum=55.0 fan=on mode=force-on high=28.0 low=26.0 [MQTT] cmd payload=fan off (len=7) [INFO] fan OFF [MQTT] status temp=30.5 hum=55.0 fan=off mode=force-off high=28.0 low=26.0 [MQTT] cmd payload=set high 30 (len=11) [MQTT] status temp=28.5 hum=55.0 fan=off mode=force-off high=30.0 low=26.0 [MQTT] cmd payload=fan on (len=11) [ERR] unknown command payload (len=11), ignored [MQTT] status temp=26.5 hum=55.0 fan=off mode=force-off high=30.0 low=26.0 [MQTT] cmd payload=fan auto (len=8) [MQTT] fan mode auto
命令按 payloadlen 精确匹配(与第五章同一个坑):载荷必须与命令等长且逐字节相同,不能先当 C 字符串再 strcmp——否则 fan on\0junk 会被截断成合法的 fan on。实测被拒的还有 status\0、set high 30\0x、fan autoo,板端一律打印 [ERR] unknown command payload,不改风扇、不发 status。
另外两处「不假装成功」:订阅失败会报错并按断线重连(不会只打一行 subscribed 就算连上);status 的发布失败会报 [ERR] status publish,不再打出假的成功行。Broker 连续不可达达上限后只收 MQTT 这一条腿,采集/控制线程继续跑本地演示(MQTT disabled; sense/control keep running),不再把整个程序带走。
[RACE];加锁后 race_hits=0 并干净退出
[RACE] inconsistent snapshot
race_hits=0,Ctrl+C 干净停fan on/off/auto 与 led on/off,画面里继电器吸合、风扇转停、LED 亮灭——三线程的控制线程驱动的就是这条信号链。同一段接线在综合项目页复用。验收
| 实验 | 项 | 通过条件 | □ |
|---|---|---|---|
| 一 | 丢更新 | race-demo 最终计数明显小于期望 | |
| 一 | TSan | 报告已保存,能指出抢变量的两行 | |
| 二 | 无锁快照 | snapshot-lock 脚手架出现 [RACE] 且 race_hits>0 | |
| 二 | 加锁快照 | make sol 后 race_hits=0 | |
| 三 | 无锁 | USE_LOCK 0 出现 [RACE] 且 race_hits>0 | |
| 三 | 加锁 | USE_LOCK 1 无 [RACE],风扇阈值正常 | |
| 三 | MQTT | 下行或上行至少一项可演示 | |
| 三 | 干净停 | Ctrl+C 后风扇关,进程退出 | |
| 三 | 实物 | 命令与实物动作对得上:继电器吸合、风扇转停、LED 亮灭 |