Add `/proc/pid/oom_score_adj`

This commit is contained in:
Wang Siyuan 2025-09-13 06:37:48 +00:00 committed by Ruihan Li
parent b42605af6b
commit 46aa437c87
7 changed files with 105 additions and 2 deletions

View File

@ -12,7 +12,8 @@ use crate::{
stat::StatFileOps,
task::{
cmdline::CmdlineFileOps, comm::CommFileOps, environ::EnvironFileOps,
exe::ExeSymOps, fd::FdDirOps, status::StatusFileOps,
exe::ExeSymOps, fd::FdDirOps, oom_score_adj::OomScoreAdjFileOps,
status::StatusFileOps,
},
},
template::{DirOps, ProcDir, ProcDirBuilder},
@ -29,6 +30,7 @@ mod comm;
mod environ;
mod exe;
mod fd;
mod oom_score_adj;
mod status;
/// Represents the inode at `/proc/[pid]/task`.
@ -79,6 +81,7 @@ impl DirOps for TidDirOps {
"environ" => EnvironFileOps::new_inode(self.process_ref.clone(), this_ptr),
"exe" => ExeSymOps::new_inode(self.process_ref.clone(), this_ptr),
"fd" => FdDirOps::new_inode(self.thread_ref.clone(), this_ptr),
"oom_score_adj" => OomScoreAdjFileOps::new_inode(self.process_ref.clone(), this_ptr),
"stat" => StatFileOps::new_inode(
self.process_ref.clone(),
self.thread_ref.clone(),
@ -126,6 +129,9 @@ impl TidDirOps {
cached_children.put_entry_if_not_found("fd", || {
FdDirOps::new_inode(self.thread_ref.clone(), this_ptr.clone())
});
cached_children.put_entry_if_not_found("oom_score_adj", || {
OomScoreAdjFileOps::new_inode(self.process_ref.clone(), this_ptr.clone())
});
cached_children.put_entry_if_not_found("stat", || {
StatFileOps::new_inode(
self.process_ref.clone(),

View File

@ -0,0 +1,77 @@
// SPDX-License-Identifier: MPL-2.0
use core::{fmt::Write, sync::atomic::Ordering};
use crate::{
fs::{
procfs::template::{FileOps, ProcFileBuilder},
utils::{Inode, InodeMode},
},
prelude::*,
process::Process,
};
/// Represents the inode at `/proc/[pid]/task/[tid]/oom_score_adj` (and also `/proc/[pid]/oom_score_adj`).
pub struct OomScoreAdjFileOps(Arc<Process>);
impl OomScoreAdjFileOps {
pub fn new_inode(process_ref: Arc<Process>, parent: Weak<dyn Inode>) -> Arc<dyn Inode> {
// Reference: <https://elixir.bootlin.com/linux/v6.16.5/source/fs/proc/base.c#L3386>
ProcFileBuilder::new(Self(process_ref), InodeMode::from_bits_truncate(0o644))
.parent(parent)
.build()
.unwrap()
}
}
impl FileOps for OomScoreAdjFileOps {
fn data(&self) -> Result<Vec<u8>> {
let oom_score_adj = self.0.oom_score_adj().load(Ordering::Relaxed);
let mut output = String::new();
writeln!(output, "{}", oom_score_adj).unwrap();
Ok(output.into_bytes())
}
fn write_at(&self, _offset: usize, reader: &mut VmReader) -> Result<usize> {
// TODO: Extend the `ReadCString` trait to read a C string without
// requiring the nul terminator.
let (val, read_bytes) = read_i32_from(reader)?;
if !(OOM_SCORE_ADJ_MIN..=OOM_SCORE_ADJ_MAX).contains(&val) {
return_errno_with_message!(Errno::EINVAL, "the OOM score adjustment is out of range");
}
// TODO: If the new adjustment value is smaller than the smallest
// adjustment value that the process has ever reached and the writer
// does not have the `SYS_RESOURCE` capability, we should fail with
// `EACCES`. See
// <https://elixir.bootlin.com/linux/v6.16.5/source/fs/proc/base.c#L1152>.
self.0.oom_score_adj().store(val as i16, Ordering::Relaxed);
Ok(read_bytes)
}
}
/// Reads an `i32` from the given `VmReader`.
///
/// Returns the read value and the number of bytes read from the given `VmReader`.
fn read_i32_from(reader: &mut VmReader) -> Result<(i32, usize)> {
let mut buf = [0u8; BUF_SIZE_I32];
let read_bytes = reader.read_fallible(&mut (&mut buf[..BUF_SIZE_I32 - 1]).into())?;
let val = CStr::from_bytes_until_nul(&buf)
.unwrap()
.to_str()
.ok()
.and_then(|str| str.parse::<i32>().ok())
.ok_or_else(|| Error::with_message(Errno::EINVAL, "the value is not a valid integer"))?;
Ok((val, read_bytes))
}
/// Worst case buffer size needed for holding an integer.
// Reference: <https://elixir.bootlin.com/linux/v6.16.5/source/fs/proc/internal.h#L163>.
const BUF_SIZE_I32: usize = 13;
// FIXME: Support OOM killer and move these constants to a more appropriate place.
const OOM_SCORE_ADJ_MIN: i32 = -1000;
const OOM_SCORE_ADJ_MAX: i32 = 1000;

View File

@ -401,6 +401,9 @@ fn clone_child_process(
// Inherit the parent's nice value
let child_nice = process.nice().load(Ordering::Relaxed);
// Inherit the parent's OOM score adjustment
let child_oom_score_adj = process.oom_score_adj().load(Ordering::Relaxed);
let child_tid = allocate_posix_tid();
let child = {
@ -437,6 +440,7 @@ fn clone_child_process(
child_process_vm,
child_resource_limits,
child_nice,
child_oom_score_adj,
child_sig_dispositions,
child_user_ns,
child_thread_builder,
@ -644,6 +648,7 @@ fn create_child_process(
process_vm: ProcessVm,
resource_limits: ResourceLimits,
nice: Nice,
oom_score_adj: i16,
sig_dispositions: Arc<Mutex<SigDispositions>>,
user_ns: Arc<UserNamespace>,
thread_builder: PosixThreadBuilder,
@ -655,6 +660,7 @@ fn create_child_process(
process_vm,
resource_limits,
nice,
oom_score_adj,
sig_dispositions,
user_ns,
);

View File

@ -51,6 +51,7 @@ fn create_init_process(
let process_vm = ProcessVm::alloc();
let resource_limits = ResourceLimits::default();
let nice = Nice::default();
let oom_score_adj = 0;
let sig_dispositions = Arc::new(Mutex::new(SigDispositions::default()));
let user_ns = UserNamespace::get_init_singleton().clone();
@ -61,6 +62,7 @@ fn create_init_process(
process_vm,
resource_limits,
nice,
oom_score_adj,
sig_dispositions,
user_ns,
);

View File

@ -1,6 +1,6 @@
// SPDX-License-Identifier: MPL-2.0
use core::sync::atomic::{AtomicBool, AtomicU32, Ordering};
use core::sync::atomic::{AtomicBool, AtomicI16, AtomicU32, Ordering};
use self::timer_manager::PosixTimerManager;
use super::{
@ -92,6 +92,9 @@ pub struct Process {
/// According to POSIX.1, the nice value is a per-process attribute,
/// the threads in a process should share a nice value.
nice: AtomicNice,
/// The adjustment value of the out-of-memory (OOM) killer score.
// FIXME: Support OOM killer.
oom_score_adj: AtomicI16,
// Child reaper attribute
/// Whether the process is a child subreaper.
@ -201,6 +204,7 @@ impl Process {
resource_limits: ResourceLimits,
nice: Nice,
oom_score_adj: i16,
sig_dispositions: Arc<Mutex<SigDispositions>>,
user_ns: Arc<UserNamespace>,
) -> Arc<Self> {
@ -228,6 +232,7 @@ impl Process {
exit_signal: AtomicSigNum::new_empty(),
resource_limits,
nice: AtomicNice::new(nice),
oom_score_adj: AtomicI16::new(oom_score_adj),
timer_manager: PosixTimerManager::new(&prof_clock, process_ref),
prof_clock,
user_ns: Mutex::new(user_ns),
@ -287,6 +292,10 @@ impl Process {
self.tasks.lock().main().as_thread().unwrap().clone()
}
pub fn oom_score_adj(&self) -> &AtomicI16 {
&self.oom_score_adj
}
// *********** Parent and child ***********
pub fn parent(&self) -> &ParentProcess {

View File

@ -34,6 +34,7 @@ TESTS ?= \
prctl_setuid_test \
pread64_test \
preadv2_test \
proc_pid_oomscore_test \
proc_test \
pselect_test \
pty_test \

View File

@ -0,0 +1,2 @@
# TODO: Support `/proc/[pid]/oom_score`
ProcPidOomscoreTest.BasicRead