rCore-Tutorial-v3

February 16, 2025 · View on GitHub

该项目是一个操作系统相关的项目,包含了多个子包,涵盖了内存管理、任务调度、系统调用、设备驱动、网络通信、同步机制、陷阱处理等多个方面的功能。其中一些关键功能和特点包括:

  • 内存管理:初始化内存管理组件,包括堆内存、帧分配器、页表管理等,实现内存的分配、回收、映射等操作。
  • 任务调度:管理操作系统中的任务,包括任务的创建、暂停、恢复、切换等,以及进程的管理和信号处理。
  • 系统调用:提供了多种系统调用功能,如文件操作、进程管理、线程管理、同步机制操作、网络通信等。
  • 设备驱动:涉及多种设备的管理和操作,如字符设备、块设备、网络设备、GPU设备等。
  • 网络通信:处理网络数据和管理网络堆栈,包括TCP和UDP连接的管理、网络中断处理等。
  • 同步机制:提供了条件变量、互斥锁、信号量等同步机制,用于多线程或多任务环境中的资源管理和同步。
  • 陷阱处理:处理内核的陷阱和中断,确保系统能够正确响应和处理各种异常和中断事件。

这个项目通过多个子包和功能函数的协同工作,构建了一个较为完整的操作系统功能体系。

os::sync::semaphore

description: 这个 os::sync::semaphore 包主要用于管理信号量,通过 Semaphore 结构体及其相关方法(downupnew),以及 SemaphoreInner 结构体,实现对共享资源的访问控制。Semaphoredown 方法用于减少信号量计数,up 方法用于增加信号量计数并唤醒等待任务,new 方法用于创建并初始化 Semaphore 实例。该包还依赖一些其他模块的功能来完成其操作。

Semaphore.up

description: up函数是Semaphore结构体的一个方法。其主要功能是增加信号量的计数,并在计数小于等于零时,从等待队列中弹出一个任务进行唤醒。具体步骤包括:首先通过exclusive_access方法获得对inner的独占访问权,然后将inner的计数增加1。如果增加后的计数仍然小于等于零,则从等待队列中弹出一个任务,并调用wakeup_task函数来唤醒该任务,使其准备好执行。

pub fn up(&self) {
        let mut inner = self.inner.exclusive_access();
        inner.count += 1;
        if inner.count <= 0 {
            if let Some(task) = inner.wait_queue.pop_front() {
                wakeup_task(task);
            }
        }
    }

Semaphore::new

description: new函数用于创建并初始化一个Semaphore类型的实例。Semaphore类型用于管理信号量,其核心功能是通过计数值控制对共享资源的访问。入参为res_count,表示信号量的初始计数值。函数内部使用UPIntrFreeCell封装SemaphoreInner结构体,其中count字段存储信号量的计数值,wait_queue是一个双端队列,用于存储等待任务。返回的是一个新的Semaphore实例,提供对信号量内部状态的独占访问。

pub fn new(res_count: usize) -> Self {
        Self {
            inner: unsafe {
                UPIntrFreeCell::new(SemaphoreInner {
                    count: res_count as isize,
                    wait_queue: VecDeque::new(),
                })
            },
        }
    }

Semaphore.down

description: down函数是Semaphore结构体的方法,用于管理信号量的减少。当调用此方法时,首先通过exclusive_access()方法获取对信号量内部状态的独占访问权限,然后将计数器减一。如果计数器小于0,表示没有可用的资源,则将当前任务加入等待队列,并释放对内部状态的访问(通过drop函数)。随后,调用block_current_and_run_next函数来阻塞当前任务,并切换到下一个任务。

pub fn down(&self) {
        let mut inner = self.inner.exclusive_access();
        inner.count -= 1;
        if inner.count < 0 {
            inner.wait_queue.push_back(current_task().unwrap());
            drop(inner);
            block_current_and_run_next();
        }
    }

os::net::udp

description: 该 os::net::udp 包主要用于 UDP 网络通信,提供了创建和管理 UDP 连接的基本功能。其中包含 UDP 结构体,具有目标 IPv4 地址、源端口、目标端口及套接字索引等字段。关键函数有 UDP::new 用于创建新的 UDP 实例,UDP.drop 用于在 UDP 结构体销毁时移除套接字,UDP.read 用于从套接字缓冲区读取数据,UDP.readable 用于检查是否可读,UDP.writable 用于检查是否可写,UDP.write 用于发送 UDP 数据包。该包还依赖一些其他模块的功能。

UDP.readable

description: readable函数的主要功能是检查UDP结构是否可读。在这个函数中,返回值始终为true,意味着UDP结构总是被认为是可读的。

fn readable(&self) -> bool {
        true
    }

UDP.drop

description: drop函数的主要功能是在UDP结构体被销毁时,从SOCKET_TABLE中移除与socket_index相关联的套接字。接收者是UDP结构体,其包含目标IPv4地址、源端口、目标端口及套接字索引等信息。调用的remove_socket函数确保线程安全地移除指定索引的套接字。

fn drop(&mut self) {
        remove_socket(self.socket_index)
    }

UDP.writable

description: writable函数用于检查UDP结构体是否可写。该函数不接受任何参数,直接返回布尔值true,表示UDP实例始终处于可写状态。

fn writable(&self) -> bool {
        true
    }

UDP::new

description: new函数是UDP结构体的构造函数。其主要功能是创建一个新的UDP实例。函数接受以下参数:

  • target: 一个IPv4类型的值,表示目标IP地址。
  • sport: 一个u16类型的值,表示源端口号。
  • dport: 一个u16类型的值,表示目标端口号。

函数通过调用add_socket函数来尝试将新的套接字添加到全局套接字表中,并获取套接字在表中的索引。若添加成功,返回一个包含目标IP、源端口、目标端口及套接字索引的新UDP结构体实例。

pub fn new(target: IPv4, sport: u16, dport: u16) -> Self {
        let index = add_socket(target, sport, dport).expect("can't add socket");

        Self {
            target,
            sport,
            dport,
            socket_index: index,
        }
    }

UDP.read

description: read函数的主要功能是从指定的UDP套接字缓冲区中读取数据到用户提供的缓冲区中。函数通过循环不断尝试从套接字中弹出数据(使用pop_data函数),如果成功获取数据,则将数据分段复制到用户缓冲区的各个部分中,直到完成为止。若没有数据可弹出,函数将调用net_interrupt_handler来处理网络中断。函数返回值为已读取的字节数。

参数:

  • buf: UserBuffer类型,包含多个静态可变字节数组,用于存储读取的数据。
fn read(&self, mut buf: crate::mm::UserBuffer) -> usize {
        loop {
            if let Some(data) = pop_data(self.socket_index) {
                let data_len = data.len();
                let mut left = 0;
                for i in 0..buf.buffers.len() {
                    let buffer_i_len = buf.buffers[i].len().min(data_len - left);

                    buf.buffers[i][..buffer_i_len]
                        .copy_from_slice(&data[left..(left + buffer_i_len)]);

                    left += buffer_i_len;
                    if left == data_len {
                        break;
                    }
                }
                return left;
            } else {
                net_interrupt_handler();
            }
        }
    }

UDP.write

description: write方法属于UDP结构体,用于发送UDP数据包。它接收一个UserBuffer类型的参数,该参数管理多个静态可变字节缓冲区。

功能:write方法从UserBuffer中提取数据,并将其复制到一个临时向量中。然后,它创建一个UDP数据包,使用LOSE_NET_STACK获取的IP和MAC地址等信息进行初始化。目标MAC地址被设置为广播地址。最后,使用NET_DEVICE发送构建好的UDP数据包。

参数:

  • buf: 包含待发送数据的UserBuffer对象。
fn write(&self, buf: crate::mm::UserBuffer) -> usize {
        let lose_net_stack = LOSE_NET_STACK.0.exclusive_access();

        let mut data = vec![0u8; buf.len()];

        let mut left = 0;
        for i in 0..buf.buffers.len() {
            data[left..(left + buf.buffers[i].len())].copy_from_slice(buf.buffers[i]);
            left += buf.buffers[i].len();
        }

        let len = data.len();

        let udp_packet = UDPPacket::new(
            lose_net_stack.ip,
            lose_net_stack.mac,
            self.sport,
            self.target,
            MacAddress::new([0xff, 0xff, 0xff, 0xff, 0xff, 0xff]),
            self.dport,
            len,
            data.as_ref(),
        );
        NET_DEVICE.transmit(&udp_packet.build_data());
        len
    }

os::net

description: 该 os::net 包主要用于处理网络数据和管理网络堆栈。其核心功能包括:

  • 提供 hexdump 函数以十六进制格式输出数据数组内容,用于调试和查看二进制数据。
  • 包含 NetStack::new 函数用于创建并初始化 NetStack 实例,构建其内部数据结构。
  • 具有 net_interrupt_handler 函数用于处理网络中断,确保网络数据包的正确接收和处理。
  • 定义了 NetStack 结构体用于封装和管理网络堆栈数据。
  • 设有全局变量 LOSE_NET_STACK 用于网络栈的管理和操作。
  • 定义了常量 PRELAND_WIDTH 用于某个预设宽度的限制或标准(具体用途不明确)。

net_interrupt_handler

description: net_interrupt_handler 函数用于处理网络中断。它从 NET_DEVICE 接收数据包,并通过 LOSE_NET_STACK 分析数据包类型。对于 ARP 包,会生成回复包并发送。对于 UDP 包,会根据来源 IP 和端口查找套接字,并将数据推入缓冲区。对于 TCP 包,会根据标志进行连接处理或断开处理,并更新套接字的序列号和确认号。此函数确保了网络数据包的正确接收和处理,维护网络通信的顺畅。

pub fn net_interrupt_handler() {
    let mut recv_buf = vec![0u8; 1024];

    let len = NET_DEVICE.receive(&mut recv_buf);

    let packet = LOSE_NET_STACK
        .0
        .exclusive_access()
        .analysis(&recv_buf[..len]);

    // println!("[kernel] receive a packet");
    // hexdump(&recv_buf[..len]);

    match packet {
        Packet::ARP(arp_packet) => {
            let lose_stack = LOSE_NET_STACK.0.exclusive_access();
            let reply_packet = arp_packet
                .reply_packet(lose_stack.ip, lose_stack.mac)
                .expect("can't build reply");
            let reply_data = reply_packet.build_data();
            NET_DEVICE.transmit(&reply_data)
        }

        Packet::UDP(udp_packet) => {
            let target = udp_packet.source_ip;
            let lport = udp_packet.dest_port;
            let rport = udp_packet.source_port;

            if let Some(socket_index) = get_socket(target, lport, rport) {
                push_data(socket_index, udp_packet.data.to_vec());
            }
        }

        Packet::TCP(tcp_packet) => {
            let target = tcp_packet.source_ip;
            let lport = tcp_packet.dest_port;
            let rport = tcp_packet.source_port;
            let flags = tcp_packet.flags;

            if flags.contains(TcpFlags::S) {
                // if it has a port to accept, then response the request
                if check_accept(lport, &tcp_packet).is_some() {
                    let mut reply_packet = tcp_packet.ack();
                    reply_packet.flags = TcpFlags::S | TcpFlags::A;
                    NET_DEVICE.transmit(&reply_packet.build_data());
                }
                return;
            } else if tcp_packet.flags.contains(TcpFlags::F) {
                // tcp disconnected
                let reply_packet = tcp_packet.ack();
                NET_DEVICE.transmit(&reply_packet.build_data());

                let mut end_packet = reply_packet.ack();
                end_packet.flags |= TcpFlags::F;
                NET_DEVICE.transmit(&end_packet.build_data());
            } else if tcp_packet.flags.contains(TcpFlags::A) && tcp_packet.data_len == 0 {
                return;
            }

            if let Some(socket_index) = get_socket(target, lport, rport) {
                push_data(socket_index, tcp_packet.data.to_vec());
                set_s_a_by_index(socket_index, tcp_packet.seq, tcp_packet.ack);
            }
        }
        _ => {}
    }
}

NetStack::new

description: new函数用于创建并初始化一个NetStack实例。它在unsafe块中调用多个构造函数来构建NetStack的内部数据结构。具体来说,它首先通过IPv4::new创建一个IPv4地址,接着通过MacAddress::new创建一个Mac地址,然后将这两个地址传递给LoseStack::new创建一个LoseStack实例。最后,这个LoseStack实例被传递给UPIntrFreeCell::new以创建一个UPIntrFreeCell实例,并最终返回一个NetStack实例。

pub fn new() -> Self {
        unsafe {
            NetStack(UPIntrFreeCell::new(LoseStack::new(
                IPv4::new(10, 0, 2, 15),
                MacAddress::new([0x52, 0x54, 0x00, 0x12, 0x34, 0x56]),
            )))
        }
    }

hexdump

description: hexdump函数的主要功能是以十六进制格式输出给定数据数组(data)的内容。它每次处理16字节的数据,首先输出每个字节的十六进制表示,然后在同一行输出对应的ASCII字符表示。如果字符不可打印,则用点号替代。函数在输出前后分别打印标识符“hexdump”和“hexdump end”。这个函数主要用于调试或查看数据的具体二进制内容。

#[allow(unused)]
pub fn hexdump(data: &[u8]) {
    const PRELAND_WIDTH: usize = 70;
    println!("[kernel] {:-^1$}", " hexdump ", PRELAND_WIDTH);
    for offset in (0..data.len()).step_by(16) {
        print!("[kernel] ");
        for i in 0..16 {
            if offset + i < data.len() {
                print!("{:02x} ", data[offset + i]);
            } else {
                print!("{:02} ", "");
            }
        }

        print!("{:>6}", ' ');

        for i in 0..16 {
            if offset + i < data.len() {
                let c = data[offset + i];
                if c >= 0x20 && c <= 0x7e {
                    print!("{}", c as char);
                } else {
                    print!(".");
                }
            } else {
                print!("{:02} ", "");
            }
        }

        println!("");
    }
    println!("[kernel] {:-^1$}", " hexdump end ", PRELAND_WIDTH);
}

os::drivers::gpu

description: 该 os::drivers::gpu 包主要用于管理和操作 GPU 设备,特别是 VirtIO GPU 设备。它提供了更新游标、刷新缓冲区以及获取帧缓冲区的功能。

关键函数包括:

  • update_cursor:用于更新游标状态,无参无返回值。
  • flush:刷新或清空某些数据或缓冲区的内容,无参无返回值。
  • get_framebuffer:返回帧缓冲区的可变字节数组引用,无参。
  • VirtIOGpuWrapper::new:初始化 VirtIO GPU 设备的封装,创建实例并设置帧缓冲区和光标,返回包含 GPU 和帧缓冲区的 VirtIOGpuWrapper 实例。

关键类型包括:

  • GpuDevice:定义 GPU 设备接口的公共 trait,确保线程安全,包含更新游标、获取帧缓冲区和刷新设备显示输出的方法。
  • VirtIOGpuWrapper:管理 VirtIO GPU 设备封装的结构体,包含 GPU 和帧缓冲区字段,提供相关方法如获取帧缓冲区、刷新 GPU 和更新光标状态。

关键变量包括:

  • BMP_DATA:包含 mouse.bmp 文件二进制数据的静态字节切片,用于设置光标图像。
  • GPU_DEVICE:用于 GPU 设备管理,与图形相关功能紧密关联。
  • VIRTIO7:常量,用于初始化 VirtIOGpu 对象,设置基础地址。

该包通过提供对 GPU 设备的基本操作和管理功能,帮助开发人员与 GPU 设备进行交互,并依赖一些相关的模块和类型。

get_framebuffer

description: get_framebuffer函数的主要功能是返回一个可变的字节数组引用,表示帧缓冲区。参数中没有输入参数,函数的用途是提供对帧缓冲区的直接访问,以便进行图像或数据的读取和修改。

fn get_framebuffer(&self) -> &mut [u8];

flush

description: flush函数的主要功能是刷新或清空某些数据或缓冲区的内容。该函数没有参数,也没有返回值。它通常用于确保所有待处理的数据被写入或处理完毕。

fn flush(&self);

VirtIOGpuWrapper.update_cursor

description: update_cursor函数是VirtIOGpuWrapper结构体的一个方法。该方法的主要功能和用途是更新光标状态或位置,但具体实现细节在当前描述中并未提供。由于它没有参数和返回值,可以推测它可能在内部更新某些状态或进行某些操作,而不需要外部输入或对外输出。

fn update_cursor(&self) {}

VirtIOGpuWrapper.flush

description: flush函数的主要功能是刷新VirtIOGpuWrapper结构体中的GPU。该方法通过获取GPU的独占访问权限,然后调用其flush方法,并使用unwrap处理可能的错误。

fn flush(&self) {
        self.gpu.exclusive_access().flush().unwrap();
    }

VirtIOGpuWrapper::new

description: VirtIOGpuWrapper的new方法用于初始化VirtIO GPU设备的封装。这个方法通过创建VirtIOGpu实例并设置帧缓冲区和光标来实现。它使用VIRTIO7常量作为基础地址,创建VirtIOGpu对象并设置帧缓冲区,随后将BMP_DATA中的数据转换为光标图像。最终,方法返回一个包含GPU和帧缓冲区的VirtIOGpuWrapper实例。

pub fn new() -> Self {
        unsafe {
            let mut virtio =
                VirtIOGpu::<VirtioHal>::new(&mut *(VIRTIO7 as *mut VirtIOHeader)).unwrap();

            let fbuffer = virtio.setup_framebuffer().unwrap();
            let len = fbuffer.len();
            let ptr = fbuffer.as_mut_ptr();
            let fb = core::slice::from_raw_parts_mut(ptr, len);

            let bmp = Bmp::<Rgb888>::from_slice(BMP_DATA).unwrap();
            let raw = bmp.as_raw();
            let mut b = Vec::new();
            for i in raw.image_data().chunks(3) {
                let mut v = i.to_vec();
                b.append(&mut v);
                if i == [255, 255, 255] {
                    b.push(0x0)
                } else {
                    b.push(0xff)
                }
            }
            virtio.setup_cursor(b.as_slice(), 50, 50, 50, 50).unwrap();

            Self {
                gpu: UPIntrFreeCell::new(virtio),
                fb,
            }
        }
    }

update_cursor

description: update_cursor函数的主要功能是更新游标。该函数没有参数,也没有返回值。由于没有相关的函数、类型或变量描述,这表明它可能是一个独立的函数,专注于更新游标的内部状态。

fn update_cursor(&self);

VirtIOGpuWrapper.get_framebuffer

description: get_framebuffer方法的主要功能是返回VirtIOGpuWrapper结构体中的帧缓冲区的可变字节数组切片。此方法通过不安全代码块获取帧缓冲区的指针,并使用from_raw_parts_mut函数将其转换为可变切片。

接收者:

  • VirtIOGpuWrapper:用于管理VirtIO GPU设备的封装,包括帧缓冲区等字段。

该方法不接收参数,返回值是帧缓冲区的可变切片,允许对帧缓冲区进行修改。

fn get_framebuffer(&self) -> &mut [u8] {
        unsafe {
            let ptr = self.fb.as_ptr() as *const _ as *mut u8;
            core::slice::from_raw_parts_mut(ptr, self.fb.len())
        }
    }

os::fs

description: 该 os::fs 包主要用于文件操作,强调多线程安全性。定义了 File trait 作为文件操作的基本接口,包含检查文件是否可读(readable)、可写(writable),从文件读取数据到 UserBufferread),将 UserBuffer 中的数据写入文件(write)等方法。还定义了用于管理多个字节缓冲区的 UserBuffer 结构体。通过提供线程安全的文件操作接口,增强了应用在多线程环境下的文件管理能力。

writable

description: writable方法用于检查File类型是否可写。

fn writable(&self) -> bool;

read

description: read函数是File trait的一部分,用于从文件中读取数据到UserBuffer中。参数buf是一个UserBuffer类型,用于存储读取的数据。返回值是读取的字节数。该函数支持多线程安全,适用于需要并发访问的文件操作场景。

fn read(&self, buf: UserBuffer) -> usize;

write

description: write方法属于File trait的一部分,主要功能是将UserBuffer中的数据写入文件。该方法接收一个参数:

  • buf: 类型为UserBuffer,表示要写入文件的数据缓冲区。

返回值为一个usize类型,表示实际写入的字节数。File trait确保其实现是线程安全的,适用于多线程环境。

fn write(&self, buf: UserBuffer) -> usize;

readable

description: readable函数是File trait中的一个方法,用于检查文件是否可读。该方法不需要参数,返回一个布尔值,表示文件当前是否具备读取权限。File trait提供了一组用于文件操作的接口,确保线程安全,适用于需要多线程处理的场景。

fn readable(&self) -> bool;

os::syscall::net

description: 该 os::syscall::net 包主要用于处理网络通信的系统调用,提供了网络连接的基本操作,包括:

  • sys_accept 函数用于接受一个 TCP 连接,输出端口信息并进行相关处理。
  • sys_listen 函数用于在指定端口上开始监听,寻找空位置存储端口信息并进行相关操作。
  • sys_connect 函数用于为 UDP 网络通信创建一个新的连接。

sys_connect

description: sys_connect函数的主要功能是为UDP网络通信创建一个新的连接。函数接受三个参数:

  • raddr: 目标IPv4地址,类型为32位无符号整数。
  • lport: 本地源端口,类型为16位无符号整数。
  • rport: 远程目标端口,类型为16位无符号整数。

在函数内部,它首先获取当前进程的控制块,然后为该进程分配一个新的文件描述符。接着,函数通过目标IPv4地址和端口号创建一个新的UDP实例,并将其存储在进程的文件描述符表中。最终,返回分配的文件描述符作为isize类型。

// just support udp
pub fn sys_connect(raddr: u32, lport: u16, rport: u16) -> isize {
    let process = current_process();
    let mut inner = process.inner_exclusive_access();
    let fd = inner.alloc_fd();
    let udp_node = UDP::new(IPv4::from_u32(raddr), lport, rport);
    inner.fd_table[fd] = Some(Arc::new(udp_node));
    fd as isize
}

sys_accept

description: sys_accept函数的主要功能是接受一个TCP连接。它首先通过println输出正在接受的端口信息。然后调用current_task获取当前任务,并通过accept函数将该任务安排到指定端口的调度中。接着,函数进入一个循环,通过net_interrupt_handler手动处理网络中断,并使用port_acceptable检查端口是否仍可接受请求。如果不可接受,则跳出循环。最后,通过current_trap_cx获取当前任务的陷阱上下文,并返回特定寄存器的值。参数port_index表示监听端口的索引。

// accept a tcp connection
pub fn sys_accept(port_index: usize) -> isize {
    println!("accepting port {}", port_index);

    let task = current_task().unwrap();
    accept(port_index, task);
    // block_current_and_run_next();

    // NOTICE: There does not have interrupt handler, just call it munually.
    loop {
        net_interrupt_handler();

        if !port_acceptable(port_index) {
            break;
        }
    }

    let cx = current_trap_cx();
    cx.x[10] as isize
}

sys_listen

description: sys_listen函数用于在指定端口上开始监听。它接受一个无符号16位整数类型的参数port,表示要监听的端口号。函数通过调用listen函数在LISTEN_TABLE中寻找空位置存储新的端口信息,并返回端口的索引。

如果成功监听,函数会获取当前进程并申请一个新的文件描述符,将新的PortFd实例与该描述符关联,并返回端口索引作为isize类型;如果监听失败,则返回-1。注意,返回的值是端口索引而不是文件描述符。

// listen a port
pub fn sys_listen(port: u16) -> isize {
    match listen(port) {
        Some(port_index) => {
            let process = current_process();
            let mut inner = process.inner_exclusive_access();
            let fd = inner.alloc_fd();
            let port_fd = PortFd::new(port_index);
            inner.fd_table[fd] = Some(Arc::new(port_fd));

            // NOTICE: this return the port index, not the fd
            port_index as isize
        }
        None => -1,
    }
}

os::mm::frame_allocator

description: 该 os::mm::frame_allocator 包主要用于内存帧的分配和管理,提供了多种函数和类型来实现内存帧的分配、测试、初始化和释放。具体包括:

  • 函数
    • frame_alloc:分配一个内存帧并返回可选的 FrameTracker 实例。
    • frame_allocator_test:测试内存帧分配器的行为。
    • FrameTracker::new:初始化 FrameTracker 实例并清理页。
    • StackFrameAllocator.init:初始化 StackFrameAllocator 结构体的字段,设定内存页帧范围。
    • frame_alloc_more:分配多个内存帧并返回 FrameTracker 实例向量。
    • frame_dealloc:释放指定物理页号对应的内存帧。
    • frame_allocator_alloc_more_test:测试内存帧的批量分配和释放。
    • init_frame_allocator:初始化帧分配器,设置内存起始和结束地址。
    • FrameTracker.drop:释放 FrameTracker 结构体中物理页号对应的内存帧。
    • FrameTracker.fmt:将 FrameTracker 结构体中的物理页号格式化为十六进制字符串。
    • StackFrameAllocator.alloc:为 StackFrameAllocator 分配一个物理页号。
    • StackFrameAllocator.alloc_more:为 StackFrameAllocator 分配更多物理页号。
    • StackFrameAllocator.dealloc:回收 StackFrameAllocator 的物理页号。
    • ekernel:初始化帧分配器,确定内存起始和结束位置。
    • new:创建并返回一个类型的实例(具体类型未明确)。
  • 类型
    • StackFrameAllocator:管理物理页号的分配和回收。
    • FrameTracker:管理物理页号,提供内存帧的管理和操作。
    • FrameAllocator:管理物理内存页的分配和释放的 trait。
    • FrameAllocatorImpl:定义为 StackFrameAllocator,用于管理物理页号的分配和回收。
  • 变量
    • FRAME_ALLOCATOR:全局变量,用于管理内存帧的分配和释放。

该包通过提供线程安全的内存管理功能和各种测试机制,确保内存帧的高效分配和释放。

StackFrameAllocator.init

description: init函数用于初始化StackFrameAllocator结构体中的current和end字段。该函数接收两个参数,分别是表示物理页号的PhysPageNum类型的l和r。函数将l的物理页号值赋给current,将r的物理页号值赋给end,从而设定一个内存页帧的范围。

pub fn init(&mut self, l: PhysPageNum, r: PhysPageNum) {
        self.current = l.0;
        self.end = r.0;
        // println!("last {} Physical Frames.", self.end - self.current);
    }

frame_dealloc

description: frame_dealloc函数的主要功能是释放指定的物理页号(PhysPageNum)对应的内存帧。它通过调用FRAME_ALLOCATOR的exclusive_access()方法来确保线程安全,然后执行dealloc操作以释放内存帧。参数ppn表示要释放的物理页号。该函数利用FRAME_ALLOCATOR全局变量来管理内存帧的分配和释放。

pub fn frame_dealloc(ppn: PhysPageNum) {
    FRAME_ALLOCATOR.exclusive_access().dealloc(ppn);
}

alloc

description: alloc函数的主要功能是尝试分配一个物理页号,并返回一个Option<PhysPageNum>类型的结果。返回值为Option类型,表示可能会返回一个有效的PhysPageNum,也可能返回None,以指示分配失败。PhysPageNum类型用于表示物理页号,采用usize类型存储,并提供了一些方法来操作和管理物理页号。

fn alloc(&mut self) -> Option<PhysPageNum>;

FrameTracker.drop

description: drop函数的主要功能是释放FrameTracker结构体中的物理页号(ppn)对应的内存帧。这个函数通过调用frame_dealloc函数来实现内存释放的操作。接收者FrameTracker包含一个名为ppn的字段,表示物理页号。drop函数在FrameTracker的生命周期结束时被调用,用于确保内存的正确释放。

fn drop(&mut self) {
        frame_dealloc(self.ppn);
    }

StackFrameAllocator::new

description: new函数是StackFrameAllocator结构体的构造函数,用于初始化一个新的实例。它将current和end字段设置为0,并初始化recycled字段为空的向量。这个结构体用于管理物理页号的分配和回收。

fn new() -> Self {
        Self {
            current: 0,
            end: 0,
            recycled: Vec::new(),
        }
    }

ekernel

description: ekernel函数主要用于初始化帧分配器。该函数通过调用外部C函数ekernel来获取内核的结束地址,并将其转换为物理地址。然后,它使用方法计算物理地址的页面编号,以确定帧分配器的内存起始和结束位置。这个过程有助于管理内存帧的分配和释放。

fn ekernel();

StackFrameAllocator.dealloc

description: dealloc函数主要用于回收物理页号。其作用是将给定的物理页号(ppn)放入回收向量(recycled)中,以便后续重新分配。函数首先检查ppn的有效性:如果ppn大于等于current或者已在recycled中存在,程序将崩溃并提示该页号尚未分配。参数ppn表示待回收的物理页号。接收者StackFrameAllocator是一个结构体,负责管理物理页号的分配和回收。

fn dealloc(&mut self, ppn: PhysPageNum) {
        let ppn = ppn.0;
        // validity check
        if ppn >= self.current || self.recycled.iter().any(|&v| v == ppn) {
            panic!("Frame ppn={:#x} has not been allocated!", ppn);
        }
        // recycle
        self.recycled.push(ppn);
    }

frame_allocator_alloc_more_test

description: frame_allocator_alloc_more_test函数的主要功能是测试内存帧的分配和释放。该函数首先创建一个FrameTracker类型的空向量v,然后调用frame_alloc_more函数尝试分配5个内存帧,并将这些帧打印输出。接着,函数将这些帧扩展添加到向量v中并清空该向量,再次分配5个内存帧并输出,最后通过drop函数释放向量v。函数结束时打印一条测试通过的信息。

#[allow(unused)]
pub fn frame_allocator_alloc_more_test() {
    let mut v: Vec<FrameTracker> = Vec::new();
    let frames = frame_alloc_more(5).unwrap();
    for frame in &frames {
        println!("{:?}", frame);
    }
    v.extend(frames);
    v.clear();
    let frames = frame_alloc_more(5).unwrap();
    for frame in &frames {
        println!("{:?}", frame);
    }
    drop(v);
    println!("frame_allocator_test passed!");
}

StackFrameAllocator.alloc

description: alloc函数的主要功能是为StackFrameAllocator分配一个物理页号。该函数会首先尝试从回收列表recycled中弹出一个元素并返回。如果回收列表为空且当前页号current已经达到end,则返回None,表示没有更多页可分配。如果还有剩余页,则增加current并返回新的页号。

参数:

  • 无参数

返回值:

  • Option:可能的物理页号,如果没有可用页,则返回None。
fn alloc(&mut self) -> Option<PhysPageNum> {
        if let Some(ppn) = self.recycled.pop() {
            Some(ppn.into())
        } else if self.current == self.end {
            None
        } else {
            self.current += 1;
            Some((self.current - 1).into())
        }
    }

dealloc

description: dealloc函数用于释放与给定物理页号(PhysPageNum)相关联的资源或内存。函数的主要功能是管理物理内存的释放过程。入参包括:

  • ppn: 表示物理页号的PhysPageNum类型,用于指定需要释放的物理页资源。
fn dealloc(&mut self, ppn: PhysPageNum);

FrameTracker::new

description: new函数的主要功能是初始化一个FrameTracker实例。该函数接收一个PhysPageNum类型的参数ppn,表示物理页号。函数首先通过调用ppn的get_bytes_array方法获取一个长度为4096字节的可变字节数组,并将其全部清零,以实现页清理。最后,函数返回一个包含ppn的FrameTracker实例。

pub fn new(ppn: PhysPageNum) -> Self {
        // page cleaning
        let bytes_array = ppn.get_bytes_array();
        for i in bytes_array {
            *i = 0;
        }
        Self { ppn }
    }

frame_allocator_test

description: frame_allocator_test 函数的主要功能是测试内存帧分配器的行为。它首先创建一个空的 FrameTracker 向量 v。接着,在第一次循环中,它尝试分配五个内存帧,将每个成功分配的帧打印出来并添加到向量中。然后,清空向量 v。在第二次循环中,重复分配和打印帧的过程,再次将这些帧添加到向量中。最后,释放向量 v 中的所有帧,并打印测试通过的信息。这一过程验证了内存帧分配和释放的正确性。

#[allow(unused)]
pub fn frame_allocator_test() {
    let mut v: Vec<FrameTracker> = Vec::new();
    for i in 0..5 {
        let frame = frame_alloc().unwrap();
        println!("{:?}", frame);
        v.push(frame);
    }
    v.clear();
    for i in 0..5 {
        let frame = frame_alloc().unwrap();
        println!("{:?}", frame);
        v.push(frame);
    }
    drop(v);
    println!("frame_allocator_test passed!");
}

new

description: new函数的主要功能是创建并返回一个类型的实例。由于没有参数,通常用于初始化默认状态的实例。

fn new() -> Self;

frame_alloc

description: frame_alloc函数的主要功能是分配一个内存帧,并返回一个可选的FrameTracker实例。它通过访问全局变量FRAME_ALLOCATOR来实现内存帧的分配。FRAME_ALLOCATOR提供了线程安全的内存管理功能,通过exclusive_access()方法获取独占访问权,然后调用alloc()方法尝试分配一个内存帧。如果分配成功,使用FrameTracker::new方法初始化一个FrameTracker实例并返回。返回值为Option类型,表示可能返回无效的结果。

pub fn frame_alloc() -> Option<FrameTracker> {
    FRAME_ALLOCATOR
        .exclusive_access()
        .alloc()
        .map(FrameTracker::new)
}

alloc_more

description: alloc_more函数的主要功能是尝试分配更多的物理页,并返回一个包含物理页号的可选向量。
入参:

  • pages: 指定需要分配的页数,类型为usize。

出参:

  • Option<Vec>: 如果分配成功,返回一个包含分配得到的物理页号的向量;如果失败,返回None。
fn alloc_more(&mut self, pages: usize) -> Option<Vec<PhysPageNum>>;

frame_alloc_more

description: frame_alloc_more函数的主要功能是从全局变量FRAME_ALLOCATOR中分配多个内存帧,并返回一个包含这些帧的FrameTracker实例向量。函数接收一个参数num,表示需要分配的帧数量。通过FRAME_ALLOCATOR的exclusive_access()方法,函数调用alloc_more方法尝试分配指定数量的内存帧,并通过map函数将分配的帧转换为FrameTracker实例集合。返回值为Option类型,表示可能为空的FrameTracker向量。

pub fn frame_alloc_more(num: usize) -> Option<Vec<FrameTracker>> {
    FRAME_ALLOCATOR
        .exclusive_access()
        .alloc_more(num)
        .map(|x| x.iter().map(|&t| FrameTracker::new(t)).collect())
}

StackFrameAllocator.alloc_more

description: alloc_more函数的主要功能是为StackFrameAllocator分配更多的物理页号。其逻辑为:如果当前页号加上请求的页数超过了结束页号,则返回None,表示无法分配;否则,更新当前页号,并创建一个向量,包含从当前页号减去1到请求页数的范围内的页号。最终返回这些页号的向量。入参:pages表示请求的页数。出参:返回一个包含分配的物理页号的向量,若无法分配则返回None。

fn alloc_more(&mut self, pages: usize) -> Option<Vec<PhysPageNum>> {
        if self.current + pages >= self.end {
            None
        } else {
            self.current += pages;
            let arr: Vec<usize> = (1..pages + 1).collect();
            let v = arr.iter().map(|x| (self.current - x).into()).collect();
            Some(v)
        }
    }

init_frame_allocator

description: init_frame_allocator函数的主要功能是初始化帧分配器。它通过访问全局变量FRAME_ALLOCATOR的独占访问接口,设置内存的起始和结束地址,以便管理内存帧的分配和释放。

参数:

  • 无参数。

在函数内部,通过调用外部C函数ekernel来获取内核的结束地址,并使用PhysAddr::from将其转换为物理地址。接着,使用ceilfloor方法计算物理地址的页面编号,以确定帧分配器的内存起始和结束位置。MEMORY_END常量定义了内存的结束地址。

pub fn init_frame_allocator() {
    extern "C" {
        fn ekernel();
    }
    FRAME_ALLOCATOR.exclusive_access().init(
        PhysAddr::from(ekernel as usize).ceil(),
        PhysAddr::from(MEMORY_END).floor(),
    );
}

FrameTracker.fmt

description: fmt函数用于将FrameTracker结构体中的物理页号(ppn)格式化为十六进制字符串,并将其写入给定的Formatter中。该函数的入参是一个可变的Formatter引用,用于接收格式化的输出。函数返回一个fmt::Result类型,表示格式化操作的结果。

fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        f.write_fmt(format_args!("FrameTracker:PPN={:#x}", self.ppn.0))
    }

os::mm::address

description: 该 os::mm::address 包主要用于内存管理中的地址操作,包括物理地址和虚拟地址的计算、转换、格式化输出,以及范围迭代等功能。具体内容如下:

  • 函数:提供了多种对物理地址(PhysAddr)、物理页号(PhysPageNum)、虚拟地址(VirtAddr)、虚拟页号(VirtPageNum)等的操作函数,如计算页面编号、检查地址对齐、获取地址引用、格式化输出等,还包括创建范围实例(SimpleRange)和范围迭代器(SimpleRangeIterator)的函数。
  • 类型
    • PhysAddr:表示物理地址的结构体,支持多种操作和特性。
    • PhysPageNum:用于表示物理页号的结构体,提供了相关管理和操作方法。
    • SimpleRange:定义一个范围,包含左右边界。
    • SimpleRangeIterator:用于在特定范围内进行迭代的结构体。
    • VirtAddr:表示虚拟地址的结构体,具有多种功能。
    • VirtPageNum:用于表示虚拟页号的结构体。
  • 变量:包含了一些用于定义地址宽度、页号宽度等的常量,如 PA_WIDTH_SV39PPN_WIDTH_SV39VA_WIDTH_SV39VPN_WIDTH_SV39,以及与页面大小相关的常量 PAGE_SIZE
  • 依赖:该包依赖了一些核心库的特性和功能。

PhysAddr.page_offset

description: page_offset函数的主要功能是计算页面内的偏移量。它通过对PhysAddr类型的实例进行按位与操作来实现。

入参:

  • 无需额外参数,因为该函数是PhysAddr结构体的方法,直接操作其内部数据。

出参:

  • 返回一个usize类型的值,表示页面内的具体偏移量。

PAGE_SIZE常量用于确定页面的大小,在此函数中帮助计算偏移量。

pub fn page_offset(&self) -> usize {
        self.0 & (PAGE_SIZE - 1)
    }

PhysAddr.aligned

description: aligned方法用于检查PhysAddr实例的页面偏移量是否为0。
入参:

  • 无(此方法是PhysAddr的一个实例方法)

出参:

  • 布尔值:如果页面偏移量为0,则返回true,否则返回false。
pub fn aligned(&self) -> bool {
        self.page_offset() == 0
    }

SimpleRange.into_iter

description: into_iter方法用于将SimpleRange转换为一个SimpleRangeIterator,以便在指定的范围内进行迭代。

  • 该方法没有参数,直接对SimpleRange结构体调用。
  • 返回值是一个SimpleRangeIterator实例,这个实例通过SimpleRangeIterator::new函数创建,包含范围的起始值和结束值。

该方法的主要用途是为SimpleRange提供迭代功能,使其能够在范围内逐步访问每个元素。

fn into_iter(self) -> Self::IntoIter {
        SimpleRangeIterator::new(self.l, self.r)
    }

SimpleRange.get_start

