refactor: 重构 SyscallCommand 类并优化文档注释- 重新组织类结构,优化代码布局

- 更新文档注释,使其更加清晰和详细
- 优化部分方法实现,提高可读性和可维护性
This commit is contained in:
Luke 2025-07-21 23:42:03 +08:00
parent e84aedc100
commit 6d79e28c51

View File

@ -16,53 +16,47 @@ import java.util.*;
import static java.nio.file.StandardOpenOption.*;
/**
* {@code SyscallCommand} I/O syscall 子命令集合指令
* SyscallCommand 虚拟机系统调用SYSCALL指令实现
*
* <p>
* 封装类 UNIX 文件描述符File Descriptor, FD操作文件/网络 I/O管道fd 复制多路复用select等系统调用能力
* 基于 Java NIO 统一实现供虚拟机指令集以 SYSCALL 形式扩展使用
* 本类负责将虚拟机指令集中的 SYSCALL 进行分派模拟现实系统常见的文件网络管道标准输出等操作
* 通过操作数栈完成参数返回值传递并借助文件描述符表FDTable进行底层资源管理
* 所有 I/O 相关功能均基于 Java NIO 实现兼容多种 I/O 场景
* </p>
*
* <b>栈操作约定</b>
* <p>参数与栈约定:</p>
* <ul>
* <li>参数按右值先入栈左值后入栈的顺序推入操作数栈执行 {@code SYSCALL <SUBCMD>} 后出栈</li>
* <li>调用 SYSCALL OPEN path flags mode入栈顺序为 path先入 flags mode后入出栈时 modeflagspath 弹出</li>
* <li>所有调用参数均按右值先入左值后入顺序压入 {@link OperandStack}</li>
* <li>SYSCALL 指令自动弹出参数并处理结果返回值如描述符读取长度是否成功等压回栈顶</li>
* </ul>
*
* <b>返回值说明</b>
* <p>异常与失败处理:</p>
* <ul>
* <li>成功压入正值 FD实际数据字节数0 表示成功</li>
* <li>失败统一压入 -1后续可扩展为 errno 机制</li>
* <li>系统调用失败或遇到异常时均向操作数栈压入 {@code -1}以便调用者统一检测</li>
* </ul>
*
* <b>支持的子命令部分</b>
* <p>支持的子命令示例:</p>
* <ul>
* <li>PRINT / PRINTLN 控制台输出</li>
* <li>OPEN / CLOSE / READ / WRITE / SEEK 文件 I/O 操作</li>
* <li>PIPE / DUP 管道 FD 复制</li>
* <li>SOCKET / CONNECT / BIND / LISTEN / ACCEPT 网络套接字</li>
* <li>SELECT I/O 多路复用</li>
* <li>OPEN / CLOSE / READ / WRITE / SEEK 文件相关操作</li>
* <li>PIPE / DUP 管道与文件描述符复制</li>
* <li>SOCKET / CONNECT / BIND / LISTEN / ACCEPT 网络通信</li>
* <li>SELECT 多通道 I/O 就绪检测</li>
* </ul>
*/
public class SyscallCommand implements Command {
/*--------------------------------------------------------------------------------*/
/**
* <b>执行 SYSCALL 子命令</b>
* <p>
* 按照 parts[1] 指定的 SYSCALL 子命令结合虚拟机栈与资源表完成对应的系统调用模拟
* 支持基础文件网络管道等 I/O 操作并对异常统一处理
* </p>
* 分发并执行 SYSCALL 子命令根据子命令类型从操作数栈取出参数操作底层资源并将结果压回栈顶
*
* @param parts 指令字符串数组parts[1] 为子命令
* @param parts 指令及子命令参数分割数组parts[1]为子命令名
* @param pc 当前指令计数器
* @param stack 操作数栈
* @param locals 局部变量表
* @param callStack 调用栈
* @return 下一条指令的 pc 默认 pc+1
* @return 下一条指令的 pc 通常为 pc+1
* @throws IllegalArgumentException 缺少子命令参数时抛出
* @throws UnsupportedOperationException 不支持的 SYSCALL 子命令时抛出
*/
@Override
public int execute(String[] parts, int pc,
@ -70,46 +64,27 @@ public class SyscallCommand implements Command {
LocalVariableStore locals,
CallStack callStack) {
if (parts.length < 2)
throw new IllegalArgumentException("SYSCALL needs sub-command");
if (parts.length < 2) {
throw new IllegalArgumentException("SYSCALL missing subcommand");
}
String cmd = parts[1].toUpperCase(Locale.ROOT);
try {
switch (cmd) {
/*==================== 文件 / 目录 ====================*/
/*
* OPEN 打开文件返回文件描述符File Descriptor, FD
* 入栈push顺序: path文件路径, String, flagsint, modeint文件权限暂未用
* 出栈pop顺序: mode flags path
* 成功返回 fd失败返回 -1
*/
// 文件相关操作
case "OPEN" -> {
int mode = (Integer) stack.pop(); // 目前未用
int mode = (Integer) stack.pop();
int flags = (Integer) stack.pop();
String path = String.valueOf(stack.pop());
FileChannel fc = FileChannel.open(Paths.get(path), flagsToOptions(flags));
stack.push(FDTable.register(fc));
}
/*
* CLOSE 关闭文件描述符
* 入栈顺序: fdint
* 出栈顺序: fd
* 返回 0 成功-1 失败
*/
case "CLOSE" -> {
int fd = (Integer) stack.pop();
FDTable.close(fd);
stack.push(0);
}
/*
* READ fd 读取指定字节数
* 入栈顺序: fdint, countint字节数
* 出栈顺序: count fd
* 返回byte[]实际读取的数据EOF 时长度为 0
*/
case "READ" -> {
int count = (Integer) stack.pop();
int fd = (Integer) stack.pop();
@ -126,12 +101,6 @@ public class SyscallCommand implements Command {
buf.get(out);
stack.push(out);
}
/*
* WRITE fd 写数据
* 入栈顺序: fdint, databyte[] String
* 出栈顺序: data fd
* 返回写入字节数失败 -1
*/
case "WRITE" -> {
Object dataObj = stack.pop();
int fd = (Integer) stack.pop();
@ -146,12 +115,6 @@ public class SyscallCommand implements Command {
int written = wch.write(ByteBuffer.wrap(data));
stack.push(written);
}
/*
* SEEK 文件定位移动文件读写指针
* 入栈顺序: fdint, offsetlong/int, whenceint, 0=SET,1=CUR,2=END
* 出栈顺序: whence offset fd
* 返回新位置long失败 -1
*/
case "SEEK" -> {
int whence = (Integer) stack.pop();
long off = ((Number) stack.pop()).longValue();
@ -165,58 +128,32 @@ public class SyscallCommand implements Command {
case 0 -> sbc.position(off);
case 1 -> sbc.position(sbc.position() + off);
case 2 -> sbc.position(sbc.size() + off);
default -> throw new IllegalArgumentException("bad whence");
default -> throw new IllegalArgumentException("Invalid offset type");
};
stack.push(newPos);
}
/*==================== 管道 / FD 相关 ====================*/
/*
* PIPE 创建匿名管道
* 入栈顺序:
* 出栈顺序:
* 返回顺序: write fd先压栈read fd后压栈
*/
// 管道与描述符操作
case "PIPE" -> {
Pipe p = Pipe.open();
stack.push(FDTable.register(p.sink())); // write fd
stack.push(FDTable.register(p.source())); // read fd
stack.push(FDTable.register(p.sink()));
stack.push(FDTable.register(p.source()));
}
/*
* DUP 复制一个已存在的 fddup
* 入栈顺序: oldfdint
* 出栈顺序: oldfd
* 返回新的 fd失败 -1
*/
case "DUP" -> {
int oldfd = (Integer) stack.pop();
stack.push(FDTable.dup(oldfd));
}
/*==================== 网络相关 ====================*/
/*
* SOCKET 创建套接字支持 stream/dgram
* 入栈顺序: domainint, AF_INET=2等, typeint, 1=STREAM,2=DGRAM, protocolint, 预留
* 出栈顺序: protocol type domain
* 返回 fd
*/
// 网络相关
case "SOCKET" -> {
int proto = (Integer) stack.pop(); // 预留暂不用
int type = (Integer) stack.pop(); // 1=STREAM,2=DGRAM
int domain = (Integer) stack.pop(); // AF_INET=2
int proto = (Integer) stack.pop();
int type = (Integer) stack.pop();
int domain = (Integer) stack.pop();
Channel ch = (type == 1)
? SocketChannel.open()
: DatagramChannel.open();
stack.push(FDTable.register(ch));
}
/*
* CONNECT 发起 TCP 连接
* 入栈顺序: fdint, hostString, portint
* 出栈顺序: port host fd
* 返回 0 成功-1 失败
*/
case "CONNECT" -> {
int port = (Integer) stack.pop();
String host = String.valueOf(stack.pop());
@ -225,14 +162,10 @@ public class SyscallCommand implements Command {
if (ch instanceof SocketChannel sc) {
sc.connect(new InetSocketAddress(host, port));
stack.push(0);
} else stack.push(-1);
} else {
stack.push(-1);
}
}
/*
* BIND 绑定端口
* 入栈顺序: fdint, hostString, portint
* 出栈顺序: port host fd
* 返回 0 成功-1 失败
*/
case "BIND" -> {
int port = (Integer) stack.pop();
String host = String.valueOf(stack.pop());
@ -241,45 +174,32 @@ public class SyscallCommand implements Command {
if (ch instanceof ServerSocketChannel ssc) {
ssc.bind(new InetSocketAddress(host, port));
stack.push(0);
} else stack.push(-1);
} else {
stack.push(-1);
}
}
/*
* LISTEN 监听 socket兼容 backlog
* 入栈顺序: fdint, backlogint
* 出栈顺序: backlog fd
* 返回 0 成功-1 失败
* <b>注意Java NIO 打开 ServerSocketChannel 已自动监听 backlog 处理行为和 UNIX 有区别</b>
*/
case "LISTEN" -> {
int backlog = (Integer) stack.pop();
int fd = (Integer) stack.pop();
Channel ch = FDTable.get(fd);
if (ch instanceof ServerSocketChannel) stack.push(0);
else stack.push(-1);
if (ch instanceof ServerSocketChannel) {
stack.push(0);
} else {
stack.push(-1);
}
}
/*
* ACCEPT 接收连接
* 入栈顺序: fdint
* 出栈顺序: fd
* 返回新连接 fd失败 -1
*/
case "ACCEPT" -> {
int fd = (Integer) stack.pop();
Channel ch = FDTable.get(fd);
if (ch instanceof ServerSocketChannel ssc) {
SocketChannel cli = ssc.accept();
stack.push(FDTable.register(cli));
} else stack.push(-1);
} else {
stack.push(-1);
}
}
/*==================== 多路复用 ====================*/
/*
* SELECT I/O 多路复用监视多个 fd 是否可读写
* 入栈顺序: fdsList<Integer>, timeout_mslong
* 出栈顺序: timeout_ms fds
* 返回: 就绪 fd 列表List<Integer>
*/
// 多路复用
case "SELECT" -> {
long timeout = ((Number) stack.pop()).longValue();
@SuppressWarnings("unchecked")
@ -298,107 +218,94 @@ public class SyscallCommand implements Command {
int ready = sel.select(timeout);
List<Integer> readyFds = new ArrayList<>();
if (ready > 0) {
for (SelectionKey k : sel.selectedKeys())
for (SelectionKey k : sel.selectedKeys()) {
readyFds.add((Integer) k.attachment());
}
}
stack.push(readyFds);
sel.close();
}
/*==================== 控制台输出 ====================*/
/*
* PRINT 控制台输出无换行
* 入栈顺序: data任意基本类型或其包装类型Stringbyte[]
* 出栈顺序: data
* 返回 0
*/
// 控制台输出
case "PRINT" -> {
Object dataObj = stack.pop();
output(dataObj, false);
stack.push(0); // success
stack.push(0);
}
/* PRINTLN —— 控制台输出(自动换行)。*/
case "PRINTLN" -> {
Object dataObj = stack.pop();
output(dataObj, true);
stack.push(0); // success
stack.push(0);
}
/*==================== 其它未实现/扩展命令 ====================*/
/*
* 其它自定义 syscall 子命令未实现
*/
default -> throw new UnsupportedOperationException("Unsupported SYSCALL: " + cmd);
default -> throw new UnsupportedOperationException("Unsupported SYSCALL subcommand: " + cmd);
}
} catch (Exception e) {
// 统一异常处理异常时压入 -1
pushErr(stack, e);
}
// 默认下一条指令
return pc + 1;
}
/*------------------------------------ 工具方法 ------------------------------------*/
/**
* <b>POSIX open 标志到 Java NIO OpenOption 的映射</b>
* 根据传入的文件打开标志构造 NIO {@link OpenOption} 集合
* <p>
* Linux/UNIX open 调用 flags 参数转换为 Java NIO OpenOption 集合
* 目前仅支持 WRITE0x1READCREATE0x40TRUNCATE0x200APPEND0x400等常用标志
* 本方法负责将底层虚拟机传递的 flags 整数型位域转换为 Java NIO 标准的文件打开选项集合
* 以支持文件读创建截断追加等多种访问场景
* 常用于 SYSCALL OPEN 子命令
* </p>
*
* @param flags POSIX 风格 open 标志 O_WRONLY=0x1, O_CREAT=0x40
* @return 映射后的 OpenOption 集合
* @param flags 文件打开模式标志各标志可组合使用具体含义请参见虚拟机文档
* @return 转换后的 OpenOption 集合可直接用于 FileChannel.open NIO 方法
*/
private static Set<OpenOption> flagsToOptions(int flags) {
Set<OpenOption> opts = new HashSet<>();
// 0x1 = WRITE否则默认 READ
// 如果有写入标志则添加WRITE否则默认为READ
if ((flags & 0x1) != 0) opts.add(WRITE);
else opts.add(READ);
// 如果包含创建标志允许创建文件
if ((flags & 0x40) != 0) opts.add(CREATE);
// 包含截断标志打开时清空内容
if ((flags & 0x200) != 0) opts.add(TRUNCATE_EXISTING);
// 包含追加标志文件写入时追加到末尾
if ((flags & 0x400) != 0) opts.add(APPEND);
return opts;
}
/**
* <b>统一异常处理</b>
* 捕获所有异常并统一处理操作数栈压入 -1 代表本次系统调用失败
* <p>
* 捕获 syscall 内部所有异常 -1 压入操作数栈表示系统调用失败暂不区分错误类型
* 常见异常如文件不存在权限不足通道类型不符网络故障等
* 本方法是全局错误屏障任何命令异常都会转换为虚拟机通用的失败信号
* 保证上层调用逻辑不会被异常打断实际应用中可拓展错误码机制
* </p>
*
* @param stack 当前操作数栈
* @param e 捕获的异常对象
* @param stack 操作数栈将失败信号写入此栈
* @param e 抛出的异常对象可在调试时输出日志
*/
private static void pushErr(OperandStack stack, Exception e) {
stack.push(-1); // 目前统一用 -1后续可按异常类型/errno 映射
stack.push(-1);
System.err.println("Syscall exception: " + e);
}
/**
* 统一的控制台输出辅助方法支持
* <ul>
* <li>所有基本类型及其包装类intdoublelongfloat</li>
* <li>Stringbyte[]char[] 以及其他原生数组</li>
* <li>任意 Object调用 {@code toString}</li>
* </ul>
* 控制台输出通用方法支持基本类型字节数组任意数组对象等
* <p>
* 该方法用于 SYSCALL PRINT/PRINTLN将任意类型对象转为易读字符串输出到标准输出流
* 字节数组自动按 UTF-8 解码其它原生数组按格式化字符串输出
* </p>
*
* @param obj 要输出的对象
* @param newline 是否追加换行
* @param obj 待输出的内容可以为任何类型如基本类型byte[]数组对象等
* @param newline 是否自动换行如果为 true则在输出后换行否则直接输出
*/
private static void output(Object obj, boolean newline) {
String str;
if (obj == null) {
str = "null";
} else if (obj instanceof byte[] bytes) {
// 字节数组作为文本输出
str = new String(bytes);
} else if (obj.getClass().isArray()) {
// 其它数组格式化输出
str = arrayToString(obj);
} else {
str = obj.toString();
@ -408,7 +315,15 @@ public class SyscallCommand implements Command {
}
/**
* 将各种原生数组转换成可读字符串
* 将各种原生数组和对象数组转换为可读字符串便于控制台输出和调试
* <p>
* 本方法针对 intlongdoublefloatshortcharbyteboolean 等所有原生数组类型
* 以及对象数组都能正确格式化统一输出格式风格避免显示为类型 hashCode
* 若为不支持的类型返回通用提示字符串
* </p>
*
* @param array 任意原生数组或对象数组
* @return 该数组的可读字符串表示
*/
private static String arrayToString(Object array) {
if (array instanceof int[] a) return Arrays.toString(a);