From dad69eabfba04b799a28e5d2dac6357cb3865af5 Mon Sep 17 00:00:00 2001 From: Luke Date: Fri, 18 Jul 2025 18:06:19 +0800 Subject: [PATCH] =?UTF-8?q?feat(vm):=20=E5=AE=9E=E7=8E=B0=20UNIX=E9=A3=8E?= =?UTF-8?q?=E6=A0=BC=E7=9A=84=E6=96=87=E4=BB=B6=E6=8F=8F=E8=BF=B0=E7=AC=A6?= =?UTF-8?q?=20I/O=20=E6=93=8D=E4=BD=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 FDTable 类,用于管理文件描述符和 NIO Channel 的映射 - 重构 SyscallCommand 类,实现多个 I/O 相关的 syscall 子命令 - 支持文件操作(open、close、read、write、seek)、管道、网络套接字、I/O 多路复用等功能 - 优化异常处理机制,统一处理 syscall内部异常 --- .../system/control/SyscallCommand.java | 400 ++++++++++++++++-- .../java/org/jcnc/snow/vm/io/FDTable.java | 62 +++ 2 files changed, 422 insertions(+), 40 deletions(-) create mode 100644 src/main/java/org/jcnc/snow/vm/io/FDTable.java 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
- * 
+ * 返回值说明: + * + * + * 支持的子命令(部分): + * */ public class SyscallCommand implements Command { + /*------------------------------------ 工具方法 ------------------------------------*/ + + /** + * POSIX open 标志到 Java NIO OpenOption 的映射 + *

+ * 将 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 flagsToOptions(int flags) { + Set opts = new HashSet<>(); + // 0x1 = 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; + } + + /** + * 统一异常处理: + *

+ * 捕获 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), timeout_ms(long) + * 出栈顺序: timeout_ms → fds + * 返回: 就绪 fd 列表(List) + */ + case "SELECT" -> { + long timeout = ((Number) stack.pop()).longValue(); + @SuppressWarnings("unchecked") + List fds = (List) stack.pop(); + + Selector sel = Selector.open(); + for (int fd : fds) { + Channel c = FDTable.get(fd); + if (c instanceof SelectableChannel sc) { + sc.configureBlocking(false); + int ops = (c instanceof ReadableByteChannel ? SelectionKey.OP_READ : 0) + | (c instanceof WritableByteChannel ? SelectionKey.OP_WRITE : 0); + sc.register(sel, ops, fd); + } + } + int ready = sel.select(timeout); + List readyFds = new ArrayList<>(); + if (ready > 0) { + for (SelectionKey k : sel.selectedKeys()) + readyFds.add((Integer) k.attachment()); + } + stack.push(readyFds); + sel.close(); + } + + + /*==================== 控制台输出 ====================*/ + + /* + * PRINT —— 控制台输出(无换行)。 + * 入栈顺序: data(String 或 byte[]) + * 出栈顺序: data + * 返回 0 + */ + case "PRINT" -> { + Object dataObj = stack.pop(); + if (dataObj instanceof byte[] b) { + System.out.print(new String(b)); + } else { + System.out.print(dataObj); + } + stack.push(0); // 表示成功 + } + /* + * PRINTLN —— 控制台输出(自动换行)。 + * 入栈顺序: data(String 或 byte[]) + * 出栈顺序: data + * 返回 0 + */ + case "PRINTLN" -> { + Object dataObj = stack.pop(); + if (dataObj instanceof byte[] b) { + System.out.println(new String(b)); + } else { + System.out.println(dataObj); + } + stack.push(0); // 表示成功 + } + + /*==================== 其它未实现/扩展命令 ====================*/ + + /* + * TODO: PRINT / PRINTLN / 其它自定义 syscall 子命令未实现 + */ + default -> throw new UnsupportedOperationException("Unsupported SYSCALL: " + cmd); } - - default: - throw new UnsupportedOperationException("Unsupported SYSCALL: " + subCmd); + } catch (Exception e) { + // 统一异常处理,异常时压入 -1 + pushErr(stack, e); } - // 下一条指令 - return currentPC + 1; + // 默认:下一条指令 + return pc + 1; } } diff --git a/src/main/java/org/jcnc/snow/vm/io/FDTable.java b/src/main/java/org/jcnc/snow/vm/io/FDTable.java new file mode 100644 index 0000000..98d6385 --- /dev/null +++ b/src/main/java/org/jcnc/snow/vm/io/FDTable.java @@ -0,0 +1,62 @@ +package org.jcnc.snow.vm.io; + +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.IOException; +import java.nio.channels.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * 维护 “虚拟 fd → Java NIO Channel” 的全局映射表。 + * + *
+ *   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 MAP = new ConcurrentHashMap<>(); + + static { + // JVM 标准流包装成 NIO Channel 后放入表中 + MAP.put(0, Channels.newChannel(new BufferedInputStream(System.in))); + MAP.put(1, Channels.newChannel(new BufferedOutputStream(System.out))); + MAP.put(2, Channels.newChannel(new BufferedOutputStream(System.err))); + } + + /** 注册新 Channel,返回分配到的虚拟 fd */ + public static int register(Channel ch) { + int fd = NEXT_FD.getAndIncrement(); + MAP.put(fd, ch); + return fd; + } + + /** 取得 Channel;如果 fd 不存在则返回 null */ + public static Channel get(int fd) { + return MAP.get(fd); + } + + /** 关闭并移除 fd(0‒2 忽略) */ + public static void close(int fd) throws IOException { + if (fd <= 2) return; // 标准流交由宿主 JVM 维护 + Channel ch = MAP.remove(fd); + if (ch != null && ch.isOpen()) ch.close(); + } + + /** 类似 dup(oldfd) —— 返回指向同一 Channel 的新 fd */ + public static int dup(int oldfd) { + Channel ch = MAP.get(oldfd); + if (ch == null) + throw new IllegalArgumentException("Bad fd: " + oldfd); + return register(ch); // 多个 fd 引用同一 Channel + } +}