description: get_start方法用于获取SimpleRange结构体中左边界的值。

  • 接收者:SimpleRange是一个泛型结构体,包含两个字段:l和r,分别表示范围的左右边界。类型T需要实现StepByOne、Copy、PartialEq、PartialOrd和Debug这些特征。
  • 出参:返回SimpleRange结构体的左边界值l。
pub fn get_start(&self) -> T {
        self.l
    }

PhysAddr.floor

description: floor方法的主要功能是计算并返回物理地址所属的页面编号。它通过将物理地址的值除以页面大小常量PAGE_SIZE来实现这一点。

接收者:

  • PhysAddr: 表示物理地址的结构体。

相关类型:

  • PhysPageNum: 表示物理页面编号的结构体。

相关变量:

  • PAGE_SIZE: 常量,表示页面大小,为4096字节。用于内存管理中确定页面大小。
pub fn floor(&self) -> PhysPageNum {
        PhysPageNum(self.0 / PAGE_SIZE)
    }

PhysPageNum.fmt

description: PhysPageNum的fmt方法用于将PhysPageNum结构体的值格式化为字符串。此方法的主要功能是将PhysPageNum中的数值以十六进制格式输出,并在前面加上"PPN:"的标识。参数f是一个Formatter类型的可变引用,用于写入格式化后的字符串。返回值是格式化操作的结果,类型为fmt::Result,表示操作是否成功。

fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        f.write_fmt(format_args!("PPN:{:#x}", self.0))
    }

SimpleRangeIterator::new

description: new函数的主要功能是创建一个SimpleRangeIterator的实例。它接受两个参数,分别是起始值和结束值,并将它们赋值给SimpleRangeIterator结构体的current和end字段。这个函数用于初始化迭代器,使其能够在指定范围内进行迭代。

pub fn new(l: T, r: T) -> Self {
        Self { current: l, end: r }
    }

VirtPageNum.step

description: step方法的主要功能是将VirtPageNum结构体中的第一个字段自增1。该方法没有参数,也没有返回值。它直接操作结构体的内部数据,将其值加1。

fn step(&mut self) {
        self.0 += 1;
    }

VirtAddr.fmt

description: fmt函数的主要功能是为VirtAddr类型的实例提供格式化输出。它将实例的数值以十六进制格式输出,并在前面加上"VA:"。入参包括:

  • self: VirtAddr类型的实例。
  • f: Formatter类型的可变引用,用于输出格式化后的字符串。 出参为fmt::Result,表示格式化操作的结果状态。
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        f.write_fmt(format_args!("VA:{:#x}", self.0))
    }

VirtAddr.page_offset

description: page_offset函数用于计算VirtAddr结构体中地址的页面内偏移量。其功能是通过与PAGE_SIZE减去1的结果进行按位与操作,提取出地址在一个页面内的偏移部分。入参是一个VirtAddr类型的实例,出参为偏移量的大小,以字节为单位。PAGE_SIZE是一个常量,表示页面的大小,为4096字节。

pub fn page_offset(&self) -> usize {
        self.0 & (PAGE_SIZE - 1)
    }

PhysAddr.get_ref

description: get_ref函数的主要功能是从PhysAddr结构体中获取一个静态引用。此函数使用了Rust语言中的unsafe代码块,将PhysAddr内的地址转换为指向类型T的指针,并尝试获取该指针的引用。由于使用了unsafe代码块,这意味着开发者需要确保指针的安全性以避免潜在的运行时错误。

pub fn get_ref<T>(&self) -> &'static T {
        unsafe { (self.0 as *const T).as_ref().unwrap() }
    }

PhysAddr.get_mut

description: get_mut函数用于从PhysAddr结构体中获取一个可变的静态引用。函数通过将PhysAddr的内部值转换为一个可变指针,并尝试将其转换为可变引用。如果成功,将返回该引用。由于使用了unsafe块,这意味着调用者需要确保指针的有效性和安全性。

pub fn get_mut<T>(&self) -> &'static mut T {
        unsafe { (self.0 as *mut T).as_mut().unwrap() }
    }

PhysPageNum.get_pte_array

description: get_pte_array函数的主要功能是返回一个可变的页表项数组。该函数属于PhysPageNum结构体的一个方法。它通过将PhysPageNum类型转换为物理地址(PhysAddr)来实现。然后,使用unsafe代码块,调用from_raw_parts_mut函数从原始指针创建一个包含512个PageTableEntry的可变切片。返回值是一个静态的可变PageTableEntry数组,表示页表项的集合。

pub fn get_pte_array(&self) -> &'static mut [PageTableEntry] {
        let pa: PhysAddr = (*self).into();
        unsafe { core::slice::from_raw_parts_mut(pa.0 as *mut PageTableEntry, 512) }
    }

SimpleRange::new

description: new函数用于创建一个SimpleRange的实例。它接受两个参数,start和end,代表范围的起始和结束边界。函数首先检查start是否小于或等于end,以确保范围的有效性。如果检查通过,函数将返回一个包含这两个边界的SimpleRange实例。参数start和end需要是实现了StepByOne、Copy、PartialEq、PartialOrd和Debug特征的类型。

pub fn new(start: T, end: T) -> Self {
        assert!(start <= end, "start {:?} > end {:?}!", start, end);
        Self { l: start, r: end }
    }

SimpleRangeIterator.next

description: next函数的主要功能是生成一个迭代器,用于遍历SimpleRangeIterator结构体内从current到end的范围。函数会检查current是否等于end,如果是,则返回None表示迭代结束;否则,将current的当前值存储到临时变量t中,随后调用step方法更新current,并返回Some(t)表示当前的值。返回值是Option类型,包含迭代器的当前项(Item类型)。

fn next(&mut self) -> Option<Self::Item> {
        if self.current == self.end {
            None
        } else {
            let t = self.current;
            self.current.step();
            Some(t)
        }
    }

PhysPageNum.get_bytes_array

description: get_bytes_array函数的主要功能是从PhysPageNum结构体中获取一个静态可变的字节数组引用。该函数使用了Rust中的unsafe代码块,通过将物理地址转换为原始指针并创建一个长度为4096字节的可变切片来实现。入参为接收者PhysPageNum自身,无其他额外参数。返回值是一个静态可变的字节数组引用,用于内存管理中的操作。

pub fn get_bytes_array(&self) -> &'static mut [u8] {
        let pa: PhysAddr = (*self).into();
        unsafe { core::slice::from_raw_parts_mut(pa.0 as *mut u8, 4096) }
    }

VirtPageNum.indexes

description: indexes函数用于从VirtPageNum结构体中提取三个索引值。它通过对结构体中的usize类型数据进行位运算,将其分解为三个索引值。入参:无,函数通过self引用VirtPageNum实例。出参:返回一个包含三个usize类型元素的数组,这些元素是通过逐步右移和按位与操作从原始值中提取的。

pub fn indexes(&self) -> [usize; 3] {
        let mut vpn = self.0;
        let mut idx = [0usize; 3];
        for i in (0..3).rev() {
            idx[i] = vpn & 511;
            vpn >>= 9;
        }
        idx
    }

VirtPageNum::from

description: from函数的主要功能是将一个虚拟地址(VirtAddr)转换为虚拟页号(VirtPageNum)。在执行转换前,它会先检查该虚拟地址的页面偏移量是否为0,以确保地址对齐。如果偏移量不为0,则会触发断言错误。然后,函数通过调用floor方法来进行转换。参数说明:v是VirtAddr类型的虚拟地址,表示要转换的地址。返回值是VirtPageNum类型,表示转换后的虚拟页号。

fn from(v: VirtAddr) -> Self {
        assert_eq!(v.page_offset(), 0);
        v.floor()
    }

PhysAddr::from

description: from函数用于创建PhysAddr类型的实例。它通过对传入的usize值进行按位与操作来截取物理地址的特定位数,这个位数由常量PA_WIDTH_SV39决定。参数v是一个usize类型的值,表示待转换的物理地址。返回值是一个PhysAddr类型的实例,包含截取后的物理地址。

fn from(v: usize) -> Self {
        Self(v & ((1 << PA_WIDTH_SV39) - 1))
    }

PhysAddr.fmt

description: fmt 方法用于格式化并输出 PhysAddr 类型的值。它将 PhysAddr 的内部值以十六进制格式输出,前缀为“PA:”。
参数:

  • f: Formatter 的可变引用,用于输出格式化后的字符串。
    返回值:
  • fmt::Result: 表示格式化操作的结果,成功与否。
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        f.write_fmt(format_args!("PA:{:#x}", self.0))
    }

from::from

description: from函数的主要功能是将一个VirtAddr类型的虚拟地址转换为其自身类型。具体来说,它检查虚拟地址的数值是否大于等于一个特定的阈值(由VA_WIDTH_SV39常量决定)。如果是,则对该地址进行位操作,以确保其在指定的虚拟地址宽度范围内;否则,返回原始地址。入参为VirtAddr类型的虚拟地址v,返回值为处理后的虚拟地址。

fn from(v: VirtAddr) -> Self {
        if v.0 >= (1 << (VA_WIDTH_SV39 - 1)) {
            v.0 | (!((1 << VA_WIDTH_SV39) - 1))
        } else {
            v.0
        }
    }

VirtAddr.ceil

description: ceil函数的主要功能是计算VirtAddr对应的虚拟页号上限。如果内部数据为0,则返回虚拟页号0;否则,计算公式为:(self.0 - 1 + PAGE_SIZE) / PAGE_SIZE。入参为VirtAddr类型,无需额外输入。出参为VirtPageNum类型,表示虚拟页号。PAGE_SIZE常量用于确定每个页面的大小(4096字节),在计算时帮助确定物理页面编号的上限。

pub fn ceil(&self) -> VirtPageNum {
        if self.0 == 0 {
            VirtPageNum(0)
        } else {
            VirtPageNum((self.0 - 1 + PAGE_SIZE) / PAGE_SIZE)
        }
    }

step

description: step函数主要用于执行某种操作或过程的一个步骤。这个函数没有参数,也没有返回值。它可能用于在某个上下文中推进到下一阶段或状态。

fn step(&mut self);

PhysPageNum::from

description: from函数用于将一个usize类型的值转换为PhysPageNum类型的实例。它通过位操作提取与常量PPN_WIDTH_SV39宽度相对应的位,以确保PhysPageNum只包含有效的物理页号部分。参数v是一个无符号整数,表示需要转换的值。该函数返回一个PhysPageNum实例,其中存储了经过处理的物理页号。

fn from(v: usize) -> Self {
        Self(v & ((1 << PPN_WIDTH_SV39) - 1))
    }

VirtAddr::from

description: from函数用于将一个usize类型的值转换为VirtAddr类型的实例。通过位操作,它确保地址在VA_WIDTH_SV39定义的宽度内有效。参数v表示输入的usize值,返回值是一个处理后的VirtAddr实例。此函数的主要目的是在创建VirtAddr时,确保地址符合虚拟地址的宽度限制。

fn from(v: usize) -> Self {
        Self(v & ((1 << VA_WIDTH_SV39) - 1))
    }

PhysPageNum.get_mut

description: get_mut函数的主要功能是从PhysPageNum结构体中获取一个可变的物理地址引用。该函数的接收者是PhysPageNum类型,该类型表示物理页号。函数通过将PhysPageNum转换为PhysAddr,然后调用PhysAddr的get_mut方法来实现这一功能。

此函数没有参数,返回值是一个指向类型T的静态可变引用。返回的引用允许对物理地址的内容进行修改。

pub fn get_mut<T>(&self) -> &'static mut T {
        let pa: PhysAddr = (*self).into();
        pa.get_mut()
    }

VirtAddr.aligned

description: aligned方法用于检查VirtAddr结构体实例的地址是否对齐。这个方法没有参数,它通过调用page_offset方法来判断地址的偏移量是否为0。如果偏移量为0,则表示地址是对齐的,方法返回true,否则返回false。

pub fn aligned(&self) -> bool {
        self.page_offset() == 0
    }

VirtAddr.floor

description: floor方法用于计算VirtAddr实例的虚拟页号。它通过将内部数据(一个usize值)除以常量PAGE_SIZE(页面大小,4096字节)来实现这一点,结果用VirtPageNum结构体表示。返回值是一个VirtPageNum类型,表示虚拟地址所属的页号。

pub fn floor(&self) -> VirtPageNum {
        VirtPageNum(self.0 / PAGE_SIZE)
    }

PhysAddr.ceil

description: ceil方法用于计算一个PhysAddr类型的物理地址所对应的物理页面编号的上限。具体来说,如果PhysAddr的内部值为0,则返回PhysPageNum(0)。否则,计算方法是将内部值减一后加上PAGE_SIZE,再除以PAGE_SIZE,从而得到对应的PhysPageNum。该方法主要用于内存管理,帮助确定一个物理地址落在哪个页面上。

pub fn ceil(&self) -> PhysPageNum {
        if self.0 == 0 {
            PhysPageNum(0)
        } else {
            PhysPageNum((self.0 - 1 + PAGE_SIZE) / PAGE_SIZE)
        }
    }

PhysPageNum.step

description: step方法的主要功能是对PhysPageNum结构体的第一个字段进行递增操作。接收者是PhysPageNum类型,它是一个包含单个usize类型字段的结构体。该方法通过增加该字段的值实现步进操作。

fn step(&mut self) {
        self.0 += 1;
    }

SimpleRange.get_end

description: get_end函数的主要功能是返回SimpleRange结构体中的r字段的值。
入参:无。
出参:返回SimpleRange中r字段的值。

pub fn get_end(&self) -> T {
        self.r
    }

VirtPageNum.fmt

description: fmt函数用于格式化VirtPageNum结构体的输出。它将VirtPageNum的值以十六进制格式(带有前缀"VPN:")写入到提供的Formatter中。入参为Formatter类型的可变引用。返回值为fmt::Result类型,指示格式化操作的结果。

fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        f.write_fmt(format_args!("VPN:{:#x}", self.0))
    }

os::drivers::input

description: 该 os::drivers::input 包主要用于处理输入设备的行为和事件,提供了线程安全的操作机制。其核心功能包括:

  • 定义了 InputDevice trait,用于规定输入设备的行为,如读取事件、处理中断请求和检查设备是否为空,确保实现此 trait 的类型可在多线程环境中安全传递和访问。
  • 包含了一系列函数,如 handle_irq 用于处理中断请求,read_event 用于从输入设备中读取事件,is_empty 用于检查对象是否为空,VirtIOInputWrapper::new 用于创建并初始化 VirtIOInputWrapper 实例来管理虚拟输入设备的事件同步和处理。
  • 定义了多种类型,如 VirtIOInputInner 用于表示虚拟输入设备的内部信息,VirtIOInputWrapper 用于管理虚拟输入设备的事件同步和处理。
  • 包含了一些设备变量,如 KEYBOARD_DEVICE 用于处理键盘相关事件和中断,MOUSE_DEVICE 用于处理鼠标相关事件和中断,以及一些常量如 VIRTIO5VIRTIO6

该包通过这些功能和类型,为多种输入设备在多线程环境中的安全使用提供了支持。

VirtIOInputWrapper.is_empty

description: is_empty方法用于检查VirtIOInputWrapper结构体中的事件队列是否为空。

入参:

出参:

  • 布尔值,表示事件队列是否为空。
fn is_empty(&self) -> bool {
        self.inner.exclusive_access().events.is_empty()
    }

read_event

description: read_event函数的主要功能是从一个实现了InputDevice trait的输入设备中读取事件,并返回一个u64类型的值。这个函数不需要任何参数,因为它是一个方法,通常在一个InputDevice类型的实例上调用。trait的设计确保该函数能够在多线程环境中安全地使用。

fn read_event(&self) -> u64;

VirtIOInputWrapper.read_event

description: read_event方法的主要功能是从VirtIOInputWrapper的事件队列中读取事件。它在一个循环中执行以下操作:

  1. 获取对inner的独占访问权。
  2. 检查inner的事件队列是否有事件,如果有,则返回该事件。
  3. 如果没有事件,则调用condvar的wait_no_sched方法,使当前任务等待,并释放inner的访问权。
  4. 使用schedule函数切换任务上下文,以便其他任务可以运行。

返回值为u64类型的事件。

fn read_event(&self) -> u64 {
        loop {
            let mut inner = self.inner.exclusive_access();
            if let Some(event) = inner.events.pop_front() {
                return event;
            } else {
                let task_cx_ptr = self.condvar.wait_no_sched();
                drop(inner);
                schedule(task_cx_ptr);
            }
        }
    }

is_empty

description: is_empty函数用于检查一个对象是否为空。入参:无。出参:布尔值,表示对象是否为空。

fn is_empty(&self) -> bool;

VirtIOInputWrapper.handle_irq

description: handle_irq函数的主要功能是处理虚拟输入设备的中断请求。它通过独占会话访问inner对象,首先确认中断,然后处理所有待处理事件。对于每个事件,通过计算其类型、代码和数值的组合结果,将其添加到事件队列中。如果处理了一个或多个事件,则通过条件变量唤醒等待的线程。

fn handle_irq(&self) {
        let mut count = 0;
        let mut result = 0;
        self.inner.exclusive_session(|inner| {
            inner.virtio_input.ack_interrupt();
            while let Some(event) = inner.virtio_input.pop_pending_event() {
                count += 1;
                result = (event.event_type as u64) << 48
                    | (event.code as u64) << 32
                    | (event.value) as u64;
                inner.events.push_back(result);
            }
        });
        if count > 0 {
            self.condvar.signal();
        };
    }

VirtIOInputWrapper::new

description: VirtIOInputWrapper的new函数用于创建并初始化一个VirtIOInputWrapper的实例。其主要功能是管理虚拟输入设备的事件同步和处理,通过封装VirtIOInputInner和条件变量来实现。函数的参数是一个地址(addr),用于初始化VirtIO输入设备。返回的对象包含两个主要部分:inner,使用UPIntrFreeCell封装,提供独占访问;condvar,用于线程同步。

pub fn new(addr: usize) -> Self {
        let inner = VirtIOInputInner {
            virtio_input: unsafe {
                VirtIOInput::<VirtioHal>::new(&mut *(addr as *mut VirtIOHeader)).unwrap()
            },
            events: VecDeque::new(),
        };
        Self {
            inner: unsafe { UPIntrFreeCell::new(inner) },
            condvar: Condvar::new(),
        }
    }

handle_irq

description: handle_irq方法是InputDevice trait中的一个方法,主要用于处理中断请求。该方法没有参数,通常用于在输入设备上处理和响应中断事件。其设计确保实现此trait的类型可以在多线程环境中安全地被传递和访问。

fn handle_irq(&self);

os::drivers::bus

description: 这个包位于 os::drivers::bus 路径,目前没有提供任何公开的函数、类型或全局变量的描述,所以无法明确其具体功能和用途。通常可能涉及到与总线驱动相关的功能,但具体情况需更多信息才能确定。

os::sync

description: 该 os::sync 包可能用于操作系统相关的同步机制,但由于提供的数据中未定义任何公开的函数、类型或变量,所以无法提供其具体功能或用途的详细信息,需要更多内容来进一步确认。

os::syscall::fs

description: 该 os::syscall::fs 包主要用于提供文件系统相关的系统调用功能,包含多个函数来处理文件描述符的操作,如读写、打开、关闭、复制和创建管道等。具体函数功能如下:

  • sys_write:将用户提供的数据缓冲区写入指定文件描述符,返回写入字节数,出错返回 -1。
  • sys_read:从文件描述符中读取数据到缓冲区,返回读取字节数,失败返回 -1。
  • sys_close:关闭指定文件描述符,成功返回 0,失败返回 -1。
  • sys_pipe:创建一个双向通信的管道,并返回其读写端的文件描述符,成功返回 0。
  • sys_open:打开指定路径的文件并返回文件描述符,失败返回 -1。
  • sys_dup:复制给定的文件描述符,返回新的文件描述符,失败返回 -1。

sys_dup

description: sys_dup函数的主要功能是复制给定文件描述符fd,返回一个新的文件描述符。该函数首先获取当前进程的控制块,并通过独占访问的方式获取其内部状态。如果给定的fd超出文件描述符表的范围或对应位置为空,则返回-1。否则,分配一个新的文件描述符,将原描述符对应的资源克隆到新的描述符位置,并返回新的文件描述符。

pub fn sys_dup(fd: usize) -> isize {
    let process = current_process();
    let mut inner = process.inner_exclusive_access();
    if fd >= inner.fd_table.len() {
        return -1;
    }
    if inner.fd_table[fd].is_none() {
        return -1;
    }
    let new_fd = inner.alloc_fd();
    inner.fd_table[new_fd] = Some(Arc::clone(inner.fd_table[fd].as_ref().unwrap()));
    new_fd as isize
}

sys_write

description: sys_write函数的主要功能是将用户提供的缓冲区数据写入指定的文件描述符。该函数首先获取当前用户的令牌和进程控制块。然后,它检查文件描述符是否在进程的文件描述符表中有效。如果文件存在且可写,则创建一个UserBuffer实例,将数据写入文件中。函数参数包括:

  • fd: 文件描述符,指定要写入的文件。
  • buf: 指向待写入数据的内存指针。
  • len: 要写入数据的长度。

返回值为写入的字节数,若出错则返回-1。

pub fn sys_write(fd: usize, buf: *const u8, len: usize) -> isize {
    let token = current_user_token();
    let process = current_process();
    let inner = process.inner_exclusive_access();
    if fd >= inner.fd_table.len() {
        return -1;
    }
    if let Some(file) = &inner.fd_table[fd] {
        if !file.writable() {
            return -1;
        }
        let file = file.clone();
        // release current task TCB manually to avoid multi-borrow
        drop(inner);
        file.write(UserBuffer::new(translated_byte_buffer(token, buf, len))) as isize
    } else {
        -1
    }
}

sys_pipe

description: sys_pipe函数的主要功能是创建一个双向通信的管道,并将其读写端的文件描述符存储到给定的指针中。具体步骤包括:获取当前进程和用户令牌,通过make_pipe创建管道,分配文件描述符,将管道的读写端分配到文件描述符表中,最后使用translated_refmut将文件描述符写入指针。函数返回值为0,表示成功。

pub fn sys_pipe(pipe: *mut usize) -> isize {
    let process = current_process();
    let token = current_user_token();
    let mut inner = process.inner_exclusive_access();
    let (pipe_read, pipe_write) = make_pipe();
    let read_fd = inner.alloc_fd();
    inner.fd_table[read_fd] = Some(pipe_read);
    let write_fd = inner.alloc_fd();
    inner.fd_table[write_fd] = Some(pipe_write);
    *translated_refmut(token, pipe) = read_fd;
    *translated_refmut(token, unsafe { pipe.add(1) }) = write_fd;
    0
}

sys_open

description: sys_open函数的主要功能是打开文件并返回文件描述符。它首先获取当前进程和用户令牌,然后将给定路径翻译成内核空间的字符串。如果成功打开文件,则为当前进程分配一个文件描述符,并将文件信息存储在文件描述符表中,最后返回文件描述符。如果打开文件失败,则返回-1。

入参:

  • path: 指向u8类型数据的指针,表示文件路径。
  • flags: u32类型,表示文件打开的标志。
pub fn sys_open(path: *const u8, flags: u32) -> isize {
    let process = current_process();
    let token = current_user_token();
    let path = translated_str(token, path);
    if let Some(inode) = open_file(path.as_str(), OpenFlags::from_bits(flags).unwrap()) {
        let mut inner = process.inner_exclusive_access();
        let fd = inner.alloc_fd();
        inner.fd_table[fd] = Some(inode);
        fd as isize
    } else {
        -1
    }
}

sys_close

description: sys_close函数的主要功能是关闭文件描述符。它首先获取当前进程的控制块,然后获取进程的内部访问权限。函数检查给定的文件描述符fd是否在文件描述符表的范围内,并且检查该位置是否有文件描述符存在。如果任何一个检查失败,函数返回-1,表示关闭失败。如果检查通过,它将文件描述符从表中移除并返回0,表示关闭成功。参数fd表示需要关闭的文件描述符。

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

sys_read

description: sys_read函数用于从文件描述符中读取数据。其主要功能是:

  • 检查所提供的文件描述符fd是否在进程的文件描述符表范围内。
  • 如果文件描述符有效,则检查该文件是否可读。
  • 如果文件可读,则通过转换用户缓冲区(由token, buf和len参数指定)为物理地址,将数据从文件中读取到该缓冲区。
  • 返回读取的字节数;如果读取失败,返回-1。

参数意义:

  • fd: 文件描述符,用于标识要读取的文件。
  • buf: 指向缓冲区的指针,用于存储读取的数据。
  • len: 缓冲区的长度,表示可以读取的最大字节数。
pub fn sys_read(fd: usize, buf: *const u8, len: usize) -> isize {
    let token = current_user_token();
    let process = current_process();
    let inner = process.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;
        }
        // release current task TCB manually to avoid multi-borrow
        drop(inner);
        file.read(UserBuffer::new(translated_byte_buffer(token, buf, len))) as isize
    } else {
        -1
    }
}

os::net::port_table

description: 这个 os::net::port_table 包主要用于管理网络监听端口和处理 TCP 连接请求,以增强系统的网络通信能力。

其包含的关键函数有:

  • port_acceptable:检查指定索引的监听端口是否可接受请求。
  • PortFd::new:创建一个新的 PortFd 实例,用于管理与端口相关的文件描述符。
  • accept_connection:处理 TCP 连接请求,为传入的 TCP 数据包创建新的 TCP 套接字。
  • listen:在指定端口上开始监听,并返回该端口在 LISTEN_TABLE 中的索引。
  • accept:接受请求,将指定监听端口设置为可接收状态,并安排任务。
  • check_accept:检查指定端口是否可以接受请求,并处理连接。

关键类型有:

  • Port:用于表示端口的信息,包括端口号、是否可接收及调度信息。
  • PortFd:用于管理与端口相关的文件描述符,包括写入、读取、检查可读写状态以及对象销毁时的清理操作。

此外,还有一个全局变量 LISTEN_TABLE,用于管理监听端口的表格。

PortFd.readable

description: readable方法用于检查PortFd是否可读。此方法没有参数,始终返回false,表示PortFd当前不可读。

fn readable(&self) -> bool {
        false
    }

accept_connection

description: accept_connection函数用于处理TCP连接请求。它的主要功能是为传入的TCP数据包创建一个新的TCP套接字,并将其关联到任务的文件描述符表中。

参数:

  • _port: u16类型,表示未使用的端口号。
  • tcp_packet: 是对TCPPacket的引用,包含了TCP数据包的信息。
  • task: 是一个TaskControlBlock的Arc类型的实例,表示任务控制块。

该函数通过获取任务的进程,分配文件描述符,创建TCP套接字,并将文件描述符写入任务的陷阱上下文。

pub fn accept_connection(_port: u16, tcp_packet: &TCPPacket, task: Arc<TaskControlBlock>) {
    let process = task.process.upgrade().unwrap();
    let mut inner = process.inner_exclusive_access();
    let fd = inner.alloc_fd();

    let tcp_socket = TCP::new(
        tcp_packet.source_ip,
        tcp_packet.dest_port,
        tcp_packet.source_port,
        tcp_packet.seq,
        tcp_packet.ack,
    );

    inner.fd_table[fd] = Some(Arc::new(tcp_socket));

    let cx = task.inner_exclusive_access().get_trap_cx();
    cx.x[10] = fd;
}

PortFd.read

description: read函数的主要功能是读取数据。它是PortFd结构体的一个方法,接受一个UserBuffer类型的参数。UserBuffer是一个结构体,用于管理多个静态可变字节缓冲区,便于数据的批量处理和存储。该函数目前返回一个固定值0。

fn read(&self, _buf: crate::mm::UserBuffer) -> usize {
        0
    }

accept

description: accept函数的主要功能是接受请求。它通过索引访问全局变量LISTEN_TABLE中的特定监听端口,并将该端口设置为可接收状态。函数首先获取LISTEN_TABLE的独占访问权,然后检查给定的索引是否在表的范围内,并确认对应的监听端口存在。接着,它将监听端口的receivable属性设置为true,并将task参数中的任务安排到该端口的schedule属性中。参数listen_index表示监听端口的索引,task是一个任务控制块的引用。

// can accept request
pub fn accept(listen_index: usize, task: Arc<TaskControlBlock>) {
    let mut listen_table = LISTEN_TABLE.exclusive_access();
    assert!(listen_index < listen_table.len());
    let listen_port = listen_table[listen_index].as_mut();
    assert!(listen_port.is_some());
    let listen_port = listen_port.unwrap();
    listen_port.receivable = true;
    listen_port.schedule = Some(task);
}

listen

description: listen函数的主要功能是在指定的端口上开始监听,并返回该端口在LISTEN_TABLE中的索引。该函数接收一个端口号(16位无符号整数)作为参数,并在LISTEN_TABLE中寻找一个空位置以存储新的端口信息。如果找不到空位置,它会将新的监听端口附加到表的末尾。返回值是该端口在LISTEN_TABLE中的索引,封装在一个可选类型中。Port类型用于存储端口信息,包括端口号、是否可接收及调度信息。

pub fn listen(port: u16) -> Option<usize> {
    let mut listen_table = LISTEN_TABLE.exclusive_access();
    let mut index = usize::MAX;
    for i in 0..listen_table.len() {
        if listen_table[i].is_none() {
            index = i;
            break;
        }
    }

    let listen_port = Port {
        port,
        receivable: false,
        schedule: None,
    };

    if index == usize::MAX {
        listen_table.push(Some(listen_port));
        Some(listen_table.len() - 1)
    } else {
        listen_table[index] = Some(listen_port);
        Some(index)
    }
}

PortFd::new

description: new函数的主要功能是创建一个新的PortFd实例。该函数接收一个参数:

  • port_index:表示端口索引的usize类型参数

返回值为一个PortFd类型的实例,其中包含提供的端口索引。PortFd是一个结构体,用于在fd_table中存储,并在关闭应用程序时删除监听表。

pub fn new(port_index: usize) -> Self {
        PortFd(port_index)
    }

PortFd.drop

description: drop方法的主要功能是,当PortFd对象被销毁时,将LISTEN_TABLE中的对应端口设置为None,从而删除监听表中的记录。该方法的接收者是PortFd结构体,PortFd用于存储在fd_table中,并在关闭应用程序时删除监听表。

fn drop(&mut self) {
        LISTEN_TABLE.exclusive_access()[self.0] = None
    }

port_acceptable

description: port_acceptable函数用于检查指定索引的监听端口是否可接受请求。函数接收一个参数:

  • listen_index:类型为usize,表示监听表中的索引。

函数首先通过exclusive_access方法获取LISTEN_TABLE的独占访问权,并使用assert确保listen_index在表的有效范围内。然后获取listen_table中对应索引的端口,并检查该端口的receivable属性是否为true,若是,则返回true,否则返回false

pub fn port_acceptable(listen_index: usize) -> bool {
    let mut listen_table = LISTEN_TABLE.exclusive_access();
    assert!(listen_index < listen_table.len());

    let listen_port = listen_table[listen_index].as_mut();
    listen_port.map_or(false, |x| x.receivable)
}

check_accept

description: check_accept函数用于检查指定端口是否可以接受请求。它通过访问全局变量LISTEN_TABLE来管理监听端口的状态。函数首先在LISTEN_TABLE中筛选出与给定端口相匹配且可接收的端口。如果没有找到匹配的端口,则返回None。如果找到,函数会处理该端口的连接请求,调用accept_connection函数来处理TCP连接,并将该端口的接收状态设置为不可接收。参数包括port(端口号)和tcp_packet(TCP数据包的引用)。

// check whether it can accept request
pub fn check_accept(port: u16, tcp_packet: &TCPPacket) -> Option<()> {
    LISTEN_TABLE.exclusive_session(|listen_table| {
        let mut listen_ports: Vec<&mut Option<Port>> = listen_table
            .iter_mut()
            .filter(|x| match x {
                Some(t) => t.port == port && t.receivable == true,
                None => false,
            })
            .collect();
        if listen_ports.len() == 0 {
            None
        } else {
            let listen_port = listen_ports[0].as_mut().unwrap();
            let task = listen_port.schedule.clone().unwrap();
            // wakeup_task(Arc::clone(&listen_port.schedule.clone().unwrap()));
            listen_port.schedule = None;
            listen_port.receivable = false;

            accept_connection(port, tcp_packet, task);
            Some(())
        }
    })
}

PortFd.write

description: write函数是PortFd结构体的方法,用于写入数据。其参数为UserBuffer类型的_buf,代表一个包含多个静态可变字节缓冲区的集合。该函数目前返回0,表示写入操作的结果。

fn write(&self, _buf: crate::mm::UserBuffer) -> usize {
        0
    }

PortFd.writable

description: writable方法用于PortFd结构体。其主要功能是返回一个布尔值false,表示PortFd当前不可写。这个方法没有参数,也不进行任何复杂操作,仅仅是返回false。

fn writable(&self) -> bool {
        false
    }

os::fs::inode

description: 该 os::fs::inode 包主要用于管理文件系统中的索引节点,提供了文件的创建、读写操作和权限管理等功能。具体包括:

  • 函数
    • OSInode::new:创建并初始化一个新的 OSInode 实例。
    • open_file:根据指定文件名和标志打开或创建文件,并返回一个 OSInode 的引用计数指针。
    • list_apps:列出应用程序并在控制台输出。
    • OpenFlags.read_write:判断 OpenFlags 对象的可读性和可写性。
    • OSInode.read_all:从 OSInode 对象中读取所有数据并返回一个字节向量。
    • OSInode.read:从 OSInode 中读取数据并存储到用户提供的缓冲区中。
    • OSInode.readable:检查 OSInode 是否可读。
    • OSInode.writable:检查 OSInode 是否可写。
    • OSInode.write:将数据从 UserBuffer 写入到 OSInode 的内部存储中。
  • 类型
    • OSInode:表示文件系统中的索引节点,包含可读性、可写性和内部数据结构。
    • OSInodeInner:表示文件系统中的索引节点,包含偏移量和指向 InodeArc 指针。
    • OpenFlags:类型功能和用途未具体描述。
  • 变量
    • CREATE:用于在文件打开操作中指示是否需要创建文件。
    • RDONLY:表示文件以只读模式打开。
    • ROOT_INODE:用于文件系统操作,如列出应用程序和打开文件。
    • TRUNC:用于在打开文件时清空文件内容。
    • WRONLY:用于表示写权限。
  • 依赖项:包含了多个与文件操作、内存管理等相关的模块。

OSInode.writable

description: writable方法用于检查OSInode结构体的writable字段的值,以确定该结构体实例是否可写。它不需要任何参数,返回一个布尔值,表示该实例当前是否具有可写权限。

fn writable(&self) -> bool {
        self.writable
    }

OSInode::new

description: new函数用于创建并初始化一个OSInode实例。该函数接收三个参数:readable和writable分别表示OSInode是否可读和可写,类型为布尔值;inode是一个Arc类型的引用计数指针,用于共享Inode对象。函数内部使用UPIntrFreeCell::new函数将OSInodeInner封装在RefCell中,以管理内部数据的可变性。最终返回一个包含这些属性的新OSInode实例。

pub fn new(readable: bool, writable: bool, inode: Arc<Inode>) -> Self {
        Self {
            readable,
            writable,
            inner: unsafe { UPIntrFreeCell::new(OSInodeInner { offset: 0, inode }) },
        }
    }

OSInode.readable

description: readable方法用于检查OSInode结构体中的readable属性的值。

入参:

出参:

  • 返回一个布尔值,表示OSInode是否可读。
fn readable(&self) -> bool {
        self.readable
    }

OSInode.read

description: read函数的主要功能是从OSInode中读取数据并将其存储到用户提供的缓冲区中。它通过逐个遍历UserBuffer中的缓冲区切片,将数据从OSInode的偏移量位置读取到这些切片中,直到无法再读取为止。函数返回总共读取的字节数。

入参:

  • buf: 一个UserBuffer类型,用于存储读取的数据。UserBuffer包含多个缓冲区切片,便于批量操作。

出参:

  • usize: 总共读取的字节数。
fn read(&self, mut buf: UserBuffer) -> usize {
        let mut inner = self.inner.exclusive_access();
        let mut total_read_size = 0usize;
        for slice in buf.buffers.iter_mut() {
            let read_size = inner.inode.read_at(inner.offset, *slice);
            if read_size == 0 {
                break;
            }
            inner.offset += read_size;
            total_read_size += read_size;
        }
        total_read_size
    }

list_apps

description: list_apps函数的主要功能是列出应用程序。它通过调用ROOT_INODE的ls()方法获取应用程序列表,并使用println宏在控制台输出这些应用程序的名称。该函数在输出应用程序列表之前和之后分别打印固定格式的字符串“/**** APPS ****”和“**************/”,以便于在控制台中分隔显示。参数和返回值:该函数没有参数,也不返回任何值。它主要用于展示ROOT_INODE中列出的应用程序。

pub fn list_apps() {
    println!("/**** APPS ****");
    for app in ROOT_INODE.ls() {
        println!("{}", app);
    }
    println!("**************/")
}

OSInode.write

description: write函数的主要功能是将数据从UserBuffer写入到OSInode的内部存储中。该函数遍历UserBuffer中的每个缓冲区片段,将其写入到OSInode的指定位置,并更新写入偏移量。参数buf表示需要写入的数据缓冲区,类型为UserBuffer,包含多个字节数组。函数返回值为写入的总字节数。

fn write(&self, buf: UserBuffer) -> usize {
        let mut inner = self.inner.exclusive_access();
        let mut total_write_size = 0usize;
        for slice in buf.buffers.iter() {
            let write_size = inner.inode.write_at(inner.offset, *slice);
            assert_eq!(write_size, slice.len());
            inner.offset += write_size;
            total_write_size += write_size;
        }
        total_write_size
    }

OSInode.read_all

description: read_all函数的主要功能是从OSInode对象中读取所有数据并返回一个字节向量。它通过循环不断从内部inode读取数据到缓冲区,然后将这些数据扩展到一个字节向量中,直到读取完成。出参为一个包含所有读取数据的字节向量。

pub fn read_all(&self) -> Vec<u8> {
        let mut inner = self.inner.exclusive_access();
        let mut buffer = [0u8; 512];
        let mut v: Vec<u8> = Vec::new();
        loop {
            let len = inner.inode.read_at(inner.offset, &mut buffer);
            if len == 0 {
                break;
            }
            inner.offset += len;
            v.extend_from_slice(&buffer[..len]);
        }
        v
    }

OpenFlags.read_write

description: read_write方法用于确定OpenFlags对象的可读性和可写性。根据对象的状态,该方法返回一个元组,指示对象是否可读和可写。如果对象为空,它被视为可读但不可写,返回(true, false);如果对象包含WRONLY(表示写权限),则返回(false, true),表示对象是可写的但不可读;否则,返回(true, true),表示对象既可读又可写。

/// Do not check validity for simplicity
    /// Return (readable, writable)
    pub fn read_write(&self) -> (bool, bool) {
        if self.is_empty() {
            (true, false)
        } else if self.contains(Self::WRONLY) {
            (false, true)
        } else {
            (true, true)
        }
    }

open_file

description: open_file函数的主要功能是根据指定的名称和标志打开或创建文件。它返回一个Option类型的OSInode引用计数指针。

入参:

  • name: 文件名称,类型为字符串引用。
  • flags: 文件打开标志,类型为OpenFlags。

函数首先通过标志确定文件的可读和可写性。如果标志包含CREATE,则尝试查找文件;若文件存在则清除其内容,否则创建新文件。如果标志不含CREATE,则查找文件并根据标志决定是否清空文件内容。最终返回OSInode实例。

