北科大操作系统Lab6

git checkout master

解释一下lab6的代码原理

在lab6中主要是去实现文件的读写操作,其中最重要的就是文件描述符和缓冲区的使用。在这个实验中,如何快速的、标准的实现sys_read()等操作呢?

首先观察一下fs.rs中的sys_fstat文件会发现一些固定的操作

kernel/src/syscall/fs.rs
pub fn sys_fstat(fd: usize, st: *mut Stat) -> isize {
    debug!(
        "kernel:pid[{}] sys_fstat(fd: {}, st: 0x{:x?})",
        current_task().unwrap().pid.0, fd, st
    );
    let task = current_task().unwrap();
    let inner = task.inner_exclusive_access();
    if fd >= inner.fd_table.len() {
        return -1;
    }
    if let Some(file) = &inner.fd_table[fd] {
        let file = file.clone();
        // release current task TCB manually to avoid multi-borrow
        drop(inner);
        let stat = file.state().unwrap();
        copy_to_virt(&stat, st);
        return 0
    }
    -1
}

阅读文档也会提取出一些操作有

    let task = current_task().unwrap();
    let inner = task.inner_exclusive_access();
    if fd >= inner.fd_table.len() {
        return -1;
    }
    if let Some(file) = &inner.fd_table[fd] {
        let file = file.clone();
        drop(inner);
        return 0
    }
    -1

那么接下来就可以快速的去写各种操作了

sys_write

阅读文档里面

对于 sys_write 和 sys_read,获取当前任务的 inner 之后,各自检查是否可写、可读. 然后将用户态缓冲区翻译成 UserBuffer 后调用文件的 write 和 read 方法.

首先判断一下文件是否可写

if !file.writable() {return -1 ;}

然后就是需要将用户态下的虚拟地址翻译成内核可写的地址。

//把用户传入的虚拟地址区间 [buf, buf + len) 翻译成内核可以访问的物理内存切片。
/* buffers = [
    第一个物理页里的一段,
    第二个物理页里的一段,
    第三个物理页里的一段,
] */
let buffers = translated_byte_buffer((current_user_token()), buf, len) ;

// 把刚才的各段包装成一个UserBuffer
let userbuf = UserBuffer::new(buffers) ; 
return  file.write(userbuf) as isize ;

完整代码

let task = current_task().unwrap() ; 
let inner = task.inner_exclusive_access() ; 
if fd >= inner.fd_table.len() {return -1 ;} 
if let Some(file) = &inner.fd_table[fd] {
    let file = file.clone() ; 
    if !file.writable() {return -1 ;}
    drop(inner);
    let buffers = translated_byte_buffer((current_user_token()), buf, len) ;
    let userbuf = UserBuffer::new(buffers) ; 
    return  file.write(userbuf) as isize ;
}
-1 

sys_read()

如果能自己理解同时加以Ai能理解了sys_write,仿照上面就能写出来sys_read()

pub fn sys_read(fd: usize, buf: *const u8, len: usize) -> isize {
    let task =current_task().unwrap() ; 
    let inner = task.inner_exclusive_access()  ;
    if fd >= inner.fd_table.len()  {
        return -1 ; 
    }
    if let Some(file) = &inner.fd_table[fd] {
        let file = file.clone()  ; 
        if !file.readable() {return -1 ;}
        drop(inner);
        let buffers = translated_byte_buffer(current_user_token(), buf, len); 
        let userbuf = UserBuffer::new(buffers) ; 
        return file.read(userbuf) as isize ; 
    }
    -1 
}

接下来就需要理解一下打开和删除文件的情况,打开和关闭文件实际上是对fd文件描述符指向对象(lab2介绍了是什么)的操作。

用户程序:

int fd = open("a.txt", 0);
read(fd, buf, 100);


内核中:

当前进程
┌──────────────────────┐
│ fd_table              │
│ 0 -> stdin            │
│ 1 -> stdout           │
│ 2 -> stderr           │
│ 3 -> file object ─────┼─────┐
└──────────────────────┘     │
                              ↓
                      ┌──────────────┐
                      │ file object  │
                      │ offset = 0   │
                      │ readable = 1 │
                      │ writable = 0 │
                      │ inode ptr ───┼─────┐
                      └──────────────┘     │
                                           ↓
                                  ┌────────────────┐
                                  │ inode 12        │
                                  │ type = file     │
                                  │ size = 1024     │
                                  │ addrs=[100...]  │
                                  └────────────────┘
                                           ↓
                                  ┌────────────────┐
                                  │ 磁盘数据块       │
                                  │ 文件真正内容     │
                                  └────────────────┘

sys_close

这个好写,先写这个 , 大部分都是板子,最后把文件描述对应的对象置空就行了

pub fn sys_close(fd: usize) -> isize {
    let task = current_task().unwrap(); 
    let mut inner = task.inner_exclusive_access()  ;
    if fd >= inner.fd_table.len()  { return -1;} 
    if inner.fd_table[fd].is_none() {return -1; }
    inner.fd_table[fd] = None ; 
    0 
}

sys_open

文档说:

sys_open 把路径从用户态翻译成 &str后,打开文件. 如果返回 Some(inode),再分配描述符并存入 fd_table,返回 fd;失败返回 -1.
  1. 根据描述把路径从用户态翻译成&str,有一个函数
let path = translated_str(current_user_token(), path) ; 

其实就是实现了

用户虚拟地址 VA
        ↓
根据当前进程页表翻译
        ↓
物理地址 PA   //对应页表
        ↓
逐字节读取
        ↓
组成 String // 对应什么from_raw_slice ? 好像是

? 哥们我好像在前面实验见过你

那flags同理,这些函数确实很难找.

let flags = OpenFlags::from_bits(flags).unwrap() ; 

然后根据其描述,open_file找到inode,分配一个文件描述符指向inode,之后返回fd

if let Some(inode) = open_file(&path, flags) 
{
    
    let task = current_task().unwrap() ; 
    let mut inner = task.inner_exclusive_access() ; 

    // 分配一个fd 
    let fd = inner.alloc_fd() ; 
    inner.fd_table[fd] = Some(inode) ; 
    fd as isize 
}else {-1}

那么实验六的这些操作,就实现了补充。

验收问题:

  1. let buffers = translated_byte_buffer((current_user_token()), buf, len) ;
    let userbuf = UserBuffer::new(buffers) ; 为什么要这么写?
  2. if let Some(inode) = open_file(&path, flags) 根据文件描述符会得到一个Inode,这个的作用是什么,是否会返回一个inode是由什么决定的?
  3. 文件描述符存放在哪里?
posted @ 2026-04-28 16:08  莫枭  阅读(56)  评论(0)    收藏  举报