Files

11 KiB
Raw Permalink Blame History

OpenVMS:AST / $QIO / 条件处理 / 镜像激活与共享镜像

OpenVMS 有**两条异步/异常控制流**,逆向时都要补进图里:
AST(Asynchronous System Trap)  → 外部事件驱动的异步控制流
条件处理(Condition Handling)    → 异常/状态传播的控制流

再加一层镜像激活语义(逻辑名、共享镜像、全局节)——三者叠起来,才解释得了"这段代码谁在调、这份映像是哪来的"。 </CORE RULE>

一、$QIO 与事件标志

SYS$QIO 的关键参数:

参数 作用
efn 完成时置位的事件标志号
astadr 完成时执行的 AST 例程地址
astprm 传给 AST 例程的参数
iosb 接收最终状态的 I/O 状态块
  • 发起时事件标志会被清除;完成时置位
  • iosb 技术上可选,但不应当作可选——并发的异步事件要各用一个独立的 IOSB

二、完成顺序与控制流

① 写 IOSB   →   ② 置事件标志   →   ③ 触发 AST
  • 指定了 astadr 时,系统在完成时中断进程并把控制交给 AST 例程;例程结束控制返回到被中断的那个点
  • 事件标志置位 ≠ 这个 I/O 完成:等待服务等的是标志,别的操作也可能置位它;判断完成要用同步等待 + IOSB
  • W 后缀 = 同步版本

三、AST 的语义

  • AST 像子程序调用,但无视主流程上下文异步投递;主线与 AST 共享的代码必须可重入
  • AST 不会中止进行中的系统调用
  • 进程休眠或在等待服务上等待时:AST 投递会把进程暂时移出等待去执行 AST,完成后原来的等待调用会被再次执行