pub fn open_file(name: &str, flags: OpenFlags) -> Option<Arc<OSInode>> {
    let (readable, writable) = flags.read_write();
    if flags.contains(OpenFlags::CREATE) {
        if let Some(inode) = ROOT_INODE.find(name) {
            // clear size
            inode.clear();
            Some(Arc::new(OSInode::new(readable, writable, inode)))
        } else {
            // create file
            ROOT_INODE
                .create(name)
                .map(|inode| Arc::new(OSInode::new(readable, writable, inode)))
        }
    } else {
        ROOT_INODE.find(name).map(|inode| {
            if flags.contains(OpenFlags::TRUNC) {
                inode.clear();
            }
            Arc::new(OSInode::new(readable, writable, inode))
        })
    }
}

os::task::id

description: 该 os::task::id 包的主要用途是管理和分配任务和进程的唯一标识符,以及用户和内核栈的内存管理。其核心功能包括:

  • 提供创建、管理和分配任务用户资源和线程 ID 的功能,如 TaskUserRes 结构体及相关函数。
  • 实现内核栈的分配、管理和资源释放,如 KernelStack 结构体及相关函数。
  • 管理和分配唯一标识符,如 RecycleAllocator 结构体及相关函数。
  • 分配新的进程 ID 并管理其生命周期,如 PidHandle 结构体及相关函数。
  • 包含一些关键变量,如 IDLE_PID 用于标识空闲进程的进程 ID,KSTACK_ALLOCATOR 用于管理内核栈分配,PID_ALLOCATOR 用于管理进程 ID 的分配和释放。

TaskUserRes.dealloc_tid

description: dealloc_tid函数的主要功能是释放与TaskUserRes结构体中的线程ID(tid)相关联的资源。该函数通过提升process字段的弱引用来获取ProcessControlBlock,并以独占访问的方式获取其内部状态,然后调用process_inner的dealloc_tid方法来释放资源。此函数没有参数和返回值。

pub fn dealloc_tid(&self) {
        let process = self.process.upgrade().unwrap();
        let mut process_inner = process.inner_exclusive_access();
        process_inner.dealloc_tid(self.tid);
    }

RecycleAllocator.dealloc

description: dealloc函数用于在RecycleAllocator中回收指定的唯一标识符。函数会先检查id是否小于当前最大分配的标识符,并且确保该id尚未被回收。如果通过检查,id将被添加到recycled向量中,以便将来重新使用。

入参:

  • id: 需要回收的唯一标识符。
pub fn dealloc(&mut self, id: usize) {
        assert!(id < self.current);
        assert!(
            !self.recycled.iter().any(|i| *i == id),
            "id {} has been deallocated!",
            id
        );
        self.recycled.push(id);
    }

TaskUserRes.ustack_base

description: ustack_base函数用于返回TaskUserRes结构体中的ustack_base字段的值。

入参:无

出参:

  • usize: 表示TaskUserRes实例的用户栈基址。
pub fn ustack_base(&self) -> usize {
        self.ustack_base
    }

RecycleAllocator.alloc

description: alloc函数用于在RecycleAllocator结构体中分配一个新的唯一标识符。其主要功能是通过检查recycled向量中是否有可重用的标识符,如果有则弹出并返回该标识符;如果没有,则增加current字段的值并返回新的标识符。入参没有特别标明,返回值为分配的唯一标识符。

pub fn alloc(&mut self) -> usize {
        if let Some(id) = self.recycled.pop() {
            id
        } else {
            self.current += 1;
            self.current - 1
        }
    }

kernel_stack_position

description: kernel_stack_position函数用于计算内核空间中某个内核栈的底部和顶部位置。它接收一个参数kstack_id,表示内核栈的标识符。函数内部根据TRAMPOLINE常量和内核栈大小KERNEL_STACK_SIZE以及页面大小PAGE_SIZE计算出栈的顶部位置top,然后通过减去内核栈大小得到栈的底部位置bottom。最终返回一个元组,包含栈的底部和顶部位置。

/// Return (bottom, top) of a kernel stack in kernel space.
pub fn kernel_stack_position(kstack_id: usize) -> (usize, usize) {
    let top = TRAMPOLINE - kstack_id * (KERNEL_STACK_SIZE + PAGE_SIZE);
    let bottom = top - KERNEL_STACK_SIZE;
    (bottom, top)
}

KernelStack.push_on_top

description: push_on_top方法用于将值压入KernelStack的顶部。它接受一个泛型参数value,要求该参数必须是Sized类型。方法首先获取当前内核栈的顶部位置,然后计算出新值应存储的位置指针。通过不安全的Rust代码,将值写入该位置。返回值是指向新压入值的指针。

#[allow(unused)]
    pub fn push_on_top<T>(&self, value: T) -> *mut T
    where
        T: Sized,
    {
        let kernel_stack_top = self.get_top();
        let ptr_mut = (kernel_stack_top - core::mem::size_of::<T>()) as *mut T;
        unsafe {
            *ptr_mut = value;
        }
        ptr_mut
    }

pid_alloc

description: pid_alloc函数的主要功能是分配一个新的进程ID,并将其封装在PidHandle类型中。PidHandle是一种结构体,包含一个无符号整数,用于表示进程ID,并管理其生命周期。该函数通过调用PID_ALLOCATOR全局变量的exclusive_access().alloc()方法来实现ID的分配。返回值是一个PidHandle实例,表示分配的进程ID。

pub fn pid_alloc() -> PidHandle {
    PidHandle(PID_ALLOCATOR.exclusive_access().alloc())
}

trap_cx_bottom_from_tid

description: trap_cx_bottom_from_tid函数的主要功能是计算特定线程ID的陷阱上下文底部地址。该函数接收一个参数:

  • tid: 表示线程ID的无符号整数。

函数通过从TRAP_CONTEXT_BASE中减去tid乘以PAGE_SIZE来获取对应线程ID的陷阱上下文底部地址。PAGE_SIZE是一个常量,表示页面大小,用于内存管理。TRAP_CONTEXT_BASE是另一个常量,表示陷阱上下文的基地址。函数的返回值是计算得到的地址,类型为无符号整数。

fn trap_cx_bottom_from_tid(tid: usize) -> usize {
    TRAP_CONTEXT_BASE - tid * PAGE_SIZE
}

TaskUserRes.trap_cx_user_va

description: trap_cx_user_va函数用于计算并返回特定线程ID的陷阱上下文底部地址。该方法属于TaskUserRes结构体,其接收者包含线程ID、用户栈基地址及进程控制块的弱引用。具体实现中,它调用trap_cx_bottom_from_tid函数,传入线程ID进行地址计算。返回值是一个无符号整数,表示该线程的陷阱上下文底部地址。

pub fn trap_cx_user_va(&self) -> usize {
        trap_cx_bottom_from_tid(self.tid)
    }

TaskUserRes.drop

description: drop函数用于释放TaskUserRes实例持有的资源。在这个函数中,它调用了两个方法:dealloc_tid和dealloc_user_res,以释放任务用户资源的相关信息。TaskUserRes结构体包含tid(任务ID)、ustack_base(用户栈基地址)和一个指向ProcessControlBlock的弱引用。这个函数的主要用途是确保当TaskUserRes不再需要时,清理和释放相关的资源。

fn drop(&mut self) {
        self.dealloc_tid();
        self.dealloc_user_res();
    }

KernelStack.drop

description: drop函数用于释放内核栈资源。其主要功能是:

  1. 通过kernel_stack_position函数计算内核栈的底部位置。
  2. 将底部位置转换为虚拟地址VirtAddr类型。
  3. 使用KERNEL_SPACE的独占访问权限,从内核空间中移除以该虚拟地址为起始的区域。
  4. 使用KSTACK_ALLOCATOR的独占访问权限释放内核栈ID。

该方法属于KernelStack结构体,用于在内核栈不再需要时清理相关资源,确保内存的有效管理和释放。

fn drop(&mut self) {
        let (kernel_stack_bottom, _) = kernel_stack_position(self.0);
        let kernel_stack_bottom_va: VirtAddr = kernel_stack_bottom.into();
        KERNEL_SPACE
            .exclusive_access()
            .remove_area_with_start_vpn(kernel_stack_bottom_va.into());
        KSTACK_ALLOCATOR.exclusive_access().dealloc(self.0);
    }

TaskUserRes.alloc_user_res

description: alloc_user_res函数的主要功能是为线程分配用户资源,包括用户栈和陷阱上下文。该函数属于TaskUserRes结构体的一部分,负责管理线程的用户资源。函数通过计算用户栈底部和顶部地址,并设置相应的内存权限,确保这些区域在用户态下可读写。此外,函数还计算陷阱上下文的地址并设置内存权限,以便内核能正确处理线程的中断和异常。

pub fn alloc_user_res(&self) {
        let process = self.process.upgrade().unwrap();
        let mut process_inner = process.inner_exclusive_access();
        // alloc user stack
        let ustack_bottom = ustack_bottom_from_tid(self.ustack_base, self.tid);
        let ustack_top = ustack_bottom + USER_STACK_SIZE;
        process_inner.memory_set.insert_framed_area(
            ustack_bottom.into(),
            ustack_top.into(),
            MapPermission::R | MapPermission::W | MapPermission::U,
        );
        // alloc trap_cx
        let trap_cx_bottom = trap_cx_bottom_from_tid(self.tid);
        let trap_cx_top = trap_cx_bottom + PAGE_SIZE;
        process_inner.memory_set.insert_framed_area(
            trap_cx_bottom.into(),
            trap_cx_top.into(),
            MapPermission::R | MapPermission::W,
        );
    }

TaskUserRes.trap_cx_ppn

description: trap_cx_ppn函数的主要功能是获取与特定线程相关的陷阱上下文的物理页号。该函数属于TaskUserRes结构体,TaskUserRes用于管理线程的用户资源。

在函数内部,首先通过self.process.upgrade()获取线程所属的进程控制块,并取得其内部独占访问权限。然后,利用trap_cx_bottom_from_tid函数计算出特定线程ID的陷阱上下文底部虚拟地址。接着,通过进程的memory_set将该虚拟地址翻译为物理页号,并返回该物理页号。

参数

  • 无参数,函数通过结构体内的字段进行操作。

返回值

  • 返回值是PhysPageNum类型,表示物理页号。
pub fn trap_cx_ppn(&self) -> PhysPageNum {
        let process = self.process.upgrade().unwrap();
        let process_inner = process.inner_exclusive_access();
        let trap_cx_bottom_va: VirtAddr = trap_cx_bottom_from_tid(self.tid).into();
        process_inner
            .memory_set
            .translate(trap_cx_bottom_va.into())
            .unwrap()
            .ppn()
    }

TaskUserRes.ustack_top

description: ustack_top函数用于计算线程的用户栈顶地址。主要功能是通过调用ustack_bottom_from_tid函数获取用户栈底部地址,然后加上USER_STACK_SIZE常量来确定用户栈的顶部位置。接收者是TaskUserRes结构体,表示线程的用户资源管理器。

pub fn ustack_top(&self) -> usize {
        ustack_bottom_from_tid(self.ustack_base, self.tid) + USER_STACK_SIZE
    }

kstack_alloc

description: kstack_alloc函数用于分配一个内核栈。它的主要功能是通过KSTACK_ALLOCATOR分配一个内核栈ID,然后使用kernel_stack_position函数计算该栈的底部和顶部位置。接着,使用KERNEL_SPACE将该栈位置映射为具有读写权限的区域。最终,该函数返回一个KernelStack结构体,这个结构体包含了分配的内核栈ID。

pub fn kstack_alloc() -> KernelStack {
    let kstack_id = KSTACK_ALLOCATOR.exclusive_access().alloc();
    let (kstack_bottom, kstack_top) = kernel_stack_position(kstack_id);
    KERNEL_SPACE.exclusive_access().insert_framed_area(
        kstack_bottom.into(),
        kstack_top.into(),
        MapPermission::R | MapPermission::W,
    );
    KernelStack(kstack_id)
}

TaskUserRes::new

description: new函数用于创建一个TaskUserRes实例。它接受以下参数:

  • process: 一个Arc类型,表示进程控制块的引用。
  • ustack_base: 一个usize类型,表示用户栈的基地址。
  • alloc_user_res: 一个bool类型,指示是否为任务分配用户资源。

在函数内部,通过调用process的inner_exclusive_access方法分配一个线程ID (tid)。然后,使用提供的参数和分配的tid创建TaskUserRes实例。如果alloc_user_res为true,则为任务分配用户资源。最后,返回创建的TaskUserRes实例。

pub fn new(
        process: Arc<ProcessControlBlock>,
        ustack_base: usize,
        alloc_user_res: bool,
    ) -> Self {
        let tid = process.inner_exclusive_access().alloc_tid();
        let task_user_res = Self {
            tid,
            ustack_base,
            process: Arc::downgrade(&process),
        };
        if alloc_user_res {
            task_user_res.alloc_user_res();
        }
        task_user_res
    }

TaskUserRes.alloc_tid

description: alloc_tid函数用于为TaskUserRes分配一个线程ID(tid)。该函数通过提升process的弱引用,获取其内部独占访问权,并调用alloc_tid方法来分配线程ID。

#[allow(unused)]
    pub fn alloc_tid(&mut self) {
        self.tid = self
            .process
            .upgrade()
            .unwrap()
            .inner_exclusive_access()
            .alloc_tid();
    }

KernelStack.get_top

description: get_top方法用于获取KernelStack结构体中内核栈的顶部位置。它调用kernel_stack_position函数,该函数计算内核栈的底部和顶部位置。方法没有参数,返回值是栈的顶部位置。

pub fn get_top(&self) -> usize {
        let (_, kernel_stack_top) = kernel_stack_position(self.0);
        kernel_stack_top
    }

ustack_bottom_from_tid

description: ustack_bottom_from_tid函数用于计算用户栈的底部地址。该函数接收两个参数:

  • ustack_base:用户栈的基址。
  • tid:线程ID。

通过计算公式 ustack_base + tid * (PAGE_SIZE + USER_STACK_SIZE),该函数返回指定线程的用户栈底部地址。PAGE_SIZE和USER_STACK_SIZE是常量,分别表示页面大小(4096字节)和用户栈大小(8192字节)。

fn ustack_bottom_from_tid(ustack_base: usize, tid: usize) -> usize {
    ustack_base + tid * (PAGE_SIZE + USER_STACK_SIZE)
}

TaskUserRes.dealloc_user_res

description: dealloc_user_res函数的主要功能是释放与任务用户资源相关的内存。它通过以下步骤实现:

  1. 升级并解包 process 弱引用,以获取独占访问权限。
  2. 使用 ustack_bottom_from_tid 函数计算用户栈的底部地址,并从进程的内存集中移除该区域。
  3. 使用 trap_cx_bottom_from_tid 函数计算陷阱上下文的底部地址,并从进程的内存集中移除该区域。

该函数涉及的类型和函数主要用于内存管理和地址计算。

fn dealloc_user_res(&self) {
        // dealloc tid
        let process = self.process.upgrade().unwrap();
        let mut process_inner = process.inner_exclusive_access();
        // dealloc ustack manually
        let ustack_bottom_va: VirtAddr = ustack_bottom_from_tid(self.ustack_base, self.tid).into();
        process_inner
            .memory_set
            .remove_area_with_start_vpn(ustack_bottom_va.into());
        // dealloc trap_cx manually
        let trap_cx_bottom_va: VirtAddr = trap_cx_bottom_from_tid(self.tid).into();
        process_inner
            .memory_set
            .remove_area_with_start_vpn(trap_cx_bottom_va.into());
    }

RecycleAllocator::new

description: new函数用于创建一个RecycleAllocator的新实例。RecycleAllocator是一个结构体,负责管理和分配唯一标识符。它包含两个字段:current,表示当前最大分配的标识符,类型为usize;recycled,一个包含usize类型元素的向量,用于存储已回收、可重用的标识符。该函数初始化current为0,并将recycled初始化为空向量,返回一个新的RecycleAllocator实例。

pub fn new() -> Self {
        RecycleAllocator {
            current: 0,
            recycled: Vec::new(),
        }
    }

PidHandle.drop

description: drop方法的主要功能是释放与PidHandle相关联的进程ID。它通过调用全局变量PID_ALLOCATOR的exclusive_access().dealloc方法来实现。该方法没有参数和返回值。

fn drop(&mut self) {
        PID_ALLOCATOR.exclusive_access().dealloc(self.0);
    }

os::sbi

description: 这个 os::sbi 包主要用于 RISC-V 架构下通过 SBI 进行系统级操作,其核心功能包括:

  • 通过 shutdown 函数根据传入的参数决定以正常或因系统故障的方式关闭内核。
  • 通过 set_timer 函数使用 SBI 调用设置计时器,并清除待处理的计时器中断位以确保准确触发下一个事件。

shutdown

description: shutdown函数的主要功能是通过sbi调用来关闭内核。根据传入的参数failure决定关闭的方式:

  • 如果failure为false,则调用system_reset函数以Shutdown和NoReason作为参数,表示正常关闭。
  • 如果failure为true,则调用system_reset函数以Shutdown和SystemFailure作为参数,表示由于系统故障而关闭。

该函数不会返回,因为system_reset是一个同步调用,成功执行后不返回。而unreachable!()用于标识不可达代码。

/// use sbi call to shutdown the kernel
pub fn shutdown(failure: bool) -> ! {
    use sbi_rt::{system_reset, NoReason, Shutdown, SystemFailure};
    if !failure {
        system_reset(Shutdown, NoReason);
    } else {
        system_reset(Shutdown, SystemFailure);
    }
    unreachable!()
}

set_timer

description: set_timer函数的主要功能是使用SBI(Supervisor Binary Interface)调用来设置计时器。它将接收的计时器参数转换为适当的类型,并调用sbi_rt::set_timer函数来安排下一个事件发生的时间。这个过程涉及到清除待处理的计时器中断位,以确保计时器能够准确地触发下一个事件。这个功能在RISC-V的SBI规范中有详细定义,适用于32位和64位的不同体系结构。

/// use sbi call to set timer
pub fn set_timer(timer: usize) {
    sbi_rt::set_timer(timer as _);
}

os::task::processor

description: 这个 os::task::processor 包的主要用途是管理和调度系统中的任务。它提供了一系列函数和一个结构体 Processor 来实现以下功能:

  • 任务信息获取:如获取当前任务的陷阱上下文、进程控制块、用户令牌、任务控制块、内核栈顶地址等。
  • 任务调度管理:通过 Processor 结构体及其相关函数,如创建 Processor 实例、获取当前执行任务、取出当前任务控制块等,实现对任务的调度和管理。
  • 任务上下文切换:包括 schedule 函数实现任务上下文的切换,以及 run_tasks 函数进行任务的管理和调度,涉及任务的获取、状态更新和上下文切换。

此外,包中还定义了一个全局变量 PROCESSOR 用于任务的管理和调度。同时,该包还存在一些依赖项。

current_trap_cx

description: current_trap_cx函数的主要功能是获取当前任务的陷阱上下文。通过调用current_task函数获取当前正在执行的任务,然后通过该任务的独占访问来获取其陷阱上下文。该函数返回一个静态可变引用,指向当前任务的陷阱上下文。

pub fn current_trap_cx() -> &'static mut TrapContext {
    current_task()
        .unwrap()
        .inner_exclusive_access()
        .get_trap_cx()
}

current_task

description: current_task函数的主要功能是获取当前正在执行的任务。它通过PROCESSOR全局变量来独占访问当前任务的状态和上下文指针,以实现对任务的管理和调度。

返回值:

  • Option<Arc>: 当前任务的控制块,如果存在的话。
pub fn current_task() -> Option<Arc<TaskControlBlock>> {
    PROCESSOR.exclusive_access().current()
}

Processor.current

description: current函数用于从Processor的current字段中获取当前正在执行的任务。它返回一个可选类型,可能包含一个指向任务控制块的智能指针(Arc)。如果current字段存在有效值,则返回该值的克隆。此函数的主要目的是提供对当前任务的安全访问,并允许外部调用者获取当前任务的引用,而不直接修改Processor的内部状态。

pub fn current(&self) -> Option<Arc<TaskControlBlock>> {
        self.current.as_ref().map(Arc::clone)
    }

current_process

description: current_process函数的主要功能是获取当前任务对应的进程控制块。它通过调用current_task函数来获取当前正在执行的任务,然后通过任务的process属性获取进程控制块,并返回一个强引用的进程控制块。

pub fn current_process() -> Arc<ProcessControlBlock> {
    current_task().unwrap().process.upgrade().unwrap()
}

Processor::new

description: new函数用于创建并返回一个Processor类型的实例。该实例初始化了两个字段:

  • current: 初始化为None,表示当前没有正在执行的任务。
  • idle_task_cx: 使用TaskContext::zero_init()进行初始化,表示空闲任务的上下文信息被设置为初始状态。

通过该函数,Processor可以有效地管理任务调度,其中current用于跟踪当前任务,而idle_task_cx用于保存空闲任务的必要上下文信息。

pub fn new() -> Self {
        Self {
            current: None,
            idle_task_cx: TaskContext::zero_init(),
        }
    }

current_user_token

description: current_user_token函数的主要功能是获取当前正在执行任务的用户令牌。它首先调用current_task函数来获取当前任务,并通过unwrap方法确保任务存在,然后调用get_user_token方法来获取用户令牌。此函数不接受任何参数,返回一个usize类型的用户令牌。

pub fn current_user_token() -> usize {
    let task = current_task().unwrap();
    task.get_user_token()
}

current_kstack_top

description: current_kstack_top函数的主要功能是获取当前任务的内核栈顶地址。其工作机制如下:

  • 如果存在当前任务,通过调用current_task获取任务信息,然后调用task.kstack.get_top()来获取该任务的内核栈顶。
  • 如果不存在当前任务,使用汇编指令获取boot_stack_top的地址,并返回该地址。

该函数返回一个usize类型的值,表示内核栈顶的地址。

pub fn current_kstack_top() -> usize {
    if let Some(task) = current_task() {
        task.kstack.get_top()
    } else {
        let mut boot_stack_top;
        unsafe { asm!("la {},boot_stack_top",out(reg) boot_stack_top) };
        boot_stack_top
    }
    // current_task().unwrap().kstack.get_top()
}

current_trap_cx_user_va

description: current_trap_cx_user_va函数的主要功能是获取当前任务的用户态Trap上下文的虚拟地址。它通过调用current_task函数获取当前任务的控制块,并获取其内部的用户态Trap上下文的虚拟地址。这个函数没有参数,返回值是一个表示虚拟地址的usize类型。

pub fn current_trap_cx_user_va() -> usize {
    current_task()
        .unwrap()
        .inner_exclusive_access()
        .res
        .as_ref()
        .unwrap()
        .trap_cx_user_va()
}

Processor.get_idle_task_cx_ptr

description: get_idle_task_cx_ptr函数的主要功能是获取Processor结构体中的idle_task_cx字段的可变指针。返回的结果是一个指向TaskContext类型的指针,TaskContext用于保存任务上下文的信息,包括返回地址、堆栈指针和寄存器的状态。

fn get_idle_task_cx_ptr(&mut self) -> *mut TaskContext {
        &mut self.idle_task_cx as *mut _
    }

take_current_task

description: take_current_task函数用于从PROCESSOR中获取当前的任务控制块。该函数返回一个Option类型,包含当前任务的Arc,如果没有当前任务,则返回None。

pub fn take_current_task() -> Option<Arc<TaskControlBlock>> {
    PROCESSOR.exclusive_access().take_current()
}

run_tasks

description: run_tasks函数的主要功能是管理和调度任务。它在一个无限循环中运行,通过PROCESSOR获取对当前任务上下文的独占访问权限。函数尝试从TASK_MANAGER中获取一个任务,如果成功获取,则将该任务的状态设置为运行,并更新处理器的当前任务。然后,通过__switch函数切换到下一个任务的上下文。如果没有可用任务,函数会输出提示信息。这个函数涉及任务的获取、状态更新和上下文切换。

pub fn run_tasks() {
    loop {
        let mut processor = PROCESSOR.exclusive_access();
        if let Some(task) = fetch_task() {
            let idle_task_cx_ptr = processor.get_idle_task_cx_ptr();
            // access coming task TCB exclusively
            let next_task_cx_ptr = task.inner.exclusive_session(|task_inner| {
                task_inner.task_status = TaskStatus::Running;
                &task_inner.task_cx as *const TaskContext
            });
            processor.current = Some(task);
            // release processor manually
            drop(processor);
            unsafe {
                __switch(idle_task_cx_ptr, next_task_cx_ptr);
            }
        } else {
            println!("no tasks available in run_tasks");
        }
    }
}

Processor.take_current

description: take_current函数用于从Processor结构体中取出当前任务控制块,并返回一个可选的Arc。如果当前没有任务控制块,则返回None。

pub fn take_current(&mut self) -> Option<Arc<TaskControlBlock>> {
        self.current.take()
    }

schedule

description: schedule函数的主要功能是实现任务上下文的切换。它接收一个参数:

  • switched_task_cx_ptr: 指向需要切换的任务的TaskContext指针。

函数内部通过调用PROCESSOR的exclusive_session方法获取空闲任务的上下文指针idle_task_cx_ptr。随后,使用不安全代码调用__switch函数进行任务切换,__switch函数负责将当前任务的上下文保存并加载下一个任务的上下文,从而实现任务间的切换。

pub fn schedule(switched_task_cx_ptr: *mut TaskContext) {
    let idle_task_cx_ptr =
        PROCESSOR.exclusive_session(|processor| processor.get_idle_task_cx_ptr());
    unsafe {
        __switch(switched_task_cx_ptr, idle_task_cx_ptr);
    }
}

os::sync::condvar

description: 该 os::sync::condvar 包提供了用于线程同步的条件变量功能。主要功能包括通过条件变量实现线程同步,允许任务在特定条件成立时暂停和继续执行,以及提供一系列方法来管理任务的等待和唤醒。关键函数有 Condvar.signal(唤醒等待队列中的第一个任务)、Condvar.wait_no_sched(将当前任务添加到条件变量的等待队列中并阻塞任务)、Condvar.wait_with_mutex(在给定的互斥锁上等待条件变量)、Condvar::new(创建并初始化一个 Condvar 实例)。关键类型有 Condvar(用于实现条件变量功能,通过 UPIntrFreeCell 封装内部状态)和 CondvarInner(管理等待队列的结构体)。该包通过提供强大的条件变量机制,确保了在操作系统内核中资源的安全访问和有效使用。

Condvar.wait_no_sched

description: wait_no_sched方法属于Condvar结构体,其主要功能是将当前任务添加到条件变量的等待队列中,并阻塞当前任务。以下是方法的详细解释:

  • 该方法通过调用inner.exclusive_session闭包,将当前任务(由current_task函数返回)推入条件变量的等待队列。
  • 然后,它调用block_current_task函数来阻塞当前任务,并返回任务上下文的指针。

主要参数:

  • 无参数。

返回值:

  • 一个指向TaskContext的指针,用于保存任务的上下文信息。
pub fn wait_no_sched(&self) -> *mut TaskContext {
        self.inner.exclusive_session(|inner| {
            inner.wait_queue.push_back(current_task().unwrap());
        });
        block_current_task()
    }

Condvar.wait_with_mutex

description: wait_with_mutex方法用于在给定的互斥锁(Mutex)上等待条件变量(Condvar)。该方法首先解锁传入的互斥锁,然后将当前任务添加到等待队列中,并阻塞当前任务,切换到下一个任务运行。最后,在任务切换回来后,重新锁定互斥锁。此方法的主要功能是通过互斥锁实现线程同步,确保在等待条件满足时,当前任务能够安全地暂停并在合适的时机继续执行。

pub fn wait_with_mutex(&self, mutex: Arc<dyn Mutex>) {
        mutex.unlock();
        self.inner.exclusive_session(|inner| {
            inner.wait_queue.push_back(current_task().unwrap());
        });
        block_current_and_run_next();
        mutex.lock();
    }

Condvar.signal

description: signal函数的主要功能是唤醒等待队列中的第一个任务。具体来说,它通过获取Condvar的内部状态的独占访问权,从等待队列中取出第一个任务,并调用wakeup_task函数将该任务设置为准备好执行的状态。该函数没有参数和返回值。

pub fn signal(&self) {
        let mut inner = self.inner.exclusive_access();
        if let Some(task) = inner.wait_queue.pop_front() {
            wakeup_task(task);
        }
    }

Condvar::new

description: new函数的主要功能是创建并初始化一个Condvar类型的实例。Condvar是一种用于线程同步的工具,内部封装了一个UPIntrFreeCell类型的结构,该结构通过RefCell管理内部数据的可变性。新创建的Condvar实例中,内部的CondvarInner包含一个空的等待队列(VecDeque)。这个初始化过程保证了Condvar的初始状态是安全且可用的。

pub fn new() -> Self {
        Self {
            inner: unsafe {
                UPIntrFreeCell::new(CondvarInner {
                    wait_queue: VecDeque::new(),
                })
            },
        }
    }

os::sync::up

description: 该 os::sync::up 包主要用于在单线程环境中提供安全的可变性和管理中断屏蔽状态,适用于需要低级别内存操作的场景。

包中包含以下关键内容:

  • 函数
    • UPIntrFreeCell::new:创建并初始化 UPIntrFreeCell 实例,将数据封装在 RefCell 中以实现安全可变操作。
    • UPIntrFreeCell.exclusive_access:提供对 UPIntrFreeCell 内部数据的独占访问,数据被借用时触发 panic
    • UPIntrFreeCell.exclusive_session:接受闭包以独占访问内部数据,确保操作安全。
    • IntrMaskingInfo::new:初始化 IntrMaskingInfo 实例,管理中断屏蔽嵌套层次。
    • IntrMaskingInfo.enter:处理进入中断屏蔽状态的操作,管理嵌套层次。
    • IntrMaskingInfo.exit:减少嵌套层次,当为零时恢复 sie 状态。
    • UPSafeCellRaw::new:创建 UPSafeCellRaw 实例,使用 UnsafeCell 封装数据以便在不安全上下文中操作。
    • UPSafeCellRaw.get_mut:提供对 UPSafeCellRaw 内部数据的可变引用,使用 unsafe 代码块操作。
    • UPIntrRefMut.deref:解引用 UPIntrRefMut 结构体的 Option 类型值以返回引用。
    • UPIntrRefMut.deref_mut:获取 UPIntrRefMut 中的可变引用。
    • UPIntrRefMut.drop:清理 UPIntrRefMut 结构体的资源,处理中断屏蔽状态。
  • 类型
    • UPSafeCellRaw:使用 UnsafeCell 封装数据,便于低级别内存操作。
    • IntrMaskingInfo:管理中断屏蔽的嵌套层次,记录状态。
    • UPIntrFreeCell:使用 RefCell 封装内部数据,提供安全可变和独占访问。
    • UPIntrRefMut:封装可选的可变引用,管理引用生命周期。
  • 变量
    • INTR_MASKING_INFO:全局变量,用于管理和控制中断屏蔽信息。

此外,该包还存在一些依赖项。

UPIntrFreeCell::new

description: new函数用于创建并初始化一个UPIntrFreeCell类型的实例。其主要功能是将传入的值封装在RefCell中,以便在单线程环境中提供安全的可变性。入参为value,即需要存储的泛型类型T的数据。返回值是一个新的UPIntrFreeCell实例,其中包含了用RefCell封装的value。

pub unsafe fn new(value: T) -> Self {
        Self {
            inner: RefCell::new(value),
        }
    }

UPIntrFreeCell.exclusive_session

description: exclusive_session函数的主要功能是提供对UPIntrFreeCell内部数据的独占访问。它接受一个闭包f作为参数,该闭包会在获得内部数据的独占访问权限后执行。闭包f的类型是FnOnce,它可以在内部数据的可变引用上执行操作,并返回一个类型为V的结果。此函数确保在执行闭包时,内部数据的访问是安全且独占的。

pub fn exclusive_session<F, V>(&self, f: F) -> V
    where
        F: FnOnce(&mut T) -> V,
    {
        let mut inner = self.exclusive_access();
        f(inner.deref_mut())
    }

UPIntrFreeCell.exclusive_access

description: exclusive_access函数的主要功能是提供对UPIntrFreeCell内部数据的独占访问。该函数会在数据已被借用的情况下触发panic,以确保安全访问。它通过进入中断屏蔽状态来管理对数据的访问。函数返回UPIntrRefMut类型的值,该值封装了一个可选的可变引用,确保在使用过程中资源的安全性和正确释放。

/// Panic if the data has been borrowed.
    pub fn exclusive_access(&self) -> UPIntrRefMut<'_, T> {
        INTR_MASKING_INFO.get_mut().enter();
        UPIntrRefMut(Some(self.inner.borrow_mut()))
    }

IntrMaskingInfo::new

description: new函数用于创建并初始化一个IntrMaskingInfo类型的实例。该函数不需要输入参数,返回一个新的IntrMaskingInfo对象,其nested_level字段被初始化为0,表示当前没有嵌套层次,sie_before_masking字段被初始化为false,表示在开始中断屏蔽之前的sie状态为关闭。

pub fn new() -> Self {
        Self {
            nested_level: 0,
            sie_before_masking: false,
        }
    }

UPSafeCellRaw.get_mut

description: get_mut函数的主要功能是提供对UPSafeCellRaw类型中的inner字段的可变引用。该函数使用unsafe代码块来获取inner字段的可变引用。由于inner是UnsafeCell类型,get_mut通过绕过Rust编译器的安全性检查,允许在不安全的上下文中对内部数据进行修改。这种设计通常用于需要直接操作内存的低级别场景。

pub fn get_mut(&self) -> &mut T {
        unsafe { &mut (*self.inner.get()) }
    }

IntrMaskingInfo.exit

description: exit函数用于减少IntrMaskingInfo的nested_level字段的值。当nested_level减少到0且sie_before_masking为真时,它会调用sstatus::set_sie()来恢复sie状态。

pub fn exit(&mut self) {
        self.nested_level -= 1;
        if self.nested_level == 0 && self.sie_before_masking {
            unsafe {
                sstatus::set_sie();
            }
        }
    }

UPSafeCellRaw::new

description: new函数用于创建一个UPSafeCellRaw类型的新实例。该函数是unsafe的,意味着调用此函数时需要确保安全性。函数的主要功能是将传入的值封装在一个UnsafeCell中,以便在不安全的上下文中操作。参数value表示要封装的值。返回值是封装了该值的UPSafeCellRaw实例。

pub unsafe fn new(value: T) -> Self {
        Self {
            inner: UnsafeCell::new(value),
        }
    }

UPIntrRefMut.deref

description: deref方法的主要功能是解引用UPIntrRefMut结构体内的Option类型值,以返回引用。入参:无。出参:返回UPIntrRefMut中存储对象的引用。

fn deref(&self) -> &Self::Target {
        self.0.as_ref().unwrap().deref()
    }

UPIntrRefMut.deref_mut

description: deref_mut方法用于获取UPIntrRefMut中的可变引用。它通过调用内部存储的可选可变引用的deref_mut方法来实现。在使用该方法时,确保内部引用是有效的,从而保证资源的安全性和正确释放。

fn deref_mut(&mut self) -> &mut Self::Target {
        self.0.as_mut().unwrap().deref_mut()
    }

IntrMaskingInfo.enter

description: enter方法属于IntrMaskingInfo结构体,主要用于在进入中断屏蔽状态时进行处理。其功能是读取当前的sie状态,并在必要时清除sie。当nested_level为0时,保存sie的状态到sie_before_masking中,接着增加nested_level的值。该方法用于管理和跟踪屏蔽中断的嵌套层次。

pub fn enter(&mut self) {
        let sie = sstatus::read().sie();
        unsafe {
            sstatus::clear_sie();
        }
        if self.nested_level == 0 {
            self.sie_before_masking = sie;
        }
        self.nested_level += 1;
    }

UPIntrRefMut.drop

description: drop函数的主要功能是清理UPIntrRefMut结构体的资源。其过程包括将内部的可选引用设置为None,并调用全局变量INTR_MASKING_INFO的get_mut().exit()方法以退出中断屏蔽状态。这个函数确保在UPIntrRefMut实例销毁时,相关资源被正确释放,并且中断屏蔽状态被处理,以维护数据访问的安全性。

fn drop(&mut self) {
        self.0 = None;
        INTR_MASKING_INFO.get_mut().exit();
    }

os::fs::stdio

description: 该 os::fs::stdio 包主要用于处理标准输入和标准输出操作。其中定义了两个结构体 StdinStdout

  • Stdin:专注于处理标准输入操作,包含 write(不支持写操作,会触发 panic)、readable(始终可读,返回 true)、read(从 UART 接口读取一个字符并写入 UserBuffer 中,返回 1 表示成功读取)方法。
  • Stdout:主要用于处理标准输出操作,包含 readable(始终不可读,返回 false)、read(不支持读取操作,会触发 panic)、write(将 UserBuffer 中的内容转换为 UTF-8 字符串并打印到控制台,返回处理的数据总长度)方法。

该包通过提供对标准输入和输出的处理功能,确保数据能够正确流入和流出应用程序。此包依赖于一些其他模块的功能,如 core::macroscore::ptr::mut_ptr 等。

Stdin.write

description: write方法用于尝试向Stdin写入数据。然而,该方法的实现直接触发了panic,表示不能向标准输入进行写操作。参数说明:

  • _user_buf: UserBuffer类型,表示一个管理多个静态可变字节缓冲区的结构体。
fn write(&self, _user_buf: UserBuffer) -> usize {
        panic!("Cannot write to stdin!");
    }

Stdin.writable

description: writable函数用于检查Stdin是否支持写操作。由于Stdin专注于处理标准输入操作,而不支持写操作,因此该函数始终返回false,表示Stdin不可写。

fn writable(&self) -> bool {
        false
    }

Stdout.writable

description: writable方法用于判断Stdout类型的实例是否可写。该方法始终返回true,表示Stdout类型的实例总是可写的。

fn writable(&self) -> bool {
        true
    }

Stdout.readable

description: readable方法用于判断Stdout结构体是否可读。这个方法没有参数,返回一个布尔值,始终为false,表示Stdout结构体不可读。

fn readable(&self) -> bool {
        false
    }

Stdout.read

description: read函数的主要功能是尝试从Stdout读取数据,但由于stdout不支持读取操作,该函数直接触发恐慌(panic)来提示错误。

入参:

  • _user_buf: 一个UserBuffer类型的参数,用于管理多个静态可变字节缓冲区,但在此函数中未被实际使用。

出参:

  • 无实际返回值,因为函数会直接触发恐慌而终止。
fn read(&self, _user_buf: UserBuffer) -> usize {
        panic!("Cannot read from stdout!");
    }

Stdout.write

description: write函数的主要功能是将UserBuffer中的缓冲区内容逐个转换为UTF-8字符串并打印到控制台。它遍历user_buf内的每个buffer,使用print宏输出转换后的字符串。最后,该函数返回user_buf的长度。参数说明:

  • user_buf: UserBuffer类型,包含多个静态可变字节缓冲区,用于批量数据处理。

返回值为user_buf的总长度。

fn write(&self, user_buf: UserBuffer) -> usize {
        for buffer in user_buf.buffers.iter() {
            print!("{}", core::str::from_utf8(*buffer).unwrap());
        }
        user_buf.len()
    }

Stdin.readable

