Asterinas 進程啟動與切換
進程啟動
進程創建:
Rust
pub fn spawn_user_process(
executable_path: &str,
argv: Vec,
envp: Vec,
) -> Result<Arc> {
// spawn user process should give an absolute path
debug_assert!(executable_path.starts_with(‘/’));
let process = Process::create_user_process(executable_path, argv, envp)?;
open_ntty_as_controlling_terminal(&process)?;process.run();Ok(process)
}
Rust
fn create_user_process(
executable_path: &str,
argv: Vec,
envp: Vec,
) -> Result<Arc> {
let process_builder = {
let pid = allocate_tid();
let parent = Weak::new();
let credentials = Credentials::new_root();let mut builder = ProcessBuilder::new(pid, executable_path, parent);builder.argv(argv).envp(envp).credentials(credentials);builder};let process = process_builder.build()?;// Lock order: session table -> group table -> process table -> group of process// -> group inner -> session innerlet mut session_table_mut = process_table::session_table_mut();let mut group_table_mut = process_table::group_table_mut();let mut process_table_mut = process_table::process_table_mut();// Creates new grouplet group = ProcessGroup::new(process.clone());*process.process_group.lock() = Arc::downgrade(&group);group_table_mut.insert(group.pgid(), group.clone());// Creates new sessionlet session = Session::new(group.clone());group.inner.lock().session = Arc::downgrade(&session);session.inner.lock().leader = Some(process.clone());session_table_mut.insert(session.sid(), session);process_table_mut.insert(process.pid(), process.clone());Ok(process)
}
創建線程:
Rust
pub fn build(self) -> Result<Arc> {
self.check_build()?;
let Self {
pid,
executable_path,
parent,
main_thread_builder,
argv,
envp,
process_vm,
file_table,
fs,
umask,
resource_limits,
sig_dispositions,
credentials,
} = self;
let process_vm = process_vm.or_else(|| Some(ProcessVm::alloc())).unwrap();let file_table = file_table.or_else(|| Some(Arc::new(Mutex::new(FileTable::new_with_stdio())))).unwrap();let fs = fs.or_else(|| Some(Arc::new(RwMutex::new(FsResolver::new())))).unwrap();let umask = umask.or_else(|| Some(Arc::new(RwLock::new(FileCreationMask::default())))).unwrap();let resource_limits = resource_limits.or_else(|| Some(ResourceLimits::default())).unwrap();let sig_dispositions = sig_dispositions.or_else(|| Some(Arc::new(Mutex::new(SigDispositions::new())))).unwrap();let process = {let threads = Vec::new();Arc::new(Process::new(pid,parent,threads,executable_path.to_string(),process_vm,file_table,fs,umask,sig_dispositions,resource_limits,))};let thread = if let Some(thread_builder) = main_thread_builder {let builder = thread_builder.process(Arc::downgrade(&process));builder.build()} else {Thread::new_posix_thread_from_executable(pid,credentials.unwrap(),process.vm(),&process.fs().read(),executable_path,Arc::downgrade(&process),argv.unwrap(),envp.unwrap(),)?};process.threads().lock().push(thread);process.set_runnable();Ok(process)
}
Rust
impl PosixThreadExt for Thread {
/// This function should only be called when launch shell()
fn new_posix_thread_from_executable(
tid: Tid,
credentials: Credentials,
process_vm: &ProcessVm,
fs_resolver: &FsResolver,
executable_path: &str,
process: Weak,
argv: Vec,
envp: Vec,
) -> Result<Arc> {
let elf_file = {
let fs_path = FsPath::new(AT_FDCWD, executable_path)?;
fs_resolver.lookup(&fs_path)?
};
let (_, elf_load_info) =
load_program_to_vm(process_vm, elf_file, argv, envp, fs_resolver, 1)?;
let vm_space = process_vm.root_vmar().vm_space().clone();let mut cpu_ctx = UserContext::default();cpu_ctx.set_rip(elf_load_info.entry_point() as _);cpu_ctx.set_rsp(elf_load_info.user_stack_top() as _);let user_space = Arc::new(UserSpace::new(vm_space, cpu_ctx));let thread_name = Some(ThreadName::new_from_executable_path(executable_path)?);let thread_builder = PosixThreadBuilder::new(tid, user_space, credentials).thread_name(thread_name).process(process);Ok(thread_builder.build())
}
從ELF文件中解析裝載到內存中,并且找到ELF文件的入口地址,并且設置相應的棧:
Rust
cpu_ctx.set_rip(elf_load_info.entry_point() as _);
cpu_ctx.set_rsp(elf_load_info.user_stack_top() as _);
vm_space與cpu_ctx都被塞進user_space 中。
Rust
let user_space = Arc::new(UserSpace::new(vm_space, cpu_ctx));
創建新線程:
Rust
pub fn build(self) -> Arc {
let Self {
tid,
user_space,
process,
credentials,
thread_name,
set_child_tid,
clear_child_tid,
sig_mask,
sig_queues,
is_main_thread,
} = self;
let thread = Arc::new_cyclic(|thread_ref| {
let task = create_new_user_task(user_space, thread_ref.clone());
let status = ThreadStatus::Init;
let posix_thread = PosixThread {
process,
is_main_thread,
name: Mutex::new(thread_name),
set_child_tid: Mutex::new(set_child_tid),
clear_child_tid: Mutex::new(clear_child_tid),
credentials,
sig_mask: Mutex::new(sig_mask),
sig_queues: Mutex::new(sig_queues),
sig_context: Mutex::new(None),
sig_stack: Mutex::new(None),
robust_list: Mutex::new(None),
};
Thread::new(tid, task, posix_thread, status)});thread_table::add_thread(thread.clone());thread
}
這里面核心是創建task,task被丟到thread中:
Rust
let task = create_new_user_task(user_space, thread_ref.clone());
let status = ThreadStatus::Init;
let posix_thread = PosixThread {
process,
is_main_thread,
name: Mutex::new(thread_name),
set_child_tid: Mutex::new(set_child_tid),
clear_child_tid: Mutex::new(clear_child_tid),
credentials,
sig_mask: Mutex::new(sig_mask),
sig_queues: Mutex::new(sig_queues),
sig_context: Mutex::new(None),
sig_stack: Mutex::new(None),
robust_list: Mutex::new(None),
};
Thread::new(tid, task, posix_thread, status)
create_new_user_task實現:
Rust
pub fn create_new_user_task(user_space: Arc, thread_ref: Weak) -> Arc {
fn user_task_entry() {
let cur = Task::current();
let user_space = cur.user_space().expect(“user task should have user space”);
let mut user_mode = UserMode::new(user_space);
debug!(
“[Task entry] rip = 0x{:x}”,
user_mode.context().instruction_pointer()
);
debug!(
“[Task entry] rsp = 0x{:x}”,
user_mode.context().stack_pointer()
);
debug!(
“[Task entry] rax = 0x{:x}”,
user_mode.context().syscall_ret()
);
loop {
let user_event: UserEvent = user_mode.execute();
let context = user_mode.context_mut();
// handle user event:
handle_user_event(user_event, context);
let current_thread = current_thread!();
// should be do this comparison before handle signal?
if current_thread.status().lock().is_exited() {
break;
}
handle_pending_signal(context).unwrap();
if current_thread.status().lock().is_exited() {
debug!(“exit due to signal”);
break;
}
// If current is suspended, wait for a signal to wake up self
while current_thread.status().lock().is_stopped() {
Thread::yield_now();
debug!(“{} is suspended.”, current_thread.tid());
handle_pending_signal(context).unwrap();
}
// a preemption point after handling user event.
preempt();
}
debug!(“exit user loop”);
// FIXME: This is a work around: exit in kernel task entry may be not called. Why this will happen?
Task::current().exit();
}
TaskOptions::new(user_task_entry).data(thread_ref).user_space(Some(user_space)).build().expect("spawn task failed")
}
從user_space鐘創建user_mode
Rust
let mut user_mode = UserMode::new(user_space);
其實就是將user_space中的context轉移給user_mode:
Rust
pub fn new(user_space: &'a Arc) -> Self {
Self {
current: Task::current(),
user_space,
context: user_space.init_ctx,
}
}
user_task_entry作為當前task的用戶入口。同時構建task的kernel側入口kernel_task_entry,kernel_task_entry被設置到task的TaskContext中:
Rust
pub fn build(self) -> Result<Arc> {
/// all task will entering this function
/// this function is mean to executing the task_fn in Task
fn kernel_task_entry() {
let current_task = current_task()
.expect(“no current task, it should have current task in kernel task entry”);
current_task.func.call(());
current_task.exit();
}
let result = Task {
func: self.func.unwrap(),
data: self.data.unwrap(),
user_space: self.user_space,
task_inner: Mutex::new(TaskInner {
task_status: TaskStatus::Runnable,
ctx: TaskContext::default(),
}),
exit_code: 0,
kstack: KernelStack::new_with_guard_page()?,
link: LinkedListAtomicLink::new(),
priority: self.priority,
cpu_affinity: self.cpu_affinity,
};
result.task_inner.lock().task_status = TaskStatus::Runnable;result.task_inner.lock().ctx.rip = kernel_task_entry as usize;result.task_inner.lock().ctx.regs.rsp =(crate::vm::paddr_to_vaddr(result.kstack.end_paddr())) as u64;Ok(Arc::new(result))
}
從上面的邏輯很清晰的流程,從創建進程process到創建線程thread到創建任務task.
Task任務切換
從上面的流程中,已經創建好了task。現在看看task如何切換以及執行。
Rust
process.run();
進程創建好后,開始執行,線程開始執行
Rust
pub fn run(&self) {
let threads = self.threads.lock();
// when run the process, the process should has only one thread
debug_assert!(threads.len() == 1);
debug_assert!(self.is_runnable());
let thread = threads[0].clone();
// should not hold the lock when run thread
drop(threads);
thread.run();
}
thread的task開始執行:
Rust
pub fn run(&self) {
self.status.lock().set_running();
self.task.run();
}
將當前task放進系統的task列表中:
Rust
pub fn add_task(task: Arc) {
GLOBAL_SCHEDULER.lock_irq_disabled().enqueue(task);
}
調度:
Rust
pub fn schedule() {
if let Some(task) = fetch_task() {
switch_to_task(task);
}
}
任務切換:
Rust
fn switch_to_task(next_task: Arc) {
if !PREEMPT_COUNT.is_preemptive() {
panic!(
“Calling schedule() while holding {} locks”,
PREEMPT_COUNT.num_locks()
);
//GLOBAL_SCHEDULER.lock_irq_disabled().enqueue(next_task);
//return;
}
let current_task_option = current_task();
let next_task_cx_ptr = &next_task.inner_ctx() as *const TaskContext;
let current_task: Arc;
let current_task_cx_ptr: *mut TaskContext = match current_task_option {
None => PROCESSOR.lock().get_idle_task_cx_ptr(),
Some(current_task) => {
if current_task.status() == TaskStatus::Runnable {
GLOBAL_SCHEDULER
.lock_irq_disabled()
.enqueue(current_task.clone());
}
&mut current_task.inner_exclusive_access().ctx as *mut TaskContext
}
};
// change the current task to the next taskPROCESSOR.lock().current = Some(next_task.clone());
unsafe {context_switch(current_task_cx_ptr, next_task_cx_ptr);
}
}
這個里面task切換的時候使用的是TaskContext,而根據前面分析可得知TaskContext中存放的是kernel_task_entry:
Rust
result.task_inner.lock().ctx.rip = kernel_task_entry as usize;
result.task_inner.lock().ctx.regs.rsp =
(crate::vm::paddr_to_vaddr(result.kstack.end_paddr())) as u64;
context_switch實現:
Rust
.text
.global context_switch
.code64
context_switch: # (cur: *mut TaskContext, nxt: *TaskContext)
Save cur’s register
mov rax, [rsp] # return address
mov [rdi + 56], rax # 56 = offsetof(Context, rip)
mov [rdi + 0], rsp
mov [rdi + 8], rbx
mov [rdi + 16], rbp
mov [rdi + 24], r12
mov [rdi + 32], r13
mov [rdi + 40], r14
mov [rdi + 48], r15
Restore nxt’s registers
mov rsp, [rsi + 0]
mov rbx, [rsi + 8]
mov rbp, [rsi + 16]
mov r12, [rsi + 24]
mov r13, [rsi + 32]
mov r14, [rsi + 40]
mov r15, [rsi + 48]
mov rax, [rsi + 56] # restore return address
mov [rsp], rax # for stack balance, must use mov instead of push
ret
在x86_64匯編中,函數調用使用的寄存器規則是:
?%rdi, %rsi, %rdx, %rcx,%r8, %r9 :六個寄存器,當參數少于7個時, 參數從左到右放入寄存器: rdi, rsi, rdx, rcx, r8, r9;當參數為7個以上時,前 6 個與前面一樣, 但后面的依次從 “右向左” 放入棧中,即和32位匯編一樣。
?那么rdi寄存器存放的是current_task_cx_ptr,而next_task_cx_ptr存放在rsi寄存器中。
?rax寄存器被放入ctx.rip = kernel_task_entry
?執行ret指令,返回地址由rax寄存器指定。
為什么rax存放的是返回地址呢?我們看看TaskContext 的定義,從這個結構體中可以看出來結構體的rip成員變量是結構體的第7個成員。因此
mov rax, [rsi + 56] # restore return address
rsi + 56指向的是第7個成員。
Rust
pub struct CalleeRegs {
pub rsp: u64,
pub rbx: u64,
pub rbp: u64,
pub r12: u64,
pub r13: u64,
pub r14: u64,
pub r15: u64,
}
#[derive(Debug, Default, Clone, Copy)]
#[repr?]
pub(crate) struct TaskContext {
pub regs: CalleeRegs,
pub rip: usize,
}
task切換完成后,下一個task回到kernel_task_entry 處執行:
Rust
fn kernel_task_entry() {
let current_task = current_task()
.expect(“no current task, it should have current task in kernel task entry”);
current_task.func.call(());
current_task.exit();
}
kernel_task_entry調用user_task_entry:
Rust
fn user_task_entry() {
let cur = Task::current();
let user_space = cur.user_space().expect(“user task should have user space”);
let mut user_mode = UserMode::new(user_space);
debug!(
“[Task entry] rip = 0x{:x}”,
user_mode.context().instruction_pointer()
);
debug!(
“[Task entry] rsp = 0x{:x}”,
user_mode.context().stack_pointer()
);
debug!(
“[Task entry] rax = 0x{:x}”,
user_mode.context().syscall_ret()
);
loop {
let user_event: UserEvent = user_mode.execute();
let context: &mut UserContext = user_mode.context_mut();
// handle user event:
handle_user_event(user_event, context);
let current_thread = current_thread!();
// should be do this comparison before handle signal?
if current_thread.status().lock().is_exited() {
break;
}
handle_pending_signal(context).unwrap();
if current_thread.status().lock().is_exited() {
debug!(“exit due to signal”);
break;
}
// If current is suspended, wait for a signal to wake up self
while current_thread.status().lock().is_stopped() {
Thread::yield_now();
debug!(“{} is suspended.”, current_thread.tid());
handle_pending_signal(context).unwrap();
}
// a preemption point after handling user event.
preempt();
}
debug!(“exit user loop”);
// FIXME: This is a work around: exit in kernel task entry may be not called. Why this will happen?
Task::current().exit();
}
Rust
let user_event: UserEvent = user_mode.execute();
Rust
impl UserContextApiInternal for UserContext {
fn execute(&mut self) -> crate::user::UserEvent {
// set interrupt flag so that in user mode it can receive external interrupts
// set ID flag which means cpu support CPUID instruction
self.user_context.general.rflags |= (RFlags::INTERRUPT_FLAG | RFlags::ID).bits() as usize;
const SYSCALL_TRAPNUM: u16 = 0x100;// return when it is syscall or cpu exception type is Fault or Trap.loop {self.user_context.run();match CpuException::to_cpu_exception(self.user_context.trap_num as u16) {Some(exception) => {#[cfg(feature = "intel_tdx")]if *exception == VIRTUALIZATION_EXCEPTION {let ve_info =tdcall::get_veinfo().expect("#VE handler: fail to get VE info\n");handle_virtual_exception(self.general_regs_mut(), &ve_info);continue;}if exception.typ == CpuExceptionType::FaultOrTrap|| exception.typ == CpuExceptionType::Fault|| exception.typ == CpuExceptionType::Trap{break;}}None => {if self.user_context.trap_num as u16 == SYSCALL_TRAPNUM {break;}}};call_irq_callback_functions(&self.as_trap_frame());}crate::arch::irq::enable_local();if self.user_context.trap_num as u16 != SYSCALL_TRAPNUM {self.cpu_exception_info = CpuExceptionInfo {page_fault_addr: unsafe { x86::controlregs::cr2() },id: self.user_context.trap_num,error_code: self.user_context.error_code,};UserEvent::Exception} else {UserEvent::Syscall}
}
最終調用到syscall_return,UserContext作為參數,rdi寄存器指向UserContext。
Rust
.global syscall_return
syscall_return:
# disable interrupt
cli
# save callee-saved registers
mov ecx, 0xC0000100
rdmsr
shl rdx, 32
or rax, rdx
push rax # push fsbase
push r15
push r14
push r13
push r12
push rbp
push rbxpush rdi
push rdi # keep rsp 16 bytes align
mov gs:4, rsp # store kernel rsp -> TSS.sp0
mov rsp, rdi # set rsp = bottom of trap frame# pop fsbase gsbase
swapgs # store kernel gsbase
mov ecx, 0xC0000100
mov edx, [rsp + 18*8+4]
mov eax, [rsp + 18*8]
wrmsr # pop fsbase
mov ecx, 0xC0000101
mov edx, [rsp + 19*8+4]
mov eax, [rsp + 19*8]
wrmsr # pop gsbasepop rax
pop rbx
pop rcx
pop rdx
pop rsi
pop rdi
pop rbp
pop r8 # skip rsp
pop r8
pop r9
pop r10
pop r11
pop r12
pop r13
pop r14
pop r15
# rip
# rflags
# fsbase
# gsbase
# trap_num
# error_code# determain sysret or iret
cmp dword ptr [rsp + 4*8], 0x100 # syscall?
je sysret
iret:
# get user cs from STAR MSR
mov ecx, 0xC0000081
rdmsr # msr[ecx] => edx:eax
shr edx, 16 # dx = user_cs32
lea ax, [edx + 8] # ax = user_ss
add dx, 16 # dx = user_cs64
# construct trap frame
push rax # push ss
push [rsp - 8*8] # push rsp
push [rsp + 3*8] # push rflags
push rdx # push cs
push [rsp + 4*8] # push rip# recover rcx, rdx, rax
mov rax, [rsp - 11*8]
mov rcx, [rsp - 9*8]
mov rdx, [rsp - 8*8]iretq
這段代碼關鍵在這里:
Rust
push rax # push ss
push [rsp - 88] # push rsp
push [rsp + 38] # push rflags
push rdx # push cs
push [rsp + 48] # push rip
iretq指令在執行的時候會從棧頂彈出返回地址,剛好對應:
Rust
push [rsp + 48] # push rip
而rsp值在前面被修改成了:
Rust
mov rsp, rdi # set rsp = bottom of trap frame
而rdi值是syscall_return函數調用中帶進來的參數,即UserContext 變量。
Rust
pub struct UserContext {
pub general: GeneralRegs,
pub trap_num: usize,
pub error_code: usize,
}
/// General registers
#[derive(Debug, Default, Clone, Copy, Eq, PartialEq)]
#[repr?]
pub struct GeneralRegs {
pub rax: usize,
pub rbx: usize,
pub rcx: usize,
pub rdx: usize,
pub rsi: usize,
pub rdi: usize,
pub rbp: usize,
pub rsp: usize,
pub r8: usize,
pub r9: usize,
pub r10: usize,
pub r11: usize,
pub r12: usize,
pub r13: usize,
pub r14: usize,
pub r15: usize,
pub rip: usize,
pub rflags: usize,
pub fsbase: usize,
pub gsbase: usize,
}
mov rsp, rdi 想到與讓rsp指向了UserContext 對象。后面一系列的pop與push操作都是在移動指針指向UserContext 對象里不同的成員,而這條指令
Rust
push [rsp + 4*8] # push rip
最終指向的是UserContext 對象里的rip成員,這個成員是被設置成應用程序的入口地址的:
Rust
cpu_ctx.set_rip(elf_load_info.entry_point() as _);
cpu_ctx.set_rsp(elf_load_info.user_stack_top() as _);
因此iretq執行的時候,從棧頂彈出的就是應用程序的入口地址,這樣就跳進應用程序的入口地址進行執行了。
系統調用與返回
從上面內容研究到了程序進入入口地址開始執行程序的邏輯了,在應用程序執行的過程中可能會碰到一些系統調用,會導致程序重新陷入內核,由內核處理相關調用后再返回應用的用戶空間繼續執行。
首先會向系統寫入一個系統調用統一的響應地址:
Rust
pub fn init() {
let cpuid = raw_cpuid::CpuId::new();
unsafe {
// enable syscall
instruction
assert!(cpuid
.get_extended_processor_and_feature_identifiers()
.unwrap()
.has_syscall_sysret());
Efer::update(|efer| {
efer.insert(EferFlags::SYSTEM_CALL_EXTENSIONS);
});
// flags to clear on syscall// copy from Linux 5.0// TF|DF|IF|IOPL|AC|NTconst RFLAGS_MASK: u64 = 0x47700;LStar::write(VirtAddr::new(syscall_entry as usize as u64));SFMask::write(RFlags::from_bits(RFLAGS_MASK).unwrap());
}
}
syscall_entry會作為系統調用的響應入口,也就是說當應用程序調用系統調用的時候,系統會捕捉到系統調用請求,然后就請求轉到syscall_entry處理。
Rust
.global syscall_entry
syscall_entry:
# syscall instruction do:
# - load cs
# - store rflags -> r11
# - mask rflags
# - store rip -> rcx
# - load rip
swapgs # swap in kernel gs
mov gs:12, rsp # store user rsp -> scratch at TSS.sp1
mov rsp, gs:4 # load kernel rsp <- TSS.sp0
pop rsp # load rsp = bottom of trap frame
add rsp, 22*8 # rsp = top of trap frame# push trap_num, error_code
push 0 # push error_code
push 0x100 # push trap_num
sub rsp, 16 # skip fsbase, gsbase
# push general registers
push r11 # push rflags
push rcx # push rip
.global trap_syscall_entry
trap_syscall_entry:
push r15
push r14
push r13
push r12
push r11
push r10
push r9
push r8
push gs:12 # push rsp
push rbp
push rdi
push rsi
push rdx
push rcx
push rbx
push rax
# push fsbase gsbase
mov ecx, 0xC0000100
rdmsr
mov [rsp + 18*8+4], edx
mov [rsp + 18*8], eax
mov ecx, 0xC0000102 # kernelgs
rdmsr
mov [rsp + 19*8+4], edx
mov [rsp + 19*8], eax# restore callee-saved registers
mov rsp, gs:4 # load kernel rsp <- TSS.sp0
pop rbx
pop rbx
pop rbx
pop rbp
pop r12
pop r13
pop r14
pop r15pop rax
mov ecx, 0xC0000100
mov rdx, rax
shr rdx, 32
wrmsr # pop fsbase# go back to Rust
ret
這段代碼的關鍵點在于:
Rust
mov rsp, gs:4 # load kernel rsp <- TSS.sp0
將kernel的棧頂放置進rsp。
而在syscall_return調用的時候:
Rust
mov gs:4, rsp # store kernel rsp -> TSS.sp0
會將kernel 棧頂保存進gs:4。
因此當kernel rsp被回復后,會進行一系列的彈棧操作:
Rust
# restore callee-saved registers
mov rsp, gs:4 # load kernel rsp <- TSS.sp0
pop rbx
pop rbx
pop rbx
pop rbp
pop r12
pop r13
pop r14
pop r15
pop rax
這個跟syscall_return調用時候的壓棧操作一一對應,這樣彈完棧后棧頂會指向函數的返回地址,即syscall_return調用的返回地址,這樣執行ret指令后程序會返回到內核里調用syscall_return處,繼續處理相關的系統調用。
所以從這個角度來看整個task的切換:
Task1.kernel_task_entry-> Task1.user_task_entry -> 通過retq/sysretq返回到用戶空間-> syscall_entry進入內核->Task1.user_task_entry處理系統調用之類的。
任務調度
在asterinas系統中構建有一個基于優先級的可搶占的調度器:
Rust
pub fn init() {
let preempt_scheduler = Box::new(PreemptScheduler::new());
let scheduler = Box::::leak(preempt_scheduler);
set_scheduler(scheduler);
}
該調度器將任務分成兩種類型,實時任務與普通任務:
Rust
struct PreemptScheduler {
/// Tasks with a priority of less than 100 are regarded as real-time tasks.
real_time_tasks: SpinLock<LinkedList>,
/// Tasks with a priority greater than or equal to 100 are regarded as normal tasks.
normal_tasks: SpinLock<LinkedList>,
}
任務搶占,在任務的user_task_entry中會有一個搶占點:
Rust
pub fn create_new_user_task(user_space: Arc, thread_ref: Weak) -> Arc {
fn user_task_entry() {
let cur = Task::current();
let user_space = cur.user_space().expect(“user task should have user space”);
let mut user_mode = UserMode::new(user_space);
debug!(
“[Task entry] rip = 0x{:x}”,
user_mode.context().instruction_pointer()
);
debug!(
“[Task entry] rsp = 0x{:x}”,
user_mode.context().stack_pointer()
);
debug!(
“[Task entry] rax = 0x{:x}”,
user_mode.context().syscall_ret()
);
loop {
let user_event = user_mode.execute();
let context = user_mode.context_mut();
// handle user event:
handle_user_event(user_event, context);
let current_thread = current_thread!();
// should be do this comparison before handle signal?
if current_thread.status().lock().is_exited() {
break;
}
handle_pending_signal(context).unwrap();
if current_thread.status().lock().is_exited() {
debug!(“exit due to signal”);
break;
}
// If current is suspended, wait for a signal to wake up self
while current_thread.status().lock().is_stopped() {
Thread::yield_now();
debug!(“{} is suspended.”, current_thread.tid());
handle_pending_signal(context).unwrap();
}
// a preemption point after handling user event.
preempt();
}
debug!(“exit user loop”);
// FIXME: This is a work around: exit in kernel task entry may be not called. Why this will happen?
Task::current().exit();
}
該搶占點會去檢查當前任務是否能被其他任務搶占:
Rust
pub fn preempt() {
// disable interrupts to avoid nested preemption.
let disable_irq = disable_local();
let Some(curr_task) = current_task() else {
return;
};
let mut scheduler = GLOBAL_SCHEDULER.lock_irq_disabled();
if !scheduler.should_preempt(&curr_task) {
return;
}
let Some(next_task) = scheduler.dequeue() else {
return;
};
drop(scheduler);
switch_to_task(next_task);
}
should_preempt邏輯很簡單,如果當前任務不是實時任務,且待執行的實時任務列表不為空則代表當前任務可以被搶占
Rust
fn should_preempt(&self, task: &Arc) -> bool {
!task.is_real_time() && !self.real_time_tasks.lock_irq_disabled().is_empty()
}
如果能被搶占,則調度一個新任務開始執行:
Rust
let Some(next_task) = scheduler.dequeue() else {
return;
};
drop(scheduler);
switch_to_task(next_task);
所以我們上面描述的簡單邏輯可以是這樣的:
Task1.kernel_task_entry-> Task1.user_task_entry -> 通過retq/sysretq返回到用戶空間-> syscall_entry進入內核->Task1.user_task_entry處理系統調用之類的->能被搶占->switch_to_task ->Task2.kernel_task_entry->Task2.user_task_entry -> 通過retq/sysretq返回到用戶空間-> syscall_entry進入內核->Task2.user_task_entry處理系統調用之類的
大概類似這樣的流程。
幾個不完善的點:
?感覺貌似對多CPU,多處理核心沒有支持。
?另外搶占不是實時發生的,是必須等到正在執行的任務處理完成到某個階段才發生。而且必須是當前任務陷入進kernel的時候才能被搶占。