pSolve enter 指令使用说明
enter 是 pSolve 的底层启动程序。它接收用户缩写和运行参数,检查工作文件,设置运行状态,再把控制权交给交互程序 OPTIN 或批处理程序 BATCH。从这个意义上说,enter 主要承担启动和调度接口的作用。VLBI 数据读取、理论时延计算、参数估计和批处理控制文件的解析,由后续程序完成。我在巴黎天文台学习 Calc/Solve 软件的使用时,导师直接使用 enter 来进行全局解算。而现在,日常使用通常从 psolve 启动。理解 enter,主要是为了弄清 psolve 如何传递参数,以及启动失败、恢复运行和工作目录问题应当如何排查。
1. 命令格式与位置参数
enter 对应的源代码文件为 psolve/progs/solve/enter/enter.f。enter 没有常见的 --help、--silent 等选项解析器。它读取前七个位置参数。用参数名称表示,其调用格式为:
enter initials control_file post_flag run_mode test_flag processor_index processor_count
其中 post_flag 等名称是本文为便于解释使用的标签,并非可以直接输入的选项名。
| 位置 | 参数 | 含义 | 常用值 | 源码行为 |
|---|---|---|---|---|
| 1 | initials |
用户缩写 | 如 NL |
转为大写,存入两个字符的 LETT,再由 LETOK 检查 |
| 2 | control_file |
控制文件或交互标记 | 0 或控制文件路径 |
空参数或通常的 0 表示交互模式;其余非空值触发批处理模式 |
| 3 | post_flag |
通用停用检查开关及历史 proceed 字段 | 0 |
首字符不是 0 时执行 POST_CHECK;批处理时还转交给 BATCH |
| 4 | run_mode |
内部前后台及恢复策略 | f、b、a、n |
设置 PRE_IP(2) 的第 6、7、8 位 |
| 5 | test_flag |
测试版本替换开关 | 0,开发调试用 -1 |
前两个字符为 -1 时启用测试版本选择 |
| 6 | processor_index |
任务分片编号 | 1 |
原始参数字符串传入 BATCH;本层数值检查存在问题 |
| 7 | processor_count |
任务分片总数 | 1 |
检查范围为 1–128;省略时第 6、7 项都重置为 1 |
这些参数是位置相关的。如果需要设置后面的参数,就要保留前面的占位值。普通运行中,第 3、5 项通常取 0,第 6、7 项取 1;第 4 项则根据运行和恢复需求选择。
例如,交互模式的调用为:
enter NL 0 0 f 0 1 1
使用批处理控制文件、从头开始解算的调用为:
enter NL solution.cnt 0 n 0 1 1
不带参数调用 enter 时,在通过前面的运行停用检查后,会进入交互流程并询问用户缩写。在这一输入提示处输入 :: 可以退出,源码返回状态为 1。
第 4 个参数run_mode用于控制两件事:一是程序遇到错误时,等待用户处理还是直接退出;二是批处理任务中断后,再次启动时如何处理上次的运行进度。它可以取 f、b、a 或 n,不区分大小写,具体含义见下表。
run_mode |
遇到错误时 | 再次启动批处理任务时 |
|---|---|---|
f |
等待用户处理 | 如果可以恢复,询问用户是否继续上次的运行 |
b |
退出程序 | 如果可以恢复,仍会询问用户 |
a |
退出程序 | 如果可以恢复,自动继续上次的运行 |
n |
退出程序 | 不恢复上次的进度,从头开始 |
2. enter 的运行逻辑
enter 的启动过程如下图所示(根据ChatGPT的输出生成),可分为:
flowchart TD
A(["启动 enter"]) --> B["读取前七个参数<br/>用户缩写转为大写"]
B --> C{"第 3 项首字符为 0?"}
C -- 否 --> D["调用 POST_CHECK"]
D --> E{"存在通用停用标记?"}
E -- 是 --> X(["退出程序"])
E -- 否 --> F
C -- 是 --> F["根据第 2 项确定运行模式"]
F --> G{"批处理模式?"}
G -- 是 --> H{"存在批处理专用停用标记?"}
H -- 是 --> X
H -- 否 --> I
G -- 否 --> I["保存终端状态<br/>建立通信管道"]
I --> J{"交互模式?"}
J -- 是 --> K["启动 curses 界面<br/>设置初始信号处理"]
J -- 否 --> L["设置初始信号处理"]
K --> M
L --> M{"交互模式且未提供用户缩写?"}
M -- 是 --> N["询问用户缩写"]
N --> O{"输入为 ::?"}
O -- 是 --> X
O -- 否 --> P
M -- 否 --> P["调用 LETOK 检查用户缩写"]
P --> Q{"用户缩写有效?"}
Q -- 否 --> R{"交互模式?"}
R -- 是 --> S["清空第 1 项,重新询问"]
S --> N
R -- 否 --> X
Q -- 是 --> T["交互模式关闭 curses<br/>调用 SETUP_PRELUDE"]
T --> U["设置前后台与恢复位<br/>保存 termxx<br/>重新注册信号处理"]
U --> V["设置测试版本<br/>处理并检查分片参数"]
V --> W{"参数检查通过?"}
W -- 否 --> X
W -- 是 --> AA["查找并打开 CONFxx<br/>读取创建日期"]
AA --> AB{"存在且打开、读取成功?"}
AB -- 否 --> X
AB -- 是 --> AC{"创建日期满足兼容要求?"}
AC -- 否 --> X
AC -- 是 --> AD["从 GLBFxx 恢复 spool 设置<br/>记录启动命令"]
AD --> AE["检查运行占用标记"]
AE --> AF{"允许继续运行?"}
AF -- 否 --> X
AF -- 是 --> AG["建立运行占用标记 LOCKxx"]
AG --> AH["调用 CHKSC<br/>检查十种主要工作文件"]
AH --> AI{"所需工作文件均存在?"}
AI -- 否 --> X
AI -- 是 --> AJ["读取并写回 GLBFxx<br/>批处理时设置 KUSER_PART 为假"]
AJ --> AK{"批处理模式?"}
AK -- 否 --> AL(["以 PASS 方式调度 OPTIN"])
AK -- 是 --> AM["构造 NEWSTR 启动字符串<br/>包含 xxx.cnt 文件名"]
AM --> AN["USE_BUFFER 写入<br/>128 字节通信缓冲区"]
AN --> AO(["以 PASS 方式调度 BATCH"])
- 读取命令行参数。 读取前七个参数,并将用户缩写转为大写,例如将
nl转为NL。 - 检查是否允许启动。 根据第 3 个参数,决定是否检查通用停用文件。如果需要检查且该文件存在,程序就会退出。随后根据第 2 个参数确定采用交互模式还是批处理模式;批处理模式还要检查专用的停用文件。
- 准备终端界面和程序间通信。 保存当前终端的设置,并建立供后续程序传递信息的通信管道。如果采用交互模式,再启动终端菜单界面。这里必须先建立管道,再启动界面。
- 检查用户缩写。 调用
LETOK,检查用户缩写是否允许使用。如果没有通过检查,交互模式会让用户重新输入,批处理模式则直接退出。 - 准备运行环境。 完成内部初始化,根据参数确定遇到错误时如何处理,以及是否恢复上次的运行。同时,将终端设置保存到
termxx文件,设置收到中断等系统信号时的处理方式,并处理测试版本选项、任务分片编号和总数。 - 检查工作文件的配置信息。 读取
CONFxx,确认文件存在且能够读取,再检查其中记录的创建日期,判断这组工作文件是否符合当前版本的要求。这里读取的是工作文件的配置信息,不是用户提供的批处理控制文件xxx.cnt。 - 读取输出设置并记录启动命令。 从
GLBFxx中读取此前保存的 spool 输出设置,并将本次启动命令的部分内容保存到该文件中。 - 检查这组工作文件是否已被占用。 检查是否已有任务使用当前工作目录中的同一用户缩写。如果已有任务占用,程序就会退出;否则,建立本次运行的运行占用标记
LOCKxx,供其他任务检查。同一工作目录下,不应同时运行使用同一用户缩写的两个 pSolve 任务。 - 检查所需的工作文件是否齐全。 调用
CHKSC,检查十种主要工作文件是否存在;缺少所需文件时退出。批处理模式还会先关闭用户自定义偏导数标记,后续再根据控制文件中的设置决定是否启用。 - 启动后续程序。 交互模式进入
OPTIN,由用户通过菜单继续操作;批处理模式则将控制文件名等启动信息传给BATCH,由它继续读取控制文件并执行批处理任务。
CHKSC 只检查文件是否存在,并不逐一打开文件验证可读写性,也不检查文件大小或二进制内容。这里涉及的十种工作文件前缀为:
PARF CORF NRMF PLTF RESF OBSF COMM NAMF GLBF SARF
每个前缀后面都附加用户缩写。例如,NL 对应 PARFNL、CORFNL 等文件。
3. 分片参数的作用与实现问题
第6个参数processor_index和第7个输入参数processor_count分别表示任务分片编号与总数。这两个参数参与批处理中的数据(session)分配:BATCH 根据它们判断当前任务应当处理哪些观测数据,并跳过其余数据。
具体划分发生在 psolve/progs/solve/batch/prces.f 中,核心代码是:
IM = MOD( IND_SES, NUM_PROC )
IF ( IM == 0 ) IM = NUM_PROC
IF ( IM .EQ. IND_PROC ) THEN
FL_SKIP = .FALSE.
ELSE
FL_SKIP = .TRUE.
END IF
这里,IND_SES 是当前数据序号,IND_PROC 和 NUM_PROC 分别是任务编号和任务总数。
例如,将 12 个数据分给 4 个任务时,分配结果为:
| 任务编号 | 任务总数 | 负责的 session 序号 |
|---|---|---|
| 1 | 4 | 1、5、9 |
| 2 | 4 | 2、6、10 |
| 3 | 4 | 3、7、11 |
| 4 | 4 | 4、8、12 |
因此,这是一种按数据序号循环分配任务的机制。不过,它们不是 OpenMP 线程数开关;enter 不会因为把最后一个数字改成 4,就自动启动四个独立任务。多个任务仍需要由外部脚本或作业调度系统组织运行。当前 psolve 启动脚本的常规分支将最后两个参数固定为 1 1,因此通过这些分支启动时,所有 session 都由同一个任务处理。这说明常规调用没有启用分片功能。这种分片机制可能缩短全局解算的总运行时间,但实际加速效果仍需测试,仅凭enter.f还无法推断。
需要指出,enter.f 中存在一处明确的参数检查问题:
CALL CHIN ( ARG_STR(7), M_PROC_I4 )
...
CALL CHIN ( ARG_STR(7), I_PROC_I4 )
第二次调用按上下文应当读取第 6 项,但实际仍读取第 7 项。结果是 enter 没有按设计验证第 6 项,而且后面的 I_PROC_I4 > M_PROC_I4 检查失去了预期作用。不过,构造发送给 BATCH 的启动字符串时,代码仍然使用原始的 ARG_STR(6) 和 ARG_STR(7)。因此,这只是参数校验错误,不影响解算。