description: readable函数的主要功能是检查Stdin结构体是否可读。对于该结构体,该函数总是返回true,表示Stdin总是处于可读状态。这个函数没有参数,也没有依赖其他函数、类型或全局变量。

fn readable(&self) -> bool {
        true
    }

Stdin.read

description: read函数是Stdin结构体的一个方法,用于从UART接口读取一个字符并将其写入到提供的UserBuffer中。该函数首先断言user_buf的长度为1,然后通过UART接口读取一个字符。接着,使用不安全代码将读取的字符写入到user_buf的第一个缓冲区中。最后,函数返回1,表示成功读取了一个字符。

入参:

  • user_buf: UserBuffer类型,包含多个静态可变字节缓冲区,用于存储读取的数据。
fn read(&self, mut user_buf: UserBuffer) -> usize {
        assert_eq!(user_buf.len(), 1);
        //println!("before UART.read() in Stdin::read()");
        let ch = UART.read();
        unsafe {
            user_buf.buffers[0].as_mut_ptr().write_volatile(ch);
        }
        1
    }

os::syscall::thread

description: 该 os::syscall::thread 包主要用于线程管理,其核心功能包括:

  • sys_gettid() 函数用于获取当前执行任务的线程 ID(TID)。
  • sys_thread_create(entry, arg) 函数用于在当前进程中创建一个新线程,并返回新线程的线程 ID。
  • sys_waittid(tid) 函数用于等待指定线程的退出状态。

此包依赖于 alloc::syncalloc::veccore::optionos::mm::memory_setos::task::idos::task::manageros::task::processos::task::processoros::task::taskos::trapos::trap::context 等模块的一些功能。

sys_thread_create

description: sys_thread_create函数用于在当前进程中创建一个新线程。其主要功能是通过获取当前任务和进程的控制块,创建一个新的任务控制块,并将其添加到调度器中。然后,该函数会把新线程添加到当前进程的任务列表中,并初始化新线程的陷阱上下文。最后,返回新线程的线程ID。

参数意义:

  • entry: 新线程的入口地址。
  • arg: 传递给新线程的参数。
pub fn sys_thread_create(entry: usize, arg: usize) -> isize {
    let task = current_task().unwrap();
    let process = task.process.upgrade().unwrap();
    // create a new thread
    let new_task = Arc::new(TaskControlBlock::new(
        Arc::clone(&process),
        task.inner_exclusive_access()
            .res
            .as_ref()
            .unwrap()
            .ustack_base,
        true,
    ));
    // add new task to scheduler
    add_task(Arc::clone(&new_task));
    let new_task_inner = new_task.inner_exclusive_access();
    let new_task_res = new_task_inner.res.as_ref().unwrap();
    let new_task_tid = new_task_res.tid;
    let mut process_inner = process.inner_exclusive_access();
    // add new thread to current process
    let tasks = &mut process_inner.tasks;
    while tasks.len() < new_task_tid + 1 {
        tasks.push(None);
    }
    tasks[new_task_tid] = Some(Arc::clone(&new_task));
    let new_task_trap_cx = new_task_inner.get_trap_cx();
    *new_task_trap_cx = TrapContext::app_init_context(
        entry,
        new_task_res.ustack_top(),
        kernel_token(),
        new_task.kstack.get_top(),
        trap_handler as usize,
    );
    (*new_task_trap_cx).x[10] = arg;
    new_task_tid as isize
}

sys_gettid

description: sys_gettid函数的主要功能是获取当前执行任务的线程ID(TID)。该函数首先通过调用current_task函数获取当前任务的控制块,并确保其存在。然后,它通过一系列方法调用独占访问任务的内部状态,最终提取任务的线程ID,并将其转换为isize类型返回。

pub fn sys_gettid() -> isize {
    current_task()
        .unwrap()
        .inner_exclusive_access()
        .res
        .as_ref()
        .unwrap()
        .tid as isize
}

sys_waittid

description: sys_waittid函数用于等待指定线程的退出状态。该函数首先获取当前任务和进程的控制块,确保当前线程不能等待自己。如果待等待的线程不存在,函数返回-1;如果线程尚未退出,则返回-2。一旦线程退出,函数返回其退出码,同时释放已退出线程的资源。参数tid表示目标线程的ID。

/// thread does not exist, return -1
/// thread has not exited yet, return -2
/// otherwise, return thread's exit code
pub fn sys_waittid(tid: usize) -> i32 {
    let task = current_task().unwrap();
    let process = task.process.upgrade().unwrap();
    let task_inner = task.inner_exclusive_access();
    let mut process_inner = process.inner_exclusive_access();
    // a thread cannot wait for itself
    if task_inner.res.as_ref().unwrap().tid == tid {
        return -1;
    }
    let mut exit_code: Option<i32> = None;
    let waited_task = process_inner.tasks[tid].as_ref();
    if let Some(waited_task) = waited_task {
        if let Some(waited_exit_code) = waited_task.inner_exclusive_access().exit_code {
            exit_code = Some(waited_exit_code);
        }
    } else {
        // waited thread does not exist
        return -1;
    }
    if let Some(exit_code) = exit_code {
        // dealloc the exited thread
        process_inner.tasks[tid] = None;
        exit_code
    } else {
        // waited thread has not exited
        -2
    }
}

os::syscall::sync

description: 该 os::syscall::sync 包主要提供任务和进程同步的功能,包括休眠、互斥锁、条件变量和信号量的操作。其中包含的函数有:sys_sleep 用于使当前任务休眠指定毫秒数;sys_condvar_wait 使当前进程在指定条件变量上等待并释放指定互斥锁;sys_mutex_create 创建互斥锁;sys_mutex_lock 锁定互斥锁;sys_condvar_signal 向指定条件变量发送信号;sys_semaphore_create 创建信号量;sys_semaphore_up 提升信号量的值;sys_mutex_unlock 解锁互斥锁;sys_semaphore_down 降低信号量的值;sys_condvar_create 创建条件变量。这些函数提供了基本的并发控制机制,帮助管理任务和进程间的同步和资源共享。

sys_sleep

description: sys_sleep函数的主要功能是使当前任务休眠指定的毫秒数。该函数通过以下步骤实现:

  1. 计算任务的到期时间:通过调用get_time_ms函数获取当前时间(以毫秒为单位),然后加上参数ms(表示需要休眠的毫秒数),得到任务的到期时间expire_ms。
  2. 获取当前任务:使用current_task函数获取当前执行的任务。
  3. 添加定时任务:调用add_timer函数,将任务和到期时间添加到定时器集合中。
  4. 阻塞当前任务并切换到下一个任务:调用block_current_and_run_next函数,实现任务的调度切换。

参数:

  • ms: 休眠时间,以毫秒为单位。

返回值:

  • isize: 固定返回0。
pub fn sys_sleep(ms: usize) -> isize {
    let expire_ms = get_time_ms() + ms;
    let task = current_task().unwrap();
    add_timer(expire_ms, task);
    block_current_and_run_next();
    0
}

sys_mutex_unlock

description: sys_mutex_unlock函数的主要功能是解锁指定的互斥锁。它的工作流程如下:首先,通过current_process函数获取当前进程的控制块,然后通过inner_exclusive_access方法获取进程的内部可独占访问权限。接着,使用互斥锁列表和给定的mutex_id克隆出目标互斥锁的引用。释放进程和内部访问权限后,调用互斥锁的unlock方法来解锁。函数返回值为0,表示操作成功。输入参数为mutex_id,表示要解锁的互斥锁在列表中的索引。

pub fn sys_mutex_unlock(mutex_id: usize) -> isize {
    let process = current_process();
    let process_inner = process.inner_exclusive_access();
    let mutex = Arc::clone(process_inner.mutex_list[mutex_id].as_ref().unwrap());
    drop(process_inner);
    drop(process);
    mutex.unlock();
    0
}

sys_condvar_signal

description: sys_condvar_signal函数的主要功能是发送信号给指定的条件变量(condvar)。该函数首先获取当前任务对应的进程控制块,然后通过进程控制块的内部独占访问获取条件变量列表。利用给定的条件变量ID,从列表中克隆出相应的条件变量,并释放对进程的独占访问。接着,调用条件变量的signal方法发送信号。函数返回值为0,表示操作成功。

pub fn sys_condvar_signal(condvar_id: usize) -> isize {
    let process = current_process();
    let process_inner = process.inner_exclusive_access();
    let condvar = Arc::clone(process_inner.condvar_list[condvar_id].as_ref().unwrap());
    drop(process_inner);
    condvar.signal();
    0
}

sys_mutex_lock

description: sys_mutex_lock函数的主要功能是锁定一个互斥锁。该函数通过获取当前进程的控制块,并访问进程的互斥锁列表来实现这一功能。具体来说,它通过current_process函数获取当前进程,然后使用inner_exclusive_access方法获取进程的内部数据,接着从互斥锁列表中克隆所需的互斥锁,并锁定它。该函数的参数mutex_id用于指定要锁定的互斥锁的索引。返回值是一个整数,表示操作结果。

pub fn sys_mutex_lock(mutex_id: usize) -> isize {
    let process = current_process();
    let process_inner = process.inner_exclusive_access();
    let mutex = Arc::clone(process_inner.mutex_list[mutex_id].as_ref().unwrap());
    drop(process_inner);
    drop(process);
    mutex.lock();
    0
}

sys_condvar_wait

description: sys_condvar_wait函数的主要功能是使当前进程在指定的条件变量上等待,并释放指定的互斥锁。它通过current_process函数获取当前进程的控制块,随后获取进程内部的条件变量和互斥锁列表,根据传入的ID进行克隆。释放进程内部的独占访问后,函数调用条件变量的wait_with_mutex方法进行等待,最终返回0。

入参:

  • condvar_id: 条件变量在列表中的索引
  • mutex_id: 互斥锁在列表中的索引

出参:

  • 等待操作的结果,固定返回0。
pub fn sys_condvar_wait(condvar_id: usize, mutex_id: usize) -> isize {
    let process = current_process();
    let process_inner = process.inner_exclusive_access();
    let condvar = Arc::clone(process_inner.condvar_list[condvar_id].as_ref().unwrap());
    let mutex = Arc::clone(process_inner.mutex_list[mutex_id].as_ref().unwrap());
    drop(process_inner);
    condvar.wait_with_mutex(mutex);
    0
}

sys_condvar_create

description: sys_condvar_create函数的主要功能是为当前进程创建一个条件变量(Condvar),并返回其在进程内的索引。该函数首先通过调用current_process获取当前进程的控制块,然后以独占方式访问该进程的内部数据。它检查进程的条件变量列表,寻找第一个空位以存放新的Condvar实例。如果找到空位,则在该位置创建新的Condvar;如果没有空位,则在列表末尾添加新的Condvar。最终返回该Condvar在列表中的索引。

pub fn sys_condvar_create() -> isize {
    let process = current_process();
    let mut process_inner = process.inner_exclusive_access();
    let id = if let Some(id) = process_inner
        .condvar_list
        .iter()
        .enumerate()
        .find(|(_, item)| item.is_none())
        .map(|(id, _)| id)
    {
        process_inner.condvar_list[id] = Some(Arc::new(Condvar::new()));
        id
    } else {
        process_inner
            .condvar_list
            .push(Some(Arc::new(Condvar::new())));
        process_inner.condvar_list.len() - 1
    };
    id as isize
}

sys_semaphore_up

description: sys_semaphore_up函数的主要功能是提升信号量的值。它首先通过current_process函数获取当前进程的控制块,然后访问进程内部以获取信号量列表中的指定信号量。接着,它使用Arc::clone克隆信号量,并释放对进程内部的独占访问。最后,调用信号量的up方法以增加其值。函数返回值为0,表示操作成功。参数sem_id表示信号量在列表中的索引。

pub fn sys_semaphore_up(sem_id: usize) -> isize {
    let process = current_process();
    let process_inner = process.inner_exclusive_access();
    let sem = Arc::clone(process_inner.semaphore_list[sem_id].as_ref().unwrap());
    drop(process_inner);
    sem.up();
    0
}

sys_semaphore_down

description: sys_semaphore_down函数的主要功能是降低指定信号量的值。它通过信号量的ID(sem_id)来识别信号量。函数首先获取当前进程的控制块,然后通过独占访问获取进程内部数据。接下来,它从进程的信号量列表中克隆出指定的信号量,并在释放进程内部数据后,调用信号量的down方法来降低其值。最后,函数返回0。

pub fn sys_semaphore_down(sem_id: usize) -> isize {
    let process = current_process();
    let process_inner = process.inner_exclusive_access();
    let sem = Arc::clone(process_inner.semaphore_list[sem_id].as_ref().unwrap());
    drop(process_inner);
    sem.down();
    0
}

sys_semaphore_create

description: sys_semaphore_create函数用于为当前进程创建一个新的信号量,并返回其在信号量列表中的索引。该函数首先获取当前进程的控制块,并通过独占访问其内部结构来操作信号量列表。函数检查信号量列表中是否存在空位,如果有,则在该位置创建一个新的信号量并返回其索引;如果没有空位,则在列表末尾添加一个新的信号量,并返回新添加信号量的索引。参数res_count表示信号量的初始计数值。

pub fn sys_semaphore_create(res_count: usize) -> isize {
    let process = current_process();
    let mut process_inner = process.inner_exclusive_access();
    let id = if let Some(id) = process_inner
        .semaphore_list
        .iter()
        .enumerate()
        .find(|(_, item)| item.is_none())
        .map(|(id, _)| id)
    {
        process_inner.semaphore_list[id] = Some(Arc::new(Semaphore::new(res_count)));
        id
    } else {
        process_inner
            .semaphore_list
            .push(Some(Arc::new(Semaphore::new(res_count))));
        process_inner.semaphore_list.len() - 1
    };
    id as isize
}

sys_mutex_create

description: sys_mutex_create函数的主要功能是创建一个互斥锁,并将其添加到当前进程的互斥锁列表中。该函数根据传入参数blocking的值决定创建自旋锁(MutexSpin)还是阻塞锁(MutexBlocking)。如果blocking为false,则创建自旋锁;否则,创建阻塞锁。函数首先获取当前进程控制块,并尝试在互斥锁列表中找到一个空位来存储新创建的锁。如果找到空位,则返回其索引;否则,将锁添加到列表末尾,并返回其索引。

pub fn sys_mutex_create(blocking: bool) -> isize {
    let process = current_process();
    let mutex: Option<Arc<dyn Mutex>> = if !blocking {
        Some(Arc::new(MutexSpin::new()))
    } else {
        Some(Arc::new(MutexBlocking::new()))
    };
    let mut process_inner = process.inner_exclusive_access();
    if let Some(id) = process_inner
        .mutex_list
        .iter()
        .enumerate()
        .find(|(_, item)| item.is_none())
        .map(|(id, _)| id)
    {
        process_inner.mutex_list[id] = mutex;
        id as isize
    } else {
        process_inner.mutex_list.push(mutex);
        process_inner.mutex_list.len() as isize - 1
    }
}

os::task::manager

description: 这个 os::task::manager 包主要用于管理操作系统中的任务和进程。它包含了以下功能:

  • 任务管理
    • wakeup_task:将任务设置为准备好执行的状态并添加到任务管理器中。
    • add_task:将任务添加到全局任务管理器 TASK_MANAGER 中。
    • TaskManager.add:将任务控制块添加到 TaskManagerready_queue 中。
    • fetch_task:从 TASK_MANAGER 中获取任务。
    • TaskManager.fetch:从 TaskManagerready_queue 中弹出并返回第一个任务控制块。
    • TaskManager::new:创建一个新的 TaskManager 实例,包含用于存储任务的 ready_queue
  • 进程管理
    • insert_into_pid2process:将新的进程控制块插入到 PID2PCB 的映射中。
    • pid2process:根据进程 ID 检索对应的进程控制块。
    • remove_from_pid2process:从 PID2PCB 中移除指定的进程控制块。

该包中的关键类型 TaskManager 是一个结构体,包含用于存储任务控制块的 ready_queue,用于管理任务的调度和执行。同时,包中还包含两个全局变量 PID2PCBTASK_MANAGER,分别用于管理进程控制块的映射和任务。此外,该包还依赖一些其他模块的功能来实现其功能。

fetch_task

description: fetch_task函数的主要功能是从TASK_MANAGER中获取一个任务。TASK_MANAGER是一个全局变量,提供了对任务的独占访问功能。函数返回一个可能包含TaskControlBlock的Arc指针,以便安全地共享任务控制块。

pub fn fetch_task() -> Option<Arc<TaskControlBlock>> {
    TASK_MANAGER.exclusive_access().fetch()
}

TaskManager.add

description: add函数用于将一个任务控制块添加到TaskManager的ready_queue中。该函数的主要功能是管理任务的调度和执行。入参为task,它是一个指向TaskControlBlock的引用计数智能指针(Arc)。TaskControlBlock是一个结构体,包含不可变的进程控制块引用、内核堆栈和可变的任务控制块内部状态。通过调用add函数,任务被放入ready_queue的队尾,以便后续的调度和执行。

pub fn add(&mut self, task: Arc<TaskControlBlock>) {
        self.ready_queue.push_back(task);
    }

remove_from_pid2process

description: remove_from_pid2process函数的主要功能是从PID2PCB的映射中移除指定的进程ID(pid)对应的进程控制块。如果指定的pid在映射中找不到,该函数将触发一个panic,提示无法在pid2task中找到该pid。入参为pid,它表示要移除的进程ID。

pub fn remove_from_pid2process(pid: usize) {
    let mut map = PID2PCB.exclusive_access();
    if map.remove(&pid).is_none() {
        panic!("cannot find pid {} in pid2task!", pid);
    }
}

TaskManager.fetch

description: fetch方法用于从TaskManager的ready_queue中弹出并返回第一个任务控制块(TaskControlBlock)。如果队列为空,则返回None。

出参:

  • 返回一个Option类型的结果,其中包含一个Arc,表示任务控制块的引用计数智能指针。
pub fn fetch(&mut self) -> Option<Arc<TaskControlBlock>> {
        self.ready_queue.pop_front()
    }

TaskManager::new

description: new函数用于创建一个新的TaskManager实例。该实例包含一个名为ready_queue的字段,类型为VecDeque。这个字段用于存储任务控制块的双端队列。TaskManager的主要功能是管理任务的调度和执行,而VecDeque允许在队列两端高效地进行元素操作。

pub fn new() -> Self {
        Self {
            ready_queue: VecDeque::new(),
        }
    }

wakeup_task

description: wakeup_task函数的主要功能是将传入的任务设置为准备好执行的状态,并将其添加到任务管理器中。它通过对任务控制块的独占访问来修改任务的状态,确保任务处于"Ready"状态后,将其添加到全局任务管理器中进行管理。入参为一个任务控制块的引用,类型为Arc

pub fn wakeup_task(task: Arc<TaskControlBlock>) {
    let mut task_inner = task.inner_exclusive_access();
    task_inner.task_status = TaskStatus::Ready;
    drop(task_inner);
    add_task(task);
}

insert_into_pid2process

description: insert_into_pid2process函数用于将一个新的进程控制块(Process Control Block)插入到PID2PCB的映射中。参数包括:

  • pid: 进程ID,用于标识进程。
  • process: 进程控制块的引用,包含进程的相关信息。

该函数通过调用PID2PCB的exclusive_access方法,将给定的pid和process插入到映射中,从而管理和操作与进程ID相关联的进程控制块。

pub fn insert_into_pid2process(pid: usize, process: Arc<ProcessControlBlock>) {
    PID2PCB.exclusive_access().insert(pid, process);
}

add_task

description: add_task函数用于将一个任务添加到TASK_MANAGER中。入参是一个Arc类型的任务控制块,该类型的任务通过TASK_MANAGER的独占访问功能被添加到任务管理器中。TASK_MANAGER是一个全局变量,负责管理任务并提供对任务的独占访问。

pub fn add_task(task: Arc<TaskControlBlock>) {
    TASK_MANAGER.exclusive_access().add(task);
}

pid2process

description: pid2process函数的主要功能是根据给定的进程ID(pid)从全局变量PID2PCB中检索对应的进程控制块(Process Control Block)。如果找到对应的进程控制块,则返回其Arc智能指针的克隆;如果未找到,则返回None。参数pid表示要查找的进程ID。

pub fn pid2process(pid: usize) -> Option<Arc<ProcessControlBlock>> {
    let map = PID2PCB.exclusive_access();
    map.get(&pid).map(Arc::clone)
}

os::task

description: 该 os::task 包主要用于管理操作系统中的任务调度和信号处理。其核心功能包括:

  • 任务调度方面,提供了多种函数用于控制任务的执行和切换,如 exit_current_and_run_next 退出当前任务并运行下一个任务,check_signals_of_current 检查当前进程的信号错误状态,suspend_current_and_run_next 暂停当前任务并运行下一个任务,block_current_and_run_next 阻塞当前任务并切换到下一个任务,add_initproc 创建新的初始化进程实例等。
  • 信号处理方面,current_add_signal 函数用于为当前进程添加信号。

此外,该包中还定义了一个全局变量 INITPROC,用于代表初始化进程,负责接管孤儿进程以确保系统稳定性。该包依赖于多个其他模块的功能来实现其任务调度和信号处理的功能。

suspend_current_and_run_next

description: suspend_current_and_run_next函数的主要功能是暂停当前任务并运行下一个任务。该函数首先获取当前运行的任务,确保有一个应用程序在运行。然后,它独占地访问当前任务的任务控制块(TCB),修改其状态为“Ready”,并将其释放。接着,该任务被推回到准备队列中。最后,函数调用调度程序,进行任务上下文的切换,以便运行下一个任务。

pub fn suspend_current_and_run_next() {
    // There must be an application running.
    let task = take_current_task().unwrap();

    // ---- access current TCB exclusively
    let mut task_inner = task.inner_exclusive_access();
    let task_cx_ptr = &mut task_inner.task_cx as *mut TaskContext;
    // Change status to Ready
    task_inner.task_status = TaskStatus::Ready;
    drop(task_inner);
    // ---- release current TCB

    // push back to ready queue.
    add_task(task);
    // jump to scheduling cycle
    schedule(task_cx_ptr);
}

block_current_task

description: block_current_task函数的主要功能是阻塞当前任务并返回任务上下文的指针。函数首先通过调用take_current_task函数获取当前任务控制块,然后通过独占访问任务的内部状态,将任务状态设置为Blocked,最后返回任务上下文的指针。

入参:无

出参:返回一个指向任务上下文的指针,用于保存任务的上下文信息。

/// This function must be followed by a schedule
pub fn block_current_task() -> *mut TaskContext {
    let task = take_current_task().unwrap();
    let mut task_inner = task.inner_exclusive_access();
    task_inner.task_status = TaskStatus::Blocked;
    &mut task_inner.task_cx as *mut TaskContext
}

add_initproc

description: add_initproc函数的主要功能是克隆一个名为INITPROC的全局变量。INITPROC通常在操作系统或任务管理系统中代表初始化进程,扮演特殊角色,负责接管孤儿进程,以确保正确管理和资源释放。在这个函数中,克隆操作用于创建新的初始化进程实例,保障系统的稳定性。

pub fn add_initproc() {
    let _initproc = INITPROC.clone();
}

current_add_signal

description: current_add_signal函数的主要功能是为当前进程添加信号。它通过调用current_process函数获取当前进程的控制块,然后通过inner_exclusive_access方法获取对进程的独占访问权限,最后将传入的信号与进程当前的信号进行按位或操作,以更新进程的信号状态。

参数:

  • signal: 要添加到当前进程的信号标志。
pub fn current_add_signal(signal: SignalFlags) {
    let process = current_process();
    let mut process_inner = process.inner_exclusive_access();
    process_inner.signals |= signal;
}

block_current_and_run_next

description: block_current_and_run_next函数的主要功能是阻塞当前任务并切换到下一个任务。该函数首先调用block_current_task函数来阻塞当前任务,并获取当前任务的上下文指针。然后,它调用schedule函数,使用获取的任务上下文指针进行任务切换。这个过程确保当前任务被正确阻塞,并且下一个任务能够顺利运行,从而实现任务的切换和调度。

pub fn block_current_and_run_next() {
    let task_cx_ptr = block_current_task();
    schedule(task_cx_ptr);
}

exit_current_and_run_next

description: exit_current_and_run_next 函数的主要功能是退出当前正在运行的任务,并在任务列表中运行下一个任务。该函数接收一个整数类型的参数 exit_code,用于记录任务的退出状态。函数首先获取当前任务并访问其内部数据,记录退出码,并在必要时处理进程的终止,特别是当当前任务是主线程时,将其标记为僵尸进程并清理资源。最后,函数调用调度器以切换到下一个任务。

/// Exit the current 'Running' task and run the next task in task list.
pub fn exit_current_and_run_next(exit_code: i32) {
    let task = take_current_task().unwrap();
    let mut task_inner = task.inner_exclusive_access();
    let process = task.process.upgrade().unwrap();
    let tid = task_inner.res.as_ref().unwrap().tid;
    // record exit code
    task_inner.exit_code = Some(exit_code);
    task_inner.res = None;
    // here we do not remove the thread since we are still using the kstack
    // it will be deallocated when sys_waittid is called
    drop(task_inner);
    drop(task);
    // however, if this is the main thread of current process
    // the process should terminate at once
    if tid == 0 {
        let pid = process.getpid();
        if pid == IDLE_PID {
            println!(
                "[kernel] Idle process exit with exit_code {} ...",
                exit_code
            );
            if exit_code != 0 {
                //crate::sbi::shutdown(255); //255 == -1 for err hint
                shutdown(true);
            } else {
                //crate::sbi::shutdown(0); //0 for success hint
                shutdown(false);
            }
        }
        remove_from_pid2process(pid);
        let mut process_inner = process.inner_exclusive_access();
        // mark this process as a zombie process
        process_inner.is_zombie = true;
        // record exit code of main process
        process_inner.exit_code = exit_code;

        {
            // move all child processes under init process
            let mut initproc_inner = INITPROC.inner_exclusive_access();
            for child in process_inner.children.iter() {
                child.inner_exclusive_access().parent = Some(Arc::downgrade(&INITPROC));
                initproc_inner.children.push(child.clone());
            }
        }

        // deallocate user res (including tid/trap_cx/ustack) of all threads
        // it has to be done before we dealloc the whole memory_set
        // otherwise they will be deallocated twice
        let mut recycle_res = Vec::<TaskUserRes>::new();
        for task in process_inner.tasks.iter().filter(|t| t.is_some()) {
            let task = task.as_ref().unwrap();
            let mut task_inner = task.inner_exclusive_access();
            if let Some(res) = task_inner.res.take() {
                recycle_res.push(res);
            }
        }
        // dealloc_tid and dealloc_user_res require access to PCB inner, so we
        // need to collect those user res first, then release process_inner
        // for now to avoid deadlock/double borrow problem.
        drop(process_inner);
        recycle_res.clear();

        let mut process_inner = process.inner_exclusive_access();
        process_inner.children.clear();
        // deallocate other data in user space i.e. program code/data section
        process_inner.memory_set.recycle_data_pages();
        // drop file descriptors
        process_inner.fd_table.clear();
        // Remove all tasks except for the main thread itself.
        // This is because we are still using the kstack under the TCB
        // of the main thread. This TCB, including its kstack, will be
        // deallocated when the process is reaped via waitpid.
        while process_inner.tasks.len() > 1 {
            process_inner.tasks.pop();
        }
    }
    drop(process);
    // we do not have to save task context
    let mut _unused = TaskContext::zero_init();
    schedule(&mut _unused as *mut _);
}

check_signals_of_current

description: check_signals_of_current函数的主要功能是检查当前进程的信号错误状态。该函数首先获取当前正在执行的进程控制块,然后通过对进程的内部进行独占访问,调用signals的check_error方法来检查信号错误。函数返回一个可选的元组,其中包含一个整数和一个静态字符串,表示检查到的信号错误信息。

pub fn check_signals_of_current() -> Option<(i32, &'static str)> {
    let process = current_process();
    let process_inner = process.inner_exclusive_access();
    process_inner.signals.check_error()
}

os::mm::page_table

description: os::mm::page_table包主要用于管理虚拟内存中的页表映射关系,实现虚拟地址到物理地址的映射,以及对页表项的管理和操作。该包提供了多种函数和结构体,包括PageTable用于管理页表映射关系,PageTableEntry表示页表项,UserBuffer管理多个静态可变字节缓冲区等。关键函数涵盖了页表项的查找、创建、映射、解除映射、地址转换等操作,以及对页表项权限和属性的检查。同时,还包括了一些与缓冲区操作和地址转换相关的函数。通过这些功能,该包能够有效管理虚拟地址空间的映射关系,确保内存访问的安全性和有效性。

PageTable::new

description: new函数的主要功能是创建并初始化一个PageTable实例。它首先调用frame_alloc函数分配一个内存帧,并确保分配成功。然后,使用该内存帧的物理页号作为root_ppn,并将该帧封装在一个FrameTracker向量中,初始化PageTable的frames字段。最终返回一个新的PageTable实例。

pub fn new() -> Self {
        let frame = frame_alloc().unwrap();
        PageTable {
            root_ppn: frame.ppn,
            frames: vec![frame],
        }
    }

translated_refmut

description: translated_refmut函数的主要功能是根据给定的token和指针ptr,获取一个静态可变引用。函数通过以下步骤实现:

  1. 使用PageTable::from_token方法,从token生成一个PageTable实例。PageTable用于管理虚拟地址到物理地址的映射关系。
  2. 将传入的指针ptr转换为usize类型的虚拟地址va。
  3. 将虚拟地址va转换为VirtAddr类型,并通过page_table调用translate_va方法进行地址转换。
  4. 最终,使用unwrap和get_mut方法获取该地址对应的可变引用。

入参:

  • token: 用于初始化PageTable的标识符。
  • ptr: 指向某个对象的裸指针。
pub fn translated_refmut<T>(token: usize, ptr: *mut T) -> &'static mut T {
    let page_table = PageTable::from_token(token);
    let va = ptr as usize;
    page_table
        .translate_va(VirtAddr::from(va))
        .unwrap()
        .get_mut()
}

PageTable::from_token

description: from_token函数是PageTable结构体的一个构造方法,用于从用户空间获取参数并初始化PageTable实例。其主要功能是通过传入的satp参数创建一个新的PageTable对象。

入参:

  • satp: 一个usize类型的值,用于表示用户空间的参数。

在函数内部,通过位操作从satp中提取有效的物理页号,并使用PhysPageNum::from函数将其转换为PhysPageNum实例,赋值给root_ppn字段。同时,frames字段被初始化为空的FrameTracker向量。

/// Temporarily used to get arguments from user space.
    pub fn from_token(satp: usize) -> Self {
        Self {
            root_ppn: PhysPageNum::from(satp & ((1usize << 44) - 1)),
            frames: Vec::new(),
        }
    }

PageTableEntry.flags

description: flags方法的主要功能是从PageTableEntry结构体中提取位标志。PageTableEntry结构体包含一个名为bits的字段,该方法通过将bits字段转换为u8类型,然后使用PTEFlags的from_bits方法将其转换为PTEFlags类型。该方法返回一个PTEFlags类型的实例。由于PTEFlags的具体功能或用途未提供,无法进一步说明。

pub fn flags(&self) -> PTEFlags {
        PTEFlags::from_bits(self.bits as u8).unwrap()
    }

UserBuffer.len

description: len函数用于计算UserBuffer结构体中所有缓冲区的总长度。入参为self,表示当前UserBuffer实例。出参为总长度,类型为usize。

pub fn len(&self) -> usize {
        let mut total: usize = 0;
        for b in self.buffers.iter() {
            total += b.len();
        }
        total
    }

PageTable.translate_va

description: translate_va函数的主要功能是将虚拟地址(VirtAddr)转换为物理地址(PhysAddr)。该函数接收一个虚拟地址作为参数,并返回一个可选的物理地址。

参数:

  • va: VirtAddr类型,表示虚拟地址。它能够存储和处理地址信息,并提供相关操作如计算虚拟页号和页面内偏移量。

translate_va函数通过查找页表条目并计算偏移量,返回与给定虚拟地址对应的物理地址(如果存在)。如果找不到对应的物理地址,则返回None。

pub fn translate_va(&self, va: VirtAddr) -> Option<PhysAddr> {
        self.find_pte(va.clone().floor()).map(|pte| {
            let aligned_pa: PhysAddr = pte.ppn().into();
            let offset = va.page_offset();
            let aligned_pa_usize: usize = aligned_pa.into();
            (aligned_pa_usize + offset).into()
        })
    }

PageTable.unmap

description: unmap函数用于解除PageTable中指定虚拟页号(vpn)的页表项映射。具体流程如下:

  • 通过find_pte方法查找对应的页表项(pte)。
  • 使用assert确保页表项有效,否则提示指定的vpn在解除映射前无效。
  • 将该页表项设置为空,即通过PageTableEntry::empty方法将其bits字段初始化为0。

入参:

  • vpn: VirtPageNum类型,表示虚拟页号。

此函数的主要功能是确保某个虚拟页号在页表中不再有效映射。

#[allow(unused)]
    pub fn unmap(&mut self, vpn: VirtPageNum) {
        let pte = self.find_pte(vpn).unwrap();
        assert!(pte.is_valid(), "vpn {:?} is invalid before unmapping", vpn);
        *pte = PageTableEntry::empty();
    }

PageTableEntry::empty

description: empty函数用于创建一个PageTableEntry类型的新实例,并将其bits字段初始化为0。这个新实例代表一个空的或默认的页表项,没有任何标志或信息设置。

pub fn empty() -> Self {
        PageTableEntry { bits: 0 }
    }

translated_ref

description: translated_ref函数的主要功能是通过页表将虚拟地址转换为物理地址,并获取该地址的引用。它接受两个参数:

  • token: 一个usize类型的值,用于初始化PageTable实例。
  • ptr: 一个指向类型T的常量指针,表示虚拟地址。

函数流程:

  1. 使用token参数通过PageTable::from_token方法创建一个PageTable实例。
  2. 将ptr转换为VirtAddr类型的实例。
  3. 调用PageTable的方法translate_va进行地址转换。
  4. 使用unwrap和get_ref方法获取转换后的物理地址的引用。
pub fn translated_ref<T>(token: usize, ptr: *const T) -> &'static T {
    let page_table = PageTable::from_token(token);
    page_table
        .translate_va(VirtAddr::from(ptr as usize))
        .unwrap()
        .get_ref()
}

UserBufferIterator.next

description: next函数用于迭代UserBufferIterator中的缓冲区。它返回一个Option类型的值,表示当前缓冲区中的下一个元素。如果当前缓冲区已经迭代完毕,函数返回None,否则返回当前元素的指针,并更新索引以指向下一个元素。当当前缓冲区的所有元素都被访问后,函数会移动到下一个缓冲区。

fn next(&mut self) -> Option<Self::Item> {
        if self.current_buffer >= self.buffers.len() {
            None
        } else {
            let r = &mut self.buffers[self.current_buffer][self.current_idx] as *mut _;
            if self.current_idx + 1 == self.buffers[self.current_buffer].len() {
                self.current_idx = 0;
                self.current_buffer += 1;
            } else {
                self.current_idx += 1;
            }
            Some(r)
        }
    }

PageTableEntry.is_valid

description: is_valid函数用于检查PageTableEntry的有效性。具体来说,它通过与PTEFlags中的V标志进行位与运算来判断页表项是否有效。如果结果不为空,则返回true,表示该页表项有效。

pub fn is_valid(&self) -> bool {
        (self.flags() & PTEFlags::V) != PTEFlags::empty()
    }

PageTableEntry::new

description: new函数用于创建一个PageTableEntry实例。该函数接受两个参数:

  • ppn: 代表物理页号(PhysPageNum类型),用于标识物理内存中的一个页。
  • flags: 代表页表项的标志(PTEFlags类型),用于存储页表项的各种属性。

函数内部将物理页号左移10位,并与flags的位信息进行按位或运算,结果存储在PageTableEntry的bits字段中。该操作整合了物理页号与标志信息,便于后续的页表管理和操作。

pub fn new(ppn: PhysPageNum, flags: PTEFlags) -> Self {
        PageTableEntry {
            bits: ppn.0 << 10 | flags.bits as usize,
        }
    }

PageTable.find_pte_create

description: find_pte_create函数的主要功能是查找或创建页表项。该函数接受一个虚拟页号(VirtPageNum)作为参数,并返回一个可变引用的页表项(PageTableEntry)的可选值。函数通过遍历虚拟页号的索引,检查每个页表项的有效性。如果无效,分配一个新的内存帧并创建新的页表项。参数vpn表示虚拟页号,用于决定要查找或创建的页表项的位置。返回值是页表项的可变引用,或无效值。

fn find_pte_create(&mut self, vpn: VirtPageNum) -> Option<&mut PageTableEntry> {
        let idxs = vpn.indexes();
        let mut ppn = self.root_ppn;
        let mut result: Option<&mut PageTableEntry> = None;
        for (i, idx) in idxs.iter().enumerate() {
            let pte = &mut ppn.get_pte_array()[*idx];
            if i == 2 {
                result = Some(pte);
                break;
            }
            if !pte.is_valid() {
                let frame = frame_alloc().unwrap();
                *pte = PageTableEntry::new(frame.ppn, PTEFlags::V);
                self.frames.push(frame);
            }
            ppn = pte.ppn();
        }
        result
    }

PageTableEntry.readable

description: readable函数的主要功能是检查PageTableEntry是否具有可读权限。它通过对标志进行按位与操作,判断结果是否与空标志集不同,从而确定可读性。

pub fn readable(&self) -> bool {
        (self.flags() & PTEFlags::R) != PTEFlags::empty()
    }

PageTable.find_pte

description: find_pte函数用于在PageTable中查找与给定虚拟页号相关联的页表项。它通过遍历虚拟页号的索引序列,逐步访问页表的各个层次,并检查每个页表项的有效性。当到达第三层时,返回对应的页表项;如果某个页表项无效,函数返回None。

入参:

  • vpn: VirtPageNum类型,表示虚拟页号,用于查找对应的页表项。

出参:

  • Option类型,返回一个可变引用的PageTableEntry,如果未找到有效项则返回None。
fn find_pte(&self, vpn: VirtPageNum) -> Option<&mut PageTableEntry> {
        let idxs = vpn.indexes();
        let mut ppn = self.root_ppn;
        let mut result: Option<&mut PageTableEntry> = None;
        for (i, idx) in idxs.iter().enumerate() {
            let pte = &mut ppn.get_pte_array()[*idx];
            if i == 2 {
                result = Some(pte);
                break;
            }
            if !pte.is_valid() {
                return None;
            }
            ppn = pte.ppn();
        }
        result
    }

translated_str

description: translated_str函数的主要功能是从其他地址空间加载一个字符串到内核空间,并且不包含结束符\\0。函数参数包括:

  • token: 一个usize类型的值,用于创建PageTable实例,从而访问用户空间内的内存。
  • ptr: 一个指向u8类型数据的指针,表示待加载字符串的起始地址。

函数通过循环遍历指针地址,将每个字符加载到内核空间的字符串中,直到遇到结束符\\0为止。

/// Load a string from other address spaces into kernel space without an end `\0`.
pub fn translated_str(token: usize, ptr: *const u8) -> String {
    let page_table = PageTable::from_token(token);
    let mut string = String::new();
    let mut va = ptr as usize;
    loop {
        let ch: u8 = *(page_table
            .translate_va(VirtAddr::from(va))
            .unwrap()
            .get_mut());
        if ch == 0 {
            break;
        }
        string.push(ch as char);
        va += 1;
    }
    string
}

