您的位置:首頁 > 軟件教程 > 教程 > [rCore學習筆記 022]多道程序與分時任務

[rCore學習筆記 022]多道程序與分時任務

來源:好特整理 | 時間:2024-08-07 10:18:52 | 閱讀:60 |  標簽: 2 C   | 分享到:

寫在前面 本隨筆是非常菜的菜雞寫的。如有問題請及時提出。 可以聯系:[email protected] GitHhub:https://github.com/WindDevil (目前啥也沒有 思考 上一節(jié)我們也提到了關于多道程序的放置和加載問題的事情.對比上一章的加載,我們需要把所有的APP全部都

寫在前面

本隨筆是非常菜的菜雞寫的。如有問題請及時提出。

可以聯系:[email protected]

GitHhub: https://github.com/WindDevil (目前啥也沒有

思考

上一節(jié)我們也提到了關于多道程序的放置和加載問題的事情.對比上一章的加載,我們需要把所有的APP全部都加載到內存中.

在這一節(jié)的描述中, 官方文檔 提出了:
但我們也會了解到,每個應用程序需要知道自己運行時在內存中的不同位置,這對應用程序的編寫帶來了一定的麻煩。而且操作系統(tǒng)也要知道每個應用程序運行時的位置,不能 任意移動應用程序所在的內存空間 ,即不能在運行時根據內存空間的動態(tài)空閑情況,把應用程序 調整到合適的空閑空間 中。

這里其實我腦子里是非常難受的,就是關于這個 調整到合適的空閑空間中 , 因為上一章的程序也沒有這個功能,我感覺是后續(xù)的內容可能會涉及到對于 碎片空間 的利用.

多道程序的放置

回想我們上一章的時候讓我們驚嘆的 link_app.S 和對應的 build.rs 腳本,我們可以猜想到大概也是要通過 build.rs 來修改每個APP的鏈接地址.

可是 build.py 已經忘記了,唉,不知道這個記憶力需要學到啥時候才能學完.

回顧 link_app.S ,可以看到,實際上在 .data 段保存了所有的APP:


    .align 3
    .section .data
    .global _num_app
_num_app:
    .quad 7
    .quad app_0_start
    .quad app_1_start
    .quad app_2_start
    .quad app_3_start
    .quad app_4_start
    .quad app_5_start
    .quad app_6_start
    .quad app_6_end

    .section .data
    .global app_0_start
    .global app_0_end
app_0_start:
    .incbin "../user/target/riscv64gc-unknown-none-elf/release/00hello_world.bin"
app_0_end:

    .section .data
    .global app_1_start
    .global app_1_end
app_1_start:
    .incbin "../user/target/riscv64gc-unknown-none-elf/release/01store_fault.bin"
app_1_end:

    .section .data
    .global app_2_start
    .global app_2_end
app_2_start:
    .incbin "../user/target/riscv64gc-unknown-none-elf/release/02power.bin"
app_2_end:

    .section .data
    .global app_3_start
    .global app_3_end
app_3_start:
    .incbin "../user/target/riscv64gc-unknown-none-elf/release/03priv_inst.bin"
app_3_end:

    .section .data
    .global app_4_start
    .global app_4_end
app_4_start:
    .incbin "../user/target/riscv64gc-unknown-none-elf/release/04priv_csr.bin"
app_4_end:

    .section .data
    .global app_5_start
    .global app_5_end
app_5_start:
    .incbin "../user/target/riscv64gc-unknown-none-elf/release/test1_write0.bin"
app_5_end:

    .section .data
    .global app_6_start
    .global app_6_end
app_6_start:
    .incbin "../user/target/riscv64gc-unknown-none-elf/release/test1_write1.bin"
app_6_end:

這時候腦子里浮現出一個想法,那么這難道不算全部都加載到內存里了嗎?

很顯然不是,只是鏈接在了 .data 段.

查看 user 下的 link.ld ,你可以看到所有的APP的起始地址都是 0x80400000 :

OUTPUT_ARCH(riscv)
ENTRY(_start)

BASE_ADDRESS = 0x80400000;

