std::os 模块教程

Rust 的 std::os 模块是标准库中提供操作系统特定功能的入口点,它通过子模块(如 std::os::unixstd::os::windowsstd::os::linux 等)扩展标准库的类型和函数,以支持平台专有操作。这些扩展包括文件系统权限、进程管理、环境变量的 OS 特定处理、原始句柄和符号链接等。std::os 强调跨平台代码的条件编译(用 #[cfg(target_os = "unix")] 等),允许开发者编写可移植代码,同时访问低级 OS API。模块的函数多返回 std::io::Result 或平台特定类型,确保安全性和显式错误处理。std::os 不直接暴露通用 API,而是作为标准库(如 std::fs、std::process)的 OS 特定补充。

1. std::os 简介

  • 导入和基本结构:通常用 use std::os::unix;(或 windows 等),因为 std::os 本身不直接导出功能,而是容器模块。子模块基于目标 OS 条件编译可用。
    • 子模块概述
      • unix:Unix-like 系统(Linux、macOS、BSD)扩展,包括 fs、net、process、thread 等。
      • windows:Windows 扩展,类似但针对 NTFS、Winsock 等。
      • linux/android/macos 等:特定 OS 子扩展(如 linux::fs 的 inotify)。
      • raw:跨平台 raw 类型,如 c_char、c_int(从 libc)。
    • 类型和 trait:扩展标准类型,如 std::os::unix::fs::PermissionsExt 添加 Unix 模式;std::os::windows::process::CommandExt 添加 Windows 创建标志。
    • 函数:平台特定,如 std::os::unix::fs::symlink 创建链接;std::os::windows::io::AsRawHandle 获取原始句柄。
  • 设计哲学std::os 是标准库的“逃生舱”,提供低级访问而不牺牲安全;用 cfg 属性隔离代码,避免编译错误。错误通过 io::Error 或 OS 特定枚举处理。
  • 跨平台注意:用 #[cfg(target_os = "unix")] 条件导入/实现;fallback 用 else 或 cfg(any(...))。测试多平台用 cross crate 或 CI。
  • 性能基础:低级操作如 raw 句柄 O(1),但符号链接解析可能涉及系统调用;缓存元数据优化重复查询。
  • 常见用例:权限管理(chmod)、进程标志(daemonize)、网络 socket 选项、raw I/O、环境变量扩展。
  • 扩展概念:与 std::fs 集成扩展 File(如 MetadataExt);与 libc crate 结合更低级 C API;错误映射 OS errno/WinError;多线程安全(OS 句柄原子)。

2. std::os::unix - Unix-like 系统扩展

std::os::unix 提供 Unix 特定功能,如文件模式、符号链接和进程组。

示例:文件权限扩展(基本 PermissionsExt)

#[cfg(unix)]
use std::os::unix::fs::PermissionsExt;
#[cfg(unix)]
use std::fs::{self, Permissions};

#[cfg(unix)]
fn main() -> std::io::Result<()> {
    let metadata = fs::metadata("file.txt")?;
    let permissions = metadata.permissions();
    println!("Unix 模式: {:#o}", permissions.mode());  // 如 0o100644 (rw-r--r--)

    let mut new_perms = permissions;
    new_perms.set_mode(0o755);  // rwxr-xr-x
    fs::set_permissions("file.txt", new_perms)?;
    Ok(())
}

#[cfg(not(unix))]
fn main() {
    println!("非 Unix 平台");
}
  • 解释PermissionsExt::mode 返回 u32 模式(八进制)。set_mode 设置权限。cfg 条件编译仅 Unix。性能:系统调用,批量操作优化。

示例:符号链接和硬链接(扩展链接操作)

#[cfg(unix)]
use std::os::unix::fs::{symlink, lchown};
#[cfg(unix)]
use std::fs;

#[cfg(unix)]
fn main() -> std::io::Result<()> {
    symlink("original.txt", "soft_link.txt")?;  // 软链接
    fs::hard_link("original.txt", "hard_link.txt")?;  // 硬链接

    let target = fs::read_link("soft_link.txt")?;
    println!("链接指向: {}", target.display());

    // 扩展:更改所有者(需权限)
    lchown("soft_link.txt", Some(1000), Some(1000))?;  // uid/gid
    Ok(())
}

#[cfg(not(unix))]
fn main() {}
  • 解释symlink 创建符号链接。fs::hard_link 共享 inode。read_link 返回目标。lchown 更改链接所有者(不跟随)。陷阱:权限不足返回 PermissionDenied;循环链接导致无限递归。扩展:用 fchown 于打开文件。

示例:进程和线程扩展(Unix 进程组扩展)

#[cfg(unix)]
use std::os::unix::process::CommandExt;
#[cfg(unix)]
use std::process::Command;

#[cfg(unix)]
fn main() {
    let mut cmd = Command::new("ls");
    cmd.uid(1000);  // 设置用户 ID(需权限)
    cmd.gid(1000);  // 设置组 ID
    cmd.exec();  // 替换当前进程
    unreachable!();  // exec 成功不返回
}

#[cfg(not(unix))]
fn main() {}
  • 解释CommandExt::uid/gid 设置执行用户/组。exec 替换进程(如 Unix execvp)。性能:fork-exec 模型,开销小。陷阱:权限错误;exec 失败返回 Err。

示例:网络扩展(Unix domain socket 扩展)

#[cfg(unix)]
use std::os::unix::net::UnixStream;
#[cfg(unix)]
use std::io::{Read, Write};

#[cfg(unix)]
fn main() -> std::io::Result<()> {
    let mut stream = UnixStream::connect("/tmp/socket")?;
    stream.write_all(b"hello")?;
    let mut buf = [0; 5];
    stream.read_exact(&mut buf)?;
    println!("接收: {:?}", buf);
    Ok(())
}

#[cfg(not(unix))]
fn main() {}
  • 解释UnixStream 是本地 IPC socket,实现 Read/Write。connect 连接路径。扩展:用 UnixListener 监听;性能高于 TCP 本地循环。

3. std::os::windows - Windows 系统扩展

std::os::windows 提供 Windows 特定功能,如 NTFS 权限、原始句柄和进程标志。

示例:文件扩展(Windows 符号链接扩展)

#[cfg(windows)]
use std::os::windows::fs::{symlink_file, symlink_dir};
#[cfg(windows)]
use std::fs;

#[cfg(windows)]
fn main() -> std::io::Result<()> {
    symlink_file("original.txt", "file_link.txt")?;  // 文件符号链接
    symlink_dir("original_dir", "dir_link")?;  // 目录符号链接

    let target = fs::read_link("file_link.txt")?;
    println!("链接指向: {}", target.display());
    Ok(())
}

#[cfg(not(windows))]
fn main() {}
  • 解释symlink_file/dir 创建符号链接(需管理员权限)。read_link 返回目标。陷阱:Windows 符号链接需启用开发者模式或权限。扩展:用 OpenOptionsExt::custom_flags 自定义打开标志。

示例:进程扩展(Windows 创建标志扩展)

#[cfg(windows)]
use std::os::windows::process::CommandExt;
#[cfg(windows)]
use std::process::Command;

#[cfg(windows)]
fn main() -> std::io::Result<()> {
    let mut cmd = Command::new("cmd.exe");
    cmd.creation_flags(0x00000008);  // DETACHED_PROCESS 标志
    let output = cmd.output()?;
    println!("状态: {}", output.status);
    Ok(())
}

#[cfg(not(windows))]
fn main() {}
  • 解释CommandExt::creation_flags 设置 Windows CreateProcess 标志(如无窗口)。output 执行并捕获。性能:标志优化启动。陷阱:无效标志返回 Err。

示例:I/O 扩展(Windows 原始句柄扩展)

#[cfg(windows)]
use std::os::windows::io::{AsRawHandle, FromRawHandle};
#[cfg(windows)]
use std::fs::File;

#[cfg(windows)]
fn main() -> std::io::Result<()> {
    let file = File::create("win_file.txt")?;
    let handle = file.as_raw_handle();  // 获取 HANDLE
    println!("原始句柄: {:?}", handle);

    // 扩展:从原始句柄创建 File
    let owned_file = unsafe { File::from_raw_handle(handle) };
    drop(owned_file);  // 管理所有权
    Ok(())
}

#[cfg(not(windows))]
fn main() {}
  • 解释AsRawHandle 获取 Windows HANDLE。FromRawHandle 从 raw 创建(unsafe,因为所有权转移)。扩展:与 WinAPI crate 集成调用系统函数。

4. std::os::raw - 原始类型

std::os::raw 导出 C 兼容类型,如 c_int,用于 FFI。

示例:原始类型使用(跨平台 FFI 扩展)

use std::os::raw::{c_char, c_int};
use std::ffi::CStr;

extern "C" {
    fn strlen(s: *const c_char) -> c_int;
}

fn main() {
    let c_string = b"hello\0";
    let ptr = c_string.as_ptr() as *const c_char;
    let len = unsafe { strlen(ptr) };
    println!("长度: {}", len);  // 5

    let cstr = unsafe { CStr::from_ptr(ptr) };
    println!("字符串: {}", cstr.to_string_lossy());
}
  • 解释c_char 是 signed/unsigned char 别名。c_int 是 i32/u32 平台依赖。unsafe 调用 C 函数。性能:直接 FFI 零开销。陷阱:空终止字符串;内存管理手动。

示例:平台特定 raw(扩展条件)

#[cfg(unix)]
use std::os::unix::raw::pid_t;
#[cfg(windows)]
use std::os::windows::raw::HANDLE;

fn main() {
    #[cfg(unix)]
    {
        let pid: pid_t = 1234;
        println!("Unix PID: {}", pid);
    }

    #[cfg(windows)]
    {
        let handle: HANDLE = std::ptr::null_mut();
        println!("Windows HANDLE: {:?}", handle);
    }
}
  • 解释:raw 子模块提供平台 typedef 如 pid_t (i32)。用于 OS API。扩展:与 bindgen 生成 FFI 绑定。

5. 高级主题:条件编译、集成和错误处理

  • 条件编译:cfg(target_os/family/arch/endian/pointer_width/env/feature)。
  • 集成:与 std::process、std::fs。

示例:跨平台权限处理(条件集成扩展)

use std::fs::{self, Permissions};

#[cfg(unix)]
use std::os::unix::fs::PermissionsExt;

fn set_executable(path: &str) -> std::io::Result<()> {
    let mut perms = fs::metadata(path)?.permissions();

    #[cfg(unix)]
    {
        perms.set_mode(perms.mode() | 0o111);  // +x for all
    }

    #[cfg(windows)]
    {
        // Windows 无直接模式,用 ACL 或简单 readonly
        perms.set_readonly(false);
    }

    fs::set_permissions(path, perms)
}

fn main() -> std::io::Result<()> {
    set_executable("script.sh")?;
    Ok(())
}
  • 解释:cfg 内代码仅目标平台编译。集成 fs::set_permissions。性能:元数据调用缓存。

示例:详细错误处理(OS 特定错误扩展)

#[cfg(unix)]
use std::os::unix::fs::symlink;

#[cfg(unix)]
fn create_symlink(src: &str, dst: &str) -> std::io::Result<()> {
    symlink(src, dst).map_err(|e| {
        if e.raw_os_error() == Some(libc::EACCES) {
            io::Error::new(io::ErrorKind::PermissionDenied, "Unix 权限拒绝")
        } else {
            e
        }
    })
}

#[cfg(unix)]
fn main() -> std::io::Result<()> {
    create_symlink("src", "dst")?;
    Ok(())
}

#[cfg(not(unix))]
fn main() {}
  • 解释raw_os_error 获取 errno。map_err 自定义错误。扩展:用 anyhow 链错误源。

6. 最佳实践和常见陷阱

  • 条件编译最佳实践:用 cfg(any(unix, windows)) 覆盖;测试 cfg(test) 模拟平台。
  • 性能陷阱:raw 调用系统开销;缓存句柄避免重复获取。
  • 错误最佳实践:检查 raw_os_error;重试 Transient 如 EAGAIN;日志 OS 码。
  • 安全性:raw 句柄 unsafe,避免泄漏;权限操作需 root/admin。
  • 跨平台扩展:抽象 trait 包装 OS 特定;用 cfg_attr(feature, ...) 启用。
  • 测试扩展:用 mockall 测试 trait;跨平台 CI 测试真实 OS。
  • 资源管理:raw 句柄用 OwnedFd/Handle 自动关闭。
  • 常见错误扩展
    • cfg 错:编译失败,用 cargo check --target。
    • 权限:EACCES/AccessDenied,重试或提示提升。
    • 无效句柄:EBADF/INVALID_HANDLE,重检查所有权。
    • 平台不匹配:用 fallback 代码。

7. 练习建议

  1. 编写跨平台工具:用 unix/windows fs 扩展设置权限。
  2. 实现 daemon:用 unix process fork/setsid,windows 创建服务。
  3. 创建本地 IPC:用 unix domain socket,windows named pipe。
  4. 处理 raw 句柄:用 FFI 调用 OS API 如 Unix fcntl。
  5. 基准测试:比较 unix hard_link vs windows CopyFile 时间。
  6. 与 fs 集成:扩展 Metadata 获取 Unix inode/Windows file ID。
  7. 错误模拟:用 mock OS 错误测试重试逻辑。