您的位置:首頁 > 軟件教程 > 教程 > [rCore學(xué)習(xí)筆記 022]多道程序與分時(shí)任務(wù)

[rCore學(xué)習(xí)筆記 022]多道程序與分時(shí)任務(wù)

來源:好特整理 | 時(shí)間:2024-08-07 10:18:52 | 閱讀:76 |  標(biāo)簽: 2 C   | 分享到:

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

寫在前面

本隨筆是非常菜的菜雞寫的。如有問題請(qǐng)及時(shí)提出。

可以聯(lián)系:[email protected]

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

思考

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

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

這里其實(shí)我腦子里是非常難受的,就是關(guān)于這個(gè) 調(diào)整到合適的空閑空間中 , 因?yàn)樯弦徽碌某绦蛞矝]有這個(gè)功能,我感覺是后續(xù)的內(nèi)容可能會(huì)涉及到對(duì)于 碎片空間 的利用.

多道程序的放置

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

可是 build.py 已經(jīng)忘記了,唉,不知道這個(gè)記憶力需要學(xué)到啥時(shí)候才能學(xué)完.

回顧 link_app.S ,可以看到,實(shí)際上在 .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:

這時(shí)候腦子里浮現(xiàn)出一個(gè)想法,那么這難道不算全部都加載到內(nèi)存里了嗎?

很顯然不是,只是鏈接在了 .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 .

為什么要這么做, 官方文檔 做出了描述:
之所以要有這么苛刻的條件,是因?yàn)槟壳暗牟僮飨到y(tǒng)內(nèi)核的能力還是比較弱的,對(duì)應(yīng)用程序通用性的支持也不夠(比如不支持加載應(yīng)用到內(nèi)存中的任意地址運(yùn)行),這也進(jìn)一步導(dǎo)致了應(yīng)用程序編程上不夠方便和通用(應(yīng)用需要指定自己運(yùn)行的內(nèi)存地址)。事實(shí)上,目前應(yīng)用程序的編址方式是基于絕對(duì)位置的,并沒做到與位置無關(guān),內(nèi)核也沒有提供相應(yīng)的地址重定位機(jī)制。

因此,通過在 user 下寫一個(gè) build.py 來對(duì)每一個(gè)APP生成一個(gè)鏈接文件,(所以還是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

這個(gè)文件是對(duì) link.ld 里的 0x80400000 進(jìn)行修改,每一個(gè)步長為 0x20000 ,修改好了之后就開始使用 cargo build --bin 單獨(dú) 構(gòu)建對(duì)應(yīng)APP.

這時(shí)候就體現(xiàn)了我的想當(dāng)然,上一部分的學(xué)習(xí)中,我們學(xué)到 build.rs 會(huì)在執(zhí)行 cargo run 之前被調(diào)用,這時(shí)候我們就盲目地認(rèn)為 build.py 也會(huì)被調(diào)用.

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

增加:

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

這里會(huì)有一些我看不太懂的地方,我們?cè)儐? 通義千問 :

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

但是我們會(huì)發(fā)現(xiàn)當(dāng)前AI的局限性,他們是懂得,我總感覺還少點(diǎn)什么少點(diǎn)提綱挈領(lǐng)的東西.

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

Makefile語法:

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

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

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

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

這里有兩個(gè)TIPS:

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

這就說明了開源世界的重要性,做完rCore,我想我們應(yīng)該去貢獻(xiàn)一下開源世界.

官方的文件 還添加了:

...
clean:
	@cargo clean

.PHONY: elf binary build clean

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

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

多道應(yīng)用程序的加載

思考上一章中應(yīng)用程序的加載是通過結(jié)構(gòu)體 AppManager load_app 方法來實(shí)現(xiàn).

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");
}

可以看到實(shí)際上是在 .data 段把APP直接拷貝到內(nèi)存之中.

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

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

那么對(duì)于已經(jīng)分別設(shè)置為不同的 BASE_ADDRESS 的APP,我們要想辦法把他們從 .data 中加載到內(nèi)存中.

替代上一節(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 計(jì)算當(dāng)前的APP的偏置地址,然后使用和上一章相同的方法,把APP的內(nèi)容加載進(jìn)去.而 get_num_app 則負(fù)責(zé)直接獲取APP的數(shù)量.

同樣地,我們即使使用的是多道程序放置及加載的程序,那么我們?nèi)匀恍枰? 內(nèi)核棧 用戶棧 .

另外,在 官方的實(shí)現(xiàn) 中,使用了一個(gè) config.rs 用來儲(chǔ)存 用戶層APP 的各項(xiàng)配置.

//! 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;

因?yàn)槌绦蛑g的數(shù)據(jù)是不能共享的,而且也為了防止出現(xiàn)上下文錯(cuò)誤,因此需要給每一個(gè)APP設(shè)置一套 用戶棧 內(nèi)核棧 :

#[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
    }
}

同時(shí),因?yàn)槟壳八械腁PP都已經(jīng)加載,因此不需要保存每個(gè)APP在未加載時(shí)候的位置,因此對(duì) AppManager 進(jìn)行裁剪,只保留當(dāng)前APP和APP總數(shù)的功能,同時(shí)在 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,
            }
        })
    };
}

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

這時(shí)候腦子里的流出就不是單純的 sp sscratch 用戶態(tài) 內(nèi)核態(tài) 互換了,而是 __restore 把第一個(gè)參數(shù) a0 里的函數(shù)入口 entry 送入了 sp ,然后又通過后續(xù)一系列操作把以這個(gè) sp 為基準(zhǔn)的 sscratch 也配置進(jìn)去.這樣就實(shí)現(xiàn)了多個(gè)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
...

那么怎么制定這個(gè)上下文呢,我們可以想到 TrapContext 結(jié)構(gòu)體的兩個(gè)組成部分一個(gè)是 用戶棧的位置 一個(gè)是 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 即可,這里的關(guān)鍵點(diǎn)在于1. 去掉加載APP的環(huán)節(jié) 2. 因?yàn)槿サ艏虞dAPP的環(huán)節(jié),因此需要在切換而不是在加載的時(shí)候判斷APP是不是運(yùn)行結(jié)束:

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 函數(shù)中把 batch 的初始化和運(yùn)行修改為 loader::load_apps(); loader::run_next_app();

嘗試運(yùn)行

根據(jù)評(píng)論區(qū)的經(jīng)驗(yàn),我建議大家先執(zhí)行一下 clean :

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

運(yùn)行結(jié)果:

[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!
小編推薦閱讀

好特網(wǎng)發(fā)布此文僅為傳遞信息,不代表好特網(wǎng)認(rèn)同期限觀點(diǎn)或證實(shí)其描述。

相關(guān)視頻攻略

更多

掃二維碼進(jìn)入好特網(wǎng)手機(jī)版本!

掃二維碼進(jìn)入好特網(wǎng)微信公眾號(hào)!

本站所有軟件,都由網(wǎng)友上傳,如有侵犯你的版權(quán),請(qǐng)發(fā)郵件[email protected]

湘ICP備2022002427號(hào)-10 湘公網(wǎng)安備:43070202000427號(hào)© 2013~2025 haote.com 好特網(wǎng)