SECTIONS
{
    . = BASE_ADDRESS;
    .text : {
        *(.text.entry)
        *(.text .text.*)
    }
    .rodata : {
        *(.rodata .rodata.*)
        *(.srodata .srodata.*)
    }
    .data : {
        *(.data .data.*)
        *(.sdata .sdata.*)
    }
    .bss : {
        start_bss = .;
        *(.bss .bss.*)
        *(.sbss .sbss.*)
        end_bss = .;
    }
    /DISCARD/ : {
        *(.eh_frame)
        *(.debug*)
    }
}

所以如果想要所有的APP都能夠加載在一起,那么需要修改的是 user 下的 link.ld .

為什么要這么做, 官方文檔 做出了描述:
之所以要有這么苛刻的條件,是因為目前的操作系統(tǒng)內核的能力還是比較弱的,對應用程序通用性的支持也不夠(比如不支持加載應用到內存中的任意地址運行),這也進一步導致了應用程序編程上不夠方便和通用(應用需要指定自己運行的內存地址)。事實上,目前應用程序的編址方式是基于絕對位置的,并沒做到與位置無關,內核也沒有提供相應的地址重定位機制。

因此,通過在 user 下寫一個 build.py 來對每一個APP生成一個鏈接文件,(所以還是python好用嗎):

 # user/build.py

 import os

 base_address = 0x80400000
 step = 0x20000
 linker = 'src/linker.ld'

 app_id = 0
 apps = os.listdir('src/bin')
 apps.sort()
 for app in apps:
     app = app[:app.find('.')]
     lines = []
     lines_before = []
     with open(linker, 'r') as f:
         for line in f.readlines():
             lines_before.append(line)
             line = line.replace(hex(base_address), hex(base_address+step*app_id))
             lines.append(line)
     with open(linker, 'w+') as f:
         f.writelines(lines)
     os.system('cargo build --bin %s --release' % app)
     print('[build.py] application %s start with address %s' %(app, hex(base_address+step*app_id)))
     with open(linker, 'w+') as f:
         f.writelines(lines_before)
     app_id = app_id + 1

這個文件是對 link.ld 里的 0x80400000 進行修改,每一個步長為 0x20000 ,修改好了之后就開始使用 cargo build --bin 單獨 構建對應APP.

這時候就體現了我的想當然,上一部分的學習中,我們學到 build.rs 會在執(zhí)行 cargo run 之前被調用,這時候我們就盲目地認為 build.py 也會被調用.

實際上不是這樣的,我們需要在 make build 的過程中調用它,因此需要修改 user/Makefile .

增加:

