【Asterinas】Asterinas 進程啟動與切換

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 + 3
8] # push rflags
push rdx # push cs
push [rsp + 48] # push rip
iretq指令在執行的時候會從棧頂彈出返回地址,剛好對應:
Rust
push [rsp + 4
8] # 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的時候才能被搶占。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/39788.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/39788.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/39788.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

數據結構 —— 二叉樹

1.樹的概念及結構 1.1樹的概念 樹是一種非線性的數據結構&#xff0c;它有著多分支&#xff0c;層次性的特點。 由于其形態類似于自然界中倒過來的數&#xff0c;所以我們將這種數據結構稱為“樹形結構” 注意&#xff1a; 樹形結構中&#xff0c;子樹之間不能有交集&#x…

降重工具大揭秘:AI如何幫你輕松搞定論文重寫?

已經天臨五年了&#xff0c;大學生們還在為論文降重煩惱……手動降重確實是個難題&#xff0c;必須要先付點小經費去靠譜的網站查重&#xff0c;再對著紅字標注去改&#xff0c;后面每一次的論文呢查重結果都像賭//博&#xff0c;誰也不知道明明是同一篇文章&#xff0c;第二次…

2024鯤鵬昇騰創新大賽集訓營Ascend C算子學習筆記

異構計算架構&#xff08;CANN&#xff09; 對標英偉達的CUDA CuDNN的核心軟件層&#xff0c;向上支持多種AI框架&#xff0c;向下服務AI處理器&#xff0c;發揮承上啟下的關鍵作用&#xff0c;是提升昇騰AI處理器計算效率的關鍵平臺。主要包括有各種引擎、編譯器、執行器、算…

(番外篇)指針的一些相關習題講解(速進,干貨滿滿)(2)

前言&#xff1a; 小編感覺最近有點太墮落&#xff0c;于是我開始從事這篇文章的撰寫&#xff0c;現在也是進入七月份了&#xff0c;我現在文章開頭定一個小目標&#xff0c;我決定在七月份發布至少十篇文章&#xff0c;希望我可以說到做到&#xff08;我前面就口頭欠了不少文章…

OpenSSL的一些使用案例

目錄 一、介紹 二、基本使用 1、Shell &#xff08;1&#xff09;文件加解密 &#xff08;2&#xff09;生成密鑰文件 2、API &#xff08;1&#xff09;md5sum &#xff08;2&#xff09;AES256加解密 一、介紹 本篇博客重點不是詳細描述 OpenSSL 的用法&#xff0c;只…

什么是校園氣象站

在科技日新月異的今天&#xff0c;氣象觀測不僅局限于專業的氣象機構&#xff0c;它已經走進了我們的校園&#xff0c;成為了學生們探索自然、學習科學知識的重要平臺。 校園氣象站是設置在學校內部&#xff0c;用于進行氣象觀測、數據記錄和科學實驗的設施。它通常由氣象傳感器…

MySQL之應用層優化和備份與恢復(一)

應用層優化 緩存 作為基礎組件的緩存 緩存有可能成為基礎設施的重要組成部分。也很容易陷入一個陷阱&#xff0c;認為緩存雖然很好用&#xff0c;但并不是重要到非有不可得東西。你也許會辯駁&#xff0c;如果緩存服務器宕機或者緩存被清空&#xff0c;請求也可以直接落在數…

常見鎖策略之可重入鎖VS不可重入鎖

可重入鎖VS不可重入鎖 有一個線程,針對同一把鎖,連續加鎖兩次,如果產生了死鎖,那就是不可重入鎖,如果沒有產生死鎖,那就是可重入鎖. 死鎖 我們之前引入多線程的時候不是講了一個加數字的案例么,我們今天以它來舉例 當我們這樣寫的時候會出現什么問題? 分析:第一個synchron…

前端基礎--Vue3

Vue3基礎 VUE3和VUE2的區別 2020年9月18日&#xff0c;Vue.js發布版3.0版本&#xff0c;代號&#xff1a;One Piece 于 2022 年 2 月 7 日星期一成為新的默認版本! Vue3性能更高,初次渲染快55%, 更新渲染快133% 。體積更小 Vue3.0 打包大小減少41%。 同時Vue3可以更好的支持T…

基于微服務智能推薦健康生活交流平臺的設計與實現(SpringCloud SpringBoot)+文檔

