RUST IO安全/文件描述符FileDesc/OwnedFd/BorrowedFd/RawFd源代码解析

本文摘自链接: 深入RUST标准库内核 如需要了解更多关于RUST标准库内核的内容,请进入链接,希望能顺便star.

std库文件描述符代码分析

以linux为例,文件系统实际上成为操作系统的所有资源的管理体系的基础设施。因此,将之放在内存管理之后来做分析。
RUST将文件管理的结构分成:

  1. 操作系统文件描述符的封装结构。对于RUST来说,操作系统的文件描述符与堆内存要处理的安全性类似。需要建立类似智能指针的结构完成对其的管理,并纳入到RUST的安全体系内。
  2. 1创建的类型结构主要用于解决安全问题,如果在同一个类型加上文件功能,会乱。因此在1的基础上建立RUST自身的文件描述符类型,用于处理文件的读写等功能
  3. 在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))
    }
}

文件描述符实际上代表了操作系统的资源,是后继各模块分析的一个基础。


版权声明:本文为weixin_45429253原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。