PageTableEntry.ppn

description: ppn函数用于从PageTableEntry中提取物理页号。入参无,返回一个PhysPageNum类型的值。该函数通过将bits字段右移10位,并与一个44位的掩码进行按位与操作,最后将结果转换为PhysPageNum类型。

pub fn ppn(&self) -> PhysPageNum {
        (self.bits >> 10 & ((1usize << 44) - 1)).into()
    }

PageTable.token

description: token方法返回一个无符号整数,用于表示PageTable的标识符。其计算方式是通过将数字8左移60位,然后与root_ppn的值进行按位或操作。

pub fn token(&self) -> usize {
        8usize << 60 | self.root_ppn.0
    }

translated_byte_buffer

description: translated_byte_buffer函数的主要功能是将给定的内存指针和长度转换为一组可变的字节数组切片。该函数通过使用传入的token参数创建一个PageTable实例,并利用页表来翻译虚拟地址到物理地址。函数遍历给定的内存区域,将每个虚拟地址转换为物理地址,并将其对应的字节数组切片添加到返回的Vec中。参数包括token(用于初始化PageTable)、ptr(指向内存的指针)和len(表示内存区域的长度)。

pub fn translated_byte_buffer(token: usize, ptr: *const u8, len: usize) -> Vec<&'static mut [u8]> {
    let page_table = PageTable::from_token(token);
    let mut start = ptr as usize;
    let end = start + len;
    let mut v = Vec::new();
    while start < end {
        let start_va = VirtAddr::from(start);
        let mut vpn = start_va.floor();
        let ppn = page_table.translate(vpn).unwrap().ppn();
        vpn.step();
        let mut end_va: VirtAddr = vpn.into();
        end_va = end_va.min(VirtAddr::from(end));
        if end_va.page_offset() == 0 {
            v.push(&mut ppn.get_bytes_array()[start_va.page_offset()..]);
        } else {
            v.push(&mut ppn.get_bytes_array()[start_va.page_offset()..end_va.page_offset()]);
        }
        start = end_va.into();
    }
    v
}

PageTable.translate

description: translate函数的主要功能是将虚拟页号(VirtPageNum)转换为页表项(PageTableEntry)。它通过调用内部的find_pte函数来查找对应的页表项,并返回一个可选类型(Option)的页表项。如果找到相应的页表项,则返回该项的副本,否则返回None。

入参:

  • vpn: VirtPageNum,表示虚拟页号,用于查找对应的页表项。

出参:

  • Option: 返回一个可选的页表项,可能包含页表项或为空。
pub fn translate(&self, vpn: VirtPageNum) -> Option<PageTableEntry> {
        self.find_pte(vpn).map(|pte| *pte)
    }

UserBuffer.into_iter

description: into_iter函数的主要功能是将UserBuffer转换为一个UserBufferIterator,以便迭代UserBuffer中包含的多个静态可变字节缓冲区。没有额外的参数,返回一个UserBufferIterator,该迭代器管理缓冲区的迭代过程。

fn into_iter(self) -> Self::IntoIter {
        UserBufferIterator {
            buffers: self.buffers,
            current_buffer: 0,
            current_idx: 0,
        }
    }

UserBuffer::new

description: new函数的主要功能是创建一个UserBuffer实例。它接收一个包含多个静态可变字节数组的向量,作为参数,并将其存储在UserBuffer结构体的buffers字段中,从而初始化UserBuffer对象。参数意义:buffers参数是一个向量,包含多个静态可变字节数组,用于处理和存储数据。

pub fn new(buffers: Vec<&'static mut [u8]>) -> Self {
        Self { buffers }
    }

PageTableEntry.writable

description: writable函数的主要功能是检查PageTableEntry对象是否具有写权限。通过调用flags方法,该函数获取当前对象的标志位,并与PTEFlags::W进行按位与操作。如果结果不为空,则表示该对象具有写权限,函数返回true;否则返回false。

pub fn writable(&self) -> bool {
        (self.flags() & PTEFlags::W) != PTEFlags::empty()
    }

PageTable.map

description: map函数用于在页表中将虚拟页号(VirtPageNum)映射到物理页号(PhysPageNum),并设置页表项的标志(PTEFlags)。它首先通过find_pte_create方法查找或创建与虚拟页号对应的页表项,并确保该页表项之前未被映射。然后,它使用PageTableEntry::new函数创建新的页表项,其中包含物理页号和标志位(包括有效位V),以便在页表中管理映射关系。

#[allow(unused)]
    pub fn map(&mut self, vpn: VirtPageNum, ppn: PhysPageNum, flags: PTEFlags) {
        let pte = self.find_pte_create(vpn).unwrap();
        assert!(!pte.is_valid(), "vpn {:?} is mapped before mapping", vpn);
        *pte = PageTableEntry::new(ppn, flags | PTEFlags::V);
    }

PageTableEntry.executable

description: executable函数的主要功能是检查PageTableEntry对象是否具有可执行标志。它通过调用flags方法并与PTEFlags::X进行按位与操作来判断结果是否不为空,从而确定该对象是否可执行。没有额外的参数或返回值需要说明。

pub fn executable(&self) -> bool {
        (self.flags() & PTEFlags::X) != PTEFlags::empty()
    }

os::trap

description: 该 os::trap 包主要用于处理内核的陷阱和中断,其核心功能包括:

  • 初始化内核的陷阱处理程序(通过 initset_kernel_trap_entry 等函数)。
  • 处理从内核传来的各种陷阱(如 trap_from_kerneltrap_handler 函数)。
  • 启用和禁用监督者中断(如 enable_supervisor_interruptdisable_supervisor_interrupt 函数)。
  • 启用定时器中断(通过 enable_timer_interrupt 函数)。
  • 设置用户陷阱处理程序的入口地址(通过 set_user_trap_entry 函数)。
  • 进行系统级的异常处理,如设置并跳转到用户态陷阱处理程序的入口(通过 trap_return 函数)。
  • 恢复某种状态或数据(通过 __restore 函数,具体操作细节不详)。
  • 在内核中设置陷阱处理程序的入口(通过 __alltraps__alltraps_k 函数)。

__restore

description: __restore函数的主要功能是恢复某种状态或数据。由于没有提供参数或返回值信息,这个函数可能是一个无参函数,执行特定的恢复操作。具体操作细节不详。

fn __restore();

disable_supervisor_interrupt

description: disable_supervisor_interrupt函数的主要功能是禁用监督者中断。该函数通过调用sstatus::clear_sie()方法来实现这一目的,sstatus::clear_sie()方法用于清除状态寄存器中的特定位,从而禁用中断。由于该操作涉及底层硬件寄存器的直接操作,因此使用了unsafe块来标识可能存在不安全操作的代码。

fn disable_supervisor_interrupt() {
    unsafe {
        sstatus::clear_sie();
    }
}

trap_handler

description: trap_handler函数的主要功能是处理不同类型的陷阱事件。它首先设置内核的陷阱入口,然后根据scause的值区分不同的陷阱类型进行处理。在用户环境调用异常时,它调整程序计数器并启用监督者中断,执行系统调用并更新返回值。在存储、指令、加载等异常时,添加SIGSEGV信号。在非法指令异常时,添加SIGILL信号。对于计时器和外部中断,则分别设置下一个触发时间或调用中断处理程序。函数最后检查信号,必要时退出当前任务并运行下一个。

#[no_mangle]
pub fn trap_handler() -> ! {
    set_kernel_trap_entry();
    let scause = scause::read();
    let stval = stval::read();
    // println!("into {:?}", scause.cause());
    match scause.cause() {
        Trap::Exception(Exception::UserEnvCall) => {
            // jump to next instruction anyway
            let mut cx = current_trap_cx();
            cx.sepc += 4;

            enable_supervisor_interrupt();

            // get system call return value
            let result = syscall(cx.x[17], [cx.x[10], cx.x[11], cx.x[12]]);
            // cx is changed during sys_exec, so we have to call it again
            cx = current_trap_cx();
            cx.x[10] = result as usize;
        }
        Trap::Exception(Exception::StoreFault)
        | Trap::Exception(Exception::StorePageFault)
        | Trap::Exception(Exception::InstructionFault)
        | Trap::Exception(Exception::InstructionPageFault)
        | Trap::Exception(Exception::LoadFault)
        | Trap::Exception(Exception::LoadPageFault) => {
            /*
            println!(
                "[kernel] {:?} in application, bad addr = {:#x}, bad instruction = {:#x}, kernel killed it.",
                scause.cause(),
                stval,
                current_trap_cx().sepc,
            );
            */
            current_add_signal(SignalFlags::SIGSEGV);
        }
        Trap::Exception(Exception::IllegalInstruction) => {
            current_add_signal(SignalFlags::SIGILL);
        }
        Trap::Interrupt(Interrupt::SupervisorTimer) => {
            set_next_trigger();
            check_timer();
            suspend_current_and_run_next();
        }
        Trap::Interrupt(Interrupt::SupervisorExternal) => {
            crate::board::irq_handler();
        }
        _ => {
            panic!(
                "Unsupported trap {:?}, stval = {:#x}!",
                scause.cause(),
                stval
            );
        }
    }
    // check signals
    if let Some((errno, msg)) = check_signals_of_current() {
        println!("[kernel] {}", msg);
        exit_current_and_run_next(errno);
    }
    trap_return();
}

__alltraps_k

description: __alltraps_k函数的主要功能是作为内核中陷阱处理程序的入口。它与set_kernel_trap_entry函数相关,后者负责在内核中设置陷阱处理程序。通过计算和调整相关地址,确保内核能够正确处理从内核传来的陷阱。

fn __alltraps_k();

enable_supervisor_interrupt

description: enable_supervisor_interrupt函数的主要功能是启用监督者中断。它通过调用sstatus::set_sie()来实现这一点,该调用在unsafe块中执行,表明该操作可能绕过Rust语言的安全检查。此函数没有参数和返回值。

fn enable_supervisor_interrupt() {
    unsafe {
        sstatus::set_sie();
    }
}

set_user_trap_entry

description: set_user_trap_entry 函数的主要功能是设置用户陷阱处理程序的入口地址。这个函数使用了不安全代码块,通过调用 stvec::write 函数,将 TRAMPOLINE 常量作为地址,并以 TrapMode::Direct 模式写入。这意味着陷阱处理程序将直接处理异常或中断,而不是使用向量化处理。

fn set_user_trap_entry() {
    unsafe {
        stvec::write(TRAMPOLINE as usize, TrapMode::Direct);
    }
}

trap_return

description: trap_return函数的主要功能是设置并跳转到用户态陷阱处理程序的入口。它首先禁用监督者中断,然后设置用户陷阱处理程序的入口地址。接着,获取当前任务的用户态Trap上下文的虚拟地址和用户令牌。通过计算,确定恢复地址,并使用内联汇编指令跳转到该地址。该函数不接受参数,也不返回值,直接进行系统级的异常处理操作。

#[no_mangle]
pub fn trap_return() -> ! {
    disable_supervisor_interrupt();
    set_user_trap_entry();
    let trap_cx_user_va = current_trap_cx_user_va();
    let user_satp = current_user_token();
    extern "C" {
        fn __alltraps();
        fn __restore();
    }
    let restore_va = __restore as usize - __alltraps as usize + TRAMPOLINE;
    //println!("before return");
    unsafe {
        asm!(
            "fence.i",
            "jr {restore_va}",
            restore_va = in(reg) restore_va,
            in("a0") trap_cx_user_va,
            in("a1") user_satp,
            options(noreturn)
        );
    }
}

set_kernel_trap_entry

description: set_kernel_trap_entry函数的主要功能是在内核中设置陷阱处理程序的入口。通过计算__alltraps_k_va的地址,它使用TRAMPOLINE常量来调整__alltraps和__alltraps_k之间的偏移。然后,它调用stvec::write和sscratch::write函数,以Direct模式将计算出的地址写入CSR寄存器,并将trap_from_kernel函数的地址写入sscratch寄存器。这确保了内核能够正确处理从内核传来的陷阱。

fn set_kernel_trap_entry() {
    extern "C" {
        fn __alltraps();
        fn __alltraps_k();
    }
    let __alltraps_k_va = __alltraps_k as usize - __alltraps as usize + TRAMPOLINE;
    unsafe {
        stvec::write(__alltraps_k_va, TrapMode::Direct);
        sscratch::write(trap_from_kernel as usize);
    }
}

trap_from_kernel

description: trap_from_kernel函数的主要功能是处理从内核传来的陷阱。该函数读取scause和stval寄存器的值,并根据scause的原因进行不同的处理:

  1. 如果是SupervisorExternal中断,则调用irq_handler函数处理中断。
  2. 如果是SupervisorTimer中断,则设置下一个计时器触发,并检查计时器,但不会立即调度。
  3. 对于其他未支持的陷阱,函数会触发恐慌并输出相应的错误信息。

参数:

  • _trap_cx: 是一个TrapContext的引用,表示陷阱上下文。
#[no_mangle]
pub fn trap_from_kernel(_trap_cx: &TrapContext) {
    let scause = scause::read();
    let stval = stval::read();
    match scause.cause() {
        Trap::Interrupt(Interrupt::SupervisorExternal) => {
            crate::board::irq_handler();
        }
        Trap::Interrupt(Interrupt::SupervisorTimer) => {
            set_next_trigger();
            check_timer();
            // do not schedule now
        }
        _ => {
            panic!(
                "Unsupported trap from kernel: {:?}, stval = {:#x}!",
                scause.cause(),
                stval
            );
        }
    }
}

init

description: init 函数的主要功能是初始化内核的陷阱处理程序。它通过调用 set_kernel_trap_entry 函数来实现这一点。set_kernel_trap_entry 函数的作用是在内核中设置陷阱处理程序的入口,确保内核能够正确处理从内核传来的陷阱。

pub fn init() {
    set_kernel_trap_entry();
}

enable_timer_interrupt

description: enable_timer_interrupt函数的主要功能是启用定时器中断。该函数通过调用sie::set_stimer()方法来实现这一功能,其中涉及到不安全代码块,以直接与底层硬件交互。

pub fn enable_timer_interrupt() {
    unsafe {
        sie::set_stimer();
    }
}

__alltraps

description: __alltraps函数的主要功能是在内核中设置陷阱处理程序的入口。通过相关函数set_kernel_trap_entry,计算得到__alltraps_k_va的地址,并使用TRAMPOLINE常量调整偏移。这些步骤确保内核能够正确处理从内核传来的陷阱。

fn __alltraps();

os::mm::heap_allocator

description: 该 os::mm::heap_allocator 包主要用于管理内核堆内存的分配和初始化。其核心功能包括:

  • init_heap 函数用于通过锁定全局分配器 HEAP_ALLOCATOR,并调用其 init 方法来设置堆的起始地址和大小。
  • handle_alloc_error 函数用于处理内存分配错误,当分配失败时会触发 panic 并输出错误信息。
  • heap_test 函数用于测试堆分配和验证内存地址范围。
  • 包中还定义了 HEAP_ALLOCATOR 变量作为全局分配器,用于管理堆内存分配;HEAP_SPACE 变量作为内核堆的基础存储区域。

该包的功能确保了内存分配的正确性和稳定性,并通过测试函数验证分配内存的有效性。

handle_alloc_error

description: handle_alloc_error函数用于处理分配错误。当内存分配失败时,该函数会被调用。其参数是core::alloc::Layout类型的layout,表示内存分配的布局。这个函数的主要功能是触发一个panic,并输出错误信息,提示“Heap allocation error, layout = ”,后接具体的布局信息。通过这种方式,程序可以在出现内存分配问题时立即停止并报告错误。

#[alloc_error_handler]
pub fn handle_alloc_error(layout: core::alloc::Layout) -> ! {
    panic!("Heap allocation error, layout = {:?}", layout);
}

sbss

description: sbss函数没有参数和返回值。该函数的主要功能和用途未在提供的信息中明确指出。

fn sbss();

init_heap

description: init_heap 函数的主要功能是初始化内核堆内存。它通过不安全代码块,使用 HEAP_ALLOCATOR 变量来管理堆空间。具体过程是:首先锁定 HEAP_ALLOCATOR,然后调用其 init 方法,将 HEAP_SPACE 的指针和 KERNEL_HEAP_SIZE 作为参数传递,以设置堆的起始地址和大小。

相关参数:

  • HEAP_ALLOCATOR: 全局分配器,用于管理堆内存。
  • HEAP_SPACE: 用作内核堆的基础存储区域。
  • KERNEL_HEAP_SIZE: 定义内核堆的大小。
pub fn init_heap() {
    unsafe {
        HEAP_ALLOCATOR
            .lock()
            .init(HEAP_SPACE.as_ptr() as usize, KERNEL_HEAP_SIZE);
    }
}

heap_test

description: heap_test函数主要用于测试堆分配和验证内存地址范围。它首先定义了一个bss_range范围,表示从sbss到ebss的内存区域。然后,分配一个Box类型的整数5,并验证其值和内存地址是否在bss_range内。接着,创建一个Vec类型的动态数组,填充0到499的整数,并验证每个值及其内存地址。最后,函数通过控制台输出确认测试通过。该函数旨在确保动态分配的内存区域在预期范围内。

#[allow(unused)]
pub fn heap_test() {
    use alloc::boxed::Box;
    use alloc::vec::Vec;
    extern "C" {
        fn sbss();
        fn ebss();
    }
    let bss_range = sbss as usize..ebss as usize;
    let a = Box::new(5);
    assert_eq!(*a, 5);
    assert!(bss_range.contains(&(a.as_ref() as *const _ as usize)));
    drop(a);
    let mut v: Vec<usize> = Vec::new();
    for i in 0..500 {
        v.push(i);
    }
    for (i, val) in v.iter().take(500).enumerate() {
        assert_eq!(*val, i);
    }
    assert!(bss_range.contains(&(v.as_ptr() as usize)));
    drop(v);
    println!("heap_test passed!");
}

ebss

description: ebss函数的主要功能和用途没有明确说明,因为相关函数、类型和变量的信息均为空。此函数可能是一个占位符或未实现的函数。

fn ebss();

os::sync::mutex

description: 该 os::sync::mutex 包主要用于实现多种锁机制,以确保在多线程或多任务环境中对共享资源的安全访问。它包含了 Mutex 接口以及 MutexBlockingMutexBlockingInnerMutexSpin 等结构体,提供了创建和初始化不同类型锁的函数,如 MutexBlocking::newMutexSpin::new,以及实现锁的锁定和解锁操作的函数,如 lockunlock 及其对应的具体结构体的锁定和解锁函数。通过这些锁机制和相关操作,增强了多线程或多任务环境中的资源管理能力。

MutexSpin.unlock

description: unlock方法用于解锁MutexSpin结构体中的锁。这个方法通过获取locked字段的独占访问权限,将其值设置为false,从而实现解锁的功能。

fn unlock(&self) {
        let mut locked = self.locked.exclusive_access();
        *locked = false;
    }

MutexBlocking.unlock

description: unlock函数的主要功能是释放MutexBlocking类型的互斥锁。它首先通过独占访问获取内部的锁状态,并检查互斥锁是否已锁定。如果有等待的任务在队列中,它会唤醒队列中的第一个任务;如果没有,该函数会将锁状态设置为未锁定。

fn unlock(&self) {
        let mut mutex_inner = self.inner.exclusive_access();
        assert!(mutex_inner.locked);
        if let Some(waking_task) = mutex_inner.wait_queue.pop_front() {
            wakeup_task(waking_task);
        } else {
            mutex_inner.locked = false;
        }
    }

MutexSpin.lock

description: lock函数用于实现自旋锁的锁定机制。其主要功能是尝试获取锁,若锁已被占用,则当前线程将放弃锁并暂停执行以让其他线程运行,直到锁可用为止。接收者MutexSpin是一个结构体,包含一个表示锁状态的布尔变量locked。

在参数方面,lock函数没有输入参数。函数通过循环不断尝试获取锁,利用exclusive_access方法访问锁状态。如果锁已被占用,线程将调用suspend_current_and_run_next暂停自身并继续调度其他任务,直到锁状态为false时将其设置为true,以成功获取锁。

fn lock(&self) {
        loop {
            let mut locked = self.locked.exclusive_access();
            if *locked {
                drop(locked);
                suspend_current_and_run_next();
                continue;
            } else {
                *locked = true;
                return;
            }
        }
    }

unlock

description: unlock函数的主要功能是解锁某个资源。由于没有提供参数信息,该函数可能不需要额外的输入参数来执行解锁操作。

fn unlock(&self);

MutexSpin::new

description: new函数用于创建并初始化一个MutexSpin类型的实例。MutexSpin结构体包含一个UPIntrFreeCell类型的字段locked。这个字段使用UPIntrFreeCell::new函数进行初始化,传入的参数为false。UPIntrFreeCell类型通过RefCell封装内部数据,在单线程环境中提供安全的可变性。最终,new函数返回一个初始化后的MutexSpin实例。

pub fn new() -> Self {
        Self {
            locked: unsafe { UPIntrFreeCell::new(false) },
        }
    }

MutexBlocking.lock

description: lock函数的主要功能是实现互斥锁的加锁操作。它首先尝试获取互斥锁的独占访问权限。如果锁已经被占用,则将当前任务加入等待队列,并阻塞当前任务以运行下一个任务。否则,将锁的状态设置为已锁定。此函数用于确保多任务环境中的资源安全访问。

fn lock(&self) {
        let mut mutex_inner = self.inner.exclusive_access();
        if mutex_inner.locked {
            mutex_inner.wait_queue.push_back(current_task().unwrap());
            drop(mutex_inner);
            block_current_and_run_next();
        } else {
            mutex_inner.locked = true;
        }
    }

lock

description: lock函数的主要功能是用于实现锁机制。它是Mutex特性的一部分,提供线程同步的基本操作。调用lock函数可以对资源进行锁定,以确保在多线程环境中对共享资源的安全访问。该函数没有参数。

fn lock(&self);

MutexBlocking::new

description: new函数用于创建并初始化一个MutexBlocking类型的实例。主要功能是通过UPIntrFreeCell封装一个MutexBlockingInner结构体,以提供安全的可变性。MutexBlockingInner结构体用于管理锁的状态及其等待队列。入参和出参均无。

pub fn new() -> Self {
        Self {
            inner: unsafe {
                UPIntrFreeCell::new(MutexBlockingInner {
                    locked: false,
                    wait_queue: VecDeque::new(),
                })
            },
        }
    }

os::drivers::bus::virtio

description: 该 os::drivers::bus::virtio 包主要涉及 Virtio 设备的驱动程序开发。其中定义了 VirtioHal 结构体,此结构体目前没有附加方法或详细使用说明。包中包含了一些函数,如 dma_alloc 用于分配指定数量的内存页并返回物理地址,dma_dealloc 用于释放指定物理地址开始的多个连续内存页,phys_to_virt 用于将物理地址简单地返回(未进行实际转换),virt_to_phys 用于将虚拟地址通过一系列操作转换为物理地址。此外,还有一个全局变量 QUEUE_FRAMES,可能用于管理或存储内存帧。该包还依赖了一些其他模块的功能。

VirtioHal::dma_dealloc

description: dma_dealloc函数的主要功能是释放从给定物理地址开始的多个连续内存页。函数接收两个参数:

  • pa: 表示起始物理地址的usize类型值。
  • pages: 表示要释放的连续内存页的数量。

函数首先将pa转换为PhysAddr类型,然后将其转换为PhysPageNum类型的ppn_base,表示物理页号。随后,通过循环调用frame_dealloc函数释放每个物理页,并使用ppn_base.step()逐步增加页号。函数最终返回0,表示操作成功。

fn dma_dealloc(pa: usize, pages: usize) -> i32 {
        let pa = PhysAddr::from(pa);
        let mut ppn_base: PhysPageNum = pa.into();
        for _ in 0..pages {
            frame_dealloc(ppn_base);
            ppn_base.step();
        }
        0
    }

VirtioHal::virt_to_phys

description: virt_to_phys函数的主要功能是将虚拟地址转换为物理地址。这个过程涉及以下步骤:

  1. 调用kernel_token函数获取内核空间的令牌。
  2. 使用该令牌通过PageTable的from_token方法创建一个PageTable实例。
  3. 通过VirtAddr的from函数将输入的虚拟地址vaddr转换为VirtAddr类型的实例。
  4. 调用PageTable的translate_va方法将VirtAddr实例转换为物理地址。
  5. 使用unwrap方法提取转换结果并返回物理地址。

入参:

  • vaddr: 待转换的虚拟地址

出参:

  • 对应的物理地址
fn virt_to_phys(vaddr: usize) -> usize {
        PageTable::from_token(kernel_token())
            .translate_va(VirtAddr::from(vaddr))
            .unwrap()
            .0
    }

VirtioHal::dma_alloc

description: dma_alloc 函数的主要功能是分配指定数量的内存页,并将其转换为物理地址返回。函数首先调用 frame_alloc_more 分配多个内存帧,并获取最后一个帧的物理页面编号 (ppn) 作为基础。然后,通过独占访问的方式,将分配的帧追加到全局变量 QUEUE_FRAMES 中。最后,将基础物理页面编号转换为 PhysAddr 类型,并返回其地址值。

参数:

  • pages: 需要分配的页数

返回值:

  • 分配的内存的物理地址。
fn dma_alloc(pages: usize) -> usize {
        let trakcers = frame_alloc_more(pages);
        let ppn_base = trakcers.as_ref().unwrap().last().unwrap().ppn;
        QUEUE_FRAMES
            .exclusive_access()
            .append(&mut trakcers.unwrap());
        let pa: PhysAddr = ppn_base.into();
        pa.0
    }

VirtioHal::phys_to_virt

description: phys_to_virt函数用于将一个物理地址转换为虚拟地址。在这个函数中,它简单地返回传入的地址参数,没有进行任何转换或处理。入参是一个无符号整数类型的地址,返回值也是同样类型的地址。这个函数可能在更复杂的系统中用于内存地址转换,但当前实现仅仅是返回输入的地址。

fn phys_to_virt(addr: usize) -> usize {
        addr
    }

os::boards::qemu

description: 该 os::boards::qemu 包主要用于虚拟化环境中设备的初始化和中断处理,提供了对虚拟化环境中硬件资源的有效管理。其核心功能包括:

  • 设备初始化:通过 device_init 函数初始化设备的中断设置,创建 PLIC 实例管理中断控制器,启用多种设备的中断。
  • 中断处理irq_handler 函数处理来自不同设备的中断请求,根据中断源 ID 调用相应处理程序。
  • 设备管理:包含 CharDeviceImpl 用于管理串行通信端口数据流,BlockDeviceImpl 用于管理 VirtIO 块设备。
  • 常量定义:定义了如 VIRT_PLIC(虚拟可编程中断控制器基地址)、VIRT_UART(虚拟 UART 基地址)、MEMORY_END(内存结束地址)、CLOCK_FREQ(时钟频率)等常量,以及 MMIO 常量数组用于内存映射输入输出。同时还定义了虚拟 GPU 的分辨率常量 VIRTGPU_XRESVIRTGPU_YRES 。 该包依赖于一些其他模块的功能,如 core::macros 中的 panic ,以及与各种设备驱动相关的模块。

irq_handler

description: irq_handler函数的主要功能是处理来自不同设备的中断请求。它首先创建一个PLIC(可编程中断控制器)实例,然后根据中断源ID(intr_src_id)来匹配不同的设备中断并调用相应的处理程序。例如,中断源ID为5时处理键盘中断,为6时处理鼠标中断,为8时处理块设备中断,为10时处理UART中断。未识别的中断ID将引发panic。处理完后,函数会标记中断处理的完成。

pub fn irq_handler() {
    let mut plic = unsafe { PLIC::new(VIRT_PLIC) };
    let intr_src_id = plic.claim(0, IntrTargetPriority::Supervisor);
    match intr_src_id {
        5 => KEYBOARD_DEVICE.handle_irq(),
        6 => MOUSE_DEVICE.handle_irq(),
        8 => BLOCK_DEVICE.handle_irq(),
        10 => UART.handle_irq(),
        _ => panic!("unsupported IRQ {}", intr_src_id),
    }
    plic.complete(0, IntrTargetPriority::Supervisor, intr_src_id);
}

device_init

description: device_init函数主要用于初始化设备的中断设置。函数首先使用VIRT_PLIC常量创建一个新的PLIC实例,以管理中断控制器。接着,定义了硬件线程ID和中断目标优先级,包括监督和机器级别。函数为这些优先级设置中断阈值,并启用键盘、鼠标、块设备和UART的中断源,同时为这些中断源设置优先级。最后,函数通过调用sie::set_sext()来启用特定的中断。

pub fn device_init() {
    use riscv::register::sie;
    let mut plic = unsafe { PLIC::new(VIRT_PLIC) };
    let hart_id: usize = 0;
    let supervisor = IntrTargetPriority::Supervisor;
    let machine = IntrTargetPriority::Machine;
    plic.set_threshold(hart_id, supervisor, 0);
    plic.set_threshold(hart_id, machine, 1);
    //irq nums: 5 keyboard, 6 mouse, 8 block, 10 uart
    for intr_src_id in [5usize, 6, 8, 10] {
        plic.enable(hart_id, supervisor, intr_src_id);
        plic.set_priority(intr_src_id, 1);
    }
    unsafe {
        sie::set_sext();
    }
}

os::drivers::plic

description: 该 os::drivers::plic 包主要用于管理中断控制器的基本地址及其相关操作。其中 PLIC 结构体用于管理中断控制器的基本地址,包含管理中断优先级和状态的方法,如设置和获取中断优先级、启用和禁用中断源、处理中断请求等。IntrTargetPriority 是一个枚举类型,用于定义中断目标的优先级。包中还包含了一些相关函数,如 PLIC.claimPLIC 结构体中根据 hart_id 和目标优先级读取并返回一个 32 位无符号整数,PLIC.set_threshold 设置 PLIC 的中断优先级阈值等。通过这些功能,确保了中断的正确处理和响应。

PLIC.priority_ptr

description: priority_ptr函数的主要功能是根据提供的intr_source_id计算一个指向u32类型的指针。该函数首先通过断言确保intr_source_id在1到132之间,然后计算偏移地址,将self.base_addr与intr_source_id乘以4的结果相加,并将其转换为指向u32的指针。接收者是PLIC结构体,其包含一个base_addr字段,用于计算基址。参数intr_source_id表示中断源的标识符。返回值是计算出的指针。

fn priority_ptr(&self, intr_source_id: usize) -> *mut u32 {
        assert!(intr_source_id > 0 && intr_source_id <= 132);
        (self.base_addr + intr_source_id * 4) as *mut u32
    }

PLIC.claim

description: claim函数的主要功能是从PLIC结构体中,根据hart_id和目标优先级(target_priority),读取并返回一个32位无符号整数。该函数通过调用claim_comp_ptr_of_hart_with_priority方法获取指向特定hart和优先级的指针,然后使用不安全的方式读取该指针所指向的值。

参数:

  • hart_id: 一个用于指定目标hart的无符号整数。
  • target_priority: 一个枚举类型IntrTargetPriority,用于指定中断目标的优先级,可能的值包括Machine和Supervisor。
pub fn claim(&mut self, hart_id: usize, target_priority: IntrTargetPriority) -> u32 {
        let claim_comp_ptr = self.claim_comp_ptr_of_hart_with_priority(hart_id, target_priority);
        unsafe { claim_comp_ptr.read_volatile() }
    }

PLIC.enable_ptr

description: enable_ptr函数的主要功能是计算并返回一个指向特定中断源的指针和位移量。该函数属于PLIC结构体,接收以下参数:

  • hart_id: 表示hart的ID,类型为usize。
  • target_priority: 表示目标优先级,类型为IntrTargetPriority,可以是Machine或Supervisor。
  • intr_source_id: 表示中断源的ID,类型为usize。

返回值包括两个部分:

  • 一个指向u32类型的可变指针,指向基于计算结果的内存地址。
  • reg_shift: 对应的位移量,类型为usize。

函数通过调用hart_id_with_priority计算带有优先级的hart ID,并根据中断源ID计算寄存器ID和位移量,最终返回计算得到的指针和位移量。

fn enable_ptr(
        &self,
        hart_id: usize,
        target_priority: IntrTargetPriority,
        intr_source_id: usize,
    ) -> (*mut u32, usize) {
        let id = Self::hart_id_with_priority(hart_id, target_priority);
        let (reg_id, reg_shift) = (intr_source_id / 32, intr_source_id % 32);
        (
            (self.base_addr + 0x2000 + 0x80 * id + 0x4 * reg_id) as *mut u32,
            reg_shift,
        )
    }

PLIC::new

description: new函数用于创建一个新的PLIC结构体实例。入参为base_addr,它是一个无符号整数,表示基础地址。该函数将基础地址赋值给新实例的base_addr字段。由于函数被标记为unsafe,使用时需谨慎,确保调用时的安全性。

pub unsafe fn new(base_addr: usize) -> Self {
        Self { base_addr }
    }

PLIC.threshold_ptr_of_hart_with_priority

description: threshold_ptr_of_hart_with_priority函数的主要功能是计算并返回一个指向u32类型的指针。该指针通过计算基地址加上偏移量得到,偏移量由hart ID和目标优先级决定。函数接收两个参数:

  • hart_id: 表示hart的ID,类型为usize。
  • target_priority: 表示目标优先级,类型为IntrTargetPriority,可以是Machine或Supervisor。

该函数利用hart_id_with_priority函数计算带有优先级的hart ID,然后通过基地址和计算出的偏移量返回一个指针。

fn threshold_ptr_of_hart_with_priority(
        &self,
        hart_id: usize,
        target_priority: IntrTargetPriority,
    ) -> *mut u32 {
        let id = Self::hart_id_with_priority(hart_id, target_priority);
        (self.base_addr + 0x20_0000 + 0x1000 * id) as *mut u32
    }

PLIC::hart_id_with_priority

description: hart_id_with_priority函数的主要功能是计算带有优先级的hart ID。这个函数接收两个参数:

  • hart_id: 类型为usize,表示hart的ID。
  • target_priority: 类型为IntrTargetPriority,表示目标优先级,可以是Machine或Supervisor。

函数通过调用IntrTargetPriority::supported_number获取支持的优先级数量(固定为2),然后返回hart_id与优先级数量的乘积加上目标优先级的值。

fn hart_id_with_priority(hart_id: usize, target_priority: IntrTargetPriority) -> usize {
        let priority_num = IntrTargetPriority::supported_number();
        hart_id * priority_num + target_priority as usize
    }

PLIC.set_priority

description: set_priority方法用于设置中断源的优先级。方法属于PLIC结构体,该结构体包含一个基地址。方法接收两个参数:

  • intr_source_id: 表示中断源的唯一标识符。
  • priority: 表示要设置的优先级,要求在0到7之间。

方法会先检查优先级参数是否小于8,然后使用不安全代码将优先级写入指定的中断源地址。

pub fn set_priority(&mut self, intr_source_id: usize, priority: u32) {
        assert!(priority < 8);
        unsafe {
            self.priority_ptr(intr_source_id).write_volatile(priority);
        }
    }

PLIC.get_threshold

description: get_threshold函数主要用于获取给定hart_id和目标优先级(target_priority)对应的中断阈值。该方法属于PLIC结构体,PLIC结构体用于管理中断控制器的基本地址。函数通过调用threshold_ptr_of_hart_with_priority方法获取特定hart和优先级的阈值指针,然后使用不安全操作读取该指针的值,并返回该值与7的按位与结果。参数说明:

  • hart_id: hart的标识符。
  • target_priority: 中断目标优先级,枚举类型IntrTargetPriority,包括Machine和Supervisor。
#[allow(unused)]
    pub fn get_threshold(&mut self, hart_id: usize, target_priority: IntrTargetPriority) -> u32 {
        let threshold_ptr = self.threshold_ptr_of_hart_with_priority(hart_id, target_priority);
        unsafe { threshold_ptr.read_volatile() & 7 }
    }

PLIC.disable

description: disable函数的主要功能是禁用特定的中断源。它使用了三个参数:

  • hart_id: 表示处理器核的ID。
  • target_priority: 表示中断的目标优先级,类型为IntrTargetPriority。
  • intr_source_id: 表示中断源的ID。

该函数通过调用enable_ptr方法获取寄存器指针和位偏移量,然后使用不安全的写操作来更新寄存器值,以禁用特定的中断。

#[allow(unused)]
    pub fn disable(
        &mut self,
        hart_id: usize,
        target_priority: IntrTargetPriority,
        intr_source_id: usize,
    ) {
        let (reg_ptr, shift) = self.enable_ptr(hart_id, target_priority, intr_source_id);
        unsafe {
            reg_ptr.write_volatile(reg_ptr.read_volatile() & (!(1u32 << shift)));
        }
    }

PLIC.claim_comp_ptr_of_hart_with_priority

description: claim_comp_ptr_of_hart_with_priority函数的主要功能是根据给定的hart ID和目标优先级计算一个指针。该函数的接收者是一个PLIC结构体,包含一个基地址。

入参:

  • hart_id: 表示hart的ID,类型为usize。
  • target_priority: 表示目标优先级,类型为IntrTargetPriority,可以是Machine或Supervisor。

通过调用hart_id_with_priority函数计算带有优先级的hart ID,然后返回计算出的内存地址作为u32指针。

fn claim_comp_ptr_of_hart_with_priority(
        &self,
        hart_id: usize,
        target_priority: IntrTargetPriority,
    ) -> *mut u32 {
        let id = Self::hart_id_with_priority(hart_id, target_priority);
        (self.base_addr + 0x20_0004 + 0x1000 * id) as *mut u32
    }

PLIC.get_priority

description: get_priority函数用于从PLIC结构体中获取指定中断源ID的优先级。入参包括一个可变的PLIC实例及一个表示中断源ID的无符号整数。函数通过读取指针的值来获取优先级,并将其与7进行按位与操作以获取最终的优先级。

#[allow(unused)]
    pub fn get_priority(&mut self, intr_source_id: usize) -> u32 {
        unsafe { self.priority_ptr(intr_source_id).read_volatile() & 7 }
    }

PLIC.complete

description: complete函数用于在指定的hart_id和目标优先级下完成一个中断操作。此函数通过获取特定hart和优先级的claim_comp_ptr指针,并使用不安全的写入操作将完成信号写入该位置。

入参:

  • hart_id: 表示硬件线程的唯一标识符。
  • target_priority: 指定中断目标的优先级,可能的值包括Machine和Supervisor。
  • completion: 一个32位的无符号整数,表示完成信号。
pub fn complete(
        &mut self,
        hart_id: usize,
        target_priority: IntrTargetPriority,
        completion: u32,
    ) {
        let claim_comp_ptr = self.claim_comp_ptr_of_hart_with_priority(hart_id, target_priority);
        unsafe {
            claim_comp_ptr.write_volatile(completion);
        }
    }

PLIC.set_threshold

description: set_threshold函数用于设置PLIC的中断优先级阈值。其主要功能是通过给定的hart_id(硬件线程标识符)和target_priority(目标优先级),将阈值设置为指定的threshold。函数首先断言阈值小于8,然后通过调用self.threshold_ptr_of_hart_with_priority方法获取指向特定hart和优先级的阈值指针,并使用不安全的写操作更新阈值。

入参:

  • hart_id: 硬件线程标识符
  • target_priority: 中断目标优先级,枚举类型
  • threshold: 要设置的优先级阈值,必须小于8
