[RustSBI output]
...
filetest_simple
fantastic_text
**************/
Rust user shell
>>
操作系统启动shell
后,用户可以在shell
中通过敲入应用名字来执行应用。
从用户界面上,没看出文件系统的影子
在这里我们运行一下本章的测例 filetest_simple :
>> filetest_simple
file_test passed!
Shell: Process 2 exited with code 0
>>
它会将 Hello, world! 输出到另一个文件 filea,并读取里面的内容确认输出正确。
我们也可以通过命令行工具 cat_filea 来更直观的查看 filea 中的内容:
>> cat_filea
Hello, world!
Shell: Process 2 exited with code 0
>>
软件架构
添加easy-fs
├── easy-fs(新增:从内核中独立出来的一个简单的文件系统 EasyFileSystem 的实现)
│ ├── Cargo.toml
│ └── src
│ ├── bitmap.rs(位图抽象)
│ ├── block_cache.rs(块缓存层,将块设备中的部分块缓存在内存中)
│ ├── block_dev.rs(声明块设备抽象接口 BlockDevice,需要库的使用者提供其实现)
│ ├── efs.rs(实现整个 EasyFileSystem 的磁盘布局)
│ ├── layout.rs(一些保存在磁盘上的数据结构的内存布局)
│ ├── lib.rs(定义的必要信息)
│ └── vfs.rs(提供虚拟文件系统的核心抽象,即索引节点 Inode)
├── easy-fs-fuse(新增:将当前 OS 上的应用可执行文件按照 easy-fs 的格式进行打包)
│ ├── Cargo.toml
│ └── src
│ └── main.rs
改进OS
├── os
│ ├── build.rs
│ ├── Cargo.toml(修改:新增 Qemu 和 K210 两个平台的块设备驱动依赖 crate)
│ ├── Makefile(修改:新增文件系统的构建流程)
│ └── src
│ ├── config.rs(修改:新增访问块设备所需的一些 MMIO 配置)
│ ├── console.rs
│ ├── drivers(修改:新增 Qemu 和 K210 两个平台的块设备驱动)
│ │ ├── block
│ │ │ ├── mod.rs(将不同平台上的块设备全局实例化为 BLOCK_DEVICE 提供给其他模块使用)
│ │ │ ├── sdcard.rs(K210 平台上的 microSD 块设备, Qemu不会用)
│ │ │ └── virtio_blk.rs(Qemu 平台的 virtio-blk 块设备)
│ │ └── mod.rs
改进OS
├── os
│ ├── fs(修改:在文件系统中新增常规文件的支持)
│ │ ├── inode.rs(新增:将 easy-fs 提供的 Inode 抽象封装为内核看到的 OSInode
│ │ 并实现 fs 子模块的 File Trait)
│ ├── loader.rs(移除:应用加载器 loader 子模块,本章开始从文件系统中加载应用)
│ ├── mm
│ │ ├── memory_set.rs(修改:在创建地址空间的时候插入 MMIO 虚拟页面)
│ ├── syscall
│ │ ├── fs.rs(修改:新增 sys_open)
│ │ └── process.rs(修改:sys_exec 改为从文件系统中加载 ELF,并支持命令行参数)
│ ├── task
│ │ ├── mod.rs(修改初始进程 INITPROC 的初始化)
应用角度:
$ hexedit os/src/main.rs
内核角度:
$ cd os ; stat src/main.rs
应用角度:
$ cd os ; ls -la
内核角度:
DirEntry
数组pub struct DirEntry {
name: [u8; NAME_LENGTH_LIMIT + 1],
inode_number: u32,
}
/// 功能:打开一个常规文件,并返回可以访问它的文件描述符。
/// 参数:path 描述要打开的文件的文件名
/// (简单起见,文件系统不需要支持目录,所有的文件都放在根目录 / 下),
/// flags 描述打开文件的标志,具体含义下面给出。
/// 返回值:如果出现了错误则返回 -1,否则返回打开常规文件的文件描述符。
/// 可能的错误原因是:文件不存在。
/// syscall ID:56
fn sys_open(path: &str, flags: u32) -> isize
/// 功能:当前进程关闭一个文件。
/// 参数:fd 表示要关闭的文件的文件描述符。
/// 返回值:如果成功关闭则返回 0 ,否则返回 -1 。
/// 可能的出错原因:传入的文件描述符并不对应一个打开的文件。
/// syscall ID:57
fn sys_close(fd: usize) -> isize
/// 功能:当前进程读取文件。
/// 参数:fd 表示要读取文件的文件描述符。
/// 返回值:如果成功读入buf,则返回 读取的字节数,否则返回 -1 。
/// 可能的出错原因:传入的文件描述符并不对应一个打开的文件。
/// syscall ID:63
sys_read(fd: usize, buf: *const u8, len: usize) -> isize
/// 功能:当前进程写入一个文件。
/// 参数:fd 表示要写入文件的文件描述符。
/// 返回值:如果成功把buf写入,则返回写入字节数 ,否则返回 -1 。
/// 可能的出错原因:传入的文件描述符并不对应一个打开的文件。
/// syscall ID:64
fn sys_write(fd: usize, buf: *const u8, len: usize) -> isize
/// 功能:当前进程等待一个子进程变为僵尸进程,回收其全部资源并收集其返回值。
/// 参数:pid 表示要等待的子进程的进程 ID,如果为 -1 的话表示等待任意一个子进程;
/// exit_code 表示保存子进程返回值的地址,如果这个地址为 0 的话表示不必保存。
/// 返回值:如果要等待的子进程不存在则返回 -1;否则如果要等待的子进程均未结束则返回 -2;
/// 否则返回结束的子进程的进程 ID。
/// syscall ID:260
pub fn sys_waitpid(pid: isize, exit_code: *mut i32) -> isize;
// user/src/bin/filetest_simple.rs
pub fn main() -> i32 {
let test_str = "Hello, world!";
let filea = "filea\0";
// 创建文件filea,返回文件描述符fd(有符号整数)
let fd = open(filea, OpenFlags::CREATE | OpenFlags::WRONLY);
write(fd, test_str.as_bytes()); // 把test_str写入文件中
close(fd); // 关闭文件
let fd = open(filea, OpenFlags::RDONLY); // 只读方式打开文件
let mut buffer = [0u8; 100]; // 100字节的数组缓冲区
let read_len = read(fd, &mut buffer) as usize;// 读取文件内容到buffer中
close(fd); // 关闭文件
}
ROOT_INODE
中DirEntry
组成的数组inode
表示pub struct DirEntry {
name: [u8; NAME_LENGTH_LIMIT + 1],
inode_number: u32,
}
...
let fd = open(filea, OpenFlags::RDONLY);
fd_table
中fd_table
是OSInode
组成的数组pub struct TaskControlBlockInner {
pub fd_table: ... //文件描述符表
pub struct OSInode {//进程管理的inode
readable: bool, writable: bool,
inner: UPSafeCell<OSInodeInner>,
}
pub struct OSInodeInner {
offset: usize, //文件读写的偏移位置
inode: Arc<Inode>,//存储设备inode
}
超级块(SuperBlock)描述文件系统全局信息
pub struct SuperBlock {
magic: u32,
pub total_blocks: u32,
pub inode_bitmap_blocks: u32,
pub inode_area_blocks: u32,
pub data_bitmap_blocks: u32,
pub data_area_blocks: u32,
}
位图(bitmap)描述文件系统全局信息
在 easy-fs 布局中存在两类不同的位图,分别对索引节点和数据块进行管理。
pub struct Bitmap {
start_block_id: usize,
blocks: usize,
}
磁盘索引节点(DiskInode)描述文件信息和数据
pub struct DiskInode {
pub size: u32,
pub direct: [u32; INODE_DIRECT_COUNT],
pub indirect1: u32,
pub indirect2: u32,
type_: DiskInodeType,
}
read_at
和write_at
把文件偏移量和buf长度转换为一系列的数据块编号,并进行通过get_block_cache
数据块的读写。get_block_id
方法体现了 DiskInode 最重要的数据块索引功能,它可以从索引中查到它自身用于保存文件内容的第 block_id 个数据块的块编号,这样后续才能对这个数据块进行访问:数据块与目录项
type DataBlock = [u8; BLOCK_SZ];
pub struct DirEntry {
name: [u8; NAME_LENGTH_LIMIT + 1],
inode_number: u32,
}
块缓存BlockCache
pub const BLOCK_SZ: usize = 512;
pub struct BlockCache {
cache: [u8; BLOCK_SZ], //512 字节数组
block_id: usize, //对应的块编号
//底层块设备的引用,可通过它进行块读写
block_device: Arc<dyn BlockDevice>,
modified: bool, //它有没有被修改过
}
get_block_cache
:取一个编号为 block_id
的块缓存
文件系统初始化
lazy_static! {
pub static ref BLOCK_DEVICE = Arc::new(BlockDeviceImpl::new());
......
lazy_static! {
pub static ref ROOT_INODE: Arc<Inode> = {
let efs = EasyFileSystem::open(BLOCK_DEVICE.clone());
Arc::new(EasyFileSystem::root_inode(&efs))
打开(创建)文件
pub fn sys_open(path: *const u8, flags: u32) -> isize {
//调用open_file函数获得一个OSInode结构的inode
if let Some(inode) = open_file(path.as_str(),
OpenFlags::from_bits(flags).unwrap()) {
let mut inner = task.inner_exclusive_access();
let fd = inner.alloc_fd(); //得到一个空闲的fd_table项的idx,即fd
inner.fd_table[fd] = Some(inode); //把inode填入fd_table[fd]中
fd as isize //返回fd
...
如果失败,会返回 -1
打开(创建)文件
fn open_file(name: &str, flags: OpenFlags) -> Option<Arc<OSInode>> {
......
ROOT_INODE.create(name) //在根目录中创建一个DireEntry<name,inode>
.map(|inode| {//创建进程中fd_table[OSInode]
Arc::new(OSInode::new(
readable,
writable,
inode, ))
})
在根目录ROOT_INODE
中创建一个文件,返回OSInode
打开(查找)文件
fn open_file(name: &str, flags: OpenFlags) -> Option<Arc<OSInode>> {
......
ROOT_INODE.find(name) //在根目录中查找DireEntry<name,inode>
.map(|inode| { //创建进程中fd_table[OSInode]
Arc::new(OSInode::new(
readable,
writable,
inode ))
})
在根目录ROOT_INODE
中找到一个文件,返回OSInode
关闭文件
pub fn sys_close(fd: usize) -> isize {
let task = current_task().unwrap();
let mut inner = task.inner_exclusive_access();
......
inner.fd_table[fd].take();
0
}
sys_close :将进程控制块中的文件描述符表对应的一项改为 None 代表它已经空闲即可,同时这也会导致文件的引用计数减一,当引用计数减少到 0 之后文件所占用的资源就会被自动回收。
基于文件加载应用(ELF可执行文件格式)
pub fn sys_exec(path: *const u8) -> isize {
if let Some(app_inode) = open_file(path.as_str(), ...) {
let all_data = app_inode.read_all();
let task = current_task().unwrap();
task.exec(all_data.as_slice()); 0
} else { -1 }
当获取应用的 ELF 文件数据时,首先调用 open_file
函数,以只读方式打开应用文件并获取它对应的 OSInode
。接下来可以通过 OSInode::read_all
将该文件的数据全部读到一个向量 all_data
中
读写文件
pub fn sys_write(fd: usize, buf: *const u8, len: usize) -> isize {
if let Some(file) = &inner.fd_table[fd] {
file.write(
UserBuffer::new(translated_byte_buffer(token, buf, len))
) as isize
操作系统都是通过文件描述符在当前进程的文件描述符表中找到某个文件,无需关心文件具体的类型。
读写文件
pub fn sys_read(fd: usize, buf: *const u8, len: usize) -> isize {
if let Some(file) = &inner.fd_table[fd] {
file.read(
UserBuffer::new(translated_byte_buffer(token, buf, len))
) as isize
操作系统都是通过文件描述符在当前进程的文件描述符表中找到某个文件,无需关心文件具体的类型。
霸王龙即雷克斯暴龙(Tyrannosaurus Rex) 操作系统
主要的事情就是**实现文件抽象*
**程序设计**
**程序设计**