MrRobot5 生也有涯,知也无涯

Insight System.out.println() 控制台打印实现

2023-06-15

最近看到一个开源项目/公开课 CS5411/4411 at Cornell,关于如何实现一个操作系统。突然对 Java 中native 调用比较感兴趣。 特意选择 System.out.println() 与操作系统的交互来了解OS System Call 实现过程。

System.out 初始化

System 使用 PrintStream 打印信息,Java 中的文件流使用的包装者模式,真正调用输出流是 FileDescriptor.out。

// System.out 初始化,包括 stdIn, stdOut, stdErr
FileOutputStream fdOut = new FileOutputStream(FileDescriptor.out);
setOut0(newPrintStream(fdOut, props.getProperty("sun.stdout.encoding")));

更多详细解读 System源码浅析- initializeSystemClass(initProperties)

FileDescriptor out

FileDescriptor 是 Java I/O库中的一种抽象,用于表示打开的文件、套接字或其他数据源句柄,不依赖具体的操作系统特性🎯。以下会分析 Windows 系统的实现。

/**
 * 标准输出流对象(In Java)/句柄(In Windows), 对应的是控制台。
 */
public static final FileDescriptor out = standardStream(1);

// set 是 native 方法,依赖具体的操作系统实现(native code)
private static native long set(int d);

private static FileDescriptor standardStream(int fd) {
    FileDescriptor desc = new FileDescriptor();
    desc.handle = set(fd);
    return desc;
}

Java Native 桥接操作系统

这里查询的 OpenJdk 基于 Windows 操作系统的实现源码。src 下载地址 🎯。

直接调用 Windows API 获取控制台输入缓冲区的句柄

/*
 * 用途:Java_java_io_FileDescriptor_set 赋值标准设备 stdIn, stdOut, stdErr
 * @see java/io/io_util_md.h
 */
#define SET_HANDLE(fd) \
if (fd == 0) { \
    return (jlong)GetStdHandle(STD_INPUT_HANDLE); \
} else if (fd == 1) { \
    // 标准输出设备, GetStdHandle是一个Windows API函数。
    return (jlong)GetStdHandle(STD_OUTPUT_HANDLE); \
} else if (fd == 2) { \
    return (jlong)GetStdHandle(STD_ERROR_HANDLE); \
} else { \
    return (jlong)-1; \
} \

GetStdHandle 函数

GetStdHandle 函数提供了一种机制,用于检索与进程关联的标准输入 (STDIN)、标准输出 (STDOUT) 和标准错误 (STDERR) 句柄。

GetStdHandle 返回的句柄可供需要在控制台中进行读取或写入的应用程序使用。

在控制台创建过程中,系统将创建这些句柄。如上述 native 获取方式。🧲

[GetStdHandle 函数 - Windows Console Microsoft Learn](https://learn.microsoft.com/zh-cn/windows/console/getstdhandle) 文档
含义
STD_INPUT_HANDLE((DWORD)-10) 标准输入设备。 最初,这是输入缓冲区 CONIN$ 的控制台。
STD_OUTPUT_HANDLE((DWORD)-11) 标准输出设备。 最初,这是活动控制台屏幕缓冲区 CONOUT$
STD_ERROR_HANDLE((DWORD)-12) 标准错误设备。 最初,这是活动控制台屏幕缓冲区 CONOUT$

Windows API demo

#include<windows.h>

/**
 * 不用stdio.h能在控制台输出信息吗?
 * 在Windows下,可以直接使用Windows API来完成. 使用 GetStdHandle WriteConsole 函数来在控制台输出信息
 * https://www.cnblogs.com/jisuanjizhishizatan/p/16149561.html
 */
int main(){
    const char *str="Use <windows.h> to output in C++.\n";
    HANDLE handle=GetStdHandle(STD_OUTPUT_HANDLE);
    WriteConsole(handle,str,strlen(str),NULL,NULL);
    return 0;
}

注意事项:

  • Window 环境编译C/C++ 可以使用 GCC 编译器。一般是安装 MinGW 插件。由于安装过 Ruby, 使用的是附带的 C:\Programs\Ruby27-x64\msys64\mingw64\bin

  • 编译上述的代码 gcc console.c -o console.exe

  • 使用 cmd 命令行工具,使用 git bash 打印会有问题,非 Windows 原生组件获取不到控制台句柄。❌

总结

  • JVM 的存在,方便了跨平台开发,另外以方便也屏蔽了操作系统底层的一些对接。

  • 从 Java SDK 到 native 实现的探索,举一反三,其他特性的 native 实现按图索骥。🙉

  • 对于基础 API 的原理探索,在使用和调优上会更有目的和方向。

  • 越接近底层,越接近真相。


Content