cgminer 3.1.1 Windows老设备验证与4028 API监控实践
简介cgminer 3.1.1 Windows 版是一款面向比特币及其衍生币挖矿的多线程、多矿池挖矿程序支持 GPU、FPGA 与 ASIC 设备并提供 ATI 显卡状态监控、超频和风扇转速调节等参数配置适合需要搭建或调试旧版挖矿环境的进阶用户。压缩包共 43 个文件约 6.83MB包含 exe 可执行程序与 dll 运行库cl 内核、bit/bin/ncd 等 FPGA 位流及配置文件以及 txt 说明文档、php/java 示例接口等便于查看编译与使用说明、API 调用方式和不同设备的配置样例。已有 560 人学习下载可作为了解早期多设备挖矿软件结构、核对依赖库和排查运行问题的参考资料。对希望研究挖矿软件目录组织、依赖库搭配及显卡内核调用方式的读者包内说明文档与示例文件能提供较完整的线索。需注意该程序不应同时用于多条区块链使用前应确认币种与硬件匹配。1. 先认清 cgminer 3.1.1 windows 这个包的边界它不是绿色软件是老设备验证器有人在 2024 年还把 cgminer 3.1.1 windows 这个压缩包翻出来动机通常不是重跑业务而是手里压着一批老显卡、老 FPGA 板卡想确认它们还能不能被识别、算力曲线还正不正常、对接的接口还通不通。这个包本质上是 2013 年前后编译出的命令行硬件调度客户端通过 stratum 协议跟上位服务端通信把哈希任务分发给本机的 GPU 或外接设备再把结果回传。它没有安装程序没有图形界面双击 exe 大概率直接闪退必须靠参数和配置文件驱动。适合两类人一类是要复现十年前软硬件栈的维护者一类是想拿它的 API 做设备监控与调度二次开发的工程师。不适合指望开箱即用的人。2. 解压之后先别双击目录结构、运行库依赖与第一次启动老版本 Windows 包最容易翻车的地方不在参数而在「点下去没反应」。这一章把压缩包拆开来看说清楚每个文件干什么、哪几个 DLL 是必须的、驱动和运行库缺了会表现成什么样最后给一条能跑通的最小启动命令。2.1 目录里的文件和它们各自的职责解压出来通常是一个扁平目录没有 bin、lib 分层。常见构成如下不同打包者会增减但主体差不多文件作用缺失后的表现cgminer.exe主程序单文件调度核心无*.clOpenCL 内核源码运行期由驱动编译启动时报 kernel 编译失败*.bin预编译内核缓存文件名带内核日期首次启动变慢或回退到源码编译pthreadGC2.dll线程库多设备并发靠它启动瞬间报找不到 pthreadGC2.dlllibcurl.dllHTTP/stratum 传输层能识别设备但连不上上游libeay32.dll / ssleay32.dll加密库stratum 握手用连接被拒或握手阶段断开libwinpthread-1.dll新版打包可能带的线程库替代同上启动期报缺 DLLcgminer.conf可选配置JSON 格式不影响启动缺了就全靠命令行.cl文件是最容易被忽略的一块。cgminer 在 3.x 时代对每种算法、每种架构都有独立内核常见的有poclbm.cl通用 OpenCL、phatk.clAMD 优化版、diakgcn.clGCN 架构专用。这些不是装饰文件运行时会由显卡驱动现场编译成机器码所以目录路径里出现中文、空格、括号编译就会失败报错往往只有一行Failed to build binary看不出是路径问题。我一般把整个目录放到D:\cgminer311\这种纯 ASCII 短路径下再动手。还有一个隐藏坑Windows 默认不显示扩展名有些打包者把cgminer.conf.txt直接改名成cgminer.conf实际仍是文本文件程序读得到但反过来把.cl存成.cl.txt程序就找不到内核报的错跟路径问题一模一样。解压后先开「显示文件扩展名」扫一眼再往下走。2.2 运行库、驱动与 OpenCL 运行时三项前置检查cgminer 3.1.1 编译时链接的是 VC 2008/2010 时代的运行库64 位系统上跑 32 位 exe 还需要 WOW64 那一套。缺运行库的典型现象是双击后窗口一闪而过或者弹一个0xc000007b。处理顺序是先装对应位数的 VC 运行库2008 和 2010 各装一遍最稳再确认 DLL 齐全最后才看驱动。驱动这块比运行库麻烦。cgminer 不直接跟显卡打交道它通过 OpenCL 运行时枚举设备。所以真正决定「能不能看到卡」的是驱动里的 OpenCL 组件而不是 cgminer 本身。老卡配新驱动经常反过来出问题新驱动把老的 OpenCL 1.1 路径改掉了cgminer 枚举时返回零设备。常见做法是给验证机单独准备一个驱动版本别跟主力机共用。检查设备枚举不用真的跑起来用-n参数只列设备就退出REM -n 只枚举设备并打印能力信息然后退出不连接上游 cgminer.exe -n输出里会逐个列出GPU 0、GPU 1以及每块设备的 Compute Units、Clock、Memory。如果这里只打印了一行No devices found后面所有参数调整都是白费先把驱动和 OpenCL 运行时解决掉。这一步我通常会截个图存档因为换驱动之后对比输出能立刻看出设备是不是被降级识别了。2.3 用最小参数跑通第一次启动设备能枚举到之后先别急着上强度参数。用一个最小集合跑通「连接—下发任务—收到结果」这条链路再逐项加参数这样出问题时能定位到具体是哪一项改坏的。echo off REM 切到脚本所在目录避免相对路径找不到 .cl 内核 cd /d %~dp0 cgminer.exe ^ -o stratumtcp://upstream.example.invalid:3333 ^ -u worker.001 ^ -p x ^ -d 0 ^ -T -t ^ --api-listen --api-port 4028 --api-allow W:127.0.0.1 REM 窗口保留方便看最后几行报错 pause逐项说明。-o是上游服务端地址stratumtcp://前缀必须带只写host:port在 3.1.1 里会被当成 getwork 协议处理握手方式完全不同。-u/-p是凭证占位值就行本文不涉及真实服务端。-d 0只启用 0 号设备多卡机器上先锁定一张卡排查全部跑通后再放开。-T打开日志时间戳-t是文本界面模式。这两个组合起来很关键3.1.1 默认的 ncurses 风格界面会把历史日志刷掉一旦出错只看到最后一帧加-T -t才能把完整日志留在窗口里往上翻。这是我踩过最多次的坑——不是程序没报错是界面把报错吃了。--api-listen打开 4028 端口--api-allow做来源白名单。注意W:前缀表示按 IP 白名单匹配不加前缀是按网段匹配两者行为不同。第一次跑建议只放127.0.0.1确认通了再扩。启动后正常的话日志里会依次出现设备初始化、内核编译、Pool 0 ... alive、Network diff set to ...然后开始周期性打印算力。看到accepted计数往上走链路就算通了。如果卡在Pool 0 ... dead反复重试问题在上游连通性如果卡在内核编译回 2.1 检查路径和.cl文件。3. 参数与配置文件把启动命令固化成可复现模板跑通之后就要考虑可复现。手工敲一长串参数隔一周自己都记不清改过什么。这一章讲清三层配置的优先级、多设备场景怎么拼参数以及怎么把整套东西固化成批处理和 JSON 配置两套入口。3.1 三层配置的优先级与覆盖规则3.1.1 的配置来源有三层优先级从高到低是命令行参数 -c指定的配置文件 同名默认配置文件当前目录的cgminer.conf 内置默认值。理解这个顺序的价值在于你可以用一份公共 conf 打底再用命令行覆盖单机差异。比如十台机器共用一份设备参数配置只有上游地址不同那就 conf 里写设备参数启动脚本里用-o覆盖地址。配置文件是标准 JSON字段名和命令行长参数一一对应去掉了前导--。一个可用的最小配置长这样{ pools: [ { url: stratumtcp://upstream.example.invalid:3333, user: worker.001, pass: x }, { url: stratumtcp://backup.example.invalid:3333, user: worker.001, pass: x } ], api-listen: true, api-port: 4028, api-allow: W:127.0.0.1, api-groups: A:stats:summary, scrypt: false, failover-only: true, queue: 1, scan-time: 60, expiry: 120, log: 5, verbose: false, text-only: true, log-timestamps: true }pools数组的顺序就是故障转移顺序第一项挂掉才切第二项。failover-only控制切回来时的行为为 true 时不主动切回主池要等主池恢复且当前池失效这样避免两个池之间来回抖动。queue是每个设备排队等待的任务数老卡上设 1 就够设大了内存涨得快。scan-time和expiry是一对容易配错的参数。scan-time是多久重新拉一次任务expiry是单个任务多久过期。经验值是expiry至少是scan-time的两倍否则任务还没算完就过期白算表现在日志里就是拒绝率居高不下但硬件错误为 0。这个组合我见过太多人抄配置抄错最后怪设备不行。api-groups是个安全项限制 API 能调用的命令组。默认全开做长期运行或者暴露到内网时应该收紧到只留stats:summary避免被人从 API 侧改配置。3.2 多设备场景下的参数拼装多卡机器上参数不是简单叠加因为不同架构的卡吃的内核和强度完全不同。-d可以重复出现来指定设备序列但强度参数-I在 3.1.1 里是全局的一旦设置就对所有设备生效没有 per-device 的写法。这就是老版本最别扭的地方混插 AMD 和 NVIDIA 时你得用两个实例分别绑不同设备。REM 实例 A0 号设备GCN 架构走 diakgcn 内核强度 9 start cgminer-A cgminer.exe -d 0 -k diakgcn -I 9 -w 256 -g 2 ^ -o stratumtcp://upstream.example.invalid:3333 -u worker.001 -p x ^ --api-listen --api-port 4028 --api-allow W:127.0.0.1 -T -t REM 实例 B1 号设备走通用 poclbm 内核强度降一档API 端口错开 start cgminer-B cgminer.exe -d 1 -k poclbm -I 8 -w 128 -g 1 ^ -o stratumtcp://upstream.example.invalid:3333 -u worker.001 -p x ^ --api-listen --api-port 4029 --api-allow W:127.0.0.1 -T -t-k指定内核名对应目录里那个.cl文件写错名字会静默回退到默认内核日志里只有一行Using kernel ...不仔细看根本发现不了。-w是 worksize-g是每设备线程数这两个和-I一起决定显存占用和算力。调这三个参数的正确姿势是每次只动一个跑满十分钟看算力稳定值和硬件错误计数稳了再动下一个。三个一起调出问题你没法归因这就是典型的玄学调参。两个实例必须错开 API 端口否则第二个直接绑定失败但主循环照跑你以为 API 开了实际上只有一个能用。日志里会有一行 bind 失败混在一堆输出里很容易漏。3.3 用批处理把启动参数固化成可复现模板参数定型之后要固化。最轻量的做法是批处理 环境变量把「机器相关」和「设备相关」拆开换机器只改一处。echo off setlocal cd /d %~dp0 REM 机器维度只在换机器时改这三行 set UPSTREAMstratumtcp://upstream.example.invalid:3333 set WORKERworker.001 set APIPORT4028 REM 设备维度换卡时改这三行 set DEV0 set KERNELdiakgcn set INTENSITY9 cgminer.exe -d %DEV% -k %KERNEL% -I %INTENSITY% -w 256 -g 2 ^ -o %UPSTREAM% -u %WORKER% -p x ^ -T -t --api-listen --api-port %APIPORT% --api-allow W:127.0.0.1 ^ --scan-time 60 --expiry 120 --log 5 if errorlevel 1 ( echo [%date% %time%] cgminer exited with code %errorlevel% start_error.log ) pause使用setlocal是为了不让环境变量泄漏到父进程避免同一台机器跑多个实例时变量互相覆盖。if errorlevel 1这一段是给长期运行准备的程序异常退出时把退出码和时间记到文件第二天看日志就知道昨晚是崩了还是正常停的。3.1.1 不写系统事件日志这行补丁是唯一的后悔药。另外注意pause的位置。调试期留着方便看报错上了看门狗之后要去掉否则进程退出会挂在pause上等键盘输入看门狗以为它还活着。4. API 接口把 cgminer 当成可编程的设备调度器命令行跑通只是「能用」真正让这个老包还有价值的是它的 4028 API。它把设备状态、算力、温度、错误计数全部以 JSON 形式吐出来可以接进任何监控系统。这一章讲协议格式、拉取脚本和字段含义。4.1 打开 API 端口与协议格式API 是一个极简的 TCP 服务连上 4028 端口发一行 JSON 命令收一行 JSON 结果连接即关。没有鉴权、没有会话、没有 TLS安全性全靠--api-allow白名单所以永远不要把它暴露到公网内网也要限定到具体管理机 IP。启动侧需要三个参数--api-listen开启监听--api-port指定端口--api-allow指定白名单。白名单写法有两种W:192.168.1.10是精确 IP192.168.1.0/24是网段。老版本对 IPv6 支持不完整绑::1经常失败建议统一用127.0.0.1。命令格式有两种写法{command:summary}和纯文本summary都能被接受前者更规范。返回结构统一是{STATUS:[{STATUS:S,When:...,Code:...,Msg:...}],SUMMARY:[{...}]}判断成功看STATUS[0].STATUS是否为S不要只看有没有返回内容——出错时它也会返回一段 JSON只是STATUS是E。4.2 用脚本拉取设备状态与算力曲线监控脚本的核心是三件事拉数据、判异常、留时间序列。下面这个最小实现可以当模板改import json import socket import time HOST, PORT 127.0.0.1, 4028 def cg_query(command, timeout3.0): 向 cgminer API 发一条命令返回解析后的 dict。 payload json.dumps({command: command}) \n with socket.create_connection((HOST, PORT), timeouttimeout) as s: s.sendall(payload.encode(utf-8)) buf b while True: chunk s.recv(4096) if not chunk: break buf chunk if buf.endswith(b}\n) or buf.endswith(b}): break data json.loads(buf.decode(utf-8, errorsreplace)) # 统一校验状态位E 表示这条命令执行失败 if data.get(STATUS, [{}])[0].get(STATUS) ! S: raise RuntimeError(data.get(STATUS, [{}])[0].get(Msg, api error)) return data def snapshot(): 汇总一次全设备快照输出扁平结构方便写时序库。 summary cg_query(summary)[SUMMARY][0] devs cg_query(devs)[DEVS] row { ts: int(time.time()), mhs_av: float(summary.get(MHS av, 0)), # 平均算力 mhs_5s: float(summary.get(MHS 5s, 0)), # 瞬时算力 accepted: int(summary.get(Accepted, 0)), rejected: int(summary.get(Rejected, 0)), hw_errors: int(summary.get(Hardware Errors, 0)), devices: [] } for d in devs: row[devices].append({ id: d.get(ID), status: d.get(Status), # Alive / Dead / Sck mhs_av: float(d.get(MHS av, 0)), temp: d.get(Temperature, None), # 无传感器时为 None fan: d.get(Fan Speed, None), hw: int(d.get(Hardware Errors, 0)), }) return row if __name__ __main__: while True: try: print(json.dumps(snapshot(), ensure_asciiFalse)) except (OSError, RuntimeError, ValueError) as e: # API 断开通常意味着主进程挂了交给上层告警 print(json.dumps({ts: int(time.time()), error: str(e)})) time.sleep(10)几个实现细节值得说。recv循环里判断结尾用}是权宜之计严格做法是按 Content-Length 或按行读但 cgminer 的返回是单行 JSON 加换行这个判断在实际使用中够用且不阻塞。errorsreplace是防某些字段里带非 UTF-8 字符导致整条解析失败——老版本对设备名的编码处理不严混进去一个非法字节整个快照就废了这个补丁能保住结构。MHS av是自启动以来的平均值做告警没意义判断「卡是不是掉了」要看MHS 5s的瞬时值和devs里的Status字段。Status有Alive、Dead、Sck几种Sck表示设备进程还在但通信异常这个状态最阴——算力在掉主界面看着还正常。4.3 关键字段含义与告警阈值怎么定把字段当黑匣子看很容易定错阈值。下面这张表是我实际用下来比较靠谱的判断口径字段含义异常信号MHS 5s最近 5 秒算力连续 3 个采样低于基线 70%MHS av启动以来均值只做趋势参考不做告警Accepted被上游接受的结果数长时间不增长说明链路空转Rejected被拒绝数增量 / 总增量 2% 要查参数Hardware Errors硬件层错误任何增长都先降强度Device Hardware%硬件错误占比超过 0.5% 立即降档Temperature核心温度老卡 85℃ 以上算高风险Status设备状态出现 Dead 或 Sck 立即告警Rejected和Hardware Errors要分开看这是新手最常混淆的一对。拒绝率上升但硬件错误为 0八成是scan-time/expiry配错或者上游网络抖动硬件错误上升那是超频超狠了或者散热不行得往下降-I。两个一起涨先降强度再看网络顺序反了会白折腾半天。告警阈值建议用相对基线而不是绝对值。老卡的算力基线本来就在漂写死一个数字第二天就误报。做法是开机稳定运行 30 分钟后采一段算力均值当基线之后按 70% 和 50% 两档告警这个我试过几批不同型号的卡通用性最好。5. 避坑与排查Windows 下最容易翻的五类车前面讲的都是顺路径这一章全是不顺的。以下五条是我在不同机器上反复遇到的按「现象 → 原因 → 解决」写遇到对应症状可以直接对号入座。5.1 双击 exe 窗口一闪而过现象双击cgminer.exe黑窗口闪一下就关什么都看不到。原因三个可能——缺 VC 运行库导致加载期崩溃目录路径含中文或空格导致内核文件定位失败没有给任何参数程序解析参数失败后直接退出。解决先在命令行窗口里手动执行别双击这样报错会留在屏幕上。然后按顺序排除cgminer.exe -n只枚举设备不连上游能列出设备说明运行库没问题报缺 DLL 就补对应位数的运行库路径问题把整个目录挪到D:\cgminer311\再试。还有一个隐蔽情况32 位 exe 在 64 位系统上某些安全软件会把 pthread 库的行为拦下来表现同样是闪退临时关掉防护验证一次就能确认。5.2 设备枚举到了但算力为零现象-n能看到设备正常启动后日志显示设备已初始化但MHS 5s一直是 0Accepted 不增长。原因内核编译失败后静默回退到了一个不兼容的内核或者-I强度设得过高设备一直卡在初始化循环里没出来。解决加-D打开调试输出能看到内核编译的完整日志。如果日志里有Failed to build binary基本就是.cl文件和当前驱动版本不匹配换成-k poclbm走通用内核试一次。如果是强度问题把-I降到 5 重新跑能起来再一档一档加。我一般从 5 开始往上试比从 9 往下调快得多。5.3 拒绝率突然升高但硬件错误为 0现象跑了几小时一直正常某一时刻开始Rejected快速增长Hardware Errors纹丝不动算力数字看着还行。原因九成是scan-time和expiry配错了。任务还没算完就过期结果提交上去被上游判为过期拒绝。次要原因是上游地址不稳定任务下发有延迟。解决先看配置里expiry是不是小于scan-time的两倍是的话改成两倍以上。然后看日志里的Pool 0 ... not responding出现频率频繁出现说明是网络侧问题跟参数无关。这条我踩过不止一次一开始总怀疑是卡要坏了其实是自己抄配置时把scan-time 60 / expiry 30一起抄进去了纯属自找。5.4 API 端口连不上现象脚本连 4028 直接 connection refused或者连上了但返回空。原因--api-listen没加--api-allow白名单没包含来源 IP端口被上一轮没退干净的进程占着或者--api-groups把要用的命令组关掉了。解决先用netstat -ano | findstr 4028确认端口在监听、被谁占着。如果是残留进程任务管理器里结束掉再起。白名单一定要写W:前缀加完整 IP写网段时确认掩码对不对。--api-groups如果设了devs这类命令不在summary组里得单独加stats组否则调用返回错误而不是空——这两者表现不一样容易看串。5.5 连续跑几天后内存缓慢上涨、设备掉线现象前两天一切正常第三天开始Status偶发Sck任务管理器里进程内存比刚启动时高出几百 MB最后某个设备彻底 Dead。原因3.1.1 时代没有日志轮转-T -t的输出如果被重定向到文件几天就能顶到几个 GB另外多设备长时间运行时线程栈回收不彻底属于那个版本的已知行为再叠加散热衰减温度上来后设备自己保护性降频。解决三条一起做。一是日志按天切分启动脚本里用日期做文件名后缀别让单文件无限长二是加一个看门狗检测到Status非 Alive 或者 API 三次无响应就重启进程三是把温度纳入监控超过阈值先降-I再考虑清灰。这三条组合起来能覆盖绝大部分「跑几天就挂」的场景。只做其中一条通常撑不过一周。6. 长期运行的两个加固技巧日志切分与外部看门狗想把 3.1.1 当长期服务用只靠它自己是撑不住的必须外挂两个东西可控的日志和能把它拉起来的看门狗。日志这块-T -t的输出重定向到固定文件名是最常见的错误做法跑三天文件几个 G回头想查某一晚的报错根本翻不动。改成按启动日期命名配合一个简单的清理策略就够了echo off cd /d %~dp0 REM 用启动时刻生成日志名避免同名覆盖 set LOGFILElog\cgminer_%date:~0,4%%date:~5,2%%date:~8,2%_%time:~0,2%%time:~3,2%.log set LOGFILE%LOGFILE: 0% if not exist log mkdir log cgminer.exe -d 0 -k diakgcn -I 9 -w 256 -g 2 ^ -o stratumtcp://upstream.example.invalid:3333 -u worker.001 -p x ^ -T -t --api-listen --api-port 4028 --api-allow W:127.0.0.1 ^ %LOGFILE% 21%time:~0,2%在零点到九点之间会产生带前导空格的字符串导致文件名里出现空格后面那句%LOGFILE: 0%就是补这个洞的——这是批处理里最经典的一个坑不知道的人会得到一堆名字奇怪的文件。日志清理用计划任务跑保留最近 7 天按文件名日期排序删旧的即可。看门狗我不建议写在批处理里因为批处理判断不了 API 状态。用一个几十行的 Python 轮询脚本更直接每 30 秒拉一次devs连续三次异常就 kill 进程并重启重启次数写进计数文件一小时内重启超过 5 次就放弃并记录避免无限重启刷日志。判断条件用两个任一设备Status不为Alive或者MHS 5s总和低于基线 50%。单看状态会漏掉「活着但不出活」的情况单看算力又会误杀正常的短时波动两个条件与在一起最稳。版本选择上也得留个心眼。3.1.1 对老架构内核的兼容性是它现在唯一的价值但对新设备基本没有支持日志、看门狗、API 白名单这些能力也都在后续版本里才补齐。所以我的做法是验证老设备用这个包跑通了立刻把参数和阈值抄到新版本上做长期运行两边并存而不是二选一。有一回图省事直接拿 3.1.1 挂了一个月第三天开始掉设备第五天日志写满磁盘那次之后我每上一个长期任务都强制先跑一遍日志切分 看门狗 基线采集这三件套缺一个都不上线。希望帮到你。本文还有配套的精品资源点击获取