mirror of
https://github.com/asterinas/asterinas.git
synced 2025-06-27 11:23:25 +00:00
Fix memory leak of Task
structures
This commit is contained in:
committed by
Tate, Hongliang Tian
parent
faf9cf7da8
commit
e0c6c29481
@ -17,6 +17,9 @@ use crate::{cpu_local, CpuLocal};
|
|||||||
|
|
||||||
pub struct Processor {
|
pub struct Processor {
|
||||||
current: Option<Arc<Task>>,
|
current: Option<Arc<Task>>,
|
||||||
|
/// A temporary variable used in [`switch_to_task`] to avoid dropping `current` while running
|
||||||
|
/// as `current`.
|
||||||
|
prev_task: Option<Arc<Task>>,
|
||||||
idle_task_ctx: TaskContext,
|
idle_task_ctx: TaskContext,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -24,6 +27,7 @@ impl Processor {
|
|||||||
pub const fn new() -> Self {
|
pub const fn new() -> Self {
|
||||||
Self {
|
Self {
|
||||||
current: None,
|
current: None,
|
||||||
|
prev_task: None,
|
||||||
idle_task_ctx: TaskContext::new(),
|
idle_task_ctx: TaskContext::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -122,22 +126,35 @@ fn switch_to_task(next_task: Arc<Task>) {
|
|||||||
|
|
||||||
let next_task_ctx_ptr = next_task.ctx().get().cast_const();
|
let next_task_ctx_ptr = next_task.ctx().get().cast_const();
|
||||||
|
|
||||||
// Change the current task to the next task.
|
|
||||||
CpuLocal::borrow_with(&PROCESSOR, |processor| {
|
|
||||||
processor.borrow_mut().current = Some(next_task.clone());
|
|
||||||
});
|
|
||||||
|
|
||||||
if let Some(next_user_space) = next_task.user_space() {
|
if let Some(next_user_space) = next_task.user_space() {
|
||||||
next_user_space.vm_space().activate();
|
next_user_space.vm_space().activate();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Change the current task to the next task.
|
||||||
|
CpuLocal::borrow_with(&PROCESSOR, |processor| {
|
||||||
|
let mut processor = processor.borrow_mut();
|
||||||
|
|
||||||
|
// We cannot directly overwrite `current` at this point. Since we are running as `current`,
|
||||||
|
// we must avoid dropping `current`. Otherwise, the kernel stack may be unmapped, leading
|
||||||
|
// to soundness problems.
|
||||||
|
let old_current = processor.current.replace(next_task);
|
||||||
|
processor.prev_task = old_current;
|
||||||
|
});
|
||||||
|
|
||||||
// SAFETY:
|
// SAFETY:
|
||||||
// 1. `ctx` is only used in `schedule()`. We have exclusive access to both the current task
|
// 1. `ctx` is only used in `schedule()`. We have exclusive access to both the current task
|
||||||
// context and the next task context.
|
// context and the next task context.
|
||||||
// 2. The next task context is a valid task context.
|
// 2. The next task context is a valid task context.
|
||||||
unsafe {
|
unsafe {
|
||||||
|
// This function may not return, for example, when the current task exits. So make sure
|
||||||
|
// that all variables on the stack can be forgotten without causing resource leakage.
|
||||||
context_switch(current_task_ctx_ptr, next_task_ctx_ptr);
|
context_switch(current_task_ctx_ptr, next_task_ctx_ptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Now it's fine to drop `prev_task`. However, we choose not to do this because it is not
|
||||||
|
// always possible. For example, `context_switch` can switch directly to the entry point of the
|
||||||
|
// next task. Not dropping is just fine because the only consequence is that we delay the drop
|
||||||
|
// to the next task switching.
|
||||||
}
|
}
|
||||||
|
|
||||||
cpu_local! {
|
cpu_local! {
|
||||||
|
@ -178,8 +178,19 @@ impl Task {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn exit(&self) -> ! {
|
/// Exits the current task.
|
||||||
|
///
|
||||||
|
/// The task `self` must be the task that is currently running.
|
||||||
|
///
|
||||||
|
/// **NOTE:** If there is anything left on the stack, it will be forgotten. This behavior may
|
||||||
|
/// lead to resource leakage.
|
||||||
|
fn exit(self: Arc<Self>) -> ! {
|
||||||
self.inner_exclusive_access().task_status = TaskStatus::Exited;
|
self.inner_exclusive_access().task_status = TaskStatus::Exited;
|
||||||
|
|
||||||
|
// `current_task()` still holds a strong reference, so nothing is destroyed at this point,
|
||||||
|
// neither is the kernel stack.
|
||||||
|
drop(self);
|
||||||
|
|
||||||
schedule();
|
schedule();
|
||||||
unreachable!()
|
unreachable!()
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
//! When create a process from elf file, we will use the elf_load_info to construct the VmSpace
|
//! When create a process from elf file, we will use the elf_load_info to construct the VmSpace
|
||||||
|
|
||||||
use align_ext::AlignExt;
|
use align_ext::AlignExt;
|
||||||
use aster_frame::{mm::VmIo, task::Task};
|
use aster_frame::mm::VmIo;
|
||||||
use aster_rights::{Full, Rights};
|
use aster_rights::{Full, Rights};
|
||||||
use xmas_elf::program::{self, ProgramHeader64};
|
use xmas_elf::program::{self, ProgramHeader64};
|
||||||
|
|
||||||
@ -75,7 +75,7 @@ pub fn load_elf_to_vm(
|
|||||||
user_stack_top,
|
user_stack_top,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
Err(_) => {
|
Err(err) => {
|
||||||
// Since the process_vm is in invalid state,
|
// Since the process_vm is in invalid state,
|
||||||
// the process cannot return to user space again,
|
// the process cannot return to user space again,
|
||||||
// so `Vmar::clear` and `do_exit_group` are called here.
|
// so `Vmar::clear` and `do_exit_group` are called here.
|
||||||
@ -87,7 +87,9 @@ pub fn load_elf_to_vm(
|
|||||||
// the macro will panic. This corner case should be handled later.
|
// the macro will panic. This corner case should be handled later.
|
||||||
// FIXME: how to set the correct exit status?
|
// FIXME: how to set the correct exit status?
|
||||||
do_exit_group(TermStatus::Exited(1));
|
do_exit_group(TermStatus::Exited(1));
|
||||||
Task::current().exit();
|
|
||||||
|
// The process will exit and the error code will be ignored.
|
||||||
|
Err(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,7 @@ pub mod signals;
|
|||||||
use core::{mem, sync::atomic::Ordering};
|
use core::{mem, sync::atomic::Ordering};
|
||||||
|
|
||||||
use align_ext::AlignExt;
|
use align_ext::AlignExt;
|
||||||
use aster_frame::{cpu::UserContext, task::Task, user::UserContextApi};
|
use aster_frame::{cpu::UserContext, user::UserContextApi};
|
||||||
use c_types::{siginfo_t, ucontext_t};
|
use c_types::{siginfo_t, ucontext_t};
|
||||||
pub use events::{SigEvents, SigEventsFilter};
|
pub use events::{SigEvents, SigEventsFilter};
|
||||||
pub use pauser::Pauser;
|
pub use pauser::Pauser;
|
||||||
@ -90,9 +90,8 @@ pub fn handle_pending_signal(
|
|||||||
current.executable_path(),
|
current.executable_path(),
|
||||||
sig_num.sig_name()
|
sig_num.sig_name()
|
||||||
);
|
);
|
||||||
do_exit_group(TermStatus::Killed(sig_num));
|
|
||||||
// We should exit current here, since we cannot restore a valid status from trap now.
|
// We should exit current here, since we cannot restore a valid status from trap now.
|
||||||
Task::current().exit();
|
do_exit_group(TermStatus::Killed(sig_num));
|
||||||
}
|
}
|
||||||
SigDefaultAction::Ign => {}
|
SigDefaultAction::Ign => {}
|
||||||
SigDefaultAction::Stop => {
|
SigDefaultAction::Stop => {
|
||||||
|
@ -57,22 +57,20 @@ pub fn create_new_user_task(user_space: Arc<UserSpace>, thread_ref: Weak<Thread>
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
handle_pending_signal(context, ¤t_thread).unwrap();
|
handle_pending_signal(context, ¤t_thread).unwrap();
|
||||||
if current_thread.status().is_exited() {
|
|
||||||
debug!("exit due to signal");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
// If current is suspended, wait for a signal to wake up self
|
// If current is suspended, wait for a signal to wake up self
|
||||||
while current_thread.status().is_stopped() {
|
while current_thread.status().is_stopped() {
|
||||||
Thread::yield_now();
|
Thread::yield_now();
|
||||||
debug!("{} is suspended.", current_thread.tid());
|
debug!("{} is suspended.", current_thread.tid());
|
||||||
handle_pending_signal(context, ¤t_thread).unwrap();
|
handle_pending_signal(context, ¤t_thread).unwrap();
|
||||||
}
|
}
|
||||||
|
if current_thread.status().is_exited() {
|
||||||
|
debug!("exit due to signal");
|
||||||
|
break;
|
||||||
|
}
|
||||||
// a preemption point after handling user event.
|
// a preemption point after handling user event.
|
||||||
preempt(current_task);
|
preempt(current_task);
|
||||||
}
|
}
|
||||||
debug!("exit user loop");
|
debug!("exit user loop");
|
||||||
// FIXME: This is a work around: exit in kernel task entry may be not called. Why this will happen?
|
|
||||||
current_task.exit();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
TaskOptions::new(user_task_entry)
|
TaskOptions::new(user_task_entry)
|
||||||
|
Reference in New Issue
Block a user