当前位置: 首页 > news >正文

【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的时候才能被抢占。

相关文章:

【Asterinas】Asterinas 进程启动与切换

Asterinas 进程启动与切换 进程启动 进程创建&#xff1a; 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…...

CVE-2024-6387 分析

文章目录 1. 漏洞成因2. 漏洞利用前置知识2.1 相关 SSH 协议报文格式2.2 Glibc 内存分配相关规则 3. POC3.1 堆内存布局3.2 服务端解析数据时间测量3.3 条件竞争3.4 FSOP 4. 相关挑战 原文链接&#xff1a;个人博客 近几天&#xff0c;OpenSSH爆出了一个非常严重的安全漏洞&am…...

STM32 ADC精度提升方法

STM32 ADC精度提升方法 Fang XS.1452512966qq.com如果有错误&#xff0c;希望被指出&#xff0c;学习技术的路难免会磕磕绊绊量的积累引起质的变化 硬件方法 优化布局布线&#xff0c;尽量减小其他干扰增加电源、Vref去耦电容使用低通滤波器&#xff0c;或加磁珠使用DCDC时尽…...

Redis为什么设计多个数据库

​关于Redis的知识前面已经介绍过很多了,但有个点没有讲,那就是一个Redis的实例并不是只有一个数据库,一般情况下,默认是Databases 0。 一 内部结构 设计如下: Redis 的源码中定义了 redisDb 结构体来表示单个数据库。这个结构有若干重要字段,比如: dict:该字段存储了…...

零基础学习MySQL---MySQL入门

顾得泉&#xff1a;个人主页 个人专栏&#xff1a;《Linux操作系统》 《C从入门到精通》 《LeedCode刷题》 键盘敲烂&#xff0c;年薪百万&#xff01; 一、什么是数据库 问&#xff1a;存储数据用文件就可以了&#xff0c;为什么还要弄个数据库呢&#xff1f; 这就不得不提…...

HUAWEI MPLS 静态配置和动态LDP配置

