寫在前面 本隨筆是非常菜的菜雞寫的。如有問題請(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è)儐? 通義千問 :
$(APPS)
是檢查這些文件有沒有更新
@
是指靜默運(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
可以看到,這一句基本語法,比我們憑借想象和經(jīng)驗(yàn)的理解要好上很多倍.這個(gè)
$(APPS)
我們把它歸類為
prerequisites
,自然就可以理解makefile在工作時(shí)會(huì)嘗試檢查文件的存在.
同樣我們可以知道使用
$()
是引用變量,使用
$(fn, arguments)
是調(diào)用函數(shù),這個(gè)不要搞不清楚,具體的還是看
Makefile教程和示例指南 (foofun.cn)
.
這里有兩個(gè)TIPS:
filetype:pdf
在尋找成體系的理論性的東西的時(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)用程序的加載是通過結(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!");
}
隨后需要在代碼里解決一些依賴問題,
main.rs
里增加
pub mod loader
batch::run_next_app
換成
loader::run_next_app
main
函數(shù)中把
batch
的初始化和運(yùn)行修改為
loader::load_apps();
和
loader::run_next_app();
根據(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!
機(jī)器學(xué)習(xí):神經(jīng)網(wǎng)絡(luò)構(gòu)建(下)
閱讀華為Mate品牌盛典:HarmonyOS NEXT加持下游戲性能得到充分釋放
閱讀實(shí)現(xiàn)對(duì)象集合與DataTable的相互轉(zhuǎn)換
閱讀鴻蒙NEXT元服務(wù):論如何免費(fèi)快速上架作品
閱讀算法與數(shù)據(jù)結(jié)構(gòu) 1 - 模擬
閱讀5. Spring Cloud OpenFeign 聲明式 WebService 客戶端的超詳細(xì)使用
閱讀Java代理模式:靜態(tài)代理和動(dòng)態(tài)代理的對(duì)比分析
閱讀Win11筆記本“自動(dòng)管理應(yīng)用的顏色”顯示規(guī)則
閱讀本站所有軟件,都由網(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)