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.
let exit_code = if initproc.exit_code().unwrap() == 0 {
let exit_code = if initproc.exit_code() == 0 {
QemuExitCode::Success
} else {
QemuExitCode::Failed

View File

@ -3,7 +3,11 @@
use super::{
posix_thread::PosixThreadExt,
process_table,
signal::signals::{user::UserSignal, Signal},
signal::{
constants::SIGCONT,
sig_num::SigNum,
signals::{user::UserSignal, Signal},
},
Pgid, Pid, Process, Sid, Uid,
};
use crate::{
@ -18,11 +22,27 @@ use crate::{
///
/// If `signal` is `None`, this method will only check permission without sending
/// 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)
.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
@ -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
/// 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)
.ok_or_else(|| Error::with_message(Errno::ESRCH, "target group does not exist"))?;
let inner = process_group.inner.lock();
for process in inner.processes.values() {
kill_process(process, signal)?;
kill_process(process, signal, ctx)?;
}
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
/// 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)
.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
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)?;
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
/// 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!();
for process in process_table::process_table().iter() {
if Arc::ptr_eq(&current, process) || process.is_init_process() {
continue;
}
kill_process(process, signal)?;
kill_process(process, signal, ctx)?;
}
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 posix_threads = threads
.iter()
.map(|thread| thread.as_posix_thread().unwrap());
let signum = signal.map(|signal| signal.num());
let sender_ids = current_thread_sender_ids(signum.as_ref(), ctx);
let mut permitted_thread = None;
for thread in threads.iter() {
let posix_thread = thread.as_posix_thread().unwrap();
// First check permission
let signum = signal.map(|signal| signal.num());
let sender_ids = current_thread_sender_ids();
let mut permitted_threads = {
posix_threads.clone().filter(|posix_thread| {
posix_thread
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(());
};
if permitted_threads.clone().count() == 0 {
return_errno_with_message!(Errno::EPERM, "cannot send signal to the target process");
if !posix_thread.has_signal_blocked(*signum) {
// Send signal to any thread that does not blocks the signal.
let signal = signal.unwrap();
posix_thread.enqueue_signal(Box::new(signal));
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(()) };
// Send signal to any thread that does not blocks the signal.
for thread in permitted_threads.clone() {
if !thread.has_signal_blocked(&signal) {
thread.enqueue_signal(Box::new(signal));
return Ok(());
}
}
// If all threads block the signal, send signal to the first thread.
let first_thread = permitted_threads.next().unwrap();
first_thread.enqueue_signal(Box::new(signal));
permitted_thread.enqueue_signal(Box::new(signal));
Ok(())
}
fn current_thread_sender_ids() -> SignalSenderIds {
let current_thread = current_thread!();
let current_posix_thread = current_thread.as_posix_thread().unwrap();
let current_process = current_posix_thread.process();
let credentials = current_posix_thread.credentials();
fn current_thread_sender_ids(signum: Option<&SigNum>, ctx: &Context) -> SignalSenderIds {
let credentials = ctx.posix_thread.credentials();
let ruid = credentials.ruid();
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)
}
@ -156,11 +184,11 @@ fn current_thread_sender_ids() -> SignalSenderIds {
pub(super) struct SignalSenderIds {
ruid: Uid,
euid: Uid,
sid: Sid,
sid: Option<Sid>,
}
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 }
}
@ -172,7 +200,7 @@ impl SignalSenderIds {
self.euid
}
pub(super) fn sid(&self) -> Sid {
pub(super) fn sid(&self) -> Option<Sid> {
self.sid
}
}

View File

@ -117,8 +117,9 @@ impl PosixThread {
}
/// Returns whether the signal is blocked by the thread.
pub(in crate::process) fn has_signal_blocked(&self, signal: &dyn Signal) -> bool {
self.sig_mask.contains(signal.num(), Ordering::Relaxed)
pub(in crate::process) fn has_signal_blocked(&self, signum: SigNum) -> bool {
// 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.
@ -141,7 +142,7 @@ impl PosixThread {
&& *signum == SIGCONT
{
let receiver_sid = self.process().session().unwrap().sid();
if receiver_sid == sender.sid() {
if receiver_sid == sender.sid().unwrap() {
return Ok(());
}

View File

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

View File

@ -4,32 +4,56 @@
//! The process status
use super::TermStatus;
use core::sync::atomic::{AtomicU64, Ordering};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ProcessStatus {
// Not ready to run
Uninit,
/// Can be scheduled to run
Runnable,
/// Exit while not reaped by parent
Zombie(TermStatus),
use super::{ExitCode, TermStatus};
/// The status of process.
///
/// The `ProcessStatus` can be viewed as two parts,
/// the highest 32 bits is the value of `TermStatus`, if any,
/// the lowest 32 bits is the value of status.
#[derive(Debug)]
pub struct ProcessStatus(AtomicU64);
#[repr(u8)]
enum Status {
Uninit = 0,
Runnable = 1,
Zombie = 2,
}
impl ProcessStatus {
pub fn set_zombie(&mut self, term_status: TermStatus) {
*self = ProcessStatus::Zombie(term_status);
const LOW_MASK: u64 = 0xffff_ffff;
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 {
matches!(self, ProcessStatus::Zombie(_))
self.0.load(Ordering::Relaxed) & Self::LOW_MASK == Status::Zombie as u64
}
pub fn set_runnable(&mut self) {
*self = ProcessStatus::Runnable;
pub fn set_runnable(&self) {
let new_val = Status::Runnable as u64;
self.0.store(new_val, Ordering::Relaxed);
}
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());
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 {
ProcessFilter::Any => kill_all(signal)?,
ProcessFilter::WithPid(pid) => kill(pid, signal)?,
ProcessFilter::WithPgid(pgid) => kill_group(pgid, signal)?,
ProcessFilter::Any => kill_all(signal, ctx)?,
ProcessFilter::WithPid(pid) => kill(pid, signal, ctx)?,
ProcessFilter::WithPgid(pgid) => kill_group(pgid, signal, ctx)?,
}
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();
UserSignal::new(sig_num, UserSignalKind::Tkill, pid, uid)
});
tgkill(tid, tgid, signal)?;
tgkill(tid, tgid, signal, ctx)?;
Ok(SyscallReturn::Return(0))
}

View File

@ -27,7 +27,7 @@ pub fn sys_wait4(
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 {
ctx.get_user_space()
.write_val(exit_status_ptr as _, &exit_code)?;

View File

@ -19,12 +19,12 @@ pub fn handle_exception(ctx: &Context, context: &UserContext) {
match *exception {
PAGE_FAULT => {
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
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,
page_fault_addr
);
let not_present = trap_info.error_code & PAGE_NOT_PRESENT_ERROR_MASK == 0;
let write = trap_info.error_code & WRITE_ACCESS_MASK != 0;
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 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!(
Arc::as_ptr(root_vmar.vm_space()),
vm_space as *const VmSpace
@ -69,10 +70,9 @@ pub(crate) fn handle_page_fault(
}
/// generate a fault signal for current process.
fn generate_fault_signal(trap_info: &CpuExceptionInfo) {
let current = current!();
fn generate_fault_signal(trap_info: &CpuExceptionInfo, ctx: &Context) {
let signal = FaultSignal::new(trap_info);
current.enqueue_signal(signal);
ctx.posix_thread.enqueue_signal(Box::new(signal));
}
macro_rules! log_trap_common {

View File

@ -7,4 +7,4 @@ set -e
echo "*** Running the LMbench protection fault latency test ***"
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