pub fn set_threshold(
        &mut self,
        hart_id: usize,
        target_priority: IntrTargetPriority,
        threshold: u32,
    ) {
        assert!(threshold < 8);
        let threshold_ptr = self.threshold_ptr_of_hart_with_priority(hart_id, target_priority);
        unsafe {
            threshold_ptr.write_volatile(threshold);
        }
    }

PLIC.enable

description: enable方法用于在PLIC结构体中启用中断。该方法有三个参数:

  • hart_id:表示硬件线程的唯一标识。
  • target_priority:表示中断的目标优先级,使用IntrTargetPriority枚举定义。
  • intr_source_id:表示中断源的唯一标识。

方法通过调用enable_ptr函数获取寄存器指针和移位值,然后在不安全块中使用这些值启用相应的中断。

pub fn enable(
        &mut self,
        hart_id: usize,
        target_priority: IntrTargetPriority,
        intr_source_id: usize,
    ) {
        let (reg_ptr, shift) = self.enable_ptr(hart_id, target_priority, intr_source_id);
        unsafe {
            reg_ptr.write_volatile(reg_ptr.read_volatile() | 1 << shift);
        }
    }

IntrTargetPriority::supported_number

description: supported_number函数的主要功能是返回一个固定的数字,该数字为2。该函数没有参数,也不依赖于其他函数、类型或全局变量。

pub fn supported_number() -> usize {
        2
    }

os::drivers

description: 该包位于 os::drivers ,但由于未提供其公开函数、类型或变量的信息,所以无法明确该包的具体功能和用途。通常可能涉及操作系统中的驱动程序管理,具体细节需更多信息来确定。

os::task::context

description: 该 os::task::context 包主要用于管理任务上下文的信息,增强了任务调度和执行的灵活性。提供了两个关键函数:

  • TaskContext::goto_trap_return(kstack_ptr):创建并返回一个包含任务上下文信息的 TaskContext 实例,设置返回地址 ra、堆栈指针 sp 及用于存储寄存器状态的数组 s
  • TaskContext::zero_init():将 TaskContext 类型实例的字段 rasp 及数组 s 的所有元素都设置为 0。

还包含一个关键类型 TaskContext 结构体,用于保存任务上下文信息,包含返回地址 ra、堆栈指针 sp 及用于存储寄存器状态的数组 s。该包依赖 os::trap 中的 trap_return

TaskContext::zero_init

description: zero_init函数的主要功能是初始化一个TaskContext类型的实例。它将TaskContext的字段ra、sp以及数组s的所有元素都设置为0。

pub fn zero_init() -> Self {
        Self {
            ra: 0,
            sp: 0,
            s: [0; 12],
        }
    }

TaskContext::goto_trap_return

description: goto_trap_return函数的主要功能是创建并返回一个包含任务上下文信息的TaskContext实例。具体来说,它将trap_return函数的地址作为返回地址ra,将传入的堆栈指针kstack_ptr作为堆栈指针sp,并初始化一个包含12个无符号整数的数组s,用于存储寄存器的状态。入参为kstack_ptr,表示堆栈指针的地址。返回的是一个TaskContext实例,包含任务上下文的详细信息。

pub fn goto_trap_return(kstack_ptr: usize) -> Self {
        Self {
            ra: trap_return as usize,
            sp: kstack_ptr,
            s: [0; 12],
        }
    }

os::task::signal

description: 该 os::task::signal 包主要用于处理与信号相关的任务。其中 SignalFlags 类型用于处理程序执行过程中可能出现的错误信号,通过 check_error 方法检测五种信号(SIGINT、SIGILL、SIGABRT、SIGFPE 和 SIGSEGV),若检测到信号则返回包含错误代码和描述信息的元组,否则返回 None。包中还定义了五个信号常量(SIGABRT、SIGFPE、SIGILL、SIGINT、SIGSEGV),用于检查相应的错误状态。

SignalFlags.check_error

description: check_error方法用于检测SignalFlags对象中是否包含特定的错误信号。该方法会检查五种信号:SIGINT、SIGILL、SIGABRT、SIGFPE和SIGSEGV。如果检测到这些信号中的任何一个,方法将返回一个包含错误代码和描述信息的元组;如果没有检测到任何信号,则返回None。该方法主要用于识别并报告程序执行过程中发生的中断、非法指令、异常终止、错误的算术操作或段错误等情况。

pub fn check_error(&self) -> Option<(i32, &'static str)> {
        if self.contains(Self::SIGINT) {
            Some((-2, "Killed, SIGINT=2"))
        } else if self.contains(Self::SIGILL) {
            Some((-4, "Illegal Instruction, SIGILL=4"))
        } else if self.contains(Self::SIGABRT) {
            Some((-6, "Aborted, SIGABRT=6"))
        } else if self.contains(Self::SIGFPE) {
            Some((-8, "Erroneous Arithmetic Operation, SIGFPE=8"))
        } else if self.contains(Self::SIGSEGV) {
            Some((-11, "Segmentation Fault, SIGSEGV=11"))
        } else {
            None
        }
    }

os::net::tcp

description: 该 os::net::tcp 包主要用于处理 TCP 协议相关的网络通信和数据传输。它提供了创建 TCP 结构体实例的函数 TCP::new,以及对 TCP 结构体进行操作的一系列方法,如 TCP.drop 用于移除套接字、TCP.read 用于从套接字缓冲区读取数据、TCP.readable 用于判断是否可读、TCP.writable 用于判断是否可写、TCP.write 用于通过 TCP 协议发送数据。同时,该包还定义了 TCP 结构体类型,用于存储 TCP 数据包的信息,包含目标地址、源端口、目标端口、序列号、确认号和套接字索引等字段。此包通过这些数据结构和函数,帮助开发者在网络应用中实现可靠的数据传输。

TCP.writable

description: writable方法用于判断TCP结构体是否可写。该方法没有参数,直接返回true,表示TCP始终是可写的。

fn writable(&self) -> bool {
        true
    }

TCP.write

description: write函数的主要功能是将用户缓冲区的数据通过TCP协议发送到目标地址。它首先从全局变量LOSE_NET_STACK中获取网络栈的独占访问权限,然后将UserBuffer类型的输入参数buf中的数据复制到一个新的字节数组中。接着,通过调用get_s_a_by_index函数获取与当前套接字关联的序列号和确认号。最后,构造一个TCPPacket数据包,并使用NET_DEVICEtransmit方法发送该数据包。函数返回发送的数据长度。

fn write(&self, buf: crate::mm::UserBuffer) -> usize {
        let lose_net_stack = LOSE_NET_STACK.0.exclusive_access();

        let mut data = vec![0u8; buf.len()];

        let mut left = 0;
        for i in 0..buf.buffers.len() {
            data[left..(left + buf.buffers[i].len())].copy_from_slice(buf.buffers[i]);
            left += buf.buffers[i].len();
        }

        let len = data.len();

        // get sock and sequence
        let (ack, seq) = get_s_a_by_index(self.socket_index).map_or((0, 0), |x| x);

        let tcp_packet = TCPPacket {
            source_ip: lose_net_stack.ip,
            source_mac: lose_net_stack.mac,
            source_port: self.sport,
            dest_ip: self.target,
            dest_mac: MacAddress::new([0xff, 0xff, 0xff, 0xff, 0xff, 0xff]),
            dest_port: self.dport,
            data_len: len,
            seq,
            ack,
            flags: TcpFlags::A,
            win: 65535,
            urg: 0,
            data: data.as_ref(),
        };
        NET_DEVICE.transmit(&tcp_packet.build_data());
        len
    }

TCP.readable

description: readable方法用于TCP结构体中,主要功能是返回一个布尔值“true”,表示TCP结构体实例在调用该方法时总是可读的。该方法没有参数,也没有依赖其他函数、类型或变量。

fn readable(&self) -> bool {
        true
    }

TCP::new

description: new函数用于创建一个新的TCP结构体实例。它的主要功能是初始化TCP结构体的各个字段,包括目标IPv4地址、源端口、目标端口、序列号、确认号,以及套接字索引。该函数首先通过调用add_socket函数来添加一个新的套接字,并获取该套接字在全局SOCKET_TABLE中的索引。然后,它使用这些参数和索引来构建一个TCP实例。

入参:

  • target: 目标的IPv4地址。
  • sport: 源端口号。
  • dport: 目标端口号。
  • seq: 序列号。
  • ack: 确认号。

出参:

  • 返回一个初始化后的TCP实例。
pub fn new(target: IPv4, sport: u16, dport: u16, seq: u32, ack: u32) -> Self {
        let index = add_socket(target, sport, dport).expect("can't add socket");

        Self {
            target,
            sport,
            dport,
            seq,
            ack,
            socket_index: index,
        }
    }

TCP.read

description: read函数的主要功能是从指定的套接字缓冲区中读取数据并将其存储到UserBuffer中。函数不断尝试从套接字索引获取数据,如果成功,它将数据复制到UserBuffer的缓冲区中,并返回读取的字节数。如果没有数据可用,函数将调用net_interrupt_handler来处理网络中断。

参数:

  • buf: UserBuffer类型,用于存储读取的数据。它包含多个静态可变字节缓冲区,可以批量操作数据。
fn read(&self, mut buf: crate::mm::UserBuffer) -> usize {
        loop {
            if let Some(data) = pop_data(self.socket_index) {
                let data_len = data.len();
                let mut left = 0;
                for i in 0..buf.buffers.len() {
                    let buffer_i_len = buf.buffers[i].len().min(data_len - left);

                    buf.buffers[i][..buffer_i_len]
                        .copy_from_slice(&data[left..(left + buffer_i_len)]);

                    left += buffer_i_len;
                    if left == data_len {
                        break;
                    }
                }
                return left;
            } else {
                net_interrupt_handler();
            }
        }
    }

TCP.drop

description: drop方法用于从SOCKET_TABLE中移除与TCP结构体关联的套接字。
入参:

  • 没有参数,因为这是一个方法,操作的是TCP结构体的实例。

功能:

  • 它调用remove_socket函数,将TCP结构体中的socket_index作为参数传递,以移除对应索引的套接字。
fn drop(&mut self) {
        remove_socket(self.socket_index)
    }

os::drivers::chardev

description: 该 os::drivers::chardev 包主要用于字符设备的操作,为字符设备提供了标准化接口,包括初始化、读取、写入和中断处理操作。包中定义了一些关键函数,如 read 用于从数据源读取一个字节,init 用于初始化相关对象或状态,handle_irq 用于处理中断请求,write 用于向目标写入一个字节。还定义了 CharDevice 公共特性(trait),提供了字符设备的初始化、读取、写入和中断处理的抽象接口。此外,包中还包含了 UART 变量,用于串行通信的数据输入和输出操作。

init

description: init函数的主要功能是初始化与self相关的对象或状态。这个函数不接收任何参数,也不返回任何值。

fn init(&self);

handle_irq

description: handle_irq函数的主要功能是处理中断请求。该函数没有参数和返回值。

fn handle_irq(&self);

write

description: write函数的主要功能是将一个字节(u8类型)写入某个目标。由于没有提供更多上下文信息,该函数的具体实现和用途可能依赖于实现该方法的类型。参数ch表示要写入的字节。

fn write(&self, ch: u8);

read

description: read函数的主要功能是读取并返回一个无符号8位整数(u8)。这个函数没有参数,通常用于从某个数据源读取一个字节。

fn read(&self) -> u8;

os::fs::pipe

description: 该 os::fs::pipe 包主要用于实现管道通信,提供了环形缓冲区的数据存储和管理功能,支持多线程环境下的安全数据传输。

包中包含以下关键内容:

  • 函数
    • PipeRingBuffer.available_read:计算当前可读字节数。
    • PipeRingBuffer.write_byte:向环形缓冲区中写入一个字节。
    • PipeRingBuffer::new:初始化一个 PipeRingBuffer 实例。
    • PipeRingBuffer.all_write_ends_closed:检查写入端是否已全部关闭。
    • Pipe::write_end_with_buffer:创建一个可写的 Pipe 实例并关联到特定缓冲区。
    • Pipe::read_end_with_buffer:创建一个可读的 Pipe 实例并分配一个共享缓冲区。
    • PipeRingBuffer.available_write:计算可写入的字节数。
    • make_pipe:创建并返回一个双向通信的管道对。
    • PipeRingBuffer.read_byte:从环形缓冲区中读取一个字节。
    • PipeRingBuffer.set_write_end:设置环形缓冲区的写入端。
    • Pipe.read:从 Pipe 的缓冲区中读取数据到 UserBuffer 中。
    • Pipe.readable:检查 Pipe 的可读状态。
    • Pipe.writable:检查 Pipe 的可写状态。
    • Pipe.write:将数据写入 Pipe 的缓冲区。
  • 类型
    • PipeRingBuffer:用于实现环形缓冲区的数据存储和管理,支持读写操作。
    • Pipe:用于管理数据流的读写操作,包含可读、可写指示器及一个共享的环形缓冲区指针。
    • RingBufferStatus:表示环形缓冲区的状态的枚举。
  • 变量
    • RING_BUFFER_SIZE:一个值为 32 的常量,用于定义环形缓冲区的大小。

make_pipe

description: make_pipe函数的主要功能是创建并返回一个双向通信的管道对,包括一个读端和一个写端。该函数通过初始化一个共享的PipeRingBuffer环形缓冲区来实现这一点。它创建一个用于读操作的Pipe实例和一个用于写操作的Pipe实例,两个实例共享同一个缓冲区。最后,函数返回这两个Pipe实例,分别作为读端和写端,以便在多线程环境中安全地进行数据传输。

/// Return (read_end, write_end)
pub fn make_pipe() -> (Arc<Pipe>, Arc<Pipe>) {
    let buffer = Arc::new(unsafe { UPIntrFreeCell::new(PipeRingBuffer::new()) });
    let read_end = Arc::new(Pipe::read_end_with_buffer(buffer.clone()));
    let write_end = Arc::new(Pipe::write_end_with_buffer(buffer.clone()));
    buffer.exclusive_access().set_write_end(&write_end);
    (read_end, write_end)
}

Pipe::write_end_with_buffer

description: write_end_with_buffer函数用于创建一个新的Pipe实例。该实例的writable属性设置为true,表示该管道是可写的,而readable属性设置为false。buffer参数是一个Arc<UPIntrFreeCell>类型,表示共享的管道环形缓冲区。此函数的主要功能是初始化Pipe结构体,使其可以进行写操作,并关联到特定的缓冲区。

pub fn write_end_with_buffer(buffer: Arc<UPIntrFreeCell<PipeRingBuffer>>) -> Self {
        Self {
            readable: false,
            writable: true,
            buffer,
        }
    }

Pipe.readable

description: readable方法用于检查Pipe结构体的可读状态。入参:无。出参:返回一个布尔值,指示Pipe是否可读。

fn readable(&self) -> bool {
        self.readable
    }

Pipe.writable

description: writable方法用于检查Pipe结构体实例是否可写。入参为空,因为它是一个方法。出参为布尔值,表示Pipe实例是否处于可写状态。

fn writable(&self) -> bool {
        self.writable
    }

Pipe.write

description: write函数用于将数据写入Pipe的缓冲区。它首先确保Pipe是可写的,然后通过UserBuffer的迭代器逐字节写入数据到Pipe的环形缓冲区中。若缓冲区已满,则当前任务会被挂起并调度下一个任务,直到有可用空间为止。函数返回实际写入的字节数。参数:UserBuffer是一个管理多个静态可变字节缓冲区的结构体,便于批量数据操作。

fn write(&self, buf: UserBuffer) -> usize {
        assert!(self.writable());
        let want_to_write = buf.len();
        let mut buf_iter = buf.into_iter();
        let mut already_write = 0usize;
        loop {
            let mut ring_buffer = self.buffer.exclusive_access();
            let loop_write = ring_buffer.available_write();
            if loop_write == 0 {
                drop(ring_buffer);
                suspend_current_and_run_next();
                continue;
            }
            // write at most loop_write bytes
            for _ in 0..loop_write {
                if let Some(byte_ref) = buf_iter.next() {
                    ring_buffer.write_byte(unsafe { *byte_ref });
                    already_write += 1;
                    if already_write == want_to_write {
                        return want_to_write;
                    }
                } else {
                    return already_write;
                }
            }
        }
    }

PipeRingBuffer.set_write_end

description: set_write_end方法用于设置PipeRingBuffer的写入端。其参数write_end是一个指向Pipe的Arc智能指针。该方法将write_end转换为弱引用(Weak)并存储在PipeRingBuffer的write_end字段中。这允许PipeRingBuffer引用Pipe而不阻止其被释放,从而防止循环引用问题。

pub fn set_write_end(&mut self, write_end: &Arc<Pipe>) {
        self.write_end = Some(Arc::downgrade(write_end));
    }

PipeRingBuffer.available_write

description: available_write函数用于计算PipeRingBuffer中可写入的字节数。其主要功能是:

  • 如果环形缓冲区的状态为Full(已满),则返回0,表示没有可用的写入空间。
  • 否则,返回RING_BUFFER_SIZE减去当前可读字节数,表示剩余的可写入空间。

参数:

  • 无参数,此函数操作的是PipeRingBuffer的实例自身。

返回值:

  • 返回一个usize类型的值,表示环形缓冲区中可写入的字节数。
pub fn available_write(&self) -> usize {
        if self.status == RingBufferStatus::Full {
            0
        } else {
            RING_BUFFER_SIZE - self.available_read()
        }
    }

PipeRingBuffer.write_byte

description: write_byte函数的主要功能是向PipeRingBuffer的环形缓冲区中写入一个字节。该函数通过更新缓冲区的尾部指针和状态来实现字节的写入。当写入操作使尾指针与头指针重合时,缓冲区状态会被标记为已满(Full)。

参数:

  • byte: 需要写入缓冲区的字节

在写入过程中,缓冲区状态会被设为正常(Normal),并且尾指针会更新为下一个位置。当尾指针与头指针重合时,状态会更新为已满。

pub fn write_byte(&mut self, byte: u8) {
        self.status = RingBufferStatus::Normal;
        self.arr[self.tail] = byte;
        self.tail = (self.tail + 1) % RING_BUFFER_SIZE;
        if self.tail == self.head {
            self.status = RingBufferStatus::Full;
        }
    }

Pipe.read

description: read函数的主要功能是从Pipe的缓冲区中读取数据到提供的UserBuffer中。函数首先确保Pipe是可读的,然后计算需要读取的字节数,并迭代UserBuffer以填充数据。在循环中,函数获取缓冲区的独占访问权,检查可读取的字节数。如果没有可读字节且所有写入端已关闭,则返回已经读取的字节数;否则,函数会挂起当前任务并切换到下一个任务。当读取完成或达到所需字节数时,函数返回读取的字节数。

参数:

  • buf: 一个UserBuffer类型,用于存储读取的数据。
fn read(&self, buf: UserBuffer) -> usize {
        assert!(self.readable());
        let want_to_read = buf.len();
        let mut buf_iter = buf.into_iter();
        let mut already_read = 0usize;
        loop {
            let mut ring_buffer = self.buffer.exclusive_access();
            let loop_read = ring_buffer.available_read();
            if loop_read == 0 {
                if ring_buffer.all_write_ends_closed() {
                    return already_read;
                }
                drop(ring_buffer);
                suspend_current_and_run_next();
                continue;
            }
            for _ in 0..loop_read {
                if let Some(byte_ref) = buf_iter.next() {
                    unsafe {
                        *byte_ref = ring_buffer.read_byte();
                    }
                    already_read += 1;
                    if already_read == want_to_read {
                        return want_to_read;
                    }
                } else {
                    return already_read;
                }
            }
        }
    }

PipeRingBuffer.read_byte

description: read_byte函数的主要功能是从PipeRingBuffer中读取一个字节,并更新缓冲区的状态和头指针。具体来说,该函数从数组中获取当前位置的字节,将头指针向前移动一位,并根据头指针和尾指针的位置更新环形缓冲区的状态。参数意义如下:

  • self: 指向PipeRingBuffer的可变引用,用于访问和修改缓冲区的状态、头指针、尾指针等属性。返回值是读取的字节。
pub fn read_byte(&mut self) -> u8 {
        self.status = RingBufferStatus::Normal;
        let c = self.arr[self.head];
        self.head = (self.head + 1) % RING_BUFFER_SIZE;
        if self.head == self.tail {
            self.status = RingBufferStatus::Empty;
        }
        c
    }

PipeRingBuffer::new

description: new函数用于初始化一个PipeRingBuffer实例。它的主要功能是创建并返回一个环形缓冲区,其初始状态为空。函数不接受任何参数。

返回的PipeRingBuffer实例包含以下字段:

  • arr: 大小为RING_BUFFER_SIZE(32)的字节数组,初始值为0。
  • head: 整数,初始值为0,指示缓冲区的读取位置。
  • tail: 整数,初始值为0,指示缓冲区的写入位置。
  • status: 初始化为RingBufferStatus::Empty,表示当前缓冲区为空。
  • write_end: 可选类型,初始值为None,用于指向可能的写入端。
pub fn new() -> Self {
        Self {
            arr: [0; RING_BUFFER_SIZE],
            head: 0,
            tail: 0,
            status: RingBufferStatus::Empty,
            write_end: None,
        }
    }

PipeRingBuffer.all_write_ends_closed

description: all_write_ends_closed函数用于检查PipeRingBuffer的写入端是否已全部关闭。通过判断write_end选项是否为空指针来实现。

pub fn all_write_ends_closed(&self) -> bool {
        self.write_end.as_ref().unwrap().upgrade().is_none()
    }

Pipe::read_end_with_buffer

description: read_end_with_buffer函数用于创建一个新的Pipe实例。该函数的主要功能是初始化Pipe结构体,使其可读而不可写,并分配一个共享的缓冲区。入参是一个共享的PipeRingBuffer缓冲区,由UPIntrFreeCell和Arc管理,确保对缓冲区的独占访问和引用计数管理。

pub fn read_end_with_buffer(buffer: Arc<UPIntrFreeCell<PipeRingBuffer>>) -> Self {
        Self {
            readable: true,
            writable: false,
            buffer,
        }
    }

PipeRingBuffer.available_read

description: available_read函数用于计算PipeRingBuffer中当前可读的字节数。该函数根据环形缓冲区的状态和头尾指针的位置来确定可读字节数。如果缓冲区为空,返回0;如果尾指针在头指针之后,返回尾指针与头指针的差值;如果尾指针在头指针之前,返回尾指针加上缓冲区大小再减去头指针的值。这个函数确保在任何状态下都能正确计算出可读字节数。

pub fn available_read(&self) -> usize {
        if self.status == RingBufferStatus::Empty {
            0
        } else if self.tail > self.head {
            self.tail - self.head
        } else {
            self.tail + RING_BUFFER_SIZE - self.head
        }
    }

os::task::switch

description: 该 os::task::switch 包主要用于管理任务上下文的切换以实现多任务处理。其中的关键函数 __switch 负责接收指向当前任务和下一个任务的 TaskContext 指针,通过保存和加载任务上下文来实现任务的切换。

__switch

description: __switch函数的主要功能是切换任务上下文。它接收两个参数:

  • current_task_cx_ptr: 指向当前任务的TaskContext指针,用于保存当前任务的上下文信息。
  • next_task_cx_ptr: 指向下一个任务的TaskContext指针,用于加载下一个任务的上下文信息。

该函数用于任务切换,通过保存和加载任务上下文来实现多任务的切换。

pub fn __switch(current_task_cx_ptr: *mut TaskContext, next_task_cx_ptr: *const TaskContext);

os::syscall::input

description: 该 os::syscall::input 包主要用于处理键盘和鼠标的输入事件,确保系统能够响应这些设备的交互。包含两个关键函数:

  • sys_event_get:从键盘或鼠标设备读取事件,先尝试从键盘设备读取,若无则检查鼠标设备,若两者都无事件则返回 0。
  • sys_key_pressed:检查 UART 的读取缓冲区是否为空,通过调用 UARTread_buffer_is_empty 方法判断,缓冲区不为空返回 1,为空返回 0,返回值类型为 isize。该包依赖 alloc::syncclone 功能,以及 os::drivers::chardev 中的 UARTos::drivers::chardev::ns16550a 中的 NS16550a.read_buffer_is_emptyos::drivers::input 中的 is_emptyread_eventKEYBOARD_DEVICEMOUSE_DEVICE

sys_event_get

description: sys_event_get函数的主要功能是从键盘或鼠标设备中读取事件。该函数首先尝试从克隆的键盘设备(KEYBOARD_DEVICE)读取事件,如果键盘设备中有事件存在,则返回该事件的值。如果键盘设备没有事件,则检查鼠标设备(MOUSE_DEVICE),并尝试从中读取事件。如果鼠标设备中也没有事件,则返回0。这个函数用于处理键盘和鼠标的输入事件,确保系统能够响应这些设备的交互。

pub fn sys_event_get() -> isize {
    let kb = KEYBOARD_DEVICE.clone();
    let mouse = MOUSE_DEVICE.clone();
    //let input=INPUT_CONDVAR.clone();
    //read_input_event() as isize
    if !kb.is_empty() {
        kb.read_event() as isize
    } else if !mouse.is_empty() {
        mouse.read_event() as isize
    } else {
        0
    }
}

sys_key_pressed

description: sys_key_pressed函数用于检查UART的读取缓冲区是否为空。通过调用UART的read_buffer_is_empty方法来判断缓冲区状态,并返回相应的值。如果缓冲区不为空(即有按键被按下),返回1;如果为空,返回0。UART是一个用于串行通信的接口,通常用于与外部设备进行数据传输。在sys_key_pressed函数中,UART用于检查其读取缓冲区是否为空,以确定是否有按键被按下。函数的返回值类型为isize。

/// check UART's read-buffer is empty or not
pub fn sys_key_pressed() -> isize {
    let res = !UART.read_buffer_is_empty();
    if res {
        1
    } else {
        0
    }
}

os::trap::context

description: 该 os::trap::context 包主要用于提供陷阱上下文管理,以保存和恢复 CPU 状态。其包含一个关键结构体 TrapContext,用于存储陷阱上下文信息,以及两个关键函数:

  • app_init_context:用于初始化 TrapContext 结构体实例,通过设置相关寄存器位和参数填充结构体字段,并将 CPU 权限级别调整为用户模式,最后返回初始化完毕的实例。
  • set_sp:用于更新 TrapContext 结构体中存储的栈指针信息。

此外,该包还存在一些依赖项。

TrapContext.set_sp

description: set_sp函数用于设置TrapContext结构体中存储的栈指针(stack pointer)。函数的唯一参数sp代表新的栈指针值,并将其赋值给TrapContext的x数组的第二个元素。此函数主要用于更新TrapContext实例的栈指针信息。

pub fn set_sp(&mut self, sp: usize) {
        self.x[2] = sp;
    }

TrapContext::app_init_context

description: app_init_context函数的主要功能是初始化一个TrapContext结构体实例。它通过设置sstatus寄存器的特定位,将CPU的权限级别调整为用户模式,并使用提供的参数来填充TrapContext的字段。

入参:

  • entry: 用于设置sepc寄存器的值。
  • sp: 用于初始化栈指针。
  • kernel_satp: 内核的satp寄存器值。
  • kernel_sp: 内核的栈指针。
  • trap_handler: 陷阱处理程序的地址。

出参:

  • 返回一个初始化完毕的TrapContext实例。
pub fn app_init_context(
        entry: usize,
        sp: usize,
        kernel_satp: usize,
        kernel_sp: usize,
        trap_handler: usize,
    ) -> Self {
        let mut sstatus = sstatus::read();
        // set CPU privilege to User after trapping back
        sstatus.set_spp(SPP::User);
        let mut cx = Self {
            x: [0; 32],
            sstatus,
            sepc: entry,
            kernel_satp,
            kernel_sp,
            trap_handler,
        };
        cx.set_sp(sp);
        cx
    }

os::task::task

description: 该 os::task::task 包用于管理操作系统内核中的任务和进程,具有以下功能:

  • 提供了多种关键函数,如获取 TaskControlBlockInnerTrapContext 的可变引用、创建并初始化 TaskControlBlock 实例、从 TaskControlBlock 的进程中获取用户令牌、获取 TaskControlBlockinner 字段的独占访问权以及获取 TaskControlBlockInner 结构体中的任务状态。
  • 定义了多种关键类型,包括用于管理任务控制信息的 TaskControlBlockInner 结构体、管理任务控制块的 TaskControlBlock 结构体以及表示任务不同状态的 TaskStatus 枚举类型。

该包的设计旨在确保任务和进程管理的安全性和效率,支持操作系统内核中的资源安全访问和有效使用。

TaskControlBlock.get_user_token

description: get_user_token函数用于从TaskControlBlock的进程中检索用户令牌。通过升级对进程的弱引用,获取其独占的内部访问权,最后从内存集合中获取并返回令牌。

pub fn get_user_token(&self) -> usize {
        let process = self.process.upgrade().unwrap();
        let inner = process.inner_exclusive_access();
        inner.memory_set.token()
    }

TaskControlBlockInner.get_trap_cx

description: get_trap_cx方法用于从TaskControlBlockInner结构体中获取可变的TrapContext引用。出参:返回一个静态可变引用,指向TrapContext结构体。

pub fn get_trap_cx(&self) -> &'static mut TrapContext {
        self.trap_cx_ppn.get_mut()
    }

TaskControlBlock::new

description: new函数用于创建一个TaskControlBlock实例。其主要功能是初始化一个任务控制块,关联进程控制块,并设置任务的初始状态。

入参:

  • process: 类型为Arc,表示一个进程控制块的引用。
  • ustack_base: 类型为usize,表示用户栈的基地址。
  • alloc_user_res: 类型为bool,指示是否为任务分配用户资源。

该函数通过调用TaskUserRes::new创建用户资源实例,然后分配内核栈,并通过UPIntrFreeCell封装任务的内部状态。最后,返回初始化的TaskControlBlock实例。

pub fn new(
        process: Arc<ProcessControlBlock>,
        ustack_base: usize,
        alloc_user_res: bool,
    ) -> Self {
        let res = TaskUserRes::new(Arc::clone(&process), ustack_base, alloc_user_res);
        let trap_cx_ppn = res.trap_cx_ppn();
        let kstack = kstack_alloc();
        let kstack_top = kstack.get_top();
        Self {
            process: Arc::downgrade(&process),
            kstack,
            inner: unsafe {
                UPIntrFreeCell::new(TaskControlBlockInner {
                    res: Some(res),
                    trap_cx_ppn,
                    task_cx: TaskContext::goto_trap_return(kstack_top),
                    task_status: TaskStatus::Ready,
                    exit_code: None,
                })
            },
        }
    }

TaskControlBlock.inner_exclusive_access

description: inner_exclusive_access函数的主要功能是获取TaskControlBlock的inner字段的独占访问权。该函数返回一个UPIntrRefMut类型的可变引用,用于封装TaskControlBlockInner类型的控制信息。

出参:

  • UPIntrRefMut: 封装一个可选的可变引用,确保在使用过程中资源的安全性和正确释放。

接收者:

  • TaskControlBlock结构体包含进程信息、内核栈以及可变的inner字段,用于管理任务的控制信息。
pub fn inner_exclusive_access(&self) -> UPIntrRefMut<'_, TaskControlBlockInner> {
        self.inner.exclusive_access()
    }

TaskControlBlockInner.get_status

description: get_status方法用于获取TaskControlBlockInner结构体中的任务状态。返回的结果是TaskStatus枚举类型,表示任务的不同状态,例如已准备好执行、正在执行中或被阻塞。

#[allow(unused)]
    fn get_status(&self) -> TaskStatus {
        self.task_status
    }

os::config

description: 该 os::config 包主要用于内存管理和系统配置,定义了一系列与内存页面、堆、栈等系统资源相关的常量。这些常量包括 PAGE_SIZE_BITS(页面大小的位数)、TRAP_CONTEXT_BASE(陷阱上下文基地址)、KERNEL_HEAP_SIZE(内核堆大小)、TRAMPOLINE(内存管理中的跳板地址)、KERNEL_STACK_SIZE(内核栈大小)、USER_STACK_SIZE(用户栈大小)、PAGE_SIZE(页面大小)。这些常量在内存管理和系统调用中发挥重要作用,确保系统资源的合理分配和管理。例如,PAGE_SIZE 在多个函数中用于确定数据拷贝块大小、计算用户栈基址偏移等操作;TRAMPOLINE 在多个与陷阱处理相关的函数中被用于设置陷阱处理程序的入口地址等。

os::task::process

description: 该 os::task::process 包主要用于操作系统内核中的进程管理。它提供了一系列函数和结构体来实现进程的创建、执行、资源分配和访问控制等功能。

关键函数包括:

  • ProcessControlBlock.getpid():从 ProcessControlBlock 结构体中获取进程标识符。
  • ProcessControlBlockInner.alloc_tid():从任务资源分配器中分配新的任务 ID。
  • ProcessControlBlock.inner_exclusive_access():提供对 ProcessControlBlock 内部结构的独占访问,确保资源安全。
  • ProcessControlBlock.fork():创建新的子进程。
  • ProcessControlBlock.exec(elf_data, args):执行单线程进程。
  • ProcessControlBlock::new(elf_data):创建新的 ProcessControlBlock 实例。
  • ProcessControlBlockInner.alloc_fd():为 ProcessControlBlockInner 分配新的文件描述符。
  • ProcessControlBlockInner.alloc_tid():从 ProcessControlBlockInner 结构体中的任务资源分配器中分配任务 ID。
  • ProcessControlBlockInner.dealloc_tid():释放 ProcessControlBlockInner 中的任务资源。
  • ProcessControlBlockInner.get_task():检索指定 ID 的任务控制块。
  • ProcessControlBlockInner.get_user_token():从 ProcessControlBlockInner 结构体中检索用户标识符。
  • ProcessControlBlockInner.thread_count():返回 ProcessControlBlockInner 结构体内的任务数量。

关键类型包括:

  • ProcessControlBlock:用于管理进程 ID、访问内部结构、创建子进程及执行单线程进程的结构体。
  • ProcessControlBlockInner:管理进程控制块的内部结构,包含内存管理、文件描述符表、任务控制块等。

该包通过提供进程标识符管理、任务和文件描述符的分配以及进程执行相关的功能,增强了操作系统内核中的进程管理能力。

ProcessControlBlockInner.get_task

description: get_task函数用于检索ProcessControlBlockInner的任务列表中指定ID的任务控制块(TaskControlBlock)。参数tid表示任务的ID,函数返回一个该任务控制块的共享引用(Arc),以便在多个地方安全使用。

pub fn get_task(&self, tid: usize) -> Arc<TaskControlBlock> {
        self.tasks[tid].as_ref().unwrap().clone()
    }

ProcessControlBlock.getpid

description: getpid函数用于从ProcessControlBlock结构体中获取进程的标识符(pid)。这个函数没有参数,返回值是进程标识符的数值类型。

pub fn getpid(&self) -> usize {
        self.pid.0
    }

ProcessControlBlockInner.alloc_fd

description: alloc_fd方法用于为ProcessControlBlockInner分配一个新的文件描述符(fd)。该方法会在fd_table中查找第一个未使用的位置(即值为None的位置),如果找到,则返回该位置的索引作为新的fd;如果没有找到,则在fd_table末尾添加一个None,并返回新添加的位置索引作为新的fd。

pub fn alloc_fd(&mut self) -> usize {
        if let Some(fd) = (0..self.fd_table.len()).find(|fd| self.fd_table[*fd].is_none()) {
            fd
        } else {
            self.fd_table.push(None);
            self.fd_table.len() - 1
        }
    }

ProcessControlBlock.inner_exclusive_access

description: inner_exclusive_access函数的主要功能是提供对ProcessControlBlock的内部结构ProcessControlBlockInner的独占访问。通过调用inner的exclusive_access方法,该函数返回一个UPIntrRefMut类型的可变引用,确保在访问过程中资源的安全性和正确释放。返回值类型UPIntrRefMut封装了一个可选的可变引用,用于管理引用的生命周期。

pub fn inner_exclusive_access(&self) -> UPIntrRefMut<'_, ProcessControlBlockInner> {
        self.inner.exclusive_access()
    }

ProcessControlBlockInner.dealloc_tid

description: dealloc_tid函数用于释放ProcessControlBlockInner中的任务资源。该函数的主要功能是调用task_res_allocator的dealloc方法来释放指定的任务ID(tid)所占用的资源。参数tid表示需要释放的任务ID。

pub fn dealloc_tid(&mut self, tid: usize) {
        self.task_res_allocator.dealloc(tid)
    }

ProcessControlBlockInner.get_user_token

description: get_user_token函数用于从ProcessControlBlockInner结构体的memory_set字段中检索用户标识符(token)。这个函数没有输入参数,返回一个无符号整数类型(usize)的值,代表用户的标识符。

#[allow(unused)]
    pub fn get_user_token(&self) -> usize {
        self.memory_set.token()
    }

ProcessControlBlockInner.alloc_tid

description: alloc_tid函数用于从ProcessControlBlockInner结构体中的任务资源分配器中分配一个任务ID。该函数的主要功能是通过调用task_res_allocator的alloc方法来获取一个新的任务ID。

pub fn alloc_tid(&mut self) -> usize {
        self.task_res_allocator.alloc()
    }

ProcessControlBlock::new

description: new函数用于创建一个ProcessControlBlock实例,主要用于操作系统内核中的进程管理。该函数从ELF数据中初始化进程的内存集合、用户栈基地址和入口点,并分配一个进程ID。接着,它创建一个主要线程,设置线程的陷阱上下文,并将其添加到进程和调度器中。入参elf_data是一个字节数组,用于解析ELF文件数据。返回值是一个包含进程控制块的共享引用,用于管理和访问进程的状态和资源。

pub fn new(elf_data: &[u8]) -> Arc<Self> {
        // memory_set with elf program headers/trampoline/trap context/user stack
        let (memory_set, ustack_base, entry_point) = MemorySet::from_elf(elf_data);
        // allocate a pid
        let pid_handle = pid_alloc();
        let process = Arc::new(Self {
            pid: pid_handle,
            inner: unsafe {
                UPIntrFreeCell::new(ProcessControlBlockInner {
                    is_zombie: false,
                    memory_set,
                    parent: None,
                    children: Vec::new(),
                    exit_code: 0,
                    fd_table: vec![
                        // 0 -> stdin
                        Some(Arc::new(Stdin)),
                        // 1 -> stdout
                        Some(Arc::new(Stdout)),
                        // 2 -> stderr
                        Some(Arc::new(Stdout)),
                    ],
                    signals: SignalFlags::empty(),
                    tasks: Vec::new(),
                    task_res_allocator: RecycleAllocator::new(),
                    mutex_list: Vec::new(),
                    semaphore_list: Vec::new(),
                    condvar_list: Vec::new(),
                })
            },
        });
        // create a main thread, we should allocate ustack and trap_cx here
        let task = Arc::new(TaskControlBlock::new(
            Arc::clone(&process),
            ustack_base,
            true,
        ));
        // prepare trap_cx of main thread
        let task_inner = task.inner_exclusive_access();
        let trap_cx = task_inner.get_trap_cx();
        let ustack_top = task_inner.res.as_ref().unwrap().ustack_top();
        let kstack_top = task.kstack.get_top();
        drop(task_inner);
        *trap_cx = TrapContext::app_init_context(
            entry_point,
            ustack_top,
            KERNEL_SPACE.exclusive_access().token(),
            kstack_top,
            trap_handler as usize,
        );
        // add main thread to the process
        let mut process_inner = process.inner_exclusive_access();
        process_inner.tasks.push(Some(Arc::clone(&task)));
        drop(process_inner);
        insert_into_pid2process(process.getpid(), Arc::clone(&process));
        // add main thread to scheduler
        add_task(task);
        process
    }

