Fix memory leak of Task structures

This commit is contained in:
Ruihan Li
2024-05-08 18:38:36 +08:00
committed by Tate, Hongliang Tian
parent faf9cf7da8
commit e0c6c29481
5 changed files with 45 additions and 18 deletions

View File

@ -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! {

View File

@ -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!()
} }

View File

@ -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)
} }
} }
} }

View File

@ -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 => {

View File

@ -57,22 +57,20 @@ pub fn create_new_user_task(user_space: Arc<UserSpace>, thread_ref: Weak<Thread>
break; break;
} }
handle_pending_signal(context, &current_thread).unwrap(); handle_pending_signal(context, &current_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, &current_thread).unwrap(); handle_pending_signal(context, &current_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)