Optimize the latency of lmbench-signal-prot

This commit is contained in:
Jianfeng Jiang
2024-08-27 06:37:02 +00:00
committed by Tate, Hongliang Tian
parent 7b58d97aa2
commit a72c7dadf3
11 changed files with 137 additions and 87 deletions

View File

@ -168,7 +168,7 @@ fn init_thread() {
} }
// TODO: exit via qemu isa debug device should not be the only way. // TODO: exit via qemu isa debug device should not be the only way.
let exit_code = if initproc.exit_code().unwrap() == 0 { let exit_code = if initproc.exit_code() == 0 {
QemuExitCode::Success QemuExitCode::Success
} else { } else {
QemuExitCode::Failed QemuExitCode::Failed

View File

@ -3,7 +3,11 @@
use super::{ use super::{
posix_thread::PosixThreadExt, posix_thread::PosixThreadExt,
process_table, process_table,
signal::signals::{user::UserSignal, Signal}, signal::{
constants::SIGCONT,
sig_num::SigNum,
signals::{user::UserSignal, Signal},
},
Pgid, Pid, Process, Sid, Uid, Pgid, Pid, Process, Sid, Uid,
}; };
use crate::{ use crate::{
@ -18,11 +22,27 @@ use crate::{
/// ///
/// If `signal` is `None`, this method will only check permission without sending /// If `signal` is `None`, this method will only check permission without sending
/// any signal. /// any signal.
pub fn kill(pid: Pid, signal: Option<UserSignal>) -> Result<()> { pub fn kill(pid: Pid, signal: Option<UserSignal>, ctx: &Context) -> Result<()> {
// Fast path: If the signal is sent to self, we can skip most check.
if pid == ctx.process.pid() {
let Some(signal) = signal else {
return Ok(());
};
if !ctx.posix_thread.has_signal_blocked(signal.num()) {
ctx.posix_thread.enqueue_signal(Box::new(signal));
return Ok(());
}
return kill_process(ctx.process, Some(signal), ctx);
}
// Slow path
let process = process_table::get_process(pid) let process = process_table::get_process(pid)
.ok_or_else(|| Error::with_message(Errno::ESRCH, "the target process does not exist"))?; .ok_or_else(|| Error::with_message(Errno::ESRCH, "the target process does not exist"))?;
kill_process(&process, signal) kill_process(&process, signal, ctx)
} }
/// Sends a signal to all processes in a group, using the current process /// Sends a signal to all processes in a group, using the current process
@ -33,13 +53,13 @@ pub fn kill(pid: Pid, signal: Option<UserSignal>) -> Result<()> {
/// ///
/// If `signal` is `None`, this method will only check permission without sending /// If `signal` is `None`, this method will only check permission without sending
/// any signal. /// any signal.
pub fn kill_group(pgid: Pgid, signal: Option<UserSignal>) -> Result<()> { pub fn kill_group(pgid: Pgid, signal: Option<UserSignal>, ctx: &Context) -> Result<()> {
let process_group = process_table::get_process_group(&pgid) let process_group = process_table::get_process_group(&pgid)
.ok_or_else(|| Error::with_message(Errno::ESRCH, "target group does not exist"))?; .ok_or_else(|| Error::with_message(Errno::ESRCH, "target group does not exist"))?;
let inner = process_group.inner.lock(); let inner = process_group.inner.lock();
for process in inner.processes.values() { for process in inner.processes.values() {
kill_process(process, signal)?; kill_process(process, signal, ctx)?;
} }
Ok(()) Ok(())
@ -50,7 +70,7 @@ pub fn kill_group(pgid: Pgid, signal: Option<UserSignal>) -> Result<()> {
/// ///
/// If `signal` is `None`, this method will only check permission without sending /// If `signal` is `None`, this method will only check permission without sending
/// any signal. /// any signal.
pub fn tgkill(tid: Tid, tgid: Pid, signal: Option<UserSignal>) -> Result<()> { pub fn tgkill(tid: Tid, tgid: Pid, signal: Option<UserSignal>, ctx: &Context) -> Result<()> {
let thread = thread_table::get_thread(tid) let thread = thread_table::get_thread(tid)
.ok_or_else(|| Error::with_message(Errno::ESRCH, "target thread does not exist"))?; .ok_or_else(|| Error::with_message(Errno::ESRCH, "target thread does not exist"))?;
@ -71,7 +91,7 @@ pub fn tgkill(tid: Tid, tgid: Pid, signal: Option<UserSignal>) -> Result<()> {
// Check permission // Check permission
let signum = signal.map(|signal| signal.num()); let signum = signal.map(|signal| signal.num());
let sender = current_thread_sender_ids(); let sender = current_thread_sender_ids(signum.as_ref(), ctx);
posix_thread.check_signal_perm(signum.as_ref(), &sender)?; posix_thread.check_signal_perm(signum.as_ref(), &sender)?;
if let Some(signal) = signal { if let Some(signal) = signal {
@ -86,66 +106,74 @@ pub fn tgkill(tid: Tid, tgid: Pid, signal: Option<UserSignal>) -> Result<()> {
/// ///
/// The credentials of the current process will be checked to determine /// The credentials of the current process will be checked to determine
/// if it is authorized to send the signal to the target group. /// if it is authorized to send the signal to the target group.
pub fn kill_all(signal: Option<UserSignal>) -> Result<()> { pub fn kill_all(signal: Option<UserSignal>, ctx: &Context) -> Result<()> {
let current = current!(); let current = current!();
for process in process_table::process_table().iter() { for process in process_table::process_table().iter() {
if Arc::ptr_eq(&current, process) || process.is_init_process() { if Arc::ptr_eq(&current, process) || process.is_init_process() {
continue; continue;
} }
kill_process(process, signal)?; kill_process(process, signal, ctx)?;
} }
Ok(()) Ok(())
} }
fn kill_process(process: &Process, signal: Option<UserSignal>) -> Result<()> { fn kill_process(process: &Process, signal: Option<UserSignal>, ctx: &Context) -> Result<()> {
let threads = process.threads().lock(); let threads = process.threads().lock();
let posix_threads = threads
.iter()
.map(|thread| thread.as_posix_thread().unwrap());
// First check permission
let signum = signal.map(|signal| signal.num()); let signum = signal.map(|signal| signal.num());
let sender_ids = current_thread_sender_ids(); let sender_ids = current_thread_sender_ids(signum.as_ref(), ctx);
let mut permitted_threads = {
posix_threads.clone().filter(|posix_thread| {
posix_thread
.check_signal_perm(signum.as_ref(), &sender_ids)
.is_ok()
})
};
if permitted_threads.clone().count() == 0 { let mut permitted_thread = None;
return_errno_with_message!(Errno::EPERM, "cannot send signal to the target process"); for thread in threads.iter() {
} let posix_thread = thread.as_posix_thread().unwrap();
let Some(signal) = signal else { return Ok(()) }; // First check permission
if posix_thread
.check_signal_perm(signum.as_ref(), &sender_ids)
.is_ok()
{
let Some(ref signum) = signum else {
// If signal is None, only permission check is required
return Ok(());
};
// Send signal to any thread that does not blocks the signal. if !posix_thread.has_signal_blocked(*signum) {
for thread in permitted_threads.clone() { // Send signal to any thread that does not blocks the signal.
if !thread.has_signal_blocked(&signal) { let signal = signal.unwrap();
thread.enqueue_signal(Box::new(signal)); posix_thread.enqueue_signal(Box::new(signal));
return Ok(()); return Ok(());
} else if permitted_thread.is_none() {
permitted_thread = Some(posix_thread);
}
} }
} }
let Some(permitted_thread) = permitted_thread else {
return_errno_with_message!(Errno::EPERM, "cannot send signal to the target process");
};
// If signal is None, only permission check is required
let Some(signal) = signal else { return Ok(()) };
// If all threads block the signal, send signal to the first thread. // If all threads block the signal, send signal to the first thread.
let first_thread = permitted_threads.next().unwrap(); permitted_thread.enqueue_signal(Box::new(signal));
first_thread.enqueue_signal(Box::new(signal));
Ok(()) Ok(())
} }
fn current_thread_sender_ids() -> SignalSenderIds { fn current_thread_sender_ids(signum: Option<&SigNum>, ctx: &Context) -> SignalSenderIds {
let current_thread = current_thread!(); let credentials = ctx.posix_thread.credentials();
let current_posix_thread = current_thread.as_posix_thread().unwrap();
let current_process = current_posix_thread.process();
let credentials = current_posix_thread.credentials();
let ruid = credentials.ruid(); let ruid = credentials.ruid();
let euid = credentials.euid(); let euid = credentials.euid();
let sid = current_process.session().unwrap().sid(); let sid = signum.and_then(|signum| {
if *signum == SIGCONT {
Some(ctx.process.session().unwrap().sid())
} else {
None
}
});
SignalSenderIds::new(ruid, euid, sid) SignalSenderIds::new(ruid, euid, sid)
} }
@ -156,11 +184,11 @@ fn current_thread_sender_ids() -> SignalSenderIds {
pub(super) struct SignalSenderIds { pub(super) struct SignalSenderIds {
ruid: Uid, ruid: Uid,
euid: Uid, euid: Uid,
sid: Sid, sid: Option<Sid>,
} }
impl SignalSenderIds { impl SignalSenderIds {
fn new(ruid: Uid, euid: Uid, sid: Sid) -> Self { fn new(ruid: Uid, euid: Uid, sid: Option<Sid>) -> Self {
Self { ruid, euid, sid } Self { ruid, euid, sid }
} }
@ -172,7 +200,7 @@ impl SignalSenderIds {
self.euid self.euid
} }
pub(super) fn sid(&self) -> Sid { pub(super) fn sid(&self) -> Option<Sid> {
self.sid self.sid
} }
} }

View File

@ -117,8 +117,9 @@ impl PosixThread {
} }
/// Returns whether the signal is blocked by the thread. /// Returns whether the signal is blocked by the thread.
pub(in crate::process) fn has_signal_blocked(&self, signal: &dyn Signal) -> bool { pub(in crate::process) fn has_signal_blocked(&self, signum: SigNum) -> bool {
self.sig_mask.contains(signal.num(), Ordering::Relaxed) // FIMXE: Some signals cannot be blocked, even set in sig_mask.
self.sig_mask.contains(signum, Ordering::Relaxed)
} }
/// Checks whether the signal can be delivered to the thread. /// Checks whether the signal can be delivered to the thread.
@ -141,7 +142,7 @@ impl PosixThread {
&& *signum == SIGCONT && *signum == SIGCONT
{ {
let receiver_sid = self.process().session().unwrap().sid(); let receiver_sid = self.process().session().unwrap().sid();
if receiver_sid == sender.sid() { if receiver_sid == sender.sid().unwrap() {
return Ok(()); return Ok(());
} }

View File

@ -69,7 +69,7 @@ pub struct Process {
/// The threads /// The threads
threads: Mutex<Vec<Arc<Thread>>>, threads: Mutex<Vec<Arc<Thread>>>,
/// Process status /// Process status
status: Mutex<ProcessStatus>, status: ProcessStatus,
/// Parent process /// Parent process
pub(super) parent: Mutex<Weak<Process>>, pub(super) parent: Mutex<Weak<Process>>,
/// Children processes /// Children processes
@ -140,7 +140,7 @@ impl Process {
executable_path: RwLock::new(executable_path), executable_path: RwLock::new(executable_path),
process_vm, process_vm,
children_pauser, children_pauser,
status: Mutex::new(ProcessStatus::Uninit), status: ProcessStatus::new_uninit(),
parent: Mutex::new(parent), parent: Mutex::new(parent),
children: Mutex::new(BTreeMap::new()), children: Mutex::new(BTreeMap::new()),
process_group: Mutex::new(Weak::new()), process_group: Mutex::new(Weak::new()),
@ -589,7 +589,7 @@ impl Process {
let threads = self.threads.lock(); let threads = self.threads.lock();
for thread in threads.iter() { for thread in threads.iter() {
let posix_thread = thread.as_posix_thread().unwrap(); let posix_thread = thread.as_posix_thread().unwrap();
if !posix_thread.has_signal_blocked(&signal) { if !posix_thread.has_signal_blocked(signal.num()) {
posix_thread.enqueue_signal(Box::new(signal)); posix_thread.enqueue_signal(Box::new(signal));
return; return;
} }
@ -622,26 +622,23 @@ impl Process {
// ******************* Status ******************** // ******************* Status ********************
fn set_runnable(&self) { fn set_runnable(&self) {
self.status.lock().set_runnable(); self.status.set_runnable();
} }
fn is_runnable(&self) -> bool { fn is_runnable(&self) -> bool {
self.status.lock().is_runnable() self.status.is_runnable()
} }
pub fn is_zombie(&self) -> bool { pub fn is_zombie(&self) -> bool {
self.status.lock().is_zombie() self.status.is_zombie()
} }
pub fn set_zombie(&self, term_status: TermStatus) { pub fn set_zombie(&self, term_status: TermStatus) {
*self.status.lock() = ProcessStatus::Zombie(term_status); self.status.set_zombie(term_status);
} }
pub fn exit_code(&self) -> Option<ExitCode> { pub fn exit_code(&self) -> ExitCode {
match &*self.status.lock() { self.status.exit_code()
ProcessStatus::Runnable | ProcessStatus::Uninit => None,
ProcessStatus::Zombie(term_status) => Some(term_status.as_u32()),
}
} }
} }

View File

@ -4,32 +4,56 @@
//! The process status //! The process status
use super::TermStatus; use core::sync::atomic::{AtomicU64, Ordering};
#[derive(Debug, Clone, Copy, PartialEq, Eq)] use super::{ExitCode, TermStatus};
pub enum ProcessStatus {
// Not ready to run /// The status of process.
Uninit, ///
/// Can be scheduled to run /// The `ProcessStatus` can be viewed as two parts,
Runnable, /// the highest 32 bits is the value of `TermStatus`, if any,
/// Exit while not reaped by parent /// the lowest 32 bits is the value of status.
Zombie(TermStatus), #[derive(Debug)]
pub struct ProcessStatus(AtomicU64);
#[repr(u8)]
enum Status {
Uninit = 0,
Runnable = 1,
Zombie = 2,
} }
impl ProcessStatus { impl ProcessStatus {
pub fn set_zombie(&mut self, term_status: TermStatus) { const LOW_MASK: u64 = 0xffff_ffff;
*self = ProcessStatus::Zombie(term_status);
pub fn new_uninit() -> Self {
Self(AtomicU64::new(Status::Uninit as u64))
}
pub fn set_zombie(&self, term_status: TermStatus) {
let new_val = (term_status.as_u32() as u64) << 32 | Status::Zombie as u64;
self.0.store(new_val, Ordering::Relaxed);
} }
pub fn is_zombie(&self) -> bool { pub fn is_zombie(&self) -> bool {
matches!(self, ProcessStatus::Zombie(_)) self.0.load(Ordering::Relaxed) & Self::LOW_MASK == Status::Zombie as u64
} }
pub fn set_runnable(&mut self) { pub fn set_runnable(&self) {
*self = ProcessStatus::Runnable; let new_val = Status::Runnable as u64;
self.0.store(new_val, Ordering::Relaxed);
} }
pub fn is_runnable(&self) -> bool { pub fn is_runnable(&self) -> bool {
*self == ProcessStatus::Runnable self.0.load(Ordering::Relaxed) & Self::LOW_MASK == Status::Runnable as u64
}
/// Returns the exit code.
///
/// If the process is not exited, the exit code is zero.
/// But if exit code is zero, the process may or may not exit.
pub fn exit_code(&self) -> ExitCode {
let val = self.0.load(Ordering::Relaxed);
(val >> 32 & Self::LOW_MASK) as ExitCode
} }
} }

View File

@ -109,5 +109,5 @@ fn reap_zombie_child(process: &Process, pid: Pid) -> ExitCode {
} }
process_table_mut.remove(&child_process.pid()); process_table_mut.remove(&child_process.pid());
child_process.exit_code().unwrap() child_process.exit_code()
} }

View File

@ -36,9 +36,9 @@ pub fn do_sys_kill(filter: ProcessFilter, sig_num: Option<SigNum>, ctx: &Context
}); });
match filter { match filter {
ProcessFilter::Any => kill_all(signal)?, ProcessFilter::Any => kill_all(signal, ctx)?,
ProcessFilter::WithPid(pid) => kill(pid, signal)?, ProcessFilter::WithPid(pid) => kill(pid, signal, ctx)?,
ProcessFilter::WithPgid(pgid) => kill_group(pgid, signal)?, ProcessFilter::WithPgid(pgid) => kill_group(pgid, signal, ctx)?,
} }
Ok(()) Ok(())
} }

View File

@ -28,6 +28,6 @@ pub fn sys_tgkill(tgid: Pid, tid: Tid, sig_num: u8, ctx: &Context) -> Result<Sys
let uid = ctx.posix_thread.credentials().ruid(); let uid = ctx.posix_thread.credentials().ruid();
UserSignal::new(sig_num, UserSignalKind::Tkill, pid, uid) UserSignal::new(sig_num, UserSignalKind::Tkill, pid, uid)
}); });
tgkill(tid, tgid, signal)?; tgkill(tid, tgid, signal, ctx)?;
Ok(SyscallReturn::Return(0)) Ok(SyscallReturn::Return(0))
} }

View File

@ -27,7 +27,7 @@ pub fn sys_wait4(
return Ok(SyscallReturn::Return(0 as _)); return Ok(SyscallReturn::Return(0 as _));
}; };
let (return_pid, exit_code) = (process.pid(), process.exit_code().unwrap()); let (return_pid, exit_code) = (process.pid(), process.exit_code());
if exit_status_ptr != 0 { if exit_status_ptr != 0 {
ctx.get_user_space() ctx.get_user_space()
.write_val(exit_status_ptr as _, &exit_code)?; .write_val(exit_status_ptr as _, &exit_code)?;

View File

@ -19,12 +19,12 @@ pub fn handle_exception(ctx: &Context, context: &UserContext) {
match *exception { match *exception {
PAGE_FAULT => { PAGE_FAULT => {
if handle_page_fault(root_vmar.vm_space(), trap_info).is_err() { if handle_page_fault(root_vmar.vm_space(), trap_info).is_err() {
generate_fault_signal(trap_info); generate_fault_signal(trap_info, ctx);
} }
} }
_ => { _ => {
// We current do nothing about other exceptions // We current do nothing about other exceptions
generate_fault_signal(trap_info); generate_fault_signal(trap_info, ctx);
} }
} }
} }
@ -42,13 +42,14 @@ pub(crate) fn handle_page_fault(
trap_info.error_code, trap_info.error_code,
page_fault_addr page_fault_addr
); );
let not_present = trap_info.error_code & PAGE_NOT_PRESENT_ERROR_MASK == 0; let not_present = trap_info.error_code & PAGE_NOT_PRESENT_ERROR_MASK == 0;
let write = trap_info.error_code & WRITE_ACCESS_MASK != 0; let write = trap_info.error_code & WRITE_ACCESS_MASK != 0;
if not_present || write { if not_present || write {
// If page is not present or due to write access, we should ask the vmar try to commit this page
let current = current!(); let current = current!();
let root_vmar = current.root_vmar(); let root_vmar = current.root_vmar();
// If page is not present or due to write access, we should ask the vmar try to commit this page
debug_assert_eq!( debug_assert_eq!(
Arc::as_ptr(root_vmar.vm_space()), Arc::as_ptr(root_vmar.vm_space()),
vm_space as *const VmSpace vm_space as *const VmSpace
@ -69,10 +70,9 @@ pub(crate) fn handle_page_fault(
} }
/// generate a fault signal for current process. /// generate a fault signal for current process.
fn generate_fault_signal(trap_info: &CpuExceptionInfo) { fn generate_fault_signal(trap_info: &CpuExceptionInfo, ctx: &Context) {
let current = current!();
let signal = FaultSignal::new(trap_info); let signal = FaultSignal::new(trap_info);
current.enqueue_signal(signal); ctx.posix_thread.enqueue_signal(Box::new(signal));
} }
macro_rules! log_trap_common { macro_rules! log_trap_common {

View File

@ -7,4 +7,4 @@ set -e
echo "*** Running the LMbench protection fault latency test ***" echo "*** Running the LMbench protection fault latency test ***"
dd if=/dev/zero of=/ext2/test_file bs=1M count=256 dd if=/dev/zero of=/ext2/test_file bs=1M count=256
/benchmark/bin/lmbench/lat_sig prot /ext2/test_file /benchmark/bin/lmbench/lat_sig -N 100 prot /ext2/test_file