MPLS(Multi-Protocol Label Switching&#xff0c;多协议标签交换技术)技术的出现&#xff0c;极大地推动了互联网的发展和应用。例如&#xff1a;利用MPLS技术&#xff0c;可以有效而灵活地部署VPN(Virtual Private Network&#xff0c;虚拟专用网)&#xff0c;TE(Traffic Eng…...

【Rust】——所有的模式语法

&#x1f4bb;博主现有专栏&#xff1a; C51单片机&#xff08;STC89C516&#xff09;&#xff0c;c语言&#xff0c;c&#xff0c;离散数学&#xff0c;算法设计与分析&#xff0c;数据结构&#xff0c;Python&#xff0c;Java基础&#xff0c;MySQL&#xff0c;linux&#xf…...

基于Python的求职招聘管理系统【附源码】

摘 要 随着互联网技术的不断发展&#xff0c;人类的生活已经逐渐离不开网络了&#xff0c;在未来的社会中&#xff0c;人类的生活与工作都离不开数字化、网络化、电子化与虚拟化的数字技术。从互联网的发展历史、当前的应用现状和发展趋势来看&#xff0c;我们完全可以肯定&…...

Python23 使用Tensorflow实现线性回归

TensorFlow 是一个开源的软件库&#xff0c;用于数值计算&#xff0c;特别适用于大规模的机器学习。它由 Google 的研究人员和工程师在 Google Brain 团队内部开发&#xff0c;并在 2015 年首次发布。TensorFlow 的核心是使用数据流图来组织计算&#xff0c;使得它可以轻松地利…...

C++:枚举类的使用案例及场景

一、使用案例 在C中&#xff0c;枚举类&#xff08;也称为枚举类型或enum class&#xff09;是C11及以后版本中引入的一种更加强大的枚举类型。与传统的枚举&#xff08;enum&#xff09;相比&#xff0c;枚举类提供了更好的类型安全性和作用域控制。下面是一个使用枚举类的案…...

中英双语介绍美国的州:明尼苏达州(Minnesota)

中文版 明尼苏达州简介 明尼苏达州位于美国中北部&#xff0c;以其万湖之州的美誉、丰富的自然资源和多样化的经济结构而著称。以下是对明尼苏达州的详细介绍&#xff0c;包括其地理位置、人口、经济、教育、文化和主要城市。 地理位置 明尼苏达州东接威斯康星州&#xff0…...

Python实现万花筒效果:创造炫目的动态图案

文章目录 引言准备工作前置条件 代码实现与解析导入必要的库初始化Pygame定义绘制万花筒图案的函数主循环 完整代码 引言 万花筒效果通过反射和旋转图案创造出美丽的对称图案。在这篇博客中&#xff0c;我们将使用Python来实现一个动态的万花筒效果。通过利用Pygame库&#xf…...

JavaScript之深入对象,详细讲讲构造函数与常见内置构造函数

前言&#xff1a;哈喽&#xff0c;大家好&#xff0c;我是前端菜鸟的自我修养&#xff01;今天给大家详细讲讲构造函数与常见内置构造函数&#xff0c;并提供具体代码帮助大家深入理解&#xff0c;彻底掌握&#xff01;原创不易&#xff0c;如果能帮助到带大家&#xff0c;欢迎…...

PyQt5水平布局--只需5分钟带你搞懂

PyQt5水平布局&#xff08;QHBoxLayout&#xff09;是一种在GUI应用程序中用于组织和排列控件的布局方式。它允许开发者将控件在水平方向上从左到右依次排列&#xff0c;非常适合于需要并排显示控件的场景&#xff0c;如工具栏、水平菜单等。 import sys from PyQt5.QtWidgets…...

telegram mini app和game实现登录功能

接上一篇文章&#xff0c;我们在创建好telegram机器人后&#xff0c;开始开发小游戏或者mini App&#xff0c;那就避免不了登录功能。 公开链接 bot设置教程:https://lengmo714.top/6e79860b.html 参考教程参考教程,telegram已经给我们提供非常多的api&#xff0c;我们在获取用…...

【Python】字典练习

python期考练习 目录 1. 首都名​编辑 2. 摩斯电码 3. 登录 4. 学生的姓名和年龄​编辑 5. 电商 6. 学生基本信息 7. 字母数 1. 首都名 初始字典 (可复制) : d{"China":"Beijing","America":"Washington","Norway":…...

Apache POI、EasyPoi、EasyExcel

目录 ​编辑 &#xff08;一&#xff09;Apache PoI 使用 &#xff08;二&#xff09;EasyPoi使用 &#xff08;三&#xff09;EasyExcel使用 写 读 最简单的读​ 最简单的读的excel示例​ 最简单的读的对象​ &#xff08;一&#xff09;Apache PoI 使用 &#xff08;二&…...

gcop:简化 Git 提交流程的高效助手 | 一键生成 commit message

&#x1f496; 大家好&#xff0c;我是Zeeland。Tags: 大模型创业、LangChain Top Contributor、算法工程师、Promptulate founder、Python开发者。&#x1f4e3; 个人说明书&#xff1a;Zeeland&#x1f4e3; 个人网站&#xff1a;https://me.zeeland.cn/&#x1f4da; Github…...

TS_类型

目录 1.类型注解 2.类型检查 3.类型推断 4.类型断言 ①尖括号&#xff08;<>&#xff09;语法 ②as语法 5.数据类型 ①boolean ②number ③string ④undefined 和 null ⑤数组和元组 ⑥枚举 ⑦any 和void ⑧symbol ⑨Function ⑩Object 和 object 6.高…...

Linux源码阅读笔记10-进程NICE案例分析2

set_user_nice set_user_nice函数功能&#xff1a;设置某一进程的NICE值&#xff0c;其NICE值的计算是根据进程的静态优先级&#xff08;task_struct->static_prio&#xff09;&#xff0c;直接通过set_user_nice函数更改进程的静态优先级。 内核源码 void set_user_nice…...

反向工程与模型迁移:打造未来商品详情API的可持续创新体系

在电商行业蓬勃发展的当下&#xff0c;商品详情API作为连接电商平台与开发者、商家及用户的关键纽带&#xff0c;其重要性日益凸显。传统商品详情API主要聚焦于商品基本信息&#xff08;如名称、价格、库存等&#xff09;的获取与展示&#xff0c;已难以满足市场对个性化、智能…...

23-Oracle 23 ai 区块链表(Blockchain Table)

小伙伴有没有在金融强合规的领域中遇见&#xff0c;必须要保持数据不可变&#xff0c;管理员都无法修改和留痕的要求。比如医疗的电子病历中&#xff0c;影像检查检验结果不可篡改行的&#xff0c;药品追溯过程中数据只可插入无法删除的特性需求&#xff1b;登录日志、修改日志…...

HTML 列表、表格、表单

1 列表标签 作用&#xff1a;布局内容排列整齐的区域 列表分类&#xff1a;无序列表、有序列表、定义列表。 例如&#xff1a; 1.1 无序列表 标签&#xff1a;ul 嵌套 li&#xff0c;ul是无序列表&#xff0c;li是列表条目。 注意事项&#xff1a; ul 标签里面只能包裹 li…...

九天毕昇深度学习平台 | 如何安装库?

pip install 库名 -i https://pypi.tuna.tsinghua.edu.cn/simple --user 举个例子&#xff1a; 报错 ModuleNotFoundError: No module named torch 那么我需要安装 torch pip install torch -i https://pypi.tuna.tsinghua.edu.cn/simple --user pip install 库名&#x…...

算法:模拟

1.替换所有的问号 1576. 替换所有的问号 - 力扣&#xff08;LeetCode&#xff09; ​遍历字符串​&#xff1a;通过外层循环逐一检查每个字符。​遇到 ? 时处理​&#xff1a; 内层循环遍历小写字母&#xff08;a 到 z&#xff09;。对每个字母检查是否满足&#xff1a; ​与…...

C#学习第29天:表达式树(Expression Trees)

目录 什么是表达式树&#xff1f; 核心概念 1.表达式树的构建 2. 表达式树与Lambda表达式 3.解析和访问表达式树 4.动态条件查询 表达式树的优势 1.动态构建查询 2.LINQ 提供程序支持&#xff1a; 3.性能优化 4.元数据处理 5.代码转换和重写 适用场景 代码复杂性…...

基于Java+VUE+MariaDB实现(Web)仿小米商城

仿小米商城 环境安装 nodejs maven JDK11 运行 mvn clean install -DskipTestscd adminmvn spring-boot:runcd ../webmvn spring-boot:runcd ../xiaomi-store-admin-vuenpm installnpm run servecd ../xiaomi-store-vuenpm installnpm run serve 注意&#xff1a;运行前…...

Bean 作用域有哪些?如何答出技术深度?

导语&#xff1a; Spring 面试绕不开 Bean 的作用域问题&#xff0c;这是面试官考察候选人对 Spring 框架理解深度的常见方式。本文将围绕“Spring 中的 Bean 作用域”展开&#xff0c;结合典型面试题及实战场景&#xff0c;帮你厘清重点&#xff0c;打破模板式回答&#xff0c…...

《Docker》架构

文章目录 架构模式单机架构应用数据分离架构应用服务器集群架构读写分离/主从分离架构冷热分离架构垂直分库架构微服务架构容器编排架构什么是容器&#xff0c;docker&#xff0c;镜像&#xff0c;k8s 架构模式 单机架构 单机架构其实就是应用服务器和单机服务器都部署在同一…...

【Linux】Linux安装并配置RabbitMQ

目录 1. 安装 Erlang 2. 安装 RabbitMQ 2.1.添加 RabbitMQ 仓库 2.2.安装 RabbitMQ 3.配置 3.1.启动和管理服务 4. 访问管理界面 5.安装问题 6.修改密码 7.修改端口 7.1.找到文件 7.2.修改文件 1. 安装 Erlang 由于 RabbitMQ 是用 Erlang 编写的&#xff0c;需要先安…...