所以:`wait 看起来"返回后又自动等待"不是 loop corruption**——这是该模型的正常行为。跳出这个循环只能靠唤醒或置位事件标志。

  • 同一访问模式下,AST 不能被同访问模式的另一个 AST 抢占,但可被更高访问模式的 AST 抢占
  • 特殊内核模式 AST 是获得进程上下文的标准机制:在下一次该进程被调度时以该进程最高优先级执行

四、屏蔽 AST

  • 专门的系统服务可关闭/开启当前及所有更低特权访问模式的 AST 投递
  • 极短临界区可用每进程 P1 空间里的两个标志位代替,避免服务调用开销:投递代码置"已禁用"位,若第一位已置则再置第二位并不投递;用户代码事后检查第二位并重新开启

五、条件处理:异常的控制流

信号与处理器:

  • 例程可以返回完成码(在 R0 里、按 bit 0 判断)或显式 signal 一个条件
  • 条件处理设施(CHF)按顺序调用已启用的条件处理器;signal 会复制参数列表并构造传给处理器的信号参数向量
  • 处理器链的行为细节(做控制流恢复时要记住):
    • 当处理器仍在活动时又发生第二次异常,CHF 会跳过已搜索过的帧,并调整传给处理器的深度值
    • 经异常向量建立的处理器每次异常都会被调用(不被跳过)——多次信号并发时用它建立处理器要谨慎
  • 处理器返回后 CHF 依据返回状态动作:再 signal 一个条件 / 继续 / 重新 signal(继续搜索下一个处理器) / unwind

所以:异常流不是"抛出—捕获"两段,而是一条可以继续、可以重新投递、可以展开的链。

六、建立处理器的两种方式(含语言差异)

  • 一种方式把处理器地址移到调用者栈帧的 longword 0(即 SF$A_HANDLER),并返回原值;直到显式恢复或控制返回到调用该建立例程的例程之前都有效
  • 它修改调用者的栈帧,主要为没有内建错误处理的语言提供;不该与有预定义处理器的语言混用(会得到不可预期的结果);手工汇编里等价于把处理器地址写进帧指针偏移 0
  • 在 Alpha 与 I64 的原生程序上不可用,但多数语言编译器会识别并映射它:
    • 每个过程可用过程描述符里的处理器字段标识处理器(Alpha),I64 上则由展开信息块的处理器字段标识
    • I64 的运行时里没有它;各语言分别处理(有的当内建、有的转成运行时入口点、有的提供自己的建立/恢复内建)
    • 32 位信号数组格式在 VAX 与 I64 相同,但条件码与机制数组格式不同(四字元素、额外寄存器、深度值)
  • C 程序必须用 C 运行时专有的建立接口:它会捕获与 C 运行时相关的异常并把你不想处理的转给你的处理器;直接用通用接口配 setjmp/longjmp 是未定义行为
    • 要让某个异常生成 UNIX 风格信号,用户处理器必须返回"重新投递";返回"正常"则阻止生成 UNIX 风格信号
    • 用空指针调用可取消该例程中已有的处理器

七、镜像启动时建立的默认处理器

每次启动新映像时,操作系统都会建立默认条件处理器(三件套,顺序有意义):

  1. traceback 处理器——在 catchall 之后建立,因此先拿到控制;经消息输出与格式化服务给出错误消息与符号化回溯,并按条件严重性决定继续或退出
  2. catchall 处理器——建在第一个栈帧,最后被调用
  3. last-chance 处理器——经系统异常向量建立;仅当栈无效、或所有栈处理器都重新投递时才被调用;若调试器在场,调试器自己的 last-chance 处理器会替换系统那个

只有这三个会输出错误消息。

八、镜像激活与共享镜像

  • 重定向共享镜像:用与该共享镜像文件名相同的逻辑名即可——镜像激活器会用这个逻辑名去找目标文件,于是无需重新链接就能替换实现
  • 共享镜像的默认目录是 SYS$SHARE:(不是 SYS$LIBRARY:,尽管当前二者指向同一批目录)
  • 逻辑名必须与"链接时使用的输入文件规格"以及"安装时使用的名字"一致
  • 有动态激活把一个共享镜像装进进程的 P0 空间的例程
  • 特权/仅执行(execute-only)镜像的限制(安全分析常撞到):
    • 激活特权镜像时,只考虑系统逻辑名表里带执行属性的逻辑名
    • 被激活的镜像、以及它引用的共享镜像与消息节,都必须作为"已知镜像"安装
    • 仅执行(有执行权无读权)的镜像受与特权镜像相同的限制;专用限定符(只对主程序有意义)允许激活"用户有执行权但无读权"的共享镜像
    • 这种受限模式下,RMS 只使用"可信"逻辑名(exec/内核模式创建的);用户模式与超级visor模式的逻辑名不被信任;进程/组逻辑名表里的重定向被忽略

九、节(sections)与调用标准

  • 安装镜像时指定共享会为其建立全局节(global sections);否则以私有节映射
  • 共享镜像可安装为让全局节驻留多端口内存(需要相应特权)
  • 已知镜像(及其全局节)的删除,要等所有使用它的进程释放之后才发生;可写全局节在移除时写回磁盘
  • 调用标准体现在参数与返回值约定上:返回值是条件值;参数可以是按描述符、按引用或按值;描述符有类别要求(VAX 上某些必须是特定类别);符号查找一类的例程返回符号的虚拟地址时会加上共享镜像的起始地址

十、RMS 与文件规格解析

  • 很多功能把文件名与默认名分开解析(而不是拼成一条完整规格)
  • 默认设备/目录、默认文件类型(如 SYS$SYSTEM / EXE)由调用方或例程提供
  • 已知镜像的查找忽略版本号,总是用现存最高版本;安装命令的规格里不允许指定版本号

决策树

拿到 OpenVMS 目标
├─ 例程没有调用者却有行为
│    ├─ 查它是否作为 astadr 传给异步服务 → 补 AST 边
│    └─ 查它是否是条件处理器 → 补条件处理边
├─ 完成路径不完整
│    └─ 补:写 IOSB → 置事件标志 → 触发 AST
├─ 异常处理不完整
│    ├─ 处理器链:继续 / 重新投递 / 展开 三种去向
│    ├─ 第二次异常时 CHF 跳过已搜索帧
│    └─ 三个默认处理器(traceback / catchall / last-chance)
├─ 等待"返回后又继续等待"
│    └─ AST 投递后原等待被重新执行,属正常
├─ 共享镜像行为与预期不符
│    ├─ 逻辑名是否与文件名一致(重定向机制)
│    ├─ 默认目录 SYS$SHARE
│    └─ 特权/仅执行镜像下:必须安装为已知镜像,且只认可信逻辑名
├─ 参数/返回值解析错位
│    └─ 按调用标准:条件值 + 描述符类别 + 按引用/按值
└─ 文件打不开 / 找到的不是那份
     └─ RMS 的文件名与默认名分开解析;已知镜像查找忽略版本号

工具与验证

  • 异步登记点:异步服务的 astadr/astprm/efn/iosb 参数
  • 条件处理:处理器建立点(栈帧字段 / 过程描述符 / 展开信息块)、处理器返回值、默认三件套
  • 镜像:镜像激活器与逻辑名(默认目录、重定向规则)、已知镜像安装状态、全局/私有/消息节
  • 调用标准:描述符类别、参数传递方式、返回条件值
  • 验证:能同时说清「这条 I/O 的完成经哪条 AST 边回来 + 异常会走哪一层处理器 + 这个映像从哪里被激活、节是全局还是私有」

该平台的坑(汇总)

  • 把 AST 例程当无调用者的死代码
  • 用同步调用图解释完成路径:必须补 AST 边
  • 把"等待返回后又自动等待"当循环被破坏
  • 把事件标志置位当该 I/O 完成
  • 忽略 IOSB:并发异步事件必须各用一个
  • 以为 AST 会中止正在进行的系统调用
  • 把异常当"抛出—捕获"两段:实际是可继续、可重投、可展开的链
  • 忽略"第二次异常跳过已搜索帧":处理器的 depth 值会变
  • 把经异常向量建立的处理器当只调用一次:每次异常都会调用
  • 在有内建错误处理的语言里混用栈帧式处理器建立方式:行为不可预期
  • 在 C 里用通用接口配 setjmp/longjmp:未定义行为,必须用 C 运行时专有接口
  • 按文件名找共享镜像却忽略逻辑名重定向:看到的可能不是你以为的那份
  • 在特权/仅执行镜像下期待普通逻辑名生效:只认可信逻辑名,且被引用的共享镜像必须安装
  • 按 ASCII/字节流理解参数与记录:调用标准与 RMS 规格解析各有自己的规则