本章节主要说明程序执行的一些关键步骤,我们可能会涉及部分的module,具体module的功能与实现说明 参考文档在线文档详见:Starry (azure-stars.github.io)。
下面的例子主要以Qemu-virt平台为主,2k1000的类似。
LoongArch的reset复位入口为0x1c000000,此处的代码我们称为BIOS,初始化设备后会将执行权交给 kernel。而StarryOS的入口地址为0x9000000092000000。对应到物理地址为0x92000000,符号为_start, 实现如下。
Qemu Virt平台的物理内存根据传入参数的不同,大小也不一样。具体的可参考平台支持中Qemu virt平台设备树的描述。 2K1000平台可参考龙芯2k1000LA处理器中第6部分地址空间分配章节的描述。需要注意的是2k1000的处理器还要参考具体的开发板的型号已经支持的最大内存等等。 为了统一,减少平台差异,我们选择0x92000000作为Qemu Virt和2K1000平台的内核入口地址。
具体如何设置,可参考文件platforms/loongarch64-2k1000.toml和platforms/loongarch64-qemu-virt.toml配置文件。
代码位置:modules/axhal/src/platform/loongarch64_qemu_virt/boot.rs
#[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映射窗(可参看文档龙芯架构参考手册卷一第五章节),然后关闭中断,关闭浮点等相关功能。打开页表,设置栈指针sp,然后跳转到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。
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,则执行:
use syscall_entry::run_testcases;
#[no_mangle]
fn main() {
run_testcases("sdcard");
}
接着交给了run_testcases。
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();
}
}
根据传入的命令,创建进程,执行程序,等待程序执行结束,返回结果。
axprocess::Process::init(args);
...
wait_pid(now_process_id, &mut exit_code as *mut i32).is_ok();
具体代码在:modules/axprocess/src/process.rs
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文件等等。
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:
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:
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();
}
然后执行相关的异常处理函数。
首先进入异常处理流程:
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
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
}
此时,会根据传入的进程号,选择相应的处理流程:
-
- syscall_net::net_syscall
-
- syscall_mem::mem_syscall
-
- syscall_fs::fs_syscall
-
- syscall_task::task_syscall
例如执行clone syscall:
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中:
// 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
返回到用户态继续执行。