ProcessControlBlockInner.thread_count

description: thread_count函数用于返回ProcessControlBlockInner结构体内的任务数量。该函数通过计算tasks向量的长度来确定任务的数量。没有参数或返回值需要特别说明。

pub fn thread_count(&self) -> usize {
        self.tasks.len()
    }

ProcessControlBlock.fork

description: fork函数的主要功能是创建一个新的子进程,只支持单线程进程。它通过克隆父进程的内存和文件描述符表,分配新的进程ID,并创建新的子进程控制块。然后,它将新的任务控制块附加到子进程中,并将其添加到调度器中。

参数意义:

  • self: 代表当前进程控制块的引用,用于获取父进程的信息并创建子进程。
/// Only support processes with a single thread.
    pub fn fork(self: &Arc<Self>) -> Arc<Self> {
        let mut parent = self.inner_exclusive_access();
        assert_eq!(parent.thread_count(), 1);
        // clone parent's memory_set completely including trampoline/ustacks/trap_cxs
        let memory_set = MemorySet::from_existed_user(&parent.memory_set);
        // alloc a pid
        let pid = pid_alloc();
        // copy fd table
        let mut new_fd_table: Vec<Option<Arc<dyn File + Send + Sync>>> = Vec::new();
        for fd in parent.fd_table.iter() {
            if let Some(file) = fd {
                new_fd_table.push(Some(file.clone()));
            } else {
                new_fd_table.push(None);
            }
        }
        // create child process pcb
        let child = Arc::new(Self {
            pid,
            inner: unsafe {
                UPIntrFreeCell::new(ProcessControlBlockInner {
                    is_zombie: false,
                    memory_set,
                    parent: Some(Arc::downgrade(self)),
                    children: Vec::new(),
                    exit_code: 0,
                    fd_table: new_fd_table,
                    signals: SignalFlags::empty(),
                    tasks: Vec::new(),
                    task_res_allocator: RecycleAllocator::new(),
                    mutex_list: Vec::new(),
                    semaphore_list: Vec::new(),
                    condvar_list: Vec::new(),
                })
            },
        });
        // add child
        parent.children.push(Arc::clone(&child));
        // create main thread of child process
        let task = Arc::new(TaskControlBlock::new(
            Arc::clone(&child),
            parent
                .get_task(0)
                .inner_exclusive_access()
                .res
                .as_ref()
                .unwrap()
                .ustack_base(),
            // here we do not allocate trap_cx or ustack again
            // but mention that we allocate a new kstack here
            false,
        ));
        // attach task to child process
        let mut child_inner = child.inner_exclusive_access();
        child_inner.tasks.push(Some(Arc::clone(&task)));
        drop(child_inner);
        // modify kstack_top in trap_cx of this thread
        let task_inner = task.inner_exclusive_access();
        let trap_cx = task_inner.get_trap_cx();
        trap_cx.kernel_sp = task.kstack.get_top();
        drop(task_inner);
        insert_into_pid2process(child.getpid(), Arc::clone(&child));
        // add this thread to scheduler
        add_task(task);
        child
    }

ProcessControlBlock.exec

description: exec函数的主要功能是执行一个进程,该进程仅支持单线程。函数首先确认线程数量为1,然后通过ELF文件数据创建内存集合,并更新内存设置。接下来,函数为主线程重新分配用户资源,并初始化陷阱上下文。它将参数推入用户栈,并确保栈指针对齐。最终,函数设置陷阱上下文以准备执行进程。

参数:

  • elf_data: 包含ELF文件数据的字节数组。
  • args: 包含执行时参数的字符串向量。
/// Only support processes with a single thread.
    pub fn exec(self: &Arc<Self>, elf_data: &[u8], args: Vec<String>) {
        assert_eq!(self.inner_exclusive_access().thread_count(), 1);
        // memory_set with elf program headers/trampoline/trap context/user stack
        let (memory_set, ustack_base, entry_point) = MemorySet::from_elf(elf_data);
        let new_token = memory_set.token();
        // substitute memory_set
        self.inner_exclusive_access().memory_set = memory_set;
        // then we alloc user resource for main thread again
        // since memory_set has been changed
        let task = self.inner_exclusive_access().get_task(0);
        let mut task_inner = task.inner_exclusive_access();
        task_inner.res.as_mut().unwrap().ustack_base = ustack_base;
        task_inner.res.as_mut().unwrap().alloc_user_res();
        task_inner.trap_cx_ppn = task_inner.res.as_mut().unwrap().trap_cx_ppn();
        // push arguments on user stack
        let mut user_sp = task_inner.res.as_mut().unwrap().ustack_top();
        user_sp -= (args.len() + 1) * core::mem::size_of::<usize>();
        let argv_base = user_sp;
        let mut argv: Vec<_> = (0..=args.len())
            .map(|arg| {
                translated_refmut(
                    new_token,
                    (argv_base + arg * core::mem::size_of::<usize>()) as *mut usize,
                )
            })
            .collect();
        *argv[args.len()] = 0;
        for i in 0..args.len() {
            user_sp -= args[i].len() + 1;
            *argv[i] = user_sp;
            let mut p = user_sp;
            for c in args[i].as_bytes() {
                *translated_refmut(new_token, p as *mut u8) = *c;
                p += 1;
            }
            *translated_refmut(new_token, p as *mut u8) = 0;
        }
        // make the user_sp aligned to 8B for k210 platform
        user_sp -= user_sp % core::mem::size_of::<usize>();
        // initialize trap_cx
        let mut trap_cx = TrapContext::app_init_context(
            entry_point,
            user_sp,
            KERNEL_SPACE.exclusive_access().token(),
            task.kstack.get_top(),
            trap_handler as usize,
        );
        trap_cx.x[10] = args.len();
        trap_cx.x[11] = argv_base;
        *task_inner.get_trap_cx() = trap_cx;
    }

os::mm

description: 这个 os::mm 包的主要用途是初始化系统的内存管理组件,以确保系统内存管理的高效和安全。其关键函数 init 用于执行具体的初始化操作,包括使用全局分配器初始化内核堆内存、设置内存帧的分配和释放管理、获取并激活内核空间的独占访问权限。该包还依赖于一些相关的模块和函数来实现其功能。

init

description: init函数的主要功能是初始化系统的内存管理组件。具体操作包括:

  1. 调用init_heap函数,初始化内核堆内存。这涉及使用全局分配器HEAP_ALLOCATOR来设置堆的起始地址和大小。
  2. 调用init_frame_allocator函数,初始化帧分配器。这通过设置内存的起始和结束地址来管理内存帧的分配和释放。
  3. 通过KERNEL_SPACE.exclusive_access().activate(),获取内核空间的独占访问权限并激活它,以确保对内核空间的安全操作和资源管理。
pub fn init() {
    heap_allocator::init_heap();
    frame_allocator::init_frame_allocator();
    KERNEL_SPACE.exclusive_access().activate();
}

os::mm::memory_set

description: 该 os::mm::memory_set 包主要用于管理虚拟内存的映射和管理。它包含了多种函数和类型来实现以下功能:

  • 函数
    • MapArea 相关函数,如 mapmap_oneunmapunmap_onecopy_datafrom_anothernew 等,用于管理虚拟页号范围到物理内存的映射、解除映射以及数据复制等操作。
    • MemorySet 相关函数,如 activateinsert_framed_areamap_trampolinepushrecycle_data_pagesremove_area_with_start_vpntokentranslatefrom_elffrom_existed_usernew_barenew_kernel 等,用于管理页表和内存映射区域,包括激活页表、插入和移除映射区域、清理数据页面等操作。
    • 其他函数如 remap_test 用于测试内核空间中内存映射的属性,ebssedataekernelerodataetextsbss_with_stacksdatasrodatastextstrampoline 等函数的功能未明确说明,kernel_token 用于获取内核空间的令牌。
  • 类型
    • MapArea:用于管理虚拟页号范围到物理内存的映射,包含虚拟页号范围、数据帧和映射类型及权限等字段。
    • MapPermission:其功能和用途未详细说明。
    • MapType:定义了三种不同的映射类型:Identical、Framed 和 Linear。
    • MemorySet:用于管理页表和内存映射区域,包含 page_tableareas 两个字段。
  • 变量
    • KERNEL_SPACE:用于管理内核空间的访问和操作。
    • R:功能未明确。
    • U:作为用户权限或用户模式的标志变量。
    • W:功能未明确。
    • X:功能未明确。

通过这些函数、类型和变量,该包能够高效地管理和使用系统的内存资源。

edata

description: edata函数没有参数和返回值。其主要功能和用途没有在给定的信息中详细说明。

fn edata();

kernel_token

description: kernel_token函数用于获取内核空间的令牌。它通过KERNEL_SPACE全局变量获取内核空间的独占访问权限,然后调用相应的方法来获取令牌。返回值是一个usize类型的内核令牌,用于标识和管理内核空间的资源。

pub fn kernel_token() -> usize {
    KERNEL_SPACE.exclusive_access().token()
}

MemorySet.activate

description: activate函数主要用于激活MemorySet实例的页表。该函数通过获取页表的token值,并调用satp::write方法写入satp寄存器,然后执行RISC-V指令“sfence.vma”以刷新虚拟内存地址的翻译缓存。此操作需要在不安全的上下文中进行,以确保对低级硬件的正确控制。

pub fn activate(&self) {
        let satp = self.page_table.token();
        unsafe {
            satp::write(satp);
            asm!("sfence.vma");
        }
    }

ebss

description: ebss函数没有参数和返回值,其主要功能和用途并未在提供的信息中明确说明。由于缺乏关于其具体实现或调用背景的描述,无法进一步总结其具体作用。

fn ebss();

MemorySet::from_elf

description: from_elf函数的主要功能是从ELF文件数据中创建一个内存集合,并返回内存集合、用户栈基地址和入口点。它首先创建一个新的空内存集合,并映射跳板,然后解析ELF文件头,验证其魔数的正确性。对于每个程序头,如果其类型为加载类型,则根据其标志设置映射权限,并创建映射区域。最后,计算用户栈基地址并返回。该函数的参数elf_data表示ELF文件的数据数组,用于解析和创建内存映射。

/// Include sections in elf and trampoline,
    /// also returns user_sp_base and entry point.
    pub fn from_elf(elf_data: &[u8]) -> (Self, usize, usize) {
        let mut memory_set = Self::new_bare();
        // map trampoline
        memory_set.map_trampoline();
        // map program headers of elf, with U flag
        let elf = xmas_elf::ElfFile::new(elf_data).unwrap();
        let elf_header = elf.header;
        let magic = elf_header.pt1.magic;
        assert_eq!(magic, [0x7f, 0x45, 0x4c, 0x46], "invalid elf!");
        let ph_count = elf_header.pt2.ph_count();
        let mut max_end_vpn = VirtPageNum(0);
        for i in 0..ph_count {
            let ph = elf.program_header(i).unwrap();
            if ph.get_type().unwrap() == xmas_elf::program::Type::Load {
                let start_va: VirtAddr = (ph.virtual_addr() as usize).into();
                let end_va: VirtAddr = ((ph.virtual_addr() + ph.mem_size()) as usize).into();
                let mut map_perm = MapPermission::U;
                let ph_flags = ph.flags();
                if ph_flags.is_read() {
                    map_perm |= MapPermission::R;
                }
                if ph_flags.is_write() {
                    map_perm |= MapPermission::W;
                }
                if ph_flags.is_execute() {
                    map_perm |= MapPermission::X;
                }
                let map_area = MapArea::new(start_va, end_va, MapType::Framed, map_perm);
                max_end_vpn = map_area.vpn_range.get_end();
                memory_set.push(
                    map_area,
                    Some(&elf.input[ph.offset() as usize..(ph.offset() + ph.file_size()) as usize]),
                );
            }
        }
        let max_end_va: VirtAddr = max_end_vpn.into();
        let mut user_stack_base: usize = max_end_va.into();
        user_stack_base += PAGE_SIZE;
        (
            memory_set,
            user_stack_base,
            elf.header.pt2.entry_point() as usize,
        )
    }

MapArea::new

description: new函数用于创建一个MapArea实例。其主要功能是初始化虚拟页号范围到物理内存的映射。函数接收以下参数:

  • start_va:表示起始虚拟地址的VirtAddr类型。
  • end_va:表示结束虚拟地址的VirtAddr类型。
  • map_type:表示映射类型的MapType枚举。
  • map_perm:表示映射权限的MapPermission

在函数内部,起始和结束虚拟地址被转换为虚拟页号范围,并创建一个空的BTreeMap来管理物理页号映射。

pub fn new(
        start_va: VirtAddr,
        end_va: VirtAddr,
        map_type: MapType,
        map_perm: MapPermission,
    ) -> Self {
        let start_vpn: VirtPageNum = start_va.floor();
        let end_vpn: VirtPageNum = end_va.ceil();
        Self {
            vpn_range: VPNRange::new(start_vpn, end_vpn),
            data_frames: BTreeMap::new(),
            map_type,
            map_perm,
        }
    }

MemorySet.map_trampoline

description: map_trampoline函数的主要功能是将虚拟地址映射到物理地址,并设置页表项的标志为可读和可执行。它通过调用self.page_table的map方法实现这一功能。入参包括将TRAMPOLINE转换为VirtAddr类型的虚拟地址和将strampoline作为usize转换为PhysAddr类型的物理地址。函数确保trampoline不被内存区域收集,并通过PTEFlags设置内存页面为可读和可执行。

/// Mention that trampoline is not collected by areas.
    fn map_trampoline(&mut self) {
        self.page_table.map(
            VirtAddr::from(TRAMPOLINE).into(),
            PhysAddr::from(strampoline as usize).into(),
            PTEFlags::R | PTEFlags::X,
        );
    }

MapArea.copy_data

description: copy_data函数的主要功能是将数据从给定的字节数组复制到页表中。该函数假设在调用之前所有的帧都已清除,并且数据是从起始位置对齐的。函数首先检查映射类型是否为Framed,然后通过循环将数据从源数组按页大小(PAGE_SIZE)分块复制到目标位置。目标位置由页表的虚拟页号(VPN)转换而来,确保数据正确地复制到相应的物理内存中。参数包括页表page_table和字节数组data。

/// data: start-aligned but maybe with shorter length
    /// assume that all frames were cleared before
    pub fn copy_data(&mut self, page_table: &PageTable, data: &[u8]) {
        assert_eq!(self.map_type, MapType::Framed);
        let mut start: usize = 0;
        let mut current_vpn = self.vpn_range.get_start();
        let len = data.len();
        loop {
            let src = &data[start..len.min(start + PAGE_SIZE)];
            let dst = &mut page_table
                .translate(current_vpn)
                .unwrap()
                .ppn()
                .get_bytes_array()[..src.len()];
            dst.copy_from_slice(src);
            start += PAGE_SIZE;
            if start >= len {
                break;
            }
            current_vpn.step();
        }
    }

MemorySet.translate

description: translate函数用于将虚拟页号(VirtPageNum)转换为页表项(PageTableEntry)。它通过调用MemorySet结构体中的page_table的translate方法来实现这种转换。

入参:

  • vpn:虚拟页号,表示需要转换的虚拟页。

出参:

  • Option类型的PageTableEntry:转换得到的页表项,可能为空(即没有对应的页表项)。
pub fn translate(&self, vpn: VirtPageNum) -> Option<PageTableEntry> {
        self.page_table.translate(vpn)
    }

strampoline

description: strampoline函数的主要功能和用途不明确,因为没有附加描述或相关信息。根据函数名推测,可能与字符串操作或某种跳跃(trampoline)机制有关,但具体细节无法确定。由于没有参数和返回值信息,因此无法进一步解释其作用。

fn strampoline();

etext

description: etext函数的主要功能是提供一个无参数且无返回值的操作。由于缺乏具体的实现细节和相关信息,无法进一步说明其用途或功能。

fn etext();

sdata

description: sdata函数没有参数和返回值,主要功能暂不明确。由于缺乏详细的描述信息,无法推断出该函数的具体用途。

fn sdata();

erodata

description: erodata函数没有参数和返回值。其主要功能和用途没有详细描述,可能是一个用于特定目的的占位函数。由于缺乏相关信息,无法提供更具体的功能细节。

fn erodata();

MapArea.map

description: map函数的主要功能是将MapArea内的虚拟页号范围映射到提供的PageTable中。它遍历MapArea的vpn_range,为每个虚拟页号调用map_one方法进行映射。

参数:

  • page_table: 这是一个PageTable类型的可变引用,用于管理页表的映射关系,其功能包括将虚拟地址映射到物理地址,并管理这些映射关系。
pub fn map(&mut self, page_table: &mut PageTable) {
        for vpn in self.vpn_range {
            self.map_one(page_table, vpn);
        }
    }

MemorySet.recycle_data_pages

description: recycle_data_pages函数用于清除MemorySet结构中的数据页面。其主要功能是通过调用areas的clear方法来清空MemorySet中的areas向量。这个方法没有参数,也没有返回值,通常用于重置或清理MemorySet实例的状态。

pub fn recycle_data_pages(&mut self) {
        //*self = Self::new_bare();
        self.areas.clear();
    }

MemorySet.remove_area_with_start_vpn

description: remove_area_with_start_vpn函数的主要功能是从MemorySet的areas中移除起始虚拟页号为给定start_vpn的区域。函数会先在areas中查找起始虚拟页号等于start_vpn的区域。如果找到,则调用该区域的unmap方法以解除映射,并从areas中移除该区域。

入参:

  • start_vpn: VirtPageNum类型,表示需要移除的区域的起始虚拟页号。
pub fn remove_area_with_start_vpn(&mut self, start_vpn: VirtPageNum) {
        if let Some((idx, area)) = self
            .areas
            .iter_mut()
            .enumerate()
            .find(|(_, area)| area.vpn_range.get_start() == start_vpn)
        {
            area.unmap(&mut self.page_table);
            self.areas.remove(idx);
        }
    }

MapArea.map_one

description: map_one函数的主要功能是根据不同的映射类型将虚拟页号(vpn)映射到物理页号(ppn),并在页表中更新映射关系。该函数根据MapType枚举的不同值执行不同的映射策略:Identical类型直接将虚拟页号转换为物理页号;Framed类型通过分配内存帧来获取物理页号并将其存储到data_frames中;Linear类型则根据偏移量计算物理页号。最后,函数将映射关系写入页表。

参数:

  • page_table: 用于管理页表的映射关系。
  • vpn: 表示虚拟页号,用于定位虚拟地址。
pub fn map_one(&mut self, page_table: &mut PageTable, vpn: VirtPageNum) {
        let ppn: PhysPageNum;
        match self.map_type {
            MapType::Identical => {
                ppn = PhysPageNum(vpn.0);
            }
            MapType::Framed => {
                let frame = frame_alloc().unwrap();
                ppn = frame.ppn;
                self.data_frames.insert(vpn, frame);
            }
            MapType::Linear(pn_offset) => {
                // check for sv39
                assert!(vpn.0 < (1usize << 27));
                ppn = PhysPageNum((vpn.0 as isize + pn_offset) as usize);
            }
        }
        let pte_flags = PTEFlags::from_bits(self.map_perm.bits).unwrap();
        page_table.map(vpn, ppn, pte_flags);
    }

MemorySet.insert_framed_area

description: insert_framed_area函数的主要功能是将一个新的映射区域插入到MemorySet中。具体来说,该函数会将一个由起始和结束虚拟地址定义的区域,以框架映射类型(Framed)和指定的映射权限(MapPermission)插入到内存集合中。函数的参数包括:

  • start_va: 起始虚拟地址,类型为VirtAddr
  • end_va: 结束虚拟地址,类型为VirtAddr
  • permission: 映射权限,类型为MapPermission

通过调用MapArea::new函数,生成一个新的MapArea实例并将其推入MemorySet的区域列表中。

/// Assume that no conflicts.
    pub fn insert_framed_area(
        &mut self,
        start_va: VirtAddr,
        end_va: VirtAddr,
        permission: MapPermission,
    ) {
        self.push(
            MapArea::new(start_va, end_va, MapType::Framed, permission),
            None,
        );
    }

srodata

description: srodata函数的主要功能和用途不明确,因为没有提供具体的实现细节或描述信息。

fn srodata();

MapArea.unmap

description: unmap函数的主要功能是解除MapArea中的虚拟页号(VPN)范围与给定PageTable的映射关系。它通过遍历MapArea的vpn_range,对每个VPN调用unmap_one方法来解除映射。

参数:

  • page_table: PageTable类型的可变引用,用于管理页表的映射关系。

通过该函数,MapArea能够有效地解除其管理的虚拟地址范围在页表中的映射。

pub fn unmap(&mut self, page_table: &mut PageTable) {
        for vpn in self.vpn_range {
            self.unmap_one(page_table, vpn);
        }
    }

MemorySet::new_kernel

description: new_kernel函数用于创建一个新的内核内存集合,主要功能是在不包含内核栈的情况下初始化内存映射。它首先创建一个空的MemorySet实例,然后映射跳板和内核的各个段(如.text, .rodata, .data, .bss),并为每个段设置相应的权限。函数还映射物理内存和内存映射输入输出(MMIO)区域,使这些区域具有读写权限。该函数不接收参数,返回一个初始化好的MemorySet实例。

/// Without kernel stacks.
    pub fn new_kernel() -> Self {
        let mut memory_set = Self::new_bare();
        // map trampoline
        memory_set.map_trampoline();
        // map kernel sections
        // println!(".text [{:#x}, {:#x})", stext as usize, etext as usize);
        // println!(".rodata [{:#x}, {:#x})", srodata as usize, erodata as usize);
        // println!(".data [{:#x}, {:#x})", sdata as usize, edata as usize);
        // println!(
        //     ".bss [{:#x}, {:#x})",
        //     sbss_with_stack as usize, ebss as usize
        // );
        // println!("mapping .text section");
        memory_set.push(
            MapArea::new(
                (stext as usize).into(),
                (etext as usize).into(),
                MapType::Identical,
                MapPermission::R | MapPermission::X,
            ),
            None,
        );
        // println!("mapping .rodata section");
        memory_set.push(
            MapArea::new(
                (srodata as usize).into(),
                (erodata as usize).into(),
                MapType::Identical,
                MapPermission::R,
            ),
            None,
        );
        // println!("mapping .data section");
        memory_set.push(
            MapArea::new(
                (sdata as usize).into(),
                (edata as usize).into(),
                MapType::Identical,
                MapPermission::R | MapPermission::W,
            ),
            None,
        );
        // println!("mapping .bss section");
        memory_set.push(
            MapArea::new(
                (sbss_with_stack as usize).into(),
                (ebss as usize).into(),
                MapType::Identical,
                MapPermission::R | MapPermission::W,
            ),
            None,
        );
        // println!("mapping physical memory");
        memory_set.push(
            MapArea::new(
                (ekernel as usize).into(),
                MEMORY_END.into(),
                MapType::Identical,
                MapPermission::R | MapPermission::W,
            ),
            None,
        );
        //println!("mapping memory-mapped registers");
        for pair in MMIO {
            memory_set.push(
                MapArea::new(
                    (*pair).0.into(),
                    ((*pair).0 + (*pair).1).into(),
                    MapType::Identical,
                    MapPermission::R | MapPermission::W,
                ),
                None,
            );
        }
        memory_set
    }

MapArea.unmap_one

description: unmap_one函数用于解除MapArea中的一个虚拟页号(VPN)的映射关系。其主要功能是在映射类型为Framed时,从data_frames中移除对应的VPN,并在PageTable中解除该VPN的映射。

参数:

  • page_table: 用于管理页表的映射关系,负责解除映射。
  • vpn: 虚拟页号,表示要解除映射的页。

该函数通过检查映射类型,并调用PageTable的方法,确保映射关系被正确解除。

pub fn unmap_one(&mut self, page_table: &mut PageTable, vpn: VirtPageNum) {
        if self.map_type == MapType::Framed {
            self.data_frames.remove(&vpn);
        }
        page_table.unmap(vpn);
    }

MemorySet::from_existed_user

description: from_existed_user函数用于从已存在的用户内存空间创建一个新的MemorySet实例。通过初始化一个新的MemorySet对象,该函数映射跳板,并逐个将用户空间中的内存区域复制到新的MemorySet中。对于每个区域,它创建一个新的MapArea实例并将其添加到MemorySet中,随后复制数据段、陷阱上下文以及用户堆栈。入参为user_space,即需要复制的MemorySet实例,返回值为新的MemorySet实例。

pub fn from_existed_user(user_space: &MemorySet) -> MemorySet {
        let mut memory_set = Self::new_bare();
        // map trampoline
        memory_set.map_trampoline();
        // copy data sections/trap_context/user_stack
        for area in user_space.areas.iter() {
            let new_area = MapArea::from_another(area);
            memory_set.push(new_area, None);
            // copy data from another space
            for vpn in area.vpn_range {
                let src_ppn = user_space.translate(vpn).unwrap().ppn();
                let dst_ppn = memory_set.translate(vpn).unwrap().ppn();
                dst_ppn
                    .get_bytes_array()
                    .copy_from_slice(src_ppn.get_bytes_array());
            }
        }
        memory_set
    }

ekernel

description: ekernel函数的主要功能是定义一个空函数,且没有任何参数或返回值。它的用途可能是作为占位符或用于实现某种接口,但在当前定义中没有具体操作。

fn ekernel();

MemorySet.token

description: MemorySet的token方法用于获取与其关联的page_table的token值。返回值是一个usize类型的整数,代表page_table的token。这个方法的主要功能是通过调用page_table的token方法来检索内存集合的标识符。

pub fn token(&self) -> usize {
        self.page_table.token()
    }

sbss_with_stack

description: sbss_with_stack函数的主要功能和用途不明确,因为缺少详细的函数体和上下文信息。由于没有提供相关函数、类型或变量的信息,我们无法进一步推测其具体实现或使用场景。

fn sbss_with_stack();

MemorySet.push

description: push方法的主要功能是将一个新的MapArea添加到MemorySet中。假设虚拟地址空间中没有冲突。

入参:

  • map_area: 一个MapArea对象,表示要添加的映射区域。
  • data: 可选的数据切片,用于在映射后复制到该区域。

在push方法中,首先将map_area映射到MemorySet的page_table中。如果提供了数据,则将数据复制到映射区域中。最后,将map_area添加到MemorySet的areas向量中。

/// Add a new MapArea into this MemorySet.
    /// Assuming that there are no conflicts in the virtual address
    /// space.
    pub fn push(&mut self, mut map_area: MapArea, data: Option<&[u8]>) {
        map_area.map(&mut self.page_table);
        if let Some(data) = data {
            map_area.copy_data(&self.page_table, data);
        }
        self.areas.push(map_area);
    }

stext

description: stext函数的主要功能是定义一个名为stext的函数,该函数没有参数和返回值。由于没有相关函数、类型或变量的描述,无法提供更多关于其用途或实现细节的信息。

fn stext();

MapArea::from_another

description: from_another函数用于创建一个新的MapArea实例。它接收一个MapArea类型的引用作为参数。该函数的主要功能是从给定的MapArea实例中复制其vpn_range、map_type和map_perm属性,同时初始化一个空的BTreeMap来存储data_frames。入参:another是一个MapArea类型的引用,用于提供创建新MapArea实例所需的数据。该函数返回一个新的MapArea实例,复制了另一个实例的虚拟页号范围、映射类型和权限。

pub fn from_another(another: &MapArea) -> Self {
        Self {
            vpn_range: VPNRange::new(another.vpn_range.get_start(), another.vpn_range.get_end()),
            data_frames: BTreeMap::new(),
            map_type: another.map_type,
            map_perm: another.map_perm,
        }
    }

remap_test

description: remap_test函数的主要功能是在内核空间中测试内存映射的属性。具体来说,它通过计算几个虚拟地址的中点,然后检查这些地址在内核空间的页表中是否具有特定的权限属性。函数使用了assert断言来确保特定地址不可写或不可执行。最后,函数通过println输出确认测试通过。这个函数没有参数或返回值,主要用于验证内存区域的权限设置是否正确。

#[allow(unused)]
pub fn remap_test() {
    let mut kernel_space = KERNEL_SPACE.exclusive_access();
    let mid_text: VirtAddr = ((stext as usize + etext as usize) / 2).into();
    let mid_rodata: VirtAddr = ((srodata as usize + erodata as usize) / 2).into();
    let mid_data: VirtAddr = ((sdata as usize + edata as usize) / 2).into();
    assert!(!kernel_space
        .page_table
        .translate(mid_text.floor())
        .unwrap()
        .writable(),);
    assert!(!kernel_space
        .page_table
        .translate(mid_rodata.floor())
        .unwrap()
        .writable(),);
    assert!(!kernel_space
        .page_table
        .translate(mid_data.floor())
        .unwrap()
        .executable(),);
    println!("remap_test passed!");
}

MemorySet::new_bare

description: new_bare函数用于创建并初始化一个MemorySet实例。该函数没有参数,返回一个新的MemorySet实例。MemorySet包含两个主要字段:

  • page_table: 一个PageTable实例,用于管理虚拟地址到物理地址的映射关系。通过调用PageTable的new函数来初始化。
  • areas: 一个Vec类型的集合,用于存储和管理多个内存映射区域,初始化为空的Vec。

该函数的主要目的是为MemorySet提供一个简单的初始化方法,使其能够有效地管理内存资源。

pub fn new_bare() -> Self {
        Self {
            page_table: PageTable::new(),
            areas: Vec::new(),
        }
    }

os::timer

description: 该 os::timer 包主要用于管理和调度定时任务,具有以下功能和特点:

  • 提供了一系列函数来添加、设置、检查和获取定时器相关信息,如 add_timer 添加定时任务,set_next_trigger 设置下一个计时器触发时间,check_timer 检查并处理已到期任务,get_time 获取当前时间,get_time_ms 获取当前时间的毫秒表示。
  • 定义了 TimerCondVar 结构体用于管理定时任务,包含 expire_mstask 字段。
  • 包含一些常量,如 MSEC_PER_SEC 表示每秒的毫秒数,TICKS_PER_SEC 用于定义每秒的时钟滴答数,以及全局变量 TIMERS 用于管理定时器集合。
  • 依赖了一些其他模块的功能,如 os::boards::qemu 中的 CLOCK_FREQos::sbi 中的 set_timer 等。

TimerCondVar.eq

description: eq函数用于比较两个TimerCondVar结构体实例的expire_ms字段是否相等。入参包括self和other两个TimerCondVar实例。返回值是一个布尔值,表示两者的expire_ms字段是否相同。

fn eq(&self, other: &Self) -> bool {
        self.expire_ms == other.expire_ms
    }

TimerCondVar.cmp

description: cmp函数用于比较两个TimerCondVar实例。此函数通过调用partial_cmp方法来比较实例,并使用unwrap方法处理比较结果。返回值是Ordering类型,用以指示比较结果的顺序。

fn cmp(&self, other: &Self) -> Ordering {
        self.partial_cmp(other).unwrap()
    }

get_time

description: get_time函数用于获取当前时间。它通过调用time::read()来实现,返回一个无符号整数类型的时间值。

pub fn get_time() -> usize {
    time::read()
}

set_next_trigger

description: set_next_trigger函数的主要功能是设置下一个计时器触发时间。它通过获取当前时间并加上每秒时钟滴答数的间隔来实现。具体步骤包括:

  • 调用get_time函数以获取当前时间。
  • 使用CLOCK_FREQ和TICKS_PER_SEC常量计算下一个事件应触发的时间。
  • 调用set_timer函数以在计算出的时间点设置计时器。

这个函数用于确保系统能在指定的时间间隔内定期触发事件。

pub fn set_next_trigger() {
    set_timer(get_time() + CLOCK_FREQ / TICKS_PER_SEC);
}

add_timer

description: add_timer函数的主要功能是添加一个定时任务。它通过对全局变量TIMERS进行独占访问,将新的定时任务(由expire_ms和task组成)添加到定时器集合中。

参数:

  • expire_ms: 任务到期时间,以毫秒为单位,决定定时任务何时触发。
  • task: 一个TaskControlBlock的引用,使用Arc进行引用计数,确保在多个任务间共享时的安全性。
pub fn add_timer(expire_ms: usize, task: Arc<TaskControlBlock>) {
    let mut timers = TIMERS.exclusive_access();
    timers.push(TimerCondVar { expire_ms, task });
}

get_time_ms

description: get_time_ms函数用于获取当前时间,以毫秒为单位。它通过调用time::read函数读取当前时间,并将其除以CLOCK_FREQ(时钟频率)和MSEC_PER_SEC(每秒的毫秒数)的比值来实现毫秒时间的转换。

出参:

  • 返回值:当前时间的毫秒表示,类型为usize。
pub fn get_time_ms() -> usize {
    time::read() / (CLOCK_FREQ / MSEC_PER_SEC)
}

check_timer

description: check_timer函数的主要功能是检查和处理已到期的定时器任务。首先,它通过get_time_ms函数获取当前时间(以毫秒为单位)。然后,它使用TIMERS的独占会话来访问定时器集合。函数会遍历定时器集合中的定时器,如果某个定时器的过期时间小于或等于当前时间,则调用wakeup_task函数唤醒与该定时器关联的任务,并将该定时器从集合中移除。此过程持续进行,直到遇到未过期的定时器。

pub fn check_timer() {
    let current_ms = get_time_ms();
    TIMERS.exclusive_session(|timers| {
        while let Some(timer) = timers.peek() {
            if timer.expire_ms <= current_ms {
                wakeup_task(Arc::clone(&timer.task));
                timers.pop();
            } else {
                break;
            }
        }
    });
}

TimerCondVar.partial_cmp

description: partial_cmp函数用于比较两个TimerCondVar实例的expire_ms字段。其目的是确定哪个实例的expire_ms值更小。参数包括:

  • self: 当前的TimerCondVar实例。
  • other: 另一个用于比较的TimerCondVar实例。

函数将expire_ms转换为有符号整数并取负值,然后使用cmp方法进行比较。返回结果是一个Option类型,包含Ordering枚举值,用于表示比较结果:小于、等于或大于。

fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        let a = -(self.expire_ms as isize);
        let b = -(other.expire_ms as isize);
        Some(a.cmp(&b))
    }

os::drivers::block

description: 该 os::drivers::block 包主要用于块设备的操作和测试。包中有一个函数 block_device_test 用于测试块设备的读写功能,它克隆全局变量 BLOCK_DEVICE 代表的块设备实例,初始化两个 512 字节的缓冲区进行读写操作并验证数据一致性。包中还有一个全局变量 BLOCK_DEVICE 用于表示块设备实例,在测试函数和中断处理程序中发挥作用。该包依赖 alloc::syncclonecore::macrosassert_eqcore::sliceiter_muteasy-fs::block_devwrite_blockBlockDevice 以及 os::consoleprintln

block_device_test

description: block_device_test函数用于测试块设备的读写功能。它克隆一个全局变量BLOCK_DEVICE,代表一个块设备实例。函数初始化两个512字节的缓冲区,一个用于写入,一个用于读取。在一个循环中,它将写缓冲区的每个字节设置为当前索引值,然后将其写入块设备的相应块。接着,它读取相同块到读缓冲区,并通过assert_eq验证写入和读取的数据是否一致。如果所有测试通过,控制台将输出“block device test passed!”的消息。

#[allow(unused)]
pub fn block_device_test() {
    let block_device = BLOCK_DEVICE.clone();
    let mut write_buffer = [0u8; 512];
    let mut read_buffer = [0u8; 512];
    for i in 0..512 {
        for byte in write_buffer.iter_mut() {
            *byte = i as u8;
        }
        block_device.write_block(i as usize, &write_buffer);
        block_device.read_block(i as usize, &mut read_buffer);
        assert_eq!(write_buffer, read_buffer);
    }
    println!("block device test passed!");
}

os

description: 该 os 包的主要用途是初始化系统环境并启动任务管理。其中包含的关键函数如下:

  • clear_bss:将程序中 BSS 段内的所有数据清零。
  • ebss:其功能和用途未详细描述。
  • rust_main:初始化系统环境并启动任务管理,包括一系列初始化操作、设置设备中断、列出应用程序、添加初始化进程、设置设备为非阻塞访问模式以及运行任务管理器,最后调用 panic 函数防止正常退出。
  • sbss:其功能和用途未明确描述。

该包中还定义了一个全局变量 DEV_NON_BLOCKING_ACCESS,用于控制设备访问的阻塞模式。此外,该包依赖了多个其他模块的功能。

rust_main

description: rust_main函数的主要功能是初始化系统环境并启动任务管理。它首先清除BSS段,初始化内存管理、UART、GPU、键盘、鼠标、陷阱处理和定时器中断。然后,初始化设备中断设置,列出应用程序,并添加初始化进程。它设置设备为非阻塞访问模式,最后运行任务管理器以处理任务调度。该函数不返回值,并在最后调用panic函数以防止正常退出。

#[no_mangle]
pub fn rust_main() -> ! {
    clear_bss();
    mm::init();
    UART.init();
    println!("KERN: init gpu");
    let _gpu = GPU_DEVICE.clone();
    println!("KERN: init keyboard");
    let _keyboard = KEYBOARD_DEVICE.clone();
    println!("KERN: init mouse");
    let _mouse = MOUSE_DEVICE.clone();
    println!("KERN: init trap");
    trap::init();
    trap::enable_timer_interrupt();
    timer::set_next_trigger();
    board::device_init();
    fs::list_apps();
    task::add_initproc();
    *DEV_NON_BLOCKING_ACCESS.exclusive_access() = true;
    task::run_tasks();
    panic!("Unreachable in rust_main!");
}

clear_bss

description: clear_bss函数的主要功能是将程序中BSS段内的所有数据清零。BSS段通常用于存储程序中未初始化的全局变量。函数通过调用外部C函数sbss和ebss获取BSS段的起始和结束地址,然后使用from_raw_parts_mut创建一个可变的切片,覆盖BSS段的内存范围,并将其所有字节设置为0。此操作在unsafe块中执行,表示需要特别注意安全性。

fn clear_bss() {
    extern "C" {
        fn sbss();
        fn ebss();
    }
    unsafe {
        core::slice::from_raw_parts_mut(sbss as usize as *mut u8, ebss as usize - sbss as usize)
            .fill(0);
    }
}

sbss

description: 函数sbss的主要功能和用途并未在提供的信息中明确描述。没有参数和返回值,该函数可能是一个简单的过程或占位符,用于执行特定的操作或逻辑。由于缺少额外的上下文和细节,无法进一步推测其具体用途。

fn sbss();

ebss

description: ebss函数没有参数或返回值。其主要功能和用途未在提供的信息中详细描述。

fn ebss();

os::drivers::chardev::ns16550a

description: os::drivers::chardev::ns16550a包主要用于管理串行通信端口,特别是 NS16550a 芯片的操作。该包提供了对串行端口的数据流管理,包括初始化、读写操作等功能。它包含了多个函数用于处理串行通信的各种操作,如初始化结构体、读写数据、处理中断请求等。同时还定义了多种结构体类型,用于表示串行通信的相关组件和状态。此外,还定义了一些常量和标识符用于控制通信过程中的各种状态和操作。该包通过提供线程安全的接口和非阻塞式数据读取,确保对串行通信端口的高效管理。

