pid 文件不是单实例锁:比特币核心的进程号文件管什么、不管什么 图 1
pid 文件不是单实例锁:比特币核心的进程号文件管什么、不管什么 · 图 1

在一台机器上跑多个比特币核心进程,或者用 systemd、supervisord 这类进程管理器托管节点时,绕不开一个问题:怎么准确地对”这一个”节点发信号。比特币核心提供的答案是 PID 文件,由 -pid 参数控制。这个参数只有半句话的文档量,但它的三个边界恰好是新手最容易踩的地方。

默认值与路径解析规则

不带参数启动节点时,进程默认就会写 PID 文件,文件名叫 bitcoind.pid-pid 的作用只是改名或换位置。关键细节在路径解析:如果给的是相对路径,程序会自动把它拼到”当前网络对应的数据目录”下面。也就是说,主网进程的 PID 文件落在主网目录,测试网进程的落在测试网目录——即便两个进程共用一个大的数据根目录,它们的 PID 文件也天然分开,不会互相覆盖。这一点正是多实例场景里最需要隔离的地方,实现替用户处理了。

只有给绝对路径时,程序才老老实实用你写的路径,此时隔离责任完全回到用户手上。用绝对路径把两个网络实例的 PID 文件指到同一个位置,是一个很容易犯的配置错误。

它只是便利,不是单实例锁

这是最重要的一条认知:PID 文件不承担”防止重复启动”的职责。源码里创建 PID 文件的逻辑只是尽力打开文件、写入当前进程号;它不会去检查文件里的旧进程号是否还活着,也不会因为文件已存在而拒绝启动。第二个进程照样能跑起来,顺带把文件里的数字改成自己的。

真正的单实例保护来自另一个机制:数据目录里的锁文件。同一份数据目录同一时刻只允许一个节点打开,这是靠文件锁实现的,与 PID 文件无关。所以”我发现有两份 bitcoind 在跑”通常意味着你给它们各配了不同的数据目录(或者不同的 -datadir),排查方向应该在这里,而不是去怀疑 PID 文件为什么没挡住。

谁该依赖这个文件

进程管理器的价值在于准确发信号。kill $(cat 数据目录/bitcoind.pid) 之所以比 pkill bitcoind 安全,是因为前者锁定的是具体进程号,后者按名字杀进程——机器上如果同时跑着主网、signet 和 regtest 三个节点,名字匹配会把它们一起带走。运维脚本里的关停动作应当读 PID 文件,并配合优雅关闭 RPC 使用:先让节点走完落盘流程,再考虑信号。

另一个边界是”这个文件由谁拥有”。实现里有一个专门记录”本进程是否成功创建过 PID 文件”的标记:只有创建成功的那个进程,退出时才会去删除它;没能创建成功时,退出时也不会去动这个文件,以免误删别人写的东西。而创建失败本身会被当作启动错误报出来并终止启动——比如目标目录没有写权限、磁盘只读,节点会直接拒绝起来,而不是”照常运行但少一个文件”。

由此推出一条实用的核对流程:当 PID 文件里的数字与某个存活进程对不上时,不要想当然认为那是脏文件直接删。先看这个数字对应哪个进程、它的命令行里有没有与你预期一致的 -datadir;再确认你要操作的节点是不是另有其人。多数”PID 文件看着不对”的案例,真相是机器上跑着不止一个节点,或者某个实例换过数据目录,而不是文件本身出了问题。

最后留意一个否定写法:参数支持取反形式,一旦取反,节点就干脆不写 PID 文件,进程照常运行。对依赖 PID 文件做关停的自动化流程来说,这等于悄悄拔掉了抓手——排查关停脚本失灵时,把”这一台是不是被关掉了 PID 文件”列进检查清单,能省掉对着空目录发愣的时间。

风险提示:本文描述客户端机制,不构成投资或运维建议;进程管理操作不当可能导致数据未落盘,关停节点优先使用关闭 RPC 并确认进程正常退出。