本文摘自链接: 深入RUST标准库内核 如需要了解更多关于RUST标准库内核的内容,请进入链接,希望能顺便star.
std库文件描述符代码分析
以linux为例,文件系统实际上成为操作系统的所有资源的管理体系的基础设施。因此,将之放在内存管理之后来做分析。
RUST将文件管理的结构分成:
- 操作系统文件描述符的封装结构。对于RUST来说,操作系统的文件描述符与堆内存要处理的安全性类似。需要建立类似智能指针的结构完成对其的管理,并纳入到RUST的安全体系内。
- 1创建的类型结构主要用于解决安全问题,如果在同一个类型加上文件功能,会乱。因此在1的基础上建立RUST自身的文件描述符类型,用于处理文件的读写等功能
- 在2的基础上,实现普通的文件,目录文件,Socket,Pipe,IO设备文件等逻辑文件类型。
本章将讨论1及2,3以后在涉及到各模块时再进行详细分析
代码目录: library/src/std/src/os/fd/raw.rs
library/src/std/src/os/fd/owned.rs
library/src/std/src/sys/unix/fd.rs
操作系统的文件描述符适配所有权封装
RUST当然要使用操作系统调用返回的fd来操作文件,操作系统返回的文件RUST定义类型RawFd。RawFd本身和裸指针一样,是没有所有权概念的,但RUST中文件显然需要具备所有权,RUST在RawFd上定义了封装类型OwnedFd来实现针对RawFd的所有权,又定义了类型BorrowedFd作为OwnedFd的借用类型。
理解这两个类型,我们可以把RawFd类比与裸指针* const T, OwnedFd类比与 T, BorrowedFd类比于&T。
以linux操作系统为基础进行分析:
//虽然是int型,但因为表示了系统的资源,所以可以类比于裸指针。后继被标识文件所有权的封装类型所封装后才能进入安全的RUST领域。
pub type RawFd = raw::c_int;
//此trait用于从封装RawFd的类型中获取RawFd, 此时返回的RawFd安全性类似于裸指针。
pub trait AsRawFd {
fn as_raw_fd(&self) -> RawFd;
}
//从RawFd创建一个封装类型,返回的Self获得了RawFd代表的文件的所有权
pub trait FromRawFd {
unsafe fn from_raw_fd(fd: RawFd) -> Self;
}
//将封装类型变量消费掉,并返回RawFd,此时RUST中没有其他变量拥有RawFd代表文件的所有权,后继要由RawFd对close负责,或者将RawFd重新封装入另一个表示所有权的封装类型变量。
pub trait IntoRawFd {
fn into_raw_fd(self) -> RawFd;
}
//获取标准输入的RawFd
impl AsRawFd for io::Stdin {
fn as_raw_fd(&self) -> RawFd {
//libc的标准输入文件标识宏
libc::STDIN_FILENO
}
}
//标准输出的RawFd
impl AsRawFd for io::Stdout {
fn as_raw_fd(&self) -> RawFd {
//libc的标准输出宏
libc::STDOUT_FILENO
}
}
//标准错误的RawFd
impl AsRawFd for io::Stderr {
fn as_raw_fd(&self) -> RawFd {
//libc的标准错误宏
libc::STDERR_FILENO
}
}
拥有RawFd所有权的OwnedFd类型结构及OwnedFd的借用类型结构BorrowedFd。
#[repr(transparent)]
pub struct BorrowedFd<'fd> {
fd: RawFd,
//用OwnedFd作为RawFd的所有权版本,RawFd实际上可认为是对OwnedFd的借用。
//但仅用fd无法表达出生命周期和借用关系,
//这里的PhantomData用OwnedFd的引用及生命周期泛型表示了这个关系
_phantom: PhantomData<&'fd OwnedFd>,
}
#[repr(transparent)]
pub struct OwnedFd {
//这个封装仅是一个形式,编译器并没有认为OwnedFd已经拥有了fd所代表文件的所有权。
//所以,OwnedFd拥有所有权这个事情实际上是代码约定,其他代码务必不能导致用RawFd创建另一份
//OwnedFd, 也不能另外调用fd的close。
//调用操作系统的系统调用获得文件Fd后,应该第一时间用OwnedFd进行封装,后继如果要使用,则应该
//用borrow的方法来借出BorrowedFd,
fd: RawFd,
}
impl BorrowedFd<'_> {
//直接在RawFd上生成BorrowFd,注意,这里的RawFd应该已经被一个OwnedFd所包装,否则这里不正确
pub unsafe fn borrow_raw(fd: RawFd) -> Self {
assert_ne!(fd, u32::MAX as RawFd);
//这里的PhantomData的赋值令人疑惑,只能认为是编译器的魔术了
unsafe { Self { fd, _phantom: PhantomData } }
}
}
impl OwnedFd {
//复制,这里是在操作系统内部复制了一个新的fd,需要调用系统调用完成
pub fn try_clone(&self) -> crate::io::Result<Self> {
// 设置复制的功能设定标志
let cmd = libc::F_DUPFD_CLOEXEC;
//调用libc库完成复制,返回新的fd
let fd = cvt(unsafe { libc::fcntl(self.as_raw_fd(), cmd, 0) })?;
//用新的fd创建新的Owned变量
Ok(unsafe { Self::from_raw_fd(fd) })
}
}
impl AsRawFd for BorrowedFd<'_> {
//此方法应该尽量仅用于调用系统调用的时候使用
fn as_raw_fd(&self) -> RawFd {
self.fd
}
}
impl AsRawFd for OwnedFd {
//此方法应该尽量仅用于调用系统调用时使用
fn as_raw_fd(&self) -> RawFd {
self.fd
}
}
impl IntoRawFd for OwnedFd {
fn into_raw_fd(self) -> RawFd {
let fd = self.fd;
//必须forget,否则会触发drop调用close(fd)
forget(self);
fd
}
}
impl FromRawFd for OwnedFd {
//应该只能用这个方法创建OwnedFd
unsafe fn from_raw_fd(fd: RawFd) -> Self {
assert_ne!(fd, u32::MAX as RawFd);
unsafe { Self { fd } }
}
}
//这个方法证明OwnedFd拥有了操作系统返回的fd的所有权
impl Drop for OwnedFd {
fn drop(&mut self) {
unsafe {
let _ = libc::close(self.fd);
}
}
}
//对OwnedFd创建借用的trait
pub trait AsFd {
fn as_fd(&self) -> BorrowedFd<'_>;
}
impl AsFd for OwnedFd {
fn as_fd(&self) -> BorrowedFd<'_> {
//BorrowedFd中的PhantomData从&self中获得
unsafe { BorrowedFd::borrow_raw(self.as_raw_fd()) }
}
}
//以下为所有的高层视角资源生成OwnedFd的借用
impl AsFd for fs::File {
fn as_fd(&self) -> BorrowedFd<'_> {
//实质是OwnedFd.as_fd
self.as_inner().as_fd()
}
}
impl From<fs::File> for OwnedFd {
fn from(file: fs::File) -> OwnedFd {
//消费了File
file.into_inner().into_inner().into_inner()
//此处不涉及对file的forget
}
}
impl From<OwnedFd> for fs::File {
fn from(owned_fd: OwnedFd) -> Self {
//创建fs::File
Self::from_inner(FromInner::from_inner(FromInner::from_inner(owned_fd)))
}
}
impl AsFd for crate::net::TcpStream {
fn as_fd(&self) -> BorrowedFd<'_> {
//socket在unix与fd没有区别,也使用OwnedFd和BorrowedFd来做所有权的解决方案
self.as_inner().socket().as_fd()
}
}
impl From<crate::net::TcpStream> for OwnedFd {
fn from(tcp_stream: crate::net::TcpStream) -> OwnedFd {
//消费掉tcp_stream,具体在tcp stream分析
tcp_stream.into_inner().into_socket().into_inner().into_inner().into()
}
}
impl From<OwnedFd> for crate::net::TcpStream {
fn from(owned_fd: OwnedFd) -> Self {
//后继在TcpStream章节分析
Self::from_inner(FromInner::from_inner(FromInner::from_inner(FromInner::from_inner(
owned_fd,
))))
}
}
impl AsFd for crate::net::TcpListener {
fn as_fd(&self) -> BorrowedFd<'_> {
//同TcpStream
self.as_inner().socket().as_fd()
}
}
impl From<crate::net::TcpListener> for OwnedFd {
fn from(tcp_listener: crate::net::TcpListener) -> OwnedFd {
tcp_listener.into_inner().into_socket().into_inner().into_inner().into()
}
}
impl From<OwnedFd> for crate::net::TcpListener {
fn from(owned_fd: OwnedFd) -> Self {
Self::from_inner(FromInner::from_inner(FromInner::from_inner(FromInner::from_inner(
owned_fd,
))))
}
}
impl AsFd for crate::net::UdpSocket {
fn as_fd(&self) -> BorrowedFd<'_> {
//UDP与TCP类似
self.as_inner().socket().as_fd()
}
}
impl From<crate::net::UdpSocket> for OwnedFd {
fn from(udp_socket: crate::net::UdpSocket) -> OwnedFd {
udp_socket.into_inner().into_socket().into_inner().into_inner().into()
}
}
impl From<OwnedFd> for crate::net::UdpSocket {
fn from(owned_fd: OwnedFd) -> Self {
Self::from_inner(FromInner::from_inner(FromInner::from_inner(FromInner::from_inner(
owned_fd,
))))
}
}
对于需要调用RUST以外语言实现的第三方库时,都会面临一个从第三方库获取的资源如何在RUST设计其所有权的问题。Unix的fd的方案给出了一个经典的设计方式。即把第三方库获取的资源在逻辑上类似于裸指针,如RawFd。然后用一个封装结构封装用于所有权实现,例如OwnedFd。用另一个封装结构用作借用,例如BorrowedFd。这个设计方案在真正的生产环境中会经常被用到。
RUST标准库文件描述符的结构与实现
在OwnedFd的基础上创建的结构,用来完成对文件的通用操作
pub struct FileDesc(OwnedFd);
impl FileDesc {
//从文件描述符读出字节流
pub fn read(&self, buf: &mut [u8]) -> io::Result<usize> {
let ret = cvt(unsafe {
//调用libc的read函数
libc::read(
//按C语言的调用进行转换
self.as_raw_fd(),
//转换成void *指针
buf.as_mut_ptr() as *mut c_void,
//不能超过buf,也不能超过一次读的最大长度
cmp::min(buf.len(), READ_LIMIT),
)
})?;
Ok(ret as usize)
}
//对应于libc的iovec读的方式,具体请参考libc的说明
pub fn read_vectored(&self, bufs: &mut [IoSliceMut<'_>]) -> io::Result<usize> {
let ret = cvt(unsafe {
libc::readv(
self.as_raw_fd(),
bufs.as_ptr() as *const libc::iovec,
cmp::min(bufs.len(), max_iov()) as c_int,
)
})?;
Ok(ret as usize)
}
//一直读到文件结束
pub fn read_to_end(&self, buf: &mut Vec<u8>) -> io::Result<usize> {
let mut me = self;
(&mut me).read_to_end(buf)
}
//从文件的某一个位置开始读,请参考libc的pread64的man
pub fn read_at(&self, buf: &mut [u8], offset: u64) -> io::Result<usize> {
use libc::pread64;
unsafe {
cvt(pread64(
self.as_raw_fd(),
buf.as_mut_ptr() as *mut c_void,
cmp::min(buf.len(), READ_LIMIT),
offset as i64,
))
.map(|n| n as usize)
}
}
//读到buffer中的某一个位置
pub fn read_buf(&self, buf: &mut ReadBuf<'_>) -> io::Result<()> {
let ret = cvt(unsafe {
libc::read(
self.as_raw_fd(),
buf.unfilled_mut().as_mut_ptr() as *mut c_void,
cmp::min(buf.remaining(), READ_LIMIT),
)
})?;
//原有的空间是MaybeUninit,读到内容后需要进行初始化标注
unsafe {
buf.assume_init(ret as usize);
}
//更新内容长度
buf.add_filled(ret as usize);
Ok(())
}
//向文件描述符写入字节流
pub fn write(&self, buf: &[u8]) -> io::Result<usize> {
let ret = cvt(unsafe {
libc::write(
self.as_raw_fd(),
buf.as_ptr() as *const c_void,
cmp::min(buf.len(), READ_LIMIT),
)
})?;
Ok(ret as usize)
}
//iovec的方式写入
pub fn write_vectored(&self, bufs: &[IoSlice<'_>]) -> io::Result<usize> {
let ret = cvt(unsafe {
libc::writev(
self.as_raw_fd(),
bufs.as_ptr() as *const libc::iovec,
cmp::min(bufs.len(), max_iov()) as c_int,
)
})?;
Ok(ret as usize)
}
//在文件的某一位置写入字节流
pub fn write_at(&self, buf: &[u8], offset: u64) -> io::Result<usize> {
use libc::pwrite64;
unsafe {
cvt(pwrite64(
self.as_raw_fd(),
buf.as_ptr() as *const c_void,
cmp::min(buf.len(), READ_LIMIT),
offset as i64,
))
.map(|n| n as usize)
}
}
//获取FD_CLOEXEC,具体请参考libc的相关手册
pub fn get_cloexec(&self) -> io::Result<bool> {
unsafe { Ok((cvt(libc::fcntl(self.as_raw_fd(), libc::F_GETFD))? & libc::FD_CLOEXEC) != 0) }
}
//设置FD_CLOEXEC的属性,一般会在打开文件时完成设置,否则要注意不同线程竞争问题
pub fn set_cloexec(&self) -> io::Result<()> {
unsafe {
let previous = cvt(libc::fcntl(self.as_raw_fd(), libc::F_GETFD))?;
let new = previous | libc::FD_CLOEXEC;
if new != previous {
cvt(libc::fcntl(self.as_raw_fd(), libc::F_SETFD, new))?;
}
Ok(())
}
}
//设置为非阻塞
pub fn set_nonblocking(&self, nonblocking: bool) -> io::Result<()> {
unsafe {
let v = nonblocking as c_int;
cvt(libc::ioctl(self.as_raw_fd(), libc::FIONBIO, &v))?;
Ok(())
}
}
//复制文件描述符
pub fn duplicate(&self) -> io::Result<FileDesc> {
Ok(Self(self.0.try_clone()?))
}
//后继可以加入其他需要的通用文件操作方法
}
impl AsInner<OwnedFd> for FileDesc {
//不消费FileDesc获取内部引用
fn as_inner(&self) -> &OwnedFd {
&self.0
}
}
impl IntoInner<OwnedFd> for FileDesc {
fn into_inner(self) -> OwnedFd {
//消费self,获得内部OwnedFd
//不必做其他资源释放操作
self.0
}
}
impl FromInner<OwnedFd> for FileDesc {
//从参数创建FileDesc类型变量
fn from_inner(owned_fd: OwnedFd) -> Self {
Self(owned_fd)
}
}
impl AsFd for FileDesc {
//创建一个引用
fn as_fd(&self) -> BorrowedFd<'_> {
self.0.as_fd()
}
}
impl AsRawFd for FileDesc {
//简化代码
fn as_raw_fd(&self) -> RawFd {
self.0.as_raw_fd()
}
}
impl IntoRawFd for FileDesc {
fn into_raw_fd(self) -> RawFd {
//见OwnedFd::into_raw_fd
self.0.into_raw_fd()
}
}
impl FromRawFd for FileDesc {
//见OwnedFd::from_raw_fd
unsafe fn from_raw_fd(raw_fd: RawFd) -> Self {
Self(FromRawFd::from_raw_fd(raw_fd))
}
}
文件描述符实际上代表了操作系统的资源,是后继各模块分析的一个基础。