NS16550a.read

description: read函数的主要功能是从NS16550a结构体的内部缓冲区中读取一个字节。该函数通过循环不断尝试从缓冲区中提取数据。如果缓冲区中存在数据,则返回该字节;如果缓冲区为空,函数会调用条件变量的wait_no_sched方法等待,并释放当前锁定的资源,然后调用schedule函数进行任务切换。该过程确保在数据可用时才进行读取,从而实现非阻塞式的数据读取。

fn read(&self) -> u8 {
        loop {
            let mut inner = self.inner.exclusive_access();
            if let Some(ch) = inner.read_buffer.pop_front() {
                return ch;
            } else {
                let task_cx_ptr = self.condvar.wait_no_sched();
                drop(inner);
                schedule(task_cx_ptr);
            }
        }
    }

NS16550aRaw.read_end

description: read_end函数的主要功能是返回一个指向ReadWithoutDLAB结构体的可变引用。它通过将NS16550aRaw结构体中的base_addr字段转换为一个指向ReadWithoutDLAB类型的指针来实现。这种操作使用了unsafe代码块,意味着开发者需要确保内存访问的安全性。参数:无;返回值:指向ReadWithoutDLAB结构体的可变引用。

fn read_end(&mut self) -> &mut ReadWithoutDLAB {
        unsafe { &mut *(self.base_addr as *mut ReadWithoutDLAB) }
    }

NS16550a::new

description: new函数的主要功能是初始化并创建一个NS16550a类型的实例。NS16550a用于管理串行通信端口的数据流。函数通过创建NS16550aInner实例,初始化NS16550aRaw和read_buffer,然后将其封装在UPIntrFreeCell中以提供安全的可变性。Condvar用于线程同步,确保线程安全。该函数没有输入参数,返回一个新的NS16550a实例,包含对串行通信的底层操作和数据缓存的管理。

pub fn new() -> Self {
        let inner = NS16550aInner {
            ns16550a: NS16550aRaw::new(BASE_ADDR),
            read_buffer: VecDeque::new(),
        };
        //inner.ns16550a.init();
        Self {
            inner: unsafe { UPIntrFreeCell::new(inner) },
            condvar: Condvar::new(),
        }
    }

NS16550a.write

description: write函数用于向NS16550a结构体的内部组件写入一个字节(u8类型)。该函数通过获取inner的独占访问权限来实现写操作。参数ch表示要写入的字节。此函数主要用于管理硬件资源的访问,以确保写入操作的线程安全。

fn write(&self, ch: u8) {
        let mut inner = self.inner.exclusive_access();
        inner.ns16550a.write(ch);
    }

NS16550aRaw.init

description: init函数用于初始化NS16550aRaw结构体的串行通信端口。该函数首先获取read_end对象,然后创建一个空的MCR(Modem Control Register)。接着,它设置MCR的几个标志位,包括DATA_TERMINAL_READY、REQUEST_TO_SEND和AUX_OUTPUT2,以配置调制解调器控制寄存器的状态。然后,这些配置被写入到read_end的mcr寄存器中。此外,函数还设置IER(Interrupt Enable Register)的RX_AVAILABLE标志,以启用接收器的可用性,并将其写入到read_end的ier寄存器中。

pub fn init(&mut self) {
        let read_end = self.read_end();
        let mut mcr = MCR::empty();
        mcr |= MCR::DATA_TERMINAL_READY;
        mcr |= MCR::REQUEST_TO_SEND;
        mcr |= MCR::AUX_OUTPUT2;
        read_end.mcr.write(mcr);
        let ier = IER::RX_AVAILABLE;
        read_end.ier.write(ier);
    }

NS16550aRaw::new

description: new函数用于创建并初始化一个NS16550aRaw结构体实例。入参base_addr表示串行通信端口的基地址。此函数返回一个包含该基地址的NS16550aRaw实例。

pub fn new(base_addr: usize) -> Self {
        Self { base_addr }
    }

NS16550aRaw.write

description: write方法用于在NS16550aRaw结构体中执行写操作。其主要功能是不断检查写入端口的状态,当检测到发送缓冲区为空(通过LSR::THR_EMPTY标识符),则将字节ch写入到发送保持寄存器中。参数ch代表要写入的字节数据。这个方法使用了一个循环,确保只有在缓冲区准备好接收新数据时,才进行写操作,以保证数据的正确传输。

pub fn write(&mut self, ch: u8) {
        let write_end = self.write_end();
        loop {
            if write_end.lsr.read().contains(LSR::THR_EMPTY) {
                write_end.thr.write(ch);
                break;
            }
        }
    }

NS16550a.handle_irq

description: handle_irq方法主要用于处理中断请求。它通过exclusive_session方法访问inner对象,读取数据并将其存入read_buffer中,同时计数。如果读取到的数据数量大于零,它将触发condvar的信号以通知其他线程。

fn handle_irq(&self) {
        let mut count = 0;
        self.inner.exclusive_session(|inner| {
            while let Some(ch) = inner.ns16550a.read() {
                count += 1;
                inner.read_buffer.push_back(ch);
            }
        });
        if count > 0 {
            self.condvar.signal();
        }
    }

NS16550aRaw.read

description: read方法的主要功能是从NS16550aRaw结构体的读取端读取一个字节的数据。如果Line Status Register (lsr)包含DATA_AVAILABLE标志,则表示有数据可读,并返回该数据;否则,返回None,表示没有数据可读。该方法不需要参数,返回值是Option类型,表示可能返回一个u8类型的数据或者没有数据可返回。

pub fn read(&mut self) -> Option<u8> {
        let read_end = self.read_end();
        let lsr = read_end.lsr.read();
        if lsr.contains(LSR::DATA_AVAILABLE) {
            Some(read_end.rbr.read())
        } else {
            None
        }
    }

NS16550a.read_buffer_is_empty

description: read_buffer_is_empty函数用于检查NS16550a类型的内部数据缓冲区是否为空。该函数通过调用exclusive_session方法来访问inner字段,并检查其read_buffer的状态。返回值为布尔类型,表示缓冲区是否为空。此函数的主要用途是帮助确定串行通信端口的数据流管理中是否有待处理的读取数据。

pub fn read_buffer_is_empty(&self) -> bool {
        self.inner
            .exclusive_session(|inner| inner.read_buffer.is_empty())
    }

NS16550aRaw.write_end

description: write_end方法的主要功能是将NS16550aRaw结构体中的base_addr转换为WriteWithoutDLAB类型的可变引用。这个方法使用了unsafe块来进行强制类型转换。

write_end方法没有参数,返回值为WriteWithoutDLAB类型的可变引用。

fn write_end(&mut self) -> &mut WriteWithoutDLAB {
        unsafe { &mut *(self.base_addr as *mut WriteWithoutDLAB) }
    }

NS16550a.init

description: init方法用于初始化NS16550a结构体中的inner字段。该方法通过exclusive_access获取inner的可变访问权限,然后调用inner.ns16550a的init方法进行初始化,最后通过drop释放inner的访问权限。

fn init(&self) {
        let mut inner = self.inner.exclusive_access();
        inner.ns16550a.init();
        drop(inner);
    }

os::syscall::process

description: 此 os::syscall::process 包主要提供与进程管理相关的系统调用,包括进程的创建(sys_fork)、终止(sys_exit)、获取信息(sys_getpidsys_get_time)、时间管理(sys_get_time)、等待子进程(sys_waitpid)、执行程序(sys_exec)、发送信号(sys_kill)以及任务切换(sys_yield)等操作。这些函数为应用程序对进程的创建、运行、终止及信号处理等提供了基本的管理和控制能力。

sys_kill

description: sys_kill函数的主要功能是向指定的进程发送信号。它接收两个参数:pid和signal。pid是要发送信号的进程ID,signal是要发送的信号类型。函数首先通过pid2process函数查找对应的进程控制块。如果找到进程,并且信号能够被转换为SignalFlags中的标志,则将该信号标志添加到进程的信号集中,并返回0表示成功;否则,返回-1表示失败。

pub fn sys_kill(pid: usize, signal: u32) -> isize {
    if let Some(process) = pid2process(pid) {
        if let Some(flag) = SignalFlags::from_bits(signal) {
            process.inner_exclusive_access().signals |= flag;
            0
        } else {
            -1
        }
    } else {
        -1
    }
}

sys_waitpid

description: sys_waitpid函数的主要功能是等待指定子进程结束,并获取其退出码。函数接收两个参数:

  1. pid: 表示子进程的进程ID (pid),如果为-1,则表示等待任意子进程。
  2. exit_code_ptr: 是一个指向整数的指针,用于存储子进程的退出码。

函数的返回值为子进程的pid。如果没有找到匹配的子进程,返回-1;如果找到但子进程仍在运行,返回-2。

/// If there is not a child process whose pid is same as given, return -1.
/// Else if there is a child process but it is still running, return -2.
pub fn sys_waitpid(pid: isize, exit_code_ptr: *mut i32) -> isize {
    let process = current_process();
    // find a child process

    let mut inner = process.inner_exclusive_access();
    if !inner
        .children
        .iter()
        .any(|p| pid == -1 || pid as usize == p.getpid())
    {
        return -1;
        // ---- release current PCB
    }
    let pair = inner.children.iter().enumerate().find(|(_, p)| {
        // ++++ temporarily access child PCB exclusively
        p.inner_exclusive_access().is_zombie && (pid == -1 || pid as usize == p.getpid())
        // ++++ release child PCB
    });
    if let Some((idx, _)) = pair {
        let child = inner.children.remove(idx);
        // confirm that child will be deallocated after being removed from children list
        assert_eq!(Arc::strong_count(&child), 1);
        let found_pid = child.getpid();
        // ++++ temporarily access child PCB exclusively
        let exit_code = child.inner_exclusive_access().exit_code;
        // ++++ release child PCB
        *translated_refmut(inner.memory_set.token(), exit_code_ptr) = exit_code;
        found_pid as isize
    } else {
        -2
    }
    // ---- release current PCB automatically
}

sys_fork

description: sys_fork函数的主要功能是创建一个新进程。首先,它获取当前进程的控制块,并调用fork方法复制该进程,生成一个新进程。接着,通过getpid方法获取新进程的进程ID。在新进程的任务中,修改其陷阱上下文以确保子进程fork后立即返回0。最终,函数返回新进程的ID作为isize类型的值。

pub fn sys_fork() -> isize {
    let current_process = current_process();
    let new_process = current_process.fork();
    let new_pid = new_process.getpid();
    // modify trap context of new_task, because it returns immediately after switching
    let new_process_inner = new_process.inner_exclusive_access();
    let task = new_process_inner.tasks[0].as_ref().unwrap();
    let trap_cx = task.inner_exclusive_access().get_trap_cx();
    // we do not have to move to next instruction since we have done it before
    // for child process, fork returns 0
    trap_cx.x[10] = 0;
    new_pid as isize
}

sys_exec

description: sys_exec函数的主要功能是执行一个位于给定路径的程序。它首先获取当前用户的令牌,然后将路径翻译为内核空间中的字符串。接着,它通过循环将所有参数字符串加载到一个字符串向量中。若成功打开指定路径的文件,则读取文件内容并执行该程序,同时返回参数个数;否则,返回-1。

参数:

  • path: 指向程序路径的指针。
  • args: 指向参数列表的指针。
pub fn sys_exec(path: *const u8, mut args: *const usize) -> isize {
    let token = current_user_token();
    let path = translated_str(token, path);
    let mut args_vec: Vec<String> = Vec::new();
    loop {
        let arg_str_ptr = *translated_ref(token, args);
        if arg_str_ptr == 0 {
            break;
        }
        args_vec.push(translated_str(token, arg_str_ptr as *const u8));
        unsafe {
            args = args.add(1);
        }
    }
    if let Some(app_inode) = open_file(path.as_str(), OpenFlags::RDONLY) {
        let all_data = app_inode.read_all();
        let process = current_process();
        let argc = args_vec.len();
        process.exec(all_data.as_slice(), args_vec);
        // return argc because cx.x[10] will be covered with it later
        argc as isize
    } else {
        -1
    }
}

sys_exit

description: sys_exit函数的主要功能是终止当前正在运行的任务,并切换到任务列表中的下一个任务。它接收一个整数参数exit_code,表示退出代码。函数通过调用exit_current_and_run_next来实现任务的退出和切换。如果代码执行到了panic调用,说明程序在sys_exit函数中出现了不可到达的状态,这通常是一个异常情况的指示。

pub fn sys_exit(exit_code: i32) -> ! {
    exit_current_and_run_next(exit_code);
    panic!("Unreachable in sys_exit!");
}

sys_get_time

description: sys_get_time函数用于获取当前时间并返回以isize类型表示的毫秒数。该函数通过调用get_time_ms函数来实现,get_time_ms函数负责获取当前时间的毫秒表示。

pub fn sys_get_time() -> isize {
    get_time_ms() as isize
}

sys_getpid

description: sys_getpid函数的主要功能是获取当前进程的进程ID(PID)。它通过调用current_task函数获取当前正在执行的任务,然后通过任务的process属性获取进程的控制块,最终调用getpid方法来获取进程ID,并将其转换为isize类型返回。

pub fn sys_getpid() -> isize {
    current_task().unwrap().process.upgrade().unwrap().getpid() as isize
}

sys_yield

description: sys_yield函数的主要功能是暂停当前任务并运行下一个任务。它内部调用了suspend_current_and_run_next函数来实现这一功能。该函数没有参数,返回值为isize类型的0,表示操作成功。

pub fn sys_yield() -> isize {
    suspend_current_and_run_next();
    0
}

os::drivers::net

description: 这个 os::drivers::net 包主要用于定义和实现网络设备的行为,提供灵活的网络通信能力。其核心功能包括:

  • 定义了 NetDevice trait,包含 transmit 发送数据和 receive 接收数据的方法,确保网络设备的基本行为。
  • 定义了 VirtIONetWrapper 结构体,用于通过底层的 VirtIONet 设备实现网络数据的发送和接收,包含 receivetransmit 方法以及构造函数 new
  • 包含一些变量,如 NET_DEVICE 用于网络设备操作,VIRTIO8 用于表示虚拟 I/O 设备的内存地址。

VirtIONetWrapper::new

description: new函数是VirtIONetWrapper结构体的构造函数。它的主要功能是创建一个新的VirtIONetWrapper实例。该函数通过不安全代码块来初始化一个VirtIONet实例。具体步骤是:首先将VIRTIO8常量转换为一个VirtIOHeader指针,然后调用VirtIONet::<VirtioHal>::new方法创建一个VirtIONet对象。如果创建失败,会返回错误信息。最后,将该对象封装进UPIntrFreeCell中,并返回一个新的VirtIONetWrapper实例。

pub fn new() -> Self {
        unsafe {
            let virtio = VirtIONet::<VirtioHal>::new(&mut *(VIRTIO8 as *mut VirtIOHeader))
                .expect("can't create net device by virtio");
            VirtIONetWrapper(UPIntrFreeCell::new(virtio))
        }
    }

VirtIONetWrapper.transmit

description: transmit函数的主要功能是通过VirtIONetWrapper的底层VirtIONet设备发送网络数据。入参:

  • data: 一个字节数组,表示需要发送的数据。

该函数通过调用exclusive_access方法获取对内部数据的独占访问,然后使用send方法发送数据。如果发送失败,将返回错误信息“can't send data”。

fn transmit(&self, data: &[u8]) {
        self.0
            .exclusive_access()
            .send(data)
            .expect("can't send data")
    }

VirtIONetWrapper.receive

description: receive函数的主要功能是从VirtIONetWrapper中接收数据。它通过exclusive_access方法获取对底层资源的独占访问权,然后调用recv方法接收数据。如果接收失败,会触发错误信息“can't receive data”。函数的参数data是一个可变的字节数组,用于存储接收到的数据。返回值是接收到的数据的字节数。

fn receive(&self, data: &mut [u8]) -> usize {
        self.0
            .exclusive_access()
            .recv(data)
            .expect("can't receive data")
    }

transmit

description: transmit函数的主要功能是发送数据。它是NetDevice这个trait的一部分,用于定义网络设备的行为。该函数接收一个字节切片作为参数,用于代表要发送的数据。

fn transmit(&self, data: &[u8]);

receive

description: receive函数的主要功能是从网络设备中接收数据。该函数接收一个可变的字节切片参数data,用于存储接收到的数据,并返回接收到的数据大小。这个函数是NetDevice trait的一部分,确保实现此特性的网络设备能够接收数据。

fn receive(&self, data: &mut [u8]) -> usize;

os::syscall

description: 该 os::syscall 包主要用于执行系统调用,通过 syscall 函数根据给定的 syscall_id 调用相应的系统调用函数,支持多种操作,包括文件操作(如打开、关闭、读写文件)、进程管理(如创建、终止进程)、线程管理(如创建线程、获取线程 ID)、同步机制(如互斥锁、信号量、条件变量)、网络通信(如连接、监听、接受请求)等。包中还定义了多个常量作为系统调用的标识符,用于各种具体的系统调用操作。

syscall

description: syscall函数的主要功能是根据syscall_id的值调用相应的系统调用函数。它支持多种系统调用操作,包括文件操作(如打开、关闭、读写文件)、进程管理(如创建、终止进程)、线程管理(如创建线程、获取线程ID)、同步机制(如互斥锁、信号量、条件变量)、网络通信(如连接、监听、接受请求)等。函数接收两个参数:syscall_id(无符号整数,表示系统调用的标识符)和args(长度为3的无符号整数数组,提供系统调用所需的参数)。返回值为isize类型,表示系统调用的结果。

pub fn syscall(syscall_id: usize, args: [usize; 3]) -> isize {
    match syscall_id {
        SYSCALL_DUP => sys_dup(args[0]),
        SYSCALL_CONNECT => sys_connect(args[0] as _, args[1] as _, args[2] as _),
        SYSCALL_LISTEN => sys_listen(args[0] as _),
        SYSCALL_ACCEPT => sys_accept(args[0] as _),
        SYSCALL_OPEN => sys_open(args[0] as *const u8, args[1] as u32),
        SYSCALL_CLOSE => sys_close(args[0]),
        SYSCALL_PIPE => sys_pipe(args[0] as *mut usize),
        SYSCALL_READ => sys_read(args[0], args[1] as *const u8, args[2]),
        SYSCALL_WRITE => sys_write(args[0], args[1] as *const u8, args[2]),
        SYSCALL_EXIT => sys_exit(args[0] as i32),
        SYSCALL_SLEEP => sys_sleep(args[0]),
        SYSCALL_YIELD => sys_yield(),
        SYSCALL_KILL => sys_kill(args[0], args[1] as u32),
        SYSCALL_GET_TIME => sys_get_time(),
        SYSCALL_GETPID => sys_getpid(),
        SYSCALL_FORK => sys_fork(),
        SYSCALL_EXEC => sys_exec(args[0] as *const u8, args[1] as *const usize),
        SYSCALL_WAITPID => sys_waitpid(args[0] as isize, args[1] as *mut i32),
        SYSCALL_THREAD_CREATE => sys_thread_create(args[0], args[1]),
        SYSCALL_GETTID => sys_gettid(),
        SYSCALL_WAITTID => sys_waittid(args[0]) as isize,
        SYSCALL_MUTEX_CREATE => sys_mutex_create(args[0] == 1),
        SYSCALL_MUTEX_LOCK => sys_mutex_lock(args[0]),
        SYSCALL_MUTEX_UNLOCK => sys_mutex_unlock(args[0]),
        SYSCALL_SEMAPHORE_CREATE => sys_semaphore_create(args[0]),
        SYSCALL_SEMAPHORE_UP => sys_semaphore_up(args[0]),
        SYSCALL_SEMAPHORE_DOWN => sys_semaphore_down(args[0]),
        SYSCALL_CONDVAR_CREATE => sys_condvar_create(),
        SYSCALL_CONDVAR_SIGNAL => sys_condvar_signal(args[0]),
        SYSCALL_CONDVAR_WAIT => sys_condvar_wait(args[0], args[1]),
        SYSCALL_FRAMEBUFFER => sys_framebuffer(),
        SYSCALL_FRAMEBUFFER_FLUSH => sys_framebuffer_flush(),
        SYSCALL_EVENT_GET => sys_event_get(),
        SYSCALL_KEY_PRESSED => sys_key_pressed(),
        _ => panic!("Unsupported syscall_id: {}", syscall_id),
    }
}

os::lang_items

description: 该 os::lang_items 包的主要功能为处理内核中的一些异常情况。其中包含两个函数:

  • backtrace 函数用于打印当前调用堆栈的回溯信息,最多打印 10 层,通过汇编指令获取帧指针和内核栈顶地址进行遍历,将格式化信息打印到控制台。
  • panic 函数作为恐慌处理程序,用于记录内核恐慌时的信息并执行系统关闭。它接收 PanicInfo 类型的参数,尝试获取恐慌发生位置并记录详细信息,调用 backtrace 函数打印调用堆栈回溯信息,最后通过 shutdown 函数关闭系统。

backtrace

description: backtrace函数的主要功能是打印当前调用堆栈的回溯信息。它使用汇编指令获取当前的帧指针(fp),并通过current_kstack_top函数获取当前任务的内核栈顶地址(stop)。函数会从当前帧指针开始,向上遍历调用堆栈,最多打印10层调用信息。当帧指针到达内核栈顶时,会停止遍历。每层调用信息包括当前层数和返回地址(ra)。函数输出的格式化信息通过println宏打印到控制台。

unsafe fn backtrace() {
    let mut fp: usize;
    let stop = current_kstack_top();
    asm!("mv {}, s0", out(reg) fp);
    println!("---START BACKTRACE---");
    for i in 0..10 {
        if fp == stop {
            break;
        }
        println!("#{}:ra={:#x}", i, *((fp - 8) as *const usize));
        fp = *((fp - 16) as *const usize);
    }
    println!("---END   BACKTRACE---");
}

panic

description: panic函数是一个恐慌处理程序,用于记录内核发生恐慌时的信息并执行系统关闭。该函数接收一个PanicInfo类型的参数info,其中包含恐慌的相关信息。当恐慌发生时,它首先尝试获取恐慌的发生位置(文件和行号)。如果获取成功,则使用error宏记录详细的恐慌信息,包括文件名、行号和恐慌消息;否则,仅记录恐慌消息。接下来,函数调用backtrace函数打印调用堆栈的回溯信息。最后,通过调用shutdown函数关闭系统,以确保系统在出现故障时安全停止。

#[panic_handler]
fn panic(info: &PanicInfo) -> ! {
    if let Some(location) = info.location() {
        error!(
            "[kernel] Panicked at {}:{} {}",
            location.file(),
            location.line(),
            info.message().unwrap()
        );
    } else {
        error!("[kernel] Panicked: {}", info.message().unwrap());
    }
    unsafe {
        backtrace();
    }
    shutdown(true)
}

os::drivers::block::virtio_blk

description: 该 os::drivers::block::virtio_blk 包主要用于管理 VirtIO 块设备。

包内包含以下内容:

  • 关键函数
    • VirtIOBlock::new:初始化一个 VirtIOBlock 类型的实例,创建并配置 VirtIO 块设备及其相关条件变量。
    • VirtIOBlock.handle_irq:处理 VirtIOBlock 中的中断请求,通过访问 virtio_blk 并处理令牌,通知等待线程条件满足。
    • VirtIOBlock.read_block:读取块设备的数据,根据全局变量决定阻塞或非阻塞方式。
    • VirtIOBlock.write_block:向 VirtIO 块设备写入数据,根据全局变量决定阻塞或非阻塞模式。
  • 关键类型
    • VirtIOBlock:用于管理 VirtIO 块设备的结构体,包含管理虚拟块设备的 virtio_blk 字段和存储条件变量的 condvars 字段,提供读取和写入块设备数据的方法。
  • 关键变量
    • VIRTIO0:常量,类型为 usize,值为 0x10008000,用于表示 VirtIO 设备的基地址。

该包依赖多个模块的相关功能来实现对 VirtIO 块设备的管理操作。

VirtIOBlock.read_block

description: read_block方法属于VirtIOBlock结构体,用于读取块设备的数据。其功能是根据全局变量DEV_NON_BLOCKING_ACCESS的值,决定使用阻塞或非阻塞方式读取数据。当DEV_NON_BLOCKING_ACCESS为true时,方法会使用非阻塞方式读取数据,通过unsafe代码调用read_block_nb,并等待任务完成后检查响应状态。当为false时,方法直接调用阻塞方式读取数据,确保数据读取成功。参数block_id表示要读取的块的ID,buf是用于存储读取数据的缓冲区。

fn read_block(&self, block_id: usize, buf: &mut [u8]) {
        let nb = *DEV_NON_BLOCKING_ACCESS.exclusive_access();
        if nb {
            let mut resp = BlkResp::default();
            let task_cx_ptr = self.virtio_blk.exclusive_session(|blk| {
                let token = unsafe { blk.read_block_nb(block_id, buf, &mut resp).unwrap() };
                self.condvars.get(&token).unwrap().wait_no_sched()
            });
            schedule(task_cx_ptr);
            assert_eq!(
                resp.status(),
                RespStatus::Ok,
                "Error when reading VirtIOBlk"
            );
        } else {
            self.virtio_blk
                .exclusive_access()
                .read_block(block_id, buf)
                .expect("Error when reading VirtIOBlk");
        }
    }

VirtIOBlock.write_block

description: write_block函数的主要功能是向VirtIO块设备写入数据。函数首先检查全局变量DEV_NON_BLOCKING_ACCESS的值,以决定使用非阻塞还是阻塞模式进行写操作。

如果启用了非阻塞模式,函数会创建一个默认的BlkResp对象,并在virtio_blk的独占会话中调用write_block_nb进行非阻塞写入,同时等待写入完成并调度任务上下文切换。写入成功后,验证响应状态为RespStatus::Ok。

如果未启用非阻塞模式,函数直接进行阻塞写入,确保写入成功。

fn write_block(&self, block_id: usize, buf: &[u8]) {
        let nb = *DEV_NON_BLOCKING_ACCESS.exclusive_access();
        if nb {
            let mut resp = BlkResp::default();
            let task_cx_ptr = self.virtio_blk.exclusive_session(|blk| {
                let token = unsafe { blk.write_block_nb(block_id, buf, &mut resp).unwrap() };
                self.condvars.get(&token).unwrap().wait_no_sched()
            });
            schedule(task_cx_ptr);
            assert_eq!(
                resp.status(),
                RespStatus::Ok,
                "Error when writing VirtIOBlk"
            );
        } else {
            self.virtio_blk
                .exclusive_access()
                .write_block(block_id, buf)
                .expect("Error when writing VirtIOBlk");
        }
    }

VirtIOBlock.handle_irq

description: handle_irq方法用于处理VirtIOBlock中的中断请求。当中断发生时,该方法通过exclusive_session方法访问virtio_blk,循环处理所有已使用的令牌。对于每个成功获取的令牌,它在condvars中找到相应的条件变量,并调用signal方法以通知等待线程该条件已满足。

fn handle_irq(&self) {
        self.virtio_blk.exclusive_session(|blk| {
            while let Ok(token) = blk.pop_used() {
                self.condvars.get(&token).unwrap().signal();
            }
        });
    }

VirtIOBlock::new

description: new函数用于初始化一个VirtIOBlock类型的实例。它的主要功能是创建并配置VirtIO块设备及其相关的条件变量。该函数首先通过UPIntrFreeCell封装VirtIOBlk类型,提供独占访问。然后,它创建一个BTreeMap来存储条件变量,使用u16作为键,以Condvar实例作为值,这些条件变量用于线程同步。返回值是一个新的VirtIOBlock实例,其中包含配置好的virtio_blk和condvars字段。

pub fn new() -> Self {
        let virtio_blk = unsafe {
            UPIntrFreeCell::new(
                VirtIOBlk::<VirtioHal>::new(&mut *(VIRTIO0 as *mut VirtIOHeader)).unwrap(),
            )
        };
        let mut condvars = BTreeMap::new();
        let channels = virtio_blk.exclusive_access().virt_queue_size();
        for i in 0..channels {
            let condvar = Condvar::new();
            condvars.insert(i, condvar);
        }
        Self {
            virtio_blk,
            condvars,
        }
    }

os::syscall::gui

description: 该 os::syscall::gui 包主要提供与 GPU 设备交互的功能,特别是帧缓冲区的管理和显示输出的刷新。其核心功能包括:

  • sys_framebuffer 函数用于获取 GPU 设备的帧缓冲区,并将其映射到当前进程的虚拟地址空间,返回帧缓冲区的虚拟地址。
  • sys_framebuffer_flush 函数用于刷新 GPU 设备的显示输出,通过调用 GPU_DEVICEflush 方法实现,返回整数值 0 表示成功。 此外,包中还定义了一个常量 FB_VADDR,类型为 usize,值为 0x10000000,用于表示帧缓冲区的虚拟地址。该包依赖于 core::convertcore::macroscore::sliceos::drivers::gpuos::mm::addressos::mm::memory_setos::task::processos::task::processor 等模块。

sys_framebuffer

description: sys_framebuffer 函数的主要功能是获取GPU设备的帧缓冲区,并将其映射到当前进程的虚拟地址空间。它首先获取帧缓冲区的地址和长度,然后通过物理地址和虚拟地址的转换,计算页码偏移量。接着,通过访问当前进程的内存设置,将帧缓冲区映射到指定的虚拟地址范围,允许读、写和用户访问。最终,它返回帧缓冲区的虚拟地址。

pub fn sys_framebuffer() -> isize {
    let fb = GPU_DEVICE.get_framebuffer();
    let len = fb.len();
    // println!("[kernel] FrameBuffer: addr 0x{:X}, len {}", fb.as_ptr() as usize , len);
    let fb_start_pa = PhysAddr::from(fb.as_ptr() as usize);
    assert!(fb_start_pa.aligned());
    let fb_start_ppn = fb_start_pa.floor();
    let fb_start_vpn = VirtAddr::from(FB_VADDR).floor();
    let pn_offset = fb_start_ppn.0 as isize - fb_start_vpn.0 as isize;

    let current_process = current_process();
    let mut inner = current_process.inner_exclusive_access();
    inner.memory_set.push(
        MapArea::new(
            (FB_VADDR as usize).into(),
            (FB_VADDR + len as usize).into(),
            MapType::Linear(pn_offset),
            MapPermission::R | MapPermission::W | MapPermission::U,
        ),
        None,
    );
    FB_VADDR as isize
}

sys_framebuffer_flush

description: sys_framebuffer_flush函数的主要功能是刷新GPU设备的显示输出。它通过调用GPU_DEVICE的flush方法来实现刷新操作,最后返回一个整数值0以表示操作成功。函数没有参数。

pub fn sys_framebuffer_flush() -> isize {
    GPU_DEVICE.flush();
    0
}

os::console

description: 该 os::console 包主要与操作系统的控制台操作相关。其中 Stdout 结构体用于通过 UART 接口进行串行通信的数据输出,包含 write_str 方法。此外,包中还包含 write_str 函数用于通过 UART 接口输出字符串,print 宏用于将格式化字符串输出到控制台,println 宏用于在控制台输出格式化字符串并自动换行。该包依赖 core::strcharsos::drivers::chardevUART 以及 os::drivers::chardev::ns16550aNS16550a.write

print

description: print宏的主要功能是将格式化的字符串输出到控制台。它接受一个格式化字符串(fmt)和可选的参数(fmt)和可选的参数(arg),通过调用format_args!宏来格式化这些参数,然后使用$crate::console::print函数将结果输出。此宏提供了一种简便的方法来实现格式化输出。

#[macro_export]
macro_rules! print {
    ($fmt: literal $(, $($arg: tt)+)?) => {
        $crate::console::print(format_args!($fmt $(, $($arg)+)?))
    }
}

Stdout.write_str

description: write_str 函数的主要功能是通过 UART 接口将输入的字符串逐个字符地写出,以实现串行通信的数据输出。该函数的接收者是 Stdout 类型的一个可变引用。函数接收一个字符串 s 作为参数,通过遍历字符串中的每一个字符,将其转换为 u8 类型后,使用 UART 接口逐个输出。函数返回一个 fmt::Result 类型,表示操作的结果状态。UART 在此函数中用于实现字符的逐个输出。

fn write_str(&mut self, s: &str) -> fmt::Result {
        for c in s.chars() {
            UART.write(c as u8);
        }
        Ok(())
    }

println

description: println宏的主要功能是用于在控制台输出格式化字符串并自动换行。它接受一个格式化字符串作为参数,并可选地接受多个参数用于格式化输出内容。该宏通过调用内部的print函数实现输出,确保输出内容以换行符结尾。

#[macro_export]
macro_rules! println {
    ($fmt: literal $(, $($arg: tt)+)?) => {
        $crate::console::print(format_args!(concat!($fmt, "\n") $(, $($arg)+)?))
    }
}

os::net::socket

description: 该 os::net::socket 包主要用于处理网络套接字的管理,其核心功能包括:

  • 提供了对套接字的添加(add_socket)、移除(remove_socket)、查找(get_socket)等操作。
  • 可以将数据推入(push_data)或弹出(pop_data)指定套接字的缓冲区。
  • 能够通过索引设置(set_s_a_by_index)和获取(get_s_a_by_index)套接字的序列号和确认号。
  • 定义了 Socket 结构体来表示网络套接字的信息,包括远程地址、本地和远程端口、数据缓冲区、序列号和确认号等字段。
  • 利用全局变量 SOCKET_TABLE 来存储套接字信息,并通过一系列函数对其进行管理,以确保线程安全。

该包依赖于一些其他模块,如 alloc::collections::vec_dequealloc::vec 等。

push_data

description: push_data函数的主要功能是将数据推入到SOCKET_TABLE中指定索引的套接字缓冲区。该函数首先通过SOCKET_TABLE的独占访问来获取套接字表。然后,它使用断言确保索引在范围内并且该索引处的套接字存在。最后,将给定的数据添加到该索引处套接字的缓冲区中。

入参:

  • index: 套接字表中的索引
  • data: 要推入缓冲区的数据,类型为字节向量(Vec)
pub fn push_data(index: usize, data: Vec<u8>) {
    let mut socket_table = SOCKET_TABLE.exclusive_access();

    assert!(socket_table.len() > index);
    assert!(socket_table[index].is_some());

    socket_table[index]
        .as_mut()
        .unwrap()
        .buffers
        .push_back(data);
}

remove_socket

description: remove_socket函数的主要功能是从SOCKET_TABLE中移除指定索引的套接字。该函数接收一个参数:

  • index: 一个无符号整数,表示要移除的套接字在SOCKET_TABLE中的位置。

函数通过获取SOCKET_TABLE的独占访问来确保线程安全,然后使用assert语句验证索引是否在有效范围内。最后,将指定索引处的套接字设置为None,以实现移除操作。

pub fn remove_socket(index: usize) {
    let mut socket_table = SOCKET_TABLE.exclusive_access();

    assert!(socket_table.len() > index);

    socket_table[index] = None;
}

pop_data

description: pop_data函数的主要功能是从SOCKET_TABLE中指定索引的套接字缓冲区中弹出数据。该函数首先获取SOCKET_TABLE的独占访问权,以确保操作的线程安全。然后,它通过两个断言检查:一是确保索引在socket_table的长度范围内,二是确保该索引处的套接字存在。若检查通过,则从该套接字的缓冲区中弹出并返回最前面的数据。函数的输入参数为索引index,返回值为一个可选的字节向量。

pub fn pop_data(index: usize) -> Option<Vec<u8>> {
    let mut socket_table = SOCKET_TABLE.exclusive_access();

    assert!(socket_table.len() > index);
    assert!(socket_table[index].is_some());

    socket_table[index].as_mut().unwrap().buffers.pop_front()
}

get_s_a_by_index

description: get_s_a_by_index函数用于通过套接字索引获取其序列号(seq)和确认号(ack)。函数接收一个参数:

  • index: 表示套接字在SOCKET_TABLE中的位置。

函数会首先通过SOCKET_TABLE进行独占访问,确保线程安全。然后,检查给定的索引是否在套接字表的范围内。如果索引有效,函数将尝试从套接字表中获取对应的套接字信息,并返回其序列号和确认号。如果索引无效或套接字不存在,则返回None。

/// get the seq and ack by socket index
pub fn get_s_a_by_index(index: usize) -> Option<(u32, u32)> {
    let socket_table = SOCKET_TABLE.exclusive_access();

    assert!(index < socket_table.len());

    socket_table.get(index).map_or(None, |x| match x {
        Some(x) => Some((x.seq, x.ack)),
        None => None,
    })
}

set_s_a_by_index

description: set_s_a_by_index函数的主要功能是通过索引设置套接字的序列号(seq)和确认号(ack)。该函数首先获取对全局变量SOCKET_TABLE的独占访问,以确保线程安全,然后检查索引是否有效并且对应的套接字存在。若条件满足,函数将更新指定索引的套接字的序列号和确认号。参数包括:index表示套接字在表中的位置,seq是新的序列号,ack是新的确认号。

pub fn set_s_a_by_index(index: usize, seq: u32, ack: u32) {
    let mut socket_table = SOCKET_TABLE.exclusive_access();

    assert!(socket_table.len() > index);
    assert!(socket_table[index].is_some());

    let sock = socket_table[index].as_mut().unwrap();

    sock.ack = ack;
    sock.seq = seq;
}

get_socket

description: get_socket函数的主要功能是从全局变量SOCKET_TABLE中查找符合指定远程地址(raddr)、本地端口(lport)和远程端口(rport)的套接字,并返回其索引。函数首先通过exclusive_access方法获取对SOCKET_TABLE的独占访问,然后遍历套接字表。对于每个套接字,如果其不为空且匹配输入参数,则返回套接字的索引;否则,继续遍历。如果未找到匹配的套接字,则返回None。

pub fn get_socket(raddr: IPv4, lport: u16, rport: u16) -> Option<usize> {
    let socket_table = SOCKET_TABLE.exclusive_access();
    for i in 0..socket_table.len() {
        let sock = &socket_table[i];
        if sock.is_none() {
            continue;
        }

        let sock = sock.as_ref().unwrap();
        if sock.raddr == raddr && sock.lport == lport && sock.rport == rport {
            return Some(i);
        }
    }
    None
}

add_socket

description: add_socket函数的主要功能是将一个新的套接字添加到全局变量SOCKET_TABLE中。首先,它检查是否已经存在具有相同远程地址(raddr)、本地端口(lport)和远程端口(rport)的套接字,如果存在,则返回None表示添加失败。否则,函数遍历SOCKET_TABLE寻找第一个空闲位置以存放新的Socket对象。若找到空位则在该位置插入新套接字,否则将其添加到表末尾。函数返回新套接字在表中的索引。

pub fn add_socket(raddr: IPv4, lport: u16, rport: u16) -> Option<usize> {
    if get_socket(raddr, lport, rport).is_some() {
        return None;
    }

    let mut socket_table = SOCKET_TABLE.exclusive_access();
    let mut index = usize::MAX;
    for i in 0..socket_table.len() {
        if socket_table[i].is_none() {
            index = i;
            break;
        }
    }

    let socket = Socket {
        raddr,
        lport,
        rport,
        buffers: VecDeque::new(),
        seq: 0,
        ack: 0,
    };

    if index == usize::MAX {
        socket_table.push(Some(socket));
        Some(socket_table.len() - 1)
    } else {
        socket_table[index] = Some(socket);
        Some(index)
    }
}