&#x1f497;博主介紹&#x1f497;&#xff1a;?在職Java研發工程師、專注于程序設計、源碼分享、技術交流、專注于Java技術領域和畢業設計? 溫馨提示&#xff1a;文末有 CSDN 平臺官方提供的老師 Wechat / QQ 名片 :) Java精品實戰案例《700套》 2025最新畢業設計選題推薦…

vb 學習簡介

vb 第一節 Visual Basic(簡稱VB)是一種高級編程語言,它最初由微軟公司開發,旨在簡化Windows應用程序的開發過程。下面,我們將介紹Visual Basic編程語言的基礎概念和用途,包括其歷史背景、主要特性以及在現代編程中的應用。 歷史背景 Visual Basic起源于1991年,當時微軟…

代碼隨想錄算法訓練營day72 | 117. 軟件構建、47. 參加科學大會

本次題目來自于卡碼網 117. 軟件構建&#xff08;拓撲排序&#xff09; python設置默認值 from collections import defaultdict aa defaultdict(int) 拓撲排序&#xff1a;找到入度為0的節點&#xff0c;然后移除。如果最后都能移除&#xff0c;則無環&#xff0c;可以排…

C#發票識別接口,再長的稅號錄入都不怕

“十二金”工程是我國政府在信息化建設中的重要一步&#xff0c;“金稅工程”總稱為中國稅收管理信息系統&#xff08;CTAIS&#xff09;&#xff0c;是我國電子政務的核心系統之一,是財政的重要環節。十二金”是面向政府辦公業務建立的十二個重點信息應用系統&#xff0c;按“…

解決使用monaco-editor編譯器,編譯器展示內容沒有超過編譯器高度,但是出現滾動條問題

前言&#xff1a; 最近在完成項目時&#xff0c;有使用編譯器進行在線編輯的功能&#xff0c;就選用了monaco-editor編譯器&#xff0c;但是實現功能之后&#xff0c;發現即使在編譯器展示的內容沒有超過編譯器高度的情況下&#xff0c;編譯器依舊存在滾動條&#xff0c;會展示…

計算機網絡--網絡層

一、網絡層的服務和功能 網絡層主要為應用層提供端對端的數據傳輸服務 網絡層接受運輸層的報文段&#xff0c;添加自己的首部&#xff0c;形成網絡層分組。分組是網絡層的傳輸單元。網絡層分組在各個站點的網絡層之間傳輸&#xff0c;最終到達接收方的網絡層。接收方網絡層將運…

如何在 Java 應用中使用 Jedis 客戶端庫來實現 Redis 緩存的基本操作

本人詳解 作者:王文峰,參加過 CSDN 2020年度博客之星,《Java王大師王天師》 公眾號:JAVA開發王大師,專注于天道酬勤的 Java 開發問題中國國學、傳統文化和代碼愛好者的程序人生,期待你的關注和支持!本人外號:神秘小峯 山峯 轉載說明:務必注明來源(注明:作者:王文峰…

構建高效盲盒小程序:數據庫設計、安全策略與性能優化

在移動互聯網時代&#xff0c;盲盒經濟以其獨特的魅力迅速崛起&#xff0c;成為連接消費者與商品的新橋梁。盲盒小程序作為這一趨勢的載體&#xff0c;不僅要求用戶體驗流暢&#xff0c;還需確保數據安全與性能卓越。本文將從數據庫設計、安全策略及性能優化三個方面&#xff0…

堆與棧的概念(RTOS)

目錄 #堆在RTOS的概念 #相關代碼表示 #堆相關特點 #棧在RTOS中的概念 #棧的代碼表示 #棧的相關特點 #為什么每個RTOS任務都要有自己的棧 前言&#xff1a;本篇參考韋東山老師的RTOS&#xff0c;連接放在最后 #堆在RTOS的概念 本文所指的堆與棧并不是數據結構中&#xff…

【unity實戰】在Unity中使用有限狀態機制作一個敵人AI

最終效果 文章目錄 最終效果前言有限狀態機的主要作用和意義素材下載邏輯圖敵人動畫配置優雅的代碼文件目錄狀態機代碼定義敵人不同狀態切換創建敵人效果更多的敵人參考源碼完結 前言 有限狀態機以前的我嗤之以鼻&#xff0c;現在的我逐幀分析。其實之前我就了解過有限狀態機&…

2.(vue3.x+vite)調用iframe的方法(vue編碼)

1、效果預覽 2.編寫代碼 (1)主頁面 <template><div><button @click="sendMessage">調用iframe,并發送信息