diff --git a/src/main/java/org/jcnc/snow/vm/commands/system/control/SyscallCommand.java b/src/main/java/org/jcnc/snow/vm/commands/system/control/SyscallCommand.java index 017d561..b45754e 100644 --- a/src/main/java/org/jcnc/snow/vm/commands/system/control/SyscallCommand.java +++ b/src/main/java/org/jcnc/snow/vm/commands/system/control/SyscallCommand.java @@ -1,72 +1,392 @@ package org.jcnc.snow.vm.commands.system.control; import org.jcnc.snow.vm.interfaces.Command; +import org.jcnc.snow.vm.io.FDTable; import org.jcnc.snow.vm.module.CallStack; import org.jcnc.snow.vm.module.LocalVariableStore; import org.jcnc.snow.vm.module.OperandStack; -import java.util.Arrays; +import java.net.InetSocketAddress; +import java.nio.ByteBuffer; +import java.nio.channels.*; +import java.nio.file.OpenOption; +import java.nio.file.Paths; +import java.util.*; + +import static java.nio.file.StandardOpenOption.*; /** - * SyscallCommand ―― 统一的系统调用入口(opcode = 0x0401)。 + * {@code SyscallCommand} —— I/O syscall 子命令集合指令。 * - *
当前支持子命令: + *
+ * 封装类 UNIX 文件描述符(File Descriptor, FD)操作、文件/网络 I/O、管道、fd 复制、多路复用(select)等系统调用能力, + * 基于 Java NIO 统一实现,供虚拟机指令集以 SYSCALL 形式扩展使用。 + *
+ * + * 栈操作约定: *用法示例(VM 指令):
- *- * 1025 PRINT "Hello, Snow!" - * I_PUSH 42 - * 1025 PRINTLN - *+ * 返回值说明: + *
+ * 将 Linux/UNIX 的 open 调用 flags 参数,转换为 Java NIO 的 OpenOption 集合。 + * 目前仅支持 WRITE(0x1)、READ、CREATE(0x40)、TRUNCATE(0x200)、APPEND(0x400)等常用标志。 + *
+ * + * @param flags POSIX 风格 open 标志(如 O_WRONLY=0x1, O_CREAT=0x40 等) + * @return 映射后的 OpenOption 集合 + */ + private static Set+ * 捕获 syscall 内部所有异常,将 -1 压入操作数栈,表示系统调用失败(暂不区分错误类型)。 + * 常见异常如文件不存在、权限不足、通道类型不符、网络故障等。 + *
+ * + * @param stack 当前操作数栈 + * @param e 捕获的异常对象 + */ + private static void pushErr(OperandStack stack, Exception e) { + stack.push(-1); // 目前统一用 -1,后续可按异常类型/errno 映射 + } + + /*--------------------------------------------------------------------------------*/ + + /** + * 执行 SYSCALL 子命令: + *+ * 按照 parts[1] 指定的 SYSCALL 子命令,结合虚拟机栈与资源表完成对应的系统调用模拟。 + * 支持基础文件、网络、管道等 I/O 操作,并对异常统一处理。 + *
+ * + * @param parts 指令字符串数组,parts[1] 为子命令 + * @param pc 当前指令计数器 + * @param stack 操作数栈 + * @param locals 局部变量表 + * @param callStack 调用栈 + * @return 下一条指令的 pc 值(默认 pc+1) + */ @Override - public int execute(String[] parts, int currentPC, - OperandStack operandStack, - LocalVariableStore localVariableStore, + public int execute(String[] parts, int pc, + OperandStack stack, + LocalVariableStore locals, CallStack callStack) { if (parts.length < 2) - throw new IllegalArgumentException("SYSCALL requires a sub-command"); + throw new IllegalArgumentException("SYSCALL needs sub-command"); - String subCmd = parts[1].toUpperCase(); + String cmd = parts[1].toUpperCase(Locale.ROOT); - switch (subCmd) { - case "PRINT": - case "PRINTLN": { - boolean newline = subCmd.equals("PRINTLN"); - String output; + try { + switch (cmd) { - // 指令里直接带字符串常量 - if (parts.length > 2) { - output = String.join(" ", Arrays.copyOfRange(parts, 2, parts.length)); - if (output.length() >= 2 && - output.startsWith("\"") && - output.endsWith("\"")) { - output = output.substring(1, output.length() - 1); // 去掉首尾引号 + /*==================== 文件 / 目录 ====================*/ + + /* + * OPEN —— 打开文件,返回文件描述符(File Descriptor, FD)。 + * 入栈(push)顺序: path(文件路径, String), flags(int), mode(int,文件权限,暂未用) + * 出栈(pop)顺序: mode → flags → path + * 成功返回 fd,失败返回 -1。 + */ + case "OPEN" -> { + 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 —— 关闭文件描述符。 + * 入栈顺序: fd(int) + * 出栈顺序: fd + * 返回 0 成功,-1 失败 + */ + case "CLOSE" -> { + int fd = (Integer) stack.pop(); + FDTable.close(fd); + stack.push(0); + } + /* + * READ —— 从 fd 读取指定字节数。 + * 入栈顺序: fd(int), count(int,字节数) + * 出栈顺序: count → fd + * 返回:byte[](实际读取的数据,EOF 时长度为 0) + */ + case "READ" -> { + int count = (Integer) stack.pop(); + int fd = (Integer) stack.pop(); + Channel ch = FDTable.get(fd); + if (!(ch instanceof ReadableByteChannel rch)) { + stack.push(new byte[0]); + break; } + ByteBuffer buf = ByteBuffer.allocate(count); + int n = rch.read(buf); + if (n < 0) n = 0; + buf.flip(); + byte[] out = new byte[n]; + buf.get(out); + stack.push(out); } - // 没带常量,则弹栈打印 - else { - Object value = operandStack.pop(); - output = String.valueOf(value); + /* + * WRITE —— 向 fd 写数据。 + * 入栈顺序: fd(int), data(byte[] 或 String) + * 出栈顺序: data → fd + * 返回写入字节数,失败 -1 + */ + case "WRITE" -> { + Object dataObj = stack.pop(); + int fd = (Integer) stack.pop(); + byte[] data = (dataObj instanceof byte[] b) + ? b + : String.valueOf(dataObj).getBytes(); + Channel ch = FDTable.get(fd); + if (!(ch instanceof WritableByteChannel wch)) { + stack.push(-1); + break; + } + int written = wch.write(ByteBuffer.wrap(data)); + stack.push(written); + } + /* + * SEEK —— 文件定位,移动文件读写指针。 + * 入栈顺序: fd(int), offset(long/int), whence(int, 0=SET,1=CUR,2=END) + * 出栈顺序: whence → offset → fd + * 返回新位置(long),失败 -1 + */ + case "SEEK" -> { + int whence = (Integer) stack.pop(); + long off = ((Number) stack.pop()).longValue(); + int fd = (Integer) stack.pop(); + Channel ch = FDTable.get(fd); + if (!(ch instanceof SeekableByteChannel sbc)) { + stack.push(-1); + break; + } + SeekableByteChannel newPos = switch (whence) { + 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"); + }; + stack.push(newPos); } - if (newline) System.out.println(output); - else System.out.print(output); - break; + /*==================== 管道 / 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 + } + /* + * DUP —— 复制一个已存在的 fd(dup)。 + * 入栈顺序: oldfd(int) + * 出栈顺序: oldfd + * 返回新的 fd,失败 -1 + */ + case "DUP" -> { + int oldfd = (Integer) stack.pop(); + stack.push(FDTable.dup(oldfd)); + } + + /*==================== 网络相关 ====================*/ + + /* + * SOCKET —— 创建套接字,支持 stream/dgram。 + * 入栈顺序: domain(int, AF_INET=2等), type(int, 1=STREAM,2=DGRAM), protocol(int, 预留) + * 出栈顺序: 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…… + Channel ch = (type == 1) + ? SocketChannel.open() + : DatagramChannel.open(); + stack.push(FDTable.register(ch)); + } + /* + * CONNECT —— 发起 TCP 连接。 + * 入栈顺序: fd(int), host(String), port(int) + * 出栈顺序: port → host → fd + * 返回 0 成功,-1 失败 + */ + case "CONNECT" -> { + int port = (Integer) stack.pop(); + String host = String.valueOf(stack.pop()); + int fd = (Integer) stack.pop(); + Channel ch = FDTable.get(fd); + if (ch instanceof SocketChannel sc) { + sc.connect(new InetSocketAddress(host, port)); + stack.push(0); + } else stack.push(-1); + } + /* + * BIND —— 绑定端口。 + * 入栈顺序: fd(int), host(String), port(int) + * 出栈顺序: port → host → fd + * 返回 0 成功,-1 失败 + */ + case "BIND" -> { + int port = (Integer) stack.pop(); + String host = String.valueOf(stack.pop()); + int fd = (Integer) stack.pop(); + Channel ch = FDTable.get(fd); + if (ch instanceof ServerSocketChannel ssc) { + ssc.bind(new InetSocketAddress(host, port)); + stack.push(0); + } else stack.push(-1); + } + /* + * LISTEN —— 监听 socket,兼容 backlog。 + * 入栈顺序: fd(int), backlog(int) + * 出栈顺序: backlog → fd + * 返回 0 成功,-1 失败 + * 注意:Java NIO 打开 ServerSocketChannel 已自动监听,无 backlog 处理,行为和 UNIX 有区别。 + */ + 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); + } + /* + * ACCEPT —— 接收连接。 + * 入栈顺序: fd(int) + * 出栈顺序: 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); + } + + /*==================== 多路复用 ====================*/ + + /* + * SELECT —— I/O 多路复用,监视多个 fd 是否可读写。 + * 入栈顺序: fds(List+ * 0 → stdin (ReadableByteChannel) + * 1 → stdout (WritableByteChannel) + * 2 → stderr (WritableByteChannel) + * 3+ → 运行期动态分配 + *+ */ +public final class FDTable { + + private FDTable() {} + + /** 下一次可用 fd(0‒2 保留给标准流) */ + private static final AtomicInteger NEXT_FD = new AtomicInteger(3); + /** 主映射表:fd → Channel */ + private static final ConcurrentHashMap