APPS := $(wildcard $(APP_DIR)/*.rs)
...
elf: $(APPS)
? ? @python3 build.py
...

這里會有一些我看不太懂的地方,我們詢問 通義千問 :

  1. 使用 $(APPS) 是檢查這些文件有沒有更新
  2. 使用 @ 是指靜默運行指令

但是我們會發(fā)現當前AI的局限性,他們是懂得,我總感覺還少點什么少點提綱挈領的東西.

于是我們可以查詢 Makefile教程和示例指南 (foofun.cn) .

Makefile語法:

Makefile由一組 rules 組成。 rule通常如下所示:

targets: prerequisites
	command
	command
	command
  • targets (目標) 是文件名,用空格分隔。 通常,每個rule只有一個。
  • commands (命令) 是通常用于創(chuàng)建目標的一系列步驟。 這些 需要以制表符 開頭,不可以是空格。
  • prerequisites (先決條件) 也是文件名,用空格分隔。 在運行目標的命令之前,這些文件需要存在。 這些也稱為 dependencies (依賴項)

可以看到,這一句基本語法,比我們憑借想象和經驗的理解要好上很多倍.這個 $(APPS) 我們把它歸類為 prerequisites ,自然就可以理解makefile在工作時會嘗試檢查文件的存在.

同樣我們可以知道使用 $() 是引用變量,使用 $(fn, arguments) 是調用函數,這個不要搞不清楚,具體的還是看 Makefile教程和示例指南 (foofun.cn) .

這里有兩個TIPS:

  1. 搜索的時候增加 filetype:pdf 在尋找成體系的理論性的東西的時候很好用
  2. 搜索的時候用 英文+cookbook 的方式往往能夠找到很好的工程手冊

這就說明了開源世界的重要性,做完rCore,我想我們應該去貢獻一下開源世界.

官方的文件 還添加了:

...
clean:
	@cargo clean

.PHONY: elf binary build clean

clean 的具體實現不再贅述,而 .PHONY 的意思是 偽目標(phony targets) ,用于列出那些并非真實文件的目標,而是代表某種操作的標簽.

聲明了偽目標, make 的過程中就不會去尋找這些文件存在與否,但是本身makefile有很強大的解析功能,因此 大部分情況不聲明 .PHONY 也是沒關系的 .

多道應用程序的加載

思考上一章中應用程序的加載是通過結構體 AppManager load_app 方法來實現.

unsafe fn load_app(&self, app_id: usize) {
	if app_id >= self.num_app {
		println!("All applications completed!");
		//panic!("Shutdown machine!");
		shutdown(false);
	}
	println!("[kernel] Loading app_{}", app_id);
	// clear app area
	core::slice::from_raw_parts_mut(APP_BASE_ADDRESS as *mut u8, APP_SIZE_LIMIT).fill(0);
	let app_src = core::slice::from_raw_parts(
		self.app_start[app_id] as *const u8,
		self.app_start[app_id + 1] - self.app_start[app_id],
	);
	let app_dst = core::slice::from_raw_parts_mut(APP_BASE_ADDRESS as *mut u8, app_src.len());
	app_dst.copy_from_slice(app_src);
	// Memory fence about fetching the instruction memory
	// It is guaranteed that a subsequent instruction fetch must
	// observes all previous writes to the instruction memory.
	// Therefore, fence.i must be executed after we have loaded
	// the code of the next app into the instruction memory.
	// See also: riscv non-priv spec chapter 3, 'Zifencei' extension.
	asm!("fence.i");
}

可以看到實際上是在 .data 段把APP直接拷貝到內存之中.

但是本章是沒這個環(huán)節(jié)的,是把應用程序一股腦加載到內存中.

這里腦子里冒出來一個問題,為什么不直接就地運行APP(指直接把 sp 寄存器指向鏈接到的位置).這里忽略了在 .data 段的APP是不能 寫入 的.

那么對于已經分別設置為不同的 BASE_ADDRESS 的APP,我們要想辦法把他們從 .data 中加載到內存中.

替代上一節(jié)的 batch.rs ,我們創(chuàng)建 os/src/loader.rs ,里邊有 load_apps get_base_i 以及``:

 // os/src/loader.rs

pub fn load_apps() {
 extern "C" { fn _num_app(); }
 let num_app_ptr = _num_app as usize as *const usize;
 let num_app = get_num_app();
 let app_start = unsafe {
	 core::slice::from_raw_parts(num_app_ptr.add(1), num_app + 1)
 };
 // load apps
 for i in 0..num_app {
	 let base_i = get_base_i(i);
	 // clear region
	 (base_i..base_i + APP_SIZE_LIMIT).for_each(|addr| unsafe {
		 (addr as *mut u8).write_volatile(0)
	 });
	 // load app from data section to memory
	 let src = unsafe {
		 core::slice::from_raw_parts(
			 app_start[i] as *const u8,
			 app_start[i + 1] - app_start[i]
		 )
	 };
	 let dst = unsafe {
		 core::slice::from_raw_parts_mut(base_i as *mut u8, src.len())
	 };
	 dst.copy_from_slice(src);
 }
 unsafe {
	 asm!("fence.i");
 }
}

fn get_base_i(app_id: usize) -> usize {
 APP_BASE_ADDRESS + app_id * APP_SIZE_LIMIT
}

pub fn get_num_app() -> usize {
    extern "C" {
        fn _num_app();
    }
    unsafe { (_num_app as usize as *const usize).read_volatile() }
}

可以看到在 load_apps 中,首先使用 get_base_i 計算當前的APP的偏置地址,然后使用和上一章相同的方法,把APP的內容加載進去.而 get_num_app 則負責直接獲取APP的數量.

同樣地,我們即使使用的是多道程序放置及加載的程序,那么我們仍然需要 內核棧 用戶棧 .

另外,在 官方的實現 中,使用了一個 config.rs 用來儲存 用戶層APP 的各項配置.

//! Constants used in rCore

pub const USER_STACK_SIZE: usize = 4096 * 2;
pub const KERNEL_STACK_SIZE: usize = 4096 * 2;
pub const MAX_APP_NUM: usize = 4;
pub const APP_BASE_ADDRESS: usize = 0x80400000;
pub const APP_SIZE_LIMIT: usize = 0x20000;

因為程序之間的數據是不能共享的,而且也為了防止出現上下文錯誤,因此需要給每一個APP設置一套 用戶棧 內核棧 :

#[repr(align(4096))]
#[derive(Copy, Clone)]
struct KernelStack {
    data: [u8; KERNEL_STACK_SIZE],
}

#[repr(align(4096))]
#[derive(Copy, Clone)]
struct UserStack {
    data: [u8; USER_STACK_SIZE],
}

static KERNEL_STACK: [KernelStack; MAX_APP_NUM] = [KernelStack {
    data: [0; KERNEL_STACK_SIZE],
}; MAX_APP_NUM];

static USER_STACK: [UserStack; MAX_APP_NUM] = [UserStack {
    data: [0; USER_STACK_SIZE],
}; MAX_APP_NUM];

impl KernelStack {
    fn get_sp(&self) -> usize {
        self.data.as_ptr() as usize + KERNEL_STACK_SIZE
    }
    pub fn push_context(&self, trap_cx: TrapContext) -> usize {
        let trap_cx_ptr = (self.get_sp() - core::mem::size_of::()) as *mut TrapContext;
        unsafe {
            *trap_cx_ptr = trap_cx;
        }
        trap_cx_ptr as usize
    }
}

impl UserStack {
    fn get_sp(&self) -> usize {
        self.data.as_ptr() as usize + USER_STACK_SIZE
    }
}

同時,因為目前所有的APP都已經加載,因此不需要保存每個APP在未加載時候的位置,因此對 AppManager 進行裁剪,只保留當前APP和APP總數的功能,同時在 lazy_static 里邊使用 get_num_app 簡化操作:

struct AppManager {
    num_app: usize,
    current_app: usize,
}

impl AppManager {
    pub fn get_current_app(&self) -> usize {
        self.current_app
    }

    pub fn move_to_next_app(&mut self) {
        self.current_app += 1;
    }
}

lazy_static! {
    static ref APP_MANAGER: UPSafeCell = unsafe {
        UPSafeCell::new({
            let num_app = get_num_app();
            AppManager {
                num_app,
                current_app: 0,
            }
        })
    };
}

同樣地,我們也需要定制一個上下文,使用 __restore 利用這個上下文 恢復(實際上可以理解為配置上下文) 用戶態(tài) .

這時候腦子里的流出就不是單純的 sp sscratch 用戶態(tài) 內核態(tài) 互換了,而是 __restore 把第一個參數 a0 里的函數入口 entry 送入了 sp ,然后又通過后續(xù)一系列操作把以這個 sp 為基準的 sscratch 也配置進去.這樣就實現了多個APP上下文的切換.

這里截取一小段 __restore :

...
mv sp, a0
ld t0, 32*8(sp)
ld t1, 33*8(sp)
ld t2, 2*8(sp)
csrw sstatus, t0
csrw sepc, t1
csrw sscratch, t2
...

那么怎么制定這個上下文呢,我們可以想到 TrapContext 結構體的兩個組成部分一個是 用戶棧的位置 一個是 APP入口 位置,這里偷取官方的代碼,

pub fn init_app_cx(app_id: usize) -> usize {
    KERNEL_STACK[app_id].push_context(TrapContext::app_init_context(
        get_base_i(app_id),
        USER_STACK[app_id].get_sp(),
    ))
}

然后改造上一章寫得 run_next_app 即可,這里的關鍵點在于1. 去掉加載APP的環(huán)節(jié) 2. 因為去掉加載APP的環(huán)節(jié),因此需要在切換而不是在加載的時候判斷APP是不是運行結束:

pub fn run_next_app() -> ! {
    let mut app_manager = APP_MANAGER.exclusive_access();
    let current_app = app_manager.get_current_app();
    if current_app >= app_manager.num_app-1 {
        println!("All applications completed!");
        shutdown(false);
    }
    app_manager.move_to_next_app();
    drop(app_manager);
    // before this we have to drop local variables related to resources manually
    // and release the resources
    extern "C" {
        fn __restore(cx_addr: usize);
    }
    unsafe {
        __restore(init_app_cx(current_app));
    }
    panic!("Unreachable in batch::run_current_app!");
}

隨后需要在代碼里解決一些依賴問題,

  1. main.rs 里增加 pub mod loader
  2. batch::run_next_app 換成 loader::run_next_app
  3. main 函數中把 batch 的初始化和運行修改為 loader::load_apps(); loader::run_next_app();

嘗試運行

根據評論區(qū)的經驗,我建議大家先執(zhí)行一下 clean :

cd user
make clean
make build
cd ../os
make run

運行結果:

[rustsbi] RustSBI version 0.3.1, adapting to RISC-V SBI v1.0.0
.______       __    __      _______.___________.  _______..______   __
|   _  \     |  |  |  |    /       |           | /       ||   _  \ |  |
|  |_)  |    |  |  |  |   |   (----`---|  |----`|   (----`|  |_)  ||  |
|      /     |  |  |  |    \   \       |  |      \   \    |   _  < |  |
|  |\  \----.|  `--'  |.----)   |      |  |  .----)   |   |  |_)  ||  |
| _| `._____| \______/ |_______/       |__|  |_______/    |______/ |__|
[rustsbi] Implementation     : RustSBI-QEMU Version 0.2.0-alpha.2
[rustsbi] Platform Name      : riscv-virtio,qemu
[rustsbi] Platform SMP       : 1
[rustsbi] Platform Memory    : 0x80000000..0x88000000
[rustsbi] Boot HART          : 0
[rustsbi] Device Tree Region : 0x87000000..0x87000f02
[rustsbi] Firmware Address   : 0x80000000
[rustsbi] Supervisor Address : 0x80200000
[rustsbi] pmp01: 0x00000000..0x80000000 (-wr)
[rustsbi] pmp02: 0x80000000..0x80200000 (---)
[rustsbi] pmp03: 0x80200000..0x88000000 (xwr)
[rustsbi] pmp04: 0x88000000..0x00000000 (-wr)
[kernel] Hello, world!
[kernel] trap init end
Hello, world!
[kernel] Application exited with code 0
Into Test store_fault, we will insert an invalid store operation...
Kernel should kill this application!
[kernel] PageFault in application, kernel killed it.
3^10000=5079(MOD 10007)
3^20000=8202(MOD 10007)
3^30000=8824(MOD 10007)
3^40000=5750(MOD 10007)
3^50000=3824(MOD 10007)
3^60000=8516(MOD 10007)
3^70000=2510(MOD 10007)
3^80000=9379(MOD 10007)
3^90000=2621(MOD 10007)
3^100000=2749(MOD 10007)
Test power OK!
[kernel] Application exited with code 0
Try to execute privileged instruction in U Mode
Kernel should kill this application!
[kernel] IllegalInstruction in application, kernel killed it.
All applications completed!
小編推薦閱讀

好特網發(fā)布此文僅為傳遞信息,不代表好特網認同期限觀點或證實其描述。

相關視頻攻略

更多

掃二維碼進入好特網手機版本!

掃二維碼進入好特網微信公眾號!

本站所有軟件,都由網友上傳,如有侵犯你的版權,請發(fā)郵件[email protected]

湘ICP備2022002427號-10 湘公網安備:43070202000427號© 2013~2025 haote.com 好特網