write sth. upfront
This essay was written by a very rookie rookie. Please feel free to ask any questions.
You can contact: 1160712160@
GitHhub:/WindDevil (Nothing so far.
reflections
In the previous section, we also mentioned something about the placement and loading problem of multi-channel programs. In contrast to the loading in the previous chapter, we need to load all the apps all the way into memory.
In this section of the description, theofficial documentPresented.
But we will also learn that each application needs to know a different location in memory when it is running, which creates a certain amount of hassle in writing the application. And the operating system also needs to know where each application is when it runs and cannotArbitrarily move the memory space where the application resides , i.e., it is not possible to put the application at runtime based on the dynamic freeing of the memory spaceAdjustment to the appropriate free space Center.
I'm actually having a really hard time getting my head around this.Adjustment to the appropriate free space , because the program in the previous chapter did not have this function, I feel that it is the content of the subsequent content may be involved in for thedebris space utilization.
Placement of multiple programs
Looking back at our last chapter that had us marveling at thelink_app.S
and the correspondingscript, we can guess that it's probably also going to pass the
to modify the link address of each app.
howeverI've already forgotten. I wonder how long I'll have to learn how to memorize.
recalls thatlink_app.S
It can be seen that, in fact, in the.data
Segment saves all 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/"
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:
A thought came to mind, so isn't this all loaded into memory?
Apparently not, it's just a link in the.data
Paragraph.
ferret outuser
lower,You can see that the starting address of all apps is
0x80400000
:
OUTPUT_ARCH(riscv)
ENTRY(_start)
BASE_ADDRESS = 0x80400000;
SECTIONS
{
. = BASE_ADDRESS;
.text : {
*(.)
*(.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*)
}
}
So if you want all the apps to load together, then you need to modify theuser
lower.
Why would you do that?official documentDescribed.
The reason for such harsh conditions is that the current operating system kernel is still weak and does not have enough support for application generalization (e.g., it does not support loading an application to run at an arbitrary address in memory), which further leads to a lack of convenience and generality in application programming (the application needs to specify the memory address at which it will run). In fact, the current way of addressing applications is based on absolute locations and is not location-independent, and the kernel does not provide a corresponding address relocation mechanism.
Therefore, by using theuser
Write one underto generate a link file for each app, (so it's still python?).
# user/
import os
base_address = 0x80400000
step = 0x20000
linker = 'src/'
app_id = 0
apps = ('src/bin')
()
for app in apps:
app = app[:('.')]
lines = []
lines_before = []
with open(linker, 'r') as f:
for line in ():
lines_before.append(line)
line = (hex(base_address), hex(base_address+step*app_id))
(line)
with open(linker, 'w+') as f:
(lines)
('cargo build --bin %s --release' % app)
print('[] application %s start with address %s' %(app, hex(base_address+step*app_id)))
with open(linker, 'w+') as f:
(lines_before)
app_id = app_id + 1
This file is a response to theinner
0x80400000
Modifications are made, each with a step size of0x20000
Once you've modified it, you can start using it.cargo build --bin
nextincommunicado Build the corresponding APP.
That's where my assumptions come into play. In the last part of the study, we learned thatWill be implemented in
cargo run
before it is called, at which point we blindly assume that thewill also be called.
That's not true, we need to add themake build
in the procedure that calls it, so you'll need to modify theuser/Makefile
.
Add.
APPS := $(wildcard $(APP_DIR)/*.rs)
...
elf: $(APPS)
@python3
...
There will be something here that I don't quite understand. Let's ask.lit. ten thousand questions on general principles (idiom); fig. a long list of questions and answers:
- utilization
$(APPS)
It's checking to see if these files have been updated. - utilization
@
is the silent run command
But we will find the limitations of the current AI, they do know, I always feel that there is something missing something missing in the outline.
So we can queryMakefile Tutorial and Sample Guide ().
Makefile syntax.
The Makefile consists of a set ofrules Composition. The rules are usually shown below:
targets: prerequisites
command
command
command
- targets (target) is the filename, separated by spaces. Usually, there is only one per rule.
- commands (command) is a series of steps typically used to create a target. TheseNeed to be tabbed Starts with a space, not a space.
- prerequisites(Prerequisites) Also filenames, separated by spaces. These files need to exist before running the target's commands. These are also calleddependencies(dependencies)
As you can see, the basic grammar of this sentence is many times better than what we understand by our imagination and experience. This$(APPS)
We categorize it asprerequisites
It is natural to understand that makefile tries to check for the existence of a file while it is working.
Similarly we can know that using$()
is a reference variable, use$(fn, arguments)
is to call the function, this do not get confused, the specific or look at theMakefile Tutorial and Sample Guide ().
Here are two TIPS.
- Add when searching
filetype:pdf
Great when looking for something systematic and theoretical! - Search with
English + Cookbook
The way you can often find good engineering manuals
This shows the importance of the open source world, and after doing rCore, I think we should go and contribute to the open source world.
Official documentsAlso added.
...
clean:
@cargo clean
.PHONY: elf binary build clean
clean
The specific realization of the.PHONY
The meaning ofphony targets , which is used to list labels that are not the target of the actual document, but represent some kind of operation.
declares the pseudo-target, themake
The process of parsing a makefile does not look for the existence of these files, but the makefile has a very powerful parsing feature, so theMostly undeclared.PHONY
It doesn't matter. .
Multi-Channel Application Loading
Think about how the application was loaded in the previous chapter through the structureAppManager
(used form a nominal expression)load_app
method.
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, 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!("");
}
It can be seen that it is actually in the.data
The app is copied directly into memory.
But this chapter doesn't do that, it just loads the application into memory.
Here's a question that popped into my head, why don't you just run the app in-place (meaning just put thesp
registers pointing to the location linked to). This ignores the fact that the registers in the.data
Segment of the APP is not able towrite The ...
Then for those who have been set to separateBASE_ADDRESS
APP, we need to find a way to get them out of the.data
loaded into memory.
Replaces the previous section'sWe create
os/src/
"It's in there.load_apps
cap (a poem)get_base_i
and ``.
// os/src/
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, ())
};
dst.copy_from_slice(src);
}
unsafe {
asm!("");
}
}
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() }
}
It can be seen in theload_apps
In the first place, use theget_base_i
Calculate the current app's bias address, then use the same method as in the previous chapter to load the app's content into it. Andget_num_app
Then it is responsible for directly acquiring the number of apps.
Similarly, even if we are using a multi-pass placement and loading program, then we still need thekernel stack cap (a poem)user terminal .
In addition, inOfficial realizationIn theTo be used for storageUser Layer APP The configurations of.
//! 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;
Because the data cannot be shared between programs, and also to prevent contextual errors, it is necessary to set a set ofuser terminal respond in singingkernel stack :
#[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 {
.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::<TrapContext>()) as *mut TrapContext;
unsafe {
*trap_cx_ptr = trap_cx;
}
trap_cx_ptr as usize
}
}
impl UserStack {
fn get_sp(&self) -> usize {
.as_ptr() as usize + USER_STACK_SIZE
}
}
At the same time, since all the apps are currently loaded, there is no need to save the position of each app when it is not loaded, so it is not necessary to save the position of each app when it is not loaded.AppManager
Trimming, only the current APP and the total number of APP functions are retained, and at the same time, in thelazy_static
Inside Useget_num_app
Simplified operation.
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<AppManager> = unsafe {
UPSafeCell::new({
let num_app = get_num_app();
AppManager {
num_app,
current_app: 0,
}
})
};
}
Similarly, we need to customize a context using the__restore
Utilizing this contextRecovery (which can actually be interpreted as a configuration context) until (a time)userland .
That's when the brain's outflow isn't meresp
respond in singingsscratch
existuserland cap (a poem)kernel state It's a swap.__restore
Put the first parametera0
The function entry in theentry
fed intosp
, which is then followed up by a series of operations to convert the data in the form of thissp
basesscratch
This will enable the switching of multiple app contexts. This enables switching between multiple app contexts.
Here's a short excerpt__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
...
So how to formulate this context, we can think ofTrapContext
The two components of the structure oneLocation of the user stack one isAPP Portal position, stealing from the official code here.
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(),
))
}
And then remodeling the last chapter was writtenrun_next_app
The key point here is 1. to remove the link of loading the app 2. because of removing the link of loading the app, we need to judge whether the app is running or not at the time of switching instead of loading.
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!");
}
Then some dependencies need to be resolved in the code, the
- exist
add
pub mod loader
- particle marking the following noun as a direct object
batch::run_next_app
exchange (sth) for (sth else)loader::run_next_app
- exist
main
function puts thebatch
The initialization and operation of theloader::load_apps();
respond in singingloader::run_next_app();
Try running
Based on the experience in the comments section, I would suggest that we start by implementing the followingclean
:
cd user
make clean
make build
cd ../os
make run
Results.
[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!