std::os 模块教程
Rust 的 std::os
模块是标准库中提供操作系统特定功能的入口点,它通过子模块(如 std::os::unix
、std::os::windows
、std::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. 练习建议
- 编写跨平台工具:用 unix/windows fs 扩展设置权限。
- 实现 daemon:用 unix process fork/setsid,windows 创建服务。
- 创建本地 IPC:用 unix domain socket,windows named pipe。
- 处理 raw 句柄:用 FFI 调用 OS API 如 Unix fcntl。
- 基准测试:比较 unix hard_link vs windows CopyFile 时间。
- 与 fs 集成:扩展 Metadata 获取 Unix inode/Windows file ID。
- 错误模拟:用 mock OS 错误测试重试逻辑。