Location>code7788 >text

[rCore Study Notes 022] Multi-Channel Programs and Time-Sharing Tasks

Popularity:360 ℃/2024-08-06 17:09:20

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.Sand the correspondingscript, we can guess that it's probably also going to pass theto 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.SIt can be seen that, in fact, in the.dataSegment 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.dataParagraph.

ferret outuserlower,You can see that the starting address of all apps is0x80400000:

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 theuserlower.

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 theuserWrite 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 theinner0x80400000Modifications are made, each with a step size of0x20000Once you've modified it, you can start using it.cargo build --binnextincommunicado Build the corresponding APP.

That's where my assumptions come into play. In the last part of the study, we learned thatWill be implemented incargo runbefore it is called, at which point we blindly assume that thewill also be called.

That's not true, we need to add themake buildin 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:

  1. utilization$(APPS)It's checking to see if these files have been updated.
  2. 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 asprerequisitesIt 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.

  1. Add when searchingfiletype:pdfGreat when looking for something systematic and theoretical!
  2. Search withEnglish + CookbookThe 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

cleanThe specific realization of the.PHONYThe 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, themakeThe 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.PHONYIt 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_appmethod.

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.dataThe 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 thespregisters pointing to the location linked to). This ignores the fact that the registers in the.dataSegment of the APP is not able towrite The ...

Then for those who have been set to separateBASE_ADDRESSAPP, we need to find a way to get them out of the.dataloaded into memory.

Replaces the previous section'sWe createos/src/"It's in there.load_appscap (a poem)get_base_iand ``.

 // 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_appsIn the first place, use theget_base_iCalculate 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_appThen 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.AppManagerTrimming, only the current APP and the total number of APP functions are retained, and at the same time, in thelazy_staticInside Useget_num_appSimplified 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__restoreUtilizing this contextRecovery (which can actually be interpreted as a configuration context) until (a time)userland .

That's when the brain's outflow isn't meresprespond in singingsscratchexistuserland cap (a poem)kernel state It's a swap.__restorePut the first parametera0 The function entry in theentryfed intosp, which is then followed up by a series of operations to convert the data in the form of thisspbasesscratchThis 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 ofTrapContextThe 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_appThe 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

  1. existaddpub mod loader
  2. particle marking the following noun as a direct objectbatch::run_next_appexchange (sth) for (sth else)loader::run_next_app
  3. existmainfunction puts thebatchThe 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!