# 启动流程 本章节主要说明程序执行的一些关键步骤,我们可能会涉及部分的module,具体module的功能与实现说明 参考文档在线文档详见:[Starry (azure-stars.github.io)](https://azure-stars.github.io/Starry/)。 下面的例子主要以Qemu-virt平台为主,2k1000的类似。 ## kernel的入口 LoongArch的reset复位入口为0x1c000000,此处的代码我们称为BIOS,初始化设备后会将执行权交给 kernel。而StarryOS的入口地址为0x9000000092000000。对应到物理地址为0x92000000,符号为_start, 实现如下。 > Qemu Virt平台的物理内存根据传入参数的不同,大小也不一样。具体的可参考[平台支持](/platforms.md)中Qemu virt平台设备树的描述。 > 2K1000平台可参考[龙芯2k1000LA处理器](https://github.com/LoongsonLab/oscomp-documents/blob/main/pdf/%E9%BE%99%E8%8A%AF2K1000LA%E5%A4%84%E7%90%86%E5%99%A8%E7%94%A8%E6%88%B7%E6%89%8B%E5%86%8C_V1.0.pdf)中第6部分**地址空间分配**章节的描述。需要注意的是2k1000的处理器还要参考具体的开发板的型号已经支持的最大内存等等。 > 为了统一,减少平台差异,我们选择0x92000000作为Qemu Virt和2K1000平台的内核入口地址。 具体如何设置,可参考文件platforms/loongarch64-2k1000.toml和platforms/loongarch64-qemu-virt.toml配置文件。 代码位置:modules/axhal/src/platform/loongarch64_qemu_virt/boot.rs ```rust #[naked] #[no_mangle] #[link_section = ".text.boot"] unsafe extern "C" fn _start() -> ! { core::arch::asm!(" ori $t0, $zero, 0x1 # CSR_DMW1_PLV0 lu52i.d $t0, $t0, -2048 # UC, PLV0, 0x8000 xxxx xxxx xxxx csrwr $t0, 0x180 # LOONGARCH_CSR_DMWIN0 ori $t0, $zero, 0x11 # CSR_DMW1_MAT | CSR_DMW1_PLV0 lu52i.d $t0, $t0, -1792 # CA, PLV0, 0x9000 xxxx xxxx xxxx csrwr $t0, 0x181 # LOONGARCH_CSR_DMWIN1 # Enable PG li.w $t0, 0xb0 # PLV=0, IE=0, PG=1 csrwr $t0, 0x0 # LOONGARCH_CSR_CRMD li.w $t0, 0x00 # PLV=0, PIE=0, PWE=0 csrwr $t0, 0x1 # LOONGARCH_CSR_PRMD li.w $t0, 0x00 # FPE=0, SXE=0, ASXE=0, BTE=0 csrwr $t0, 0x2 # LOONGARCH_CSR_EUEN la.global $sp, {boot_stack} li.d $t0, {boot_stack_size} add.d $sp, $sp, $t0 # setup boot stack csrrd $a0, 0x20 # cpuid la.global $t0, {entry} jirl $zero,$t0,0 ", boot_stack_size = const TASK_STACK_SIZE, boot_stack = sym BOOT_STACK, entry = sym super::rust_entry, options(noreturn), ) } ``` 首先设置DMW映射窗(可参看文档[龙芯架构参考手册卷一](https://github.com/LoongsonLab/oscomp-documents/blob/main/pdf/%E9%BE%99%E8%8A%AF%E6%9E%B6%E6%9E%84%E5%8F%82%E8%80%83%E6%89%8B%E5%86%8C%E5%8D%B7%E4%B8%80.pdf)第五章节),然后关闭中断,关闭浮点等相关功能。打开页表,设置栈指针sp,然后跳转到rust的环境中执行,此时执行权交给了rust。 ```rust #[no_mangle] unsafe extern "C" fn rust_entry(cpu_id: usize, _dtb: usize) { crate::mem::clear_bss(); crate::cpu::init_primary(cpu_id); crate::arch::set_trap_vector_base(trap_vector_base as usize); crate::arch::tlb_init(boot::KERNEL_PAGE_TABLE.as_ptr() as usize, handle_tlb_refill as usize); rust_main(cpu_id, 0); } ``` 做一些初始化工作,然后进入rust_main,此函数位于modules/axruntime/src/lib.rs。 ```rust pub extern "C" fn rust_main(cpu_id: usize, dtb: usize) -> ! { ax_println!("{}", LOGO); ax_println!( "\ arch = {}\n\ platform = {}\n\ target = {}\n\ smp = {}\n\ build_mode = {}\n\ log_level = {}\n\ ", option_env!("AX_ARCH").unwrap_or(""), option_env!("AX_PLATFORM").unwrap_or(""), option_env!("AX_TARGET").unwrap_or(""), option_env!("AX_SMP").unwrap_or(""), option_env!("AX_MODE").unwrap_or(""), option_env!("AX_LOG").unwrap_or(""), ); axlog::init(); axlog::set_max_level(option_env!("AX_LOG").unwrap_or("")); // no effect if set `log-level-*` features info!("Logging is enabled."); info!("Primary CPU {} started, dtb = {:#x}.", cpu_id, dtb); info!("Found physcial memory regions:"); for r in axhal::mem::memory_regions() { info!( " [{:x?}, {:x?}) {} ({:?})", r.paddr, r.paddr + r.size, r.name, r.flags ); } #[cfg(feature = "alloc")] init_allocator(); #[cfg(all(feature = "paging", not(target_arch = "loongarch64")))] { info!("Initialize kernel page table..."); remap_kernel_memory().expect("remap kernel memoy failed"); } #[cfg(all(feature = "paging", target_arch = "loongarch64"))] { extern "C" { fn img_start();} trace!("IMG: {:p}", img_start as *const()); } info!("Initialize platform devices..."); axhal::platform_init(); cfg_if::cfg_if! { if #[cfg(feature = "monolithic")] { info!("Initialize Kernel Process"); axprocess::init_kernel_process(); } else { #[cfg(feature = "multitask")] axtask::init_scheduler(); } } #[cfg(any(feature = "fs", feature = "net", feature = "display"))] { #[allow(unused_variables)] let all_devices = axdriver::init_drivers(); #[cfg(feature = "fs")] axfs::init_filesystems(all_devices.block); #[cfg(feature = "net")] axnet::init_network(all_devices.net); #[cfg(feature = "display")] axdisplay::init_display(all_devices.display); } #[cfg(feature = "smp")] self::mp::start_secondary_cpus(cpu_id); #[cfg(feature = "irq")] { info!("Initialize interrupt handlers..."); init_interrupt(); } #[cfg(all(feature = "tls", not(feature = "multitask")))] { info!("Initialize thread local storage..."); init_tls(); } info!("Primary CPU {} init OK.", cpu_id); INITED_CPUS.fetch_add(1, Ordering::Relaxed); while !is_init_ok() { core::hint::spin_loop(); } unsafe { main() }; #[cfg(feature = "multitask")] axtask::exit(0); #[cfg(not(feature = "multitask"))] { debug!("main task exited: exit_code={}", 0); axhal::misc::terminate(); } } ``` rust_main执行,执行各个module,完成初始化工作后,将执行权交给main函数。此时的main函数和执行的应用相关。 比如我执行的是apps/oscomp,则执行: ```rust use syscall_entry::run_testcases; #[no_mangle] fn main() { run_testcases("sdcard"); } ``` 接着交给了run_testcases。 ```rust pub fn run_testcases(case: &'static str) { ... loop { let mut ans = None; if let Some(command_line) = test_iter.next() { let args = get_args(command_line.as_bytes()); let testcase = args.clone(); let main_task = axprocess::Process::init(args).unwrap(); let now_process_id = main_task.get_process_id() as isize; TESTRESULT.lock().load(&(testcase)); let mut exit_code = 0; ans = loop { if wait_pid(now_process_id, &mut exit_code as *mut i32).is_ok() { break Some(exit_code); } yield_now_task(); }; } TaskId::clear(); #[cfg(not(target_arch = "loongarch64"))] { write_page_table_root(KERNEL_PAGE_TABLE.root_paddr()); }; flush_tlb(None); EXITED_TASKS.lock().clear(); if let Some(exit_code) = ans { let kernel_process = Arc::clone(PID2PC.lock().get(&KERNEL_PROCESS_ID).unwrap()); kernel_process .children .lock() .retain(|x| x.pid() == KERNEL_PROCESS_ID); // 去除指针引用,此时process_id对应的进程已经被释放 // 释放所有非内核进程 finish_one_test(exit_code); } else { // 已经测试完所有的测例 TESTRESULT.lock().show_result(); break; } // chdir会改变当前目录,需要重新设置 init_current_dir(); } } ``` 根据传入的命令,创建进程,执行程序,等待程序执行结束,返回结果。 ```rust axprocess::Process::init(args); ... wait_pid(now_process_id, &mut exit_code as *mut i32).is_ok(); ``` ## 进程创建 具体代码在:modules/axprocess/src/process.rs ```rust pub fn init(args: Vec<String>) -> AxResult<AxTaskRef> { let path = args[0].clone(); let mut memory_set = MemorySet::new_with_kernel_mapped(); let page_table_token = memory_set.page_table_token(); if page_table_token != 0 { { write_page_table_root(page_table_token.into()); let task = current(); info!("Cur Task: {}", task.id_name()); warn!("0x{:x}",page_table_token); // riscv::register::sstatus::set_sum(); }; } // 运行gcc程序时需要预先加载的环境变量 let envs:Vec<String> = vec![ "SHLVL=1".into(), "PATH=/usr/sbin:/usr/bin:/sbin:/bin".into(), "PWD=/".into(), "GCC_EXEC_PREFIX=/riscv64-linux-musl-native/bin/../lib/gcc/".into(), "COLLECT_GCC=./riscv64-linux-musl-native/bin/riscv64-linux-musl-gcc".into(), "COLLECT_LTO_WRAPPER=/riscv64-linux-musl-native/bin/../libexec/gcc/riscv64-linux-musl/11.2.1/lto-wrapper".into(), "COLLECT_GCC_OPTIONS='-march=rv64gc' '-mabi=lp64d' '-march=rv64imafdc' '-dumpdir' 'a.'".into(), "LIBRARY_PATH=/lib/".into(), "LD_LIBRARY_PATH=/lib/".into(), ]; let (entry, user_stack_bottom, heap_bottom) = if let Ok(ans) = load_app(path.clone(), args, envs, &mut memory_set) { ans } else { error!("Failed to load app {}", path); return Err(AxError::NotFound); }; let new_process = Arc::new(Self::new( TaskId::new().as_u64(), KERNEL_PROCESS_ID, Arc::new(Mutex::new(memory_set)), heap_bottom.as_usize() as u64, vec![ // 标准输入 Some(Arc::new(Stdin { flags: Mutex::new(OpenFlags::empty()), })), // 标准输出 Some(Arc::new(Stdout { flags: Mutex::new(OpenFlags::empty()), })), // 标准错误 Some(Arc::new(Stderr { flags: Mutex::new(OpenFlags::empty()), })), ], )); let new_task = TaskInner::new( || {}, path, axconfig::TASK_STACK_SIZE, new_process.pid(), page_table_token, #[cfg(feature = "signal")] false, ); TID2TASK .lock() .insert(new_task.id().as_u64(), Arc::clone(&new_task)); new_task.set_leader(true); info!("Process entry{:?}, stack{:?}", entry, user_stack_bottom); let new_trap_frame = TrapFrame::app_init_context(entry.as_usize(), user_stack_bottom.as_usize()); new_task.set_trap_context(new_trap_frame); // 需要将完整内容写入到内核栈上,first_into_user并不会复制到内核栈上 new_task.set_trap_in_kernel_stack(); new_process.tasks.lock().push(Arc::clone(&new_task)); #[cfg(feature = "signal")] new_process .signal_modules .lock() .insert(new_task.id().as_u64(), SignalModule::init_signal(None)); new_process .robust_list .lock() .insert(new_task.id().as_u64(), FutexRobustList::default()); PID2PC .lock() .insert(new_process.pid(), Arc::clone(&new_process)); // 将其作为内核进程的子进程 match PID2PC.lock().get(&KERNEL_PROCESS_ID) { Some(kernel_process) => { kernel_process.children.lock().push(new_process); } None => { return Err(AxError::NotFound); } } RUN_QUEUE.lock().add_task(Arc::clone(&new_task)); Ok(new_task) } } ``` 创建一些进程所需要的资源,比如加载elf文件,进行解析,创建对应的Task,执行elf文件等等。 ```rust TrapFrame::app_init_context(entry.as_usize(), user_stack_bottom.as_usize()); impl TrapFrame { fn set_user_sp(&mut self, user_sp: usize) { self.regs[3] = user_sp; } /// 用于第一次进入应用程序时的初始化 pub fn app_init_context(app_entry: usize, user_sp: usize) -> Self { let mut trap_frame = TrapFrame::default(); trap_frame.set_user_sp(user_sp); trap_frame.era = app_entry; trap_frame.prmd = 3 | 1<<2; // user and enable int trap_frame } } ``` 将应用程序elf的入口地址设置到era中,并且设置好sp指针。 将进程创建好后,会将Task加入到队列,然后等待调度执行。 ## 用户态与内核态的交互 异常的入口函数为trap_vector_base: ```assembly trap_vector_base: csrwr $t0, KSAVE_T0 csrrd $t0, 0x1 andi $t0, $t0, 0x3 bnez $t0, .Lfrom_userspace .Lfrom_kernel: move $t0, $sp addi.d $sp, $sp, -{trapframe_size} // allocate space // save kernel sp st.d $t0, $sp, 3*8 b .Lcommon .Lfrom_userspace: csrwr $sp, KSAVE_USP // save user sp into SAVE1 CSR csrrd $sp, KSAVE_KSP // restore kernel sp addi.d $sp, $sp, -{trapframe_size} // allocate space // switch tp and r21 st.d $tp, $sp, 2*8 ld.d $tp, $sp, 36*8 st.d $r21, $sp, 21*8 ld.d $r21, $sp, 37*8 // save user sp csrrd $t0, KSAVE_USP st.d $t0, $sp, 3*8 // sp .Lcommon: // save the registers. SAVE_REGS csrrd $t2, 0x1 st.d $t2, $sp, 8*32 // prmd csrrd $t1, 0x6 st.d $t1, $sp, 8*33 // era csrrd $t1, 0x7 st.d $t1, $sp, 8*34 // badv csrrd $t1, 0x0 st.d $t1, $sp, 8*35 // crmd move $a0, $sp csrrd $t0, 0x1 andi $a1, $t0, 0x3 // if user or kernel bl loongarch64_trap_handler // restore the registers. ld.d $t1, $sp, 8*33 // era csrwr $t1, 0x6 ld.d $t2, $sp, 8*32 // prmd csrwr $t2, 0x1 // Save kernel sp when exit kernel mode addi.d $t1, $sp, {trapframe_size} csrwr $t1, KSAVE_KSP RESTORE_REGS // restore sp ld.d $sp, $sp, 3*8 ertn ``` 首先保存用户态的寄存器,然后切换到内核栈,然后执行loongarch64_trap_handler: ```rust fn loongarch64_trap_handler(tf: &mut TrapFrame, from_user: bool) { ... match estat.cause() { Trap::Exception(Exception::Breakpoint) => handle_breakpoint(&mut tf.era), Trap::Exception(Exception::AddressNotAligned) => handle_unaligned(tf), Trap::Interrupt(_) => { let irq_num: usize = estat.is().trailing_zeros() as usize; crate::trap::handle_irq_extern(irq_num) } #[cfg(feature = "monolithic")] Trap::Exception(Exception::Syscall) => { enable_irqs(); // jump to next instruction anyway tf.era += 4; let result = handle_syscall( tf.regs[11], [ tf.regs[4], tf.regs[5], tf.regs[6], tf.regs[7], tf.regs[8], tf.regs[9], ], ); // cx is changed during sys_exec, so we have to call it again tf.regs[4] = result as usize; } #[cfg(feature = "monolithic")] Trap::Exception(Exception::FetchPageFault) => { let flags = MappingFlags::USER | MappingFlags::EXECUTE; handle_page_fault(addr.into(), flags, tf); } #[cfg(feature = "monolithic")] Trap::Exception(Exception::LoadPageFault) => { let flags = if from_user { MappingFlags::USER | MappingFlags::READ } else { MappingFlags::READ }; handle_page_fault(addr.into(), flags, tf); } #[cfg(feature = "monolithic")] Trap::Exception(Exception::StorePageFault) => { let addr = tf.badv; let flags = MappingFlags::USER | MappingFlags::WRITE; handle_page_fault(addr.into(), flags, tf); } #[cfg(feature = "monolithic")] Trap::Exception(Exception::PageModifyFault) => { let addr = tf.badv; let flags = MappingFlags::USER | MappingFlags::WRITE | MappingFlags::DIRTY; handle_page_fault(addr.into(), flags, tf); } #[cfg(feature = "monolithic")] Trap::Exception(Exception::PagePrivilegeIllegal) => { let flags = MappingFlags::USER; handle_page_fault(addr.into(), flags, tf); } #[cfg(feature = "monolithic")] Trap::Exception(Exception::InstructionNotExist) => { } _ => { panic!( "Unhandled trap {:?} @ {:#x}:\n{:#x?}", estat.cause(), tf.era, tf ); } } #[cfg(feature = "signal")] if from_user == true { handle_signal(); } #[cfg(feature = "monolithic")] // 在保证将寄存器都存储好之后,再开启中断 // 否则此时会因为写入csr寄存器过程中出现中断,导致出现异常 disable_irqs(); } ``` 然后执行相关的异常处理函数。 ## Syscall执行流程 首先进入异常处理流程: ```rust Trap::Exception(Exception::Syscall) => { enable_irqs(); // jump to next instruction anyway tf.era += 4; let result = handle_syscall( tf.regs[11], [ tf.regs[4], tf.regs[5], tf.regs[6], tf.regs[7], tf.regs[8], tf.regs[9], ], ); // cx is changed during sys_exec, so we have to call it again tf.regs[4] = result as usize; } ``` 接着进入ulib/axstarry/syscall_entry/src/syscall.rs ```rust fn handle_syscall(syscall_id: usize, args: [usize; 6]) -> isize { axprocess::time_stat_from_user_to_kernel(); let ans = syscall(syscall_id, args); axprocess::time_stat_from_kernel_to_user(); ans } pub fn syscall(syscall_id: usize, args: [usize; 6]) -> isize { #[cfg(feature = "futex")] syscall_task::check_dead_wait(); let ans = loop { #[cfg(feature = "syscall_net")] { if let Ok(net_syscall_id) = syscall_net::NetSyscallId::try_from(syscall_id) { break syscall_net::net_syscall(net_syscall_id, args); } } #[cfg(feature = "syscall_mem")] { if let Ok(mem_syscall_id) = syscall_mem::MemSyscallId::try_from(syscall_id) { break syscall_mem::mem_syscall(mem_syscall_id, args); } } #[cfg(feature = "syscall_fs")] { if let Ok(fs_syscall_id) = syscall_fs::FsSyscallId::try_from(syscall_id) { break syscall_fs::fs_syscall(fs_syscall_id, args); } } { if let Ok(task_syscall_id) = syscall_task::TaskSyscallId::try_from(syscall_id) { break syscall_task::task_syscall(task_syscall_id, args); } } panic!("unknown syscall id: {}", syscall_id); }; let ans = deal_result(ans); ans } ``` 此时,会根据传入的进程号,选择相应的处理流程: - 1. syscall_net::net_syscall - 2. syscall_mem::mem_syscall - 3. syscall_fs::fs_syscall - 4. syscall_task::task_syscall 例如执行clone syscall: ```rust CLONE => syscall_clone(args[0], args[1], args[2], args[4], args[3]), pub fn syscall_clone( flags: usize, user_stack: usize, ptid: usize, tls: usize, ctid: usize, ) -> SyscallResult { let clone_flags = CloneFlags::from_bits((flags & !0x3f) as u32).unwrap(); let stack = if user_stack == 0 { None } else { Some(user_stack) }; let curr_process = current_process(); #[cfg(feature = "signal")] let sig_child = SignalNo::from(flags as usize & 0x3f) == SignalNo::SIGCHLD; info!("syscall_clone"); if let Ok(new_task_id) = curr_process.clone_task( clone_flags, stack, ptid, tls, ctid, #[cfg(feature = "signal")] sig_child, ) { Ok(new_task_id as isize) } else { return Err(SyscallError::ENOMEM); } } ``` 执行完进程或者线程的clone之后,然后返回,将执行结果保存在中`TrapFrame` `tf.regs[4] = result as usize`。 此时系统调用完成,返回到trap_vector_base中: ```c // restore the registers. ld.d $t1, $sp, 8*33 // era csrwr $t1, 0x6 ld.d $t2, $sp, 8*32 // prmd csrwr $t2, 0x1 // Save kernel sp when exit kernel mode addi.d $t1, $sp, {trapframe_size} csrwr $t1, KSAVE_KSP RESTORE_REGS // restore sp ld.d $sp, $sp, 3*8 ertn ``` 将用户态的寄存器恢复后,保护执行环境栈sp,然后通过指令`ertn`返回到用户态继续执行。