mirror of
https://github.com/DragonOS-Community/DragonOS.git
synced 2026-09-08 23:57:59 +08:00
feat(sched): support reset-on-fork scheduler semantics (#2239)
* feat(sched): support reset-on-fork scheduler semantics Align the legacy sched_getparam and sched_getscheduler interfaces with Linux by using the four-byte kernel ABI, signed TID lookup, unrestricted query semantics, consistent policy snapshots, and strict RT priority conversion. Implement the SCHED_OTHER reset-on-fork fast path for sched_setscheduler with Linux-compatible validation ordering, owner and CAP_SYS_NICE checks, protected-flag clearing rules, and per-task fork and clone reset behavior. Keep unsupported class transitions explicit and outside the runqueue mutation path. Tighten the internal FIFO priority endpoint, update the virtio block worker priority, and add dunitest coverage for ABI boundaries, errno ordering, other-TID access, credentials, fork, clone, and task lifetime behavior. Signed-off-by: longjin <longjin@dragonos.org> * fix(net): reserve ifindex one for loopback Linux assigns interface index 1 to the loopback device in every network namespace. DragonOS allocated the root loopback index from a global counter shared with device initcalls, so link-order changes could place lo after enough test interfaces that a bounded SIOCGIFCONF result omitted it. Define the loopback index invariant once, construct loopback devices with that index, and start dynamic root-device allocation at 2. Remove the allocation-based loopback constructor and strengthen the ioctl dunitest to assert the Linux ABI value. This restores all 41 ioctl_test cases while preserving normal interface enumeration semantics. Signed-off-by: longjin <longjin@dragonos.org> * fix(net): avoid veth ingress MAC lock recursion AF_PACKET classifies unicast ingress while the smoltcp interface lock is already held. VethInterface::mac previously reacquired that lock, deadlocking the NAPI worker and every later user of the interface. Keep the immutable veth MAC address in the interface metadata and make mac() a lock-free read. Document the data-path locking contract and extend the SO_BINDTODEVICE regression to verify the interface remains usable after ingress processing. Signed-off-by: longjin <longjin@dragonos.org> --------- Signed-off-by: longjin <longjin@dragonos.org>
This commit is contained in:
@@ -705,7 +705,7 @@ impl VirtIOBlkDevice {
|
||||
|
||||
if let Some(io_thread) = io_thread {
|
||||
// 设置FIFO调度策略
|
||||
if let Err(err) = ProcessManager::set_fifo_policy(&io_thread, MAX_RT_PRIO - 1) {
|
||||
if let Err(err) = ProcessManager::set_fifo_policy(&io_thread, MAX_RT_PRIO - 2) {
|
||||
error!("Failed to set FIFO policy for {}: {:?}", thread_name, err);
|
||||
}
|
||||
|
||||
|
||||
@@ -72,6 +72,10 @@ pub trait Iface: crate::driver::base::device::Device {
|
||||
|
||||
/// # `mac`
|
||||
/// 获取网卡的MAC地址
|
||||
///
|
||||
/// This method is used from the RX data path while the caller may already
|
||||
/// hold [`IfaceCommon::smol_iface`]. Implementations must therefore read
|
||||
/// device metadata directly and must not acquire `smol_iface` again.
|
||||
fn mac(&self) -> smoltcp::wire::EthernetAddress;
|
||||
|
||||
/// # `name`
|
||||
|
||||
@@ -351,6 +351,7 @@ impl phy::Device for VethDriver {
|
||||
pub struct VethInterface {
|
||||
driver: VethDriver,
|
||||
common: IfaceCommon,
|
||||
mac_address: EthernetAddress,
|
||||
inner: SpinLock<VethCommonData>,
|
||||
locked_kobj_state: LockedKObjectState,
|
||||
}
|
||||
@@ -382,7 +383,8 @@ impl VethInterface {
|
||||
(iface_id >> 8) as u8,
|
||||
iface_id as u8,
|
||||
];
|
||||
let hw_addr = HardwareAddress::Ethernet(EthernetAddress(mac));
|
||||
let mac_address = EthernetAddress(mac);
|
||||
let hw_addr = HardwareAddress::Ethernet(mac_address);
|
||||
let mut iface_config = smoltcp::iface::Config::new(hw_addr);
|
||||
iface_config.random_seed = rand() as u64;
|
||||
let mut iface = smoltcp::iface::Interface::new(
|
||||
@@ -409,6 +411,7 @@ impl VethInterface {
|
||||
flags,
|
||||
iface,
|
||||
),
|
||||
mac_address,
|
||||
inner: SpinLock::new(VethCommonData::default()),
|
||||
locked_kobj_state: LockedKObjectState::default(),
|
||||
});
|
||||
@@ -649,11 +652,7 @@ impl Iface for VethInterface {
|
||||
}
|
||||
|
||||
fn mac(&self) -> EthernetAddress {
|
||||
if let HardwareAddress::Ethernet(mac) = self.common.smol_iface.lock().hardware_addr() {
|
||||
mac
|
||||
} else {
|
||||
EthernetAddress([0, 0, 0, 0, 0, 0])
|
||||
}
|
||||
self.mac_address
|
||||
}
|
||||
|
||||
fn poll(&self) -> bool {
|
||||
|
||||
@@ -141,14 +141,14 @@ impl ProcessManager {
|
||||
return Err(SystemError::EPERM);
|
||||
}
|
||||
|
||||
if !(0..crate::sched::prio::MAX_RT_PRIO).contains(&prio) {
|
||||
if !(0..crate::sched::prio::MAX_RT_PRIO - 1).contains(&prio) {
|
||||
return Err(SystemError::EINVAL);
|
||||
}
|
||||
|
||||
let _irq_guard = unsafe { CurrentIrqArch::save_and_disable_irq() };
|
||||
|
||||
// Lock ordering: pi_lock → rq_lock, matching Linux task_rq_lock().
|
||||
let pi_guard = pcb.sched_info().pi_lock_irqsave();
|
||||
let mut pi_guard = pcb.sched_info().pi_lock_irqsave();
|
||||
|
||||
let target_cpu = pcb.sched_info().on_cpu().unwrap_or(current_cpu_id());
|
||||
let update_clock = target_cpu == smp_get_processor_id();
|
||||
@@ -201,6 +201,11 @@ impl ProcessManager {
|
||||
pcb.sched_info().set_policy(SchedPolicy::FIFO);
|
||||
pcb.sched_info().set_prio(prio);
|
||||
pcb.sched_info().set_normal_prio(prio);
|
||||
// This internal API has no RESET_ON_FORK argument. Do not carry a
|
||||
// stale CFS flag into FIFO, where this PR deliberately does not
|
||||
// implement the otherwise unreachable RT fork-reset path.
|
||||
pi_guard.set_sched_reset_on_fork(false);
|
||||
debug_assert!(!pi_guard.sched_reset_on_fork());
|
||||
|
||||
// Re-enqueue.
|
||||
if queued {
|
||||
|
||||
@@ -79,6 +79,7 @@ pub struct SchedInfo {
|
||||
pub struct PiProtected {
|
||||
pub cpus_allowed: CpuMask,
|
||||
pub nr_cpus_allowed: usize,
|
||||
sched_reset_on_fork: bool,
|
||||
}
|
||||
|
||||
impl PiProtected {
|
||||
@@ -87,6 +88,7 @@ impl PiProtected {
|
||||
Self {
|
||||
cpus_allowed,
|
||||
nr_cpus_allowed,
|
||||
sched_reset_on_fork: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,6 +96,16 @@ impl PiProtected {
|
||||
self.cpus_allowed = new_mask;
|
||||
self.nr_cpus_allowed = self.cpus_allowed.iter_cpu().count();
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn sched_reset_on_fork(&self) -> bool {
|
||||
self.sched_reset_on_fork
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_sched_reset_on_fork(&mut self, reset: bool) {
|
||||
self.sched_reset_on_fork = reset;
|
||||
}
|
||||
}
|
||||
|
||||
impl ProcessSchedulerInfo {
|
||||
|
||||
+22
-5
@@ -1300,20 +1300,37 @@ fn __schedule_inner(sched_mod: SchedMode, current: Option<Arc<ProcessControlBloc
|
||||
|
||||
pub fn sched_fork(pcb: &Arc<ProcessControlBlock>) -> Result<(), SystemError> {
|
||||
let current = ProcessManager::current_pcb();
|
||||
let fork_prio = current.sched_info().normal_prio();
|
||||
let (mut fork_prio, mut fork_static_prio, parent_policy, reset_on_fork) = {
|
||||
let pi_guard = current.sched_info().pi_lock_irqsave();
|
||||
(
|
||||
current.sched_info().normal_prio(),
|
||||
current.sched_info().static_prio(),
|
||||
current.sched_info().policy(),
|
||||
pi_guard.sched_reset_on_fork(),
|
||||
)
|
||||
};
|
||||
|
||||
// This PR can only set RESET_ON_FORK on CFS tasks. Match Linux's fair
|
||||
// policy rule: preserve non-negative nice, but reset negative nice to 0.
|
||||
if reset_on_fork && parent_policy == SchedPolicy::CFS && fork_static_prio < prio::DEFAULT_PRIO {
|
||||
fork_prio = prio::DEFAULT_PRIO;
|
||||
fork_static_prio = prio::DEFAULT_PRIO;
|
||||
}
|
||||
|
||||
// 子进程是 TASK_NEW,不可见。可以直接写裸字段
|
||||
// 子进程继承父进程的 prio、static_prio、normal_prio
|
||||
pcb.sched_info().set_prio(fork_prio);
|
||||
pcb.sched_info()
|
||||
.set_static_prio(current.sched_info().static_prio());
|
||||
pcb.sched_info().set_static_prio(fork_static_prio);
|
||||
pcb.sched_info().set_normal_prio(fork_prio);
|
||||
pcb.sched_info()
|
||||
.pi_lock_irqsave()
|
||||
.set_sched_reset_on_fork(false);
|
||||
|
||||
if PrioUtil::dl_prio(fork_prio) {
|
||||
return Err(SystemError::EAGAIN_OR_EWOULDBLOCK);
|
||||
} else if PrioUtil::rt_prio(fork_prio) {
|
||||
// 子进程继承父进程的调度策略(FIFO/RR),而非统一设为 RT
|
||||
pcb.sched_info().set_policy(current.sched_info().policy());
|
||||
pcb.sched_info().set_policy(parent_policy);
|
||||
} else {
|
||||
pcb.sched_info().set_policy(SchedPolicy::CFS);
|
||||
}
|
||||
@@ -1322,7 +1339,7 @@ pub fn sched_fork(pcb: &Arc<ProcessControlBlock>) -> Result<(), SystemError> {
|
||||
.sched_entity()
|
||||
.force_mut()
|
||||
.load
|
||||
.set_load_weight_from_prio(current.sched_info().static_prio());
|
||||
.set_load_weight_from_prio(fork_static_prio);
|
||||
|
||||
pcb.sched_info()
|
||||
.sched_entity()
|
||||
|
||||
@@ -30,4 +30,13 @@ impl PrioUtil {
|
||||
pub fn rt_prio(prio: i32) -> bool {
|
||||
return prio < MAX_RT_PRIO;
|
||||
}
|
||||
|
||||
/// Convert the internal RT priority (0..=98, high to low) to the legacy
|
||||
/// Linux userspace value (99..=1).
|
||||
#[inline]
|
||||
pub fn internal_rt_prio_to_user(prio: i32) -> Option<i32> {
|
||||
(0..MAX_RT_PRIO - 1)
|
||||
.contains(&prio)
|
||||
.then_some((MAX_RT_PRIO - 1) - prio)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,5 +3,7 @@ mod sys_pause;
|
||||
|
||||
mod sys_sched_getparam;
|
||||
mod sys_sched_getscheduler;
|
||||
mod sys_sched_setscheduler;
|
||||
mod sys_sched_yield;
|
||||
mod types;
|
||||
pub(crate) mod util;
|
||||
|
||||
@@ -1,162 +1,77 @@
|
||||
use alloc::{string::ToString, vec::Vec};
|
||||
|
||||
use system_error::SystemError;
|
||||
|
||||
use crate::arch::interrupt::TrapFrame;
|
||||
use crate::arch::syscall::nr::SYS_SCHED_GETPARAM;
|
||||
use crate::process::ProcessManager;
|
||||
use crate::process::RawPid;
|
||||
use crate::sched::prio::PrioUtil;
|
||||
use crate::sched::prio::MAX_RT_PRIO;
|
||||
use crate::sched::SchedPolicy;
|
||||
use crate::syscall::table::FormattedSyscallParam;
|
||||
use crate::syscall::table::Syscall;
|
||||
use crate::syscall::user_access::UserBufferWriter;
|
||||
use alloc::string::ToString;
|
||||
use alloc::vec::Vec;
|
||||
use crate::{
|
||||
arch::{interrupt::TrapFrame, syscall::nr::SYS_SCHED_GETPARAM},
|
||||
sched::{prio::PrioUtil, SchedPolicy},
|
||||
syscall::{
|
||||
table::{FormattedSyscallParam, Syscall},
|
||||
user_access::UserBufferWriter,
|
||||
},
|
||||
};
|
||||
|
||||
/// Linux sched_param 结构体
|
||||
/// 与 musl-libc 中的定义保持一致
|
||||
#[repr(C)]
|
||||
#[derive(Clone, Copy)]
|
||||
struct PosixSchedParam {
|
||||
sched_priority: i32,
|
||||
__reserved1: i32,
|
||||
__reserved2: [i64; 4],
|
||||
__reserved3: i32,
|
||||
}
|
||||
use super::{types::KernelSchedParam, util::find_sched_target};
|
||||
|
||||
/// System call handler for the `sched_getparam` syscall
|
||||
///
|
||||
/// This handler implements the `Syscall` trait to provide functionality for getting
|
||||
/// scheduling parameters of a process.
|
||||
struct SysSchedGetparam;
|
||||
|
||||
impl Syscall for SysSchedGetparam {
|
||||
/// Returns the number of arguments expected by the `sched_getparam` syscall
|
||||
fn num_args(&self) -> usize {
|
||||
2
|
||||
}
|
||||
|
||||
/// Handles the `sched_getparam` system call
|
||||
///
|
||||
/// Gets the scheduling parameters of the specified process.
|
||||
/// If pid is 0, gets the scheduling parameters of the current process.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `args` - Array containing:
|
||||
/// - args[0]: Process ID (pid_t), 0 for current process
|
||||
/// - args[1]: Pointer to sched_param structure (*mut SchedParam)
|
||||
/// * `frame` - Trap frame, used to determine if call originates from user space
|
||||
///
|
||||
/// # Returns
|
||||
/// * `Ok(0)`: Success
|
||||
/// * `Err(SystemError::ESRCH)`: Process not found
|
||||
/// * `Err(SystemError::EFAULT)`: Invalid user space pointer
|
||||
/// * `Err(SystemError::EPERM)`: Permission denied
|
||||
fn handle(&self, args: &[usize], frame: &mut TrapFrame) -> Result<usize, SystemError> {
|
||||
let pid = Self::pid(args);
|
||||
let param = Self::param(args);
|
||||
let pid = args[0] as i32;
|
||||
let param = args[1] as *mut KernelSchedParam;
|
||||
|
||||
// 验证用户空间指针
|
||||
if param.is_null() {
|
||||
return Err(SystemError::EFAULT);
|
||||
if param.is_null() || pid < 0 {
|
||||
return Err(SystemError::EINVAL);
|
||||
}
|
||||
|
||||
// 获取目标进程
|
||||
let target_pcb = if pid == 0 {
|
||||
// pid 为 0 表示当前进程
|
||||
ProcessManager::current_pcb()
|
||||
} else {
|
||||
// 查找指定进程
|
||||
let raw_pid = RawPid::from(pid);
|
||||
ProcessManager::find_task_by_vpid(raw_pid).ok_or(SystemError::ESRCH)?
|
||||
let target = find_sched_target(pid)?;
|
||||
let (policy, normal_prio) = {
|
||||
let _pi_guard = target.sched_info().pi_lock_irqsave();
|
||||
(
|
||||
target.sched_info().policy(),
|
||||
target.sched_info().normal_prio(),
|
||||
)
|
||||
};
|
||||
|
||||
// 权限检查:只有进程自己或具有 CAP_SYS_NICE 权限的进程可以查询
|
||||
let current_pcb = ProcessManager::current_pcb();
|
||||
if !super::util::has_sched_permission(¤t_pcb, &target_pcb) {
|
||||
return Err(SystemError::EPERM);
|
||||
}
|
||||
|
||||
// 获取调度策略和优先级
|
||||
let policy = target_pcb.sched_info().policy();
|
||||
let prio = target_pcb.sched_info().prio();
|
||||
|
||||
// 根据调度策略计算 sched_priority
|
||||
// Linux 行为:
|
||||
// - 对于普通进程(SCHED_OTHER/CFS/IDLE),sched_priority 始终为 0
|
||||
// - 对于实时进程(SCHED_FIFO/SCHED_RR),sched_priority 范围是 1-99
|
||||
// 其中 1 是最低优先级,99 是最高优先级
|
||||
// - 内部优先级 prio 范围是 0-99(对于实时进程),其中 0 是最高优先级
|
||||
// - 转换公式:sched_priority = MAX_RT_PRIO (100) - prio
|
||||
// 但需要限制在 1-99 范围内(因为 prio=0 时 sched_priority=100,需要限制为 99)
|
||||
let sched_priority = match policy {
|
||||
SchedPolicy::CFS | SchedPolicy::IDLE => {
|
||||
// 普通进程的 sched_priority 始终为 0
|
||||
0
|
||||
}
|
||||
SchedPolicy::RT | SchedPolicy::FIFO => {
|
||||
// 检查是否为有效的实时优先级
|
||||
// 实时进程的 prio 应该在 0-99 范围内(prio < MAX_RT_PRIO)
|
||||
if !PrioUtil::rt_prio(prio) {
|
||||
// 如果优先级不在实时范围内,返回 0(表示普通进程)
|
||||
// 这通常不应该发生,但为了健壮性,我们处理这种情况
|
||||
0
|
||||
} else {
|
||||
// 实时优先级转换:sched_priority = MAX_RT_PRIO - prio
|
||||
// prio = 0(最高)→ sched_priority = 100,限制为 99
|
||||
// prio = 99(最低)→ sched_priority = 1
|
||||
let rt_prio = MAX_RT_PRIO - prio;
|
||||
// 确保结果在 1-99 范围内
|
||||
rt_prio.clamp(1, 99)
|
||||
}
|
||||
}
|
||||
SchedPolicy::CFS | SchedPolicy::IDLE => 0,
|
||||
SchedPolicy::RT | SchedPolicy::FIFO => PrioUtil::internal_rt_prio_to_user(normal_prio)
|
||||
.ok_or_else(|| {
|
||||
log::error!(
|
||||
"task {} has invalid internal RT priority {}",
|
||||
target.raw_pid().data(),
|
||||
normal_prio
|
||||
);
|
||||
debug_assert!(
|
||||
false,
|
||||
"invalid internal RT priority {normal_prio} for task {}",
|
||||
target.raw_pid().data()
|
||||
);
|
||||
SystemError::EIO
|
||||
})?,
|
||||
};
|
||||
|
||||
// 构造 sched_param 结构
|
||||
let sched_param = PosixSchedParam {
|
||||
sched_priority,
|
||||
__reserved1: 0,
|
||||
__reserved2: [0; 4],
|
||||
__reserved3: 0,
|
||||
};
|
||||
|
||||
// 将结果写入用户空间
|
||||
let sched_param = KernelSchedParam { sched_priority };
|
||||
let mut writer = UserBufferWriter::new(
|
||||
param,
|
||||
core::mem::size_of::<PosixSchedParam>(),
|
||||
core::mem::size_of::<KernelSchedParam>(),
|
||||
frame.is_from_user(),
|
||||
)?;
|
||||
|
||||
writer.buffer_protected(0)?.write_one(0, &sched_param)?;
|
||||
|
||||
Ok(0)
|
||||
}
|
||||
|
||||
/// Formats the syscall parameters for display/debug purposes
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `args` - The raw syscall arguments
|
||||
///
|
||||
/// # Returns
|
||||
/// Vector of formatted parameters with descriptive names
|
||||
fn entry_format(&self, args: &[usize]) -> Vec<FormattedSyscallParam> {
|
||||
vec![
|
||||
FormattedSyscallParam::new("pid", Self::pid(args).to_string()),
|
||||
FormattedSyscallParam::new("param", format!("{:#x}", Self::param(args) as usize)),
|
||||
FormattedSyscallParam::new("pid", (args[0] as i32).to_string()),
|
||||
FormattedSyscallParam::new("param", format!("{:#x}", args[1])),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
impl SysSchedGetparam {
|
||||
/// Extracts the process ID from syscall arguments
|
||||
fn pid(args: &[usize]) -> usize {
|
||||
args[0]
|
||||
}
|
||||
|
||||
/// Extracts the sched_param pointer from syscall arguments
|
||||
fn param(args: &[usize]) -> *mut PosixSchedParam {
|
||||
args[1] as *mut PosixSchedParam
|
||||
}
|
||||
}
|
||||
|
||||
syscall_table_macros::declare_syscall!(SYS_SCHED_GETPARAM, SysSchedGetparam);
|
||||
|
||||
@@ -1,122 +1,45 @@
|
||||
use alloc::{string::ToString, vec::Vec};
|
||||
|
||||
use system_error::SystemError;
|
||||
|
||||
use crate::arch::interrupt::TrapFrame;
|
||||
use crate::arch::syscall::nr::SYS_SCHED_GETSCHEDULER;
|
||||
use crate::process::ProcessManager;
|
||||
use crate::process::RawPid;
|
||||
use crate::sched::SchedPolicy;
|
||||
use crate::syscall::table::FormattedSyscallParam;
|
||||
use crate::syscall::table::Syscall;
|
||||
use alloc::string::ToString;
|
||||
use alloc::vec::Vec;
|
||||
use crate::{
|
||||
arch::{interrupt::TrapFrame, syscall::nr::SYS_SCHED_GETSCHEDULER},
|
||||
syscall::table::{FormattedSyscallParam, Syscall},
|
||||
};
|
||||
|
||||
/// Linux 调度策略枚举
|
||||
/// 与 musl-libc 和 Linux 内核保持一致
|
||||
#[repr(i32)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
enum PosixLinuxSchedPolicy {
|
||||
/// 普通调度策略(对应 CFS)
|
||||
Other = 0,
|
||||
/// 先进先出实时调度
|
||||
Fifo = 1,
|
||||
/// 轮转实时调度
|
||||
Rr = 2,
|
||||
/// 批处理调度(DragonOS 暂不支持)
|
||||
#[allow(dead_code)]
|
||||
Batch = 3,
|
||||
/// IDLE 调度
|
||||
Idle = 5,
|
||||
/// 截止时间调度(DragonOS 暂不支持)
|
||||
#[allow(dead_code)]
|
||||
Deadline = 6,
|
||||
}
|
||||
use super::{
|
||||
types::{policy_to_linux, SCHED_RESET_ON_FORK},
|
||||
util::find_sched_target,
|
||||
};
|
||||
|
||||
/// System call handler for the `sched_getscheduler` syscall
|
||||
///
|
||||
/// This handler implements the `Syscall` trait to provide functionality for getting
|
||||
/// the scheduling policy of a process.
|
||||
struct SysSchedGetscheduler;
|
||||
|
||||
impl Syscall for SysSchedGetscheduler {
|
||||
/// Returns the number of arguments expected by the `sched_getscheduler` syscall
|
||||
fn num_args(&self) -> usize {
|
||||
1
|
||||
}
|
||||
|
||||
/// Handles the `sched_getscheduler` system call
|
||||
///
|
||||
/// Gets the scheduling policy of the specified process.
|
||||
/// If pid is 0, gets the scheduling policy of the current process.
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `args` - Array containing:
|
||||
/// - args[0]: Process ID (pid_t), 0 for current process
|
||||
/// * `_frame` - Trap frame (unused in this implementation)
|
||||
///
|
||||
/// # Returns
|
||||
/// * `Ok(policy)`: Success, returns the scheduling policy value
|
||||
/// * `Err(SystemError::ESRCH)`: Process not found
|
||||
/// * `Err(SystemError::EPERM)`: Permission denied
|
||||
fn handle(&self, args: &[usize], _frame: &mut TrapFrame) -> Result<usize, SystemError> {
|
||||
let pid = Self::pid(args);
|
||||
|
||||
// 获取目标进程
|
||||
let target_pcb = if pid == 0 {
|
||||
// pid 为 0 表示当前进程
|
||||
ProcessManager::current_pcb()
|
||||
} else {
|
||||
// 查找指定进程
|
||||
let raw_pid = RawPid::from(pid);
|
||||
ProcessManager::find_task_by_vpid(raw_pid).ok_or(SystemError::ESRCH)?
|
||||
let pid = args[0] as i32;
|
||||
let target = find_sched_target(pid)?;
|
||||
let (policy, reset_on_fork) = {
|
||||
let pi_guard = target.sched_info().pi_lock_irqsave();
|
||||
(target.sched_info().policy(), pi_guard.sched_reset_on_fork())
|
||||
};
|
||||
|
||||
// 权限检查:只有进程自己或具有 CAP_SYS_NICE 权限的进程可以查询
|
||||
let current_pcb = ProcessManager::current_pcb();
|
||||
if !super::util::has_sched_permission(¤t_pcb, &target_pcb) {
|
||||
return Err(SystemError::EPERM);
|
||||
let mut linux_policy = policy_to_linux(policy);
|
||||
if reset_on_fork {
|
||||
linux_policy |= SCHED_RESET_ON_FORK;
|
||||
}
|
||||
|
||||
// 获取调度策略
|
||||
let policy = target_pcb.sched_info().policy();
|
||||
|
||||
// 将 DragonOS 的 SchedPolicy 映射到 Linux 的调度策略值
|
||||
// Linux 调度策略值:
|
||||
// - SCHED_OTHER = 0 (对应 CFS)
|
||||
// - SCHED_FIFO = 1
|
||||
// - SCHED_RR = 2 (实时轮转调度)
|
||||
// - SCHED_BATCH = 3 (DragonOS 暂不支持)
|
||||
// - SCHED_IDLE = 5
|
||||
// - SCHED_DEADLINE = 6 (DragonOS 暂不支持)
|
||||
let linux_policy = match policy {
|
||||
SchedPolicy::CFS => PosixLinuxSchedPolicy::Other,
|
||||
SchedPolicy::FIFO => PosixLinuxSchedPolicy::Fifo,
|
||||
SchedPolicy::RT => PosixLinuxSchedPolicy::Rr, // RT 策略映射到 SCHED_RR
|
||||
SchedPolicy::IDLE => PosixLinuxSchedPolicy::Idle,
|
||||
};
|
||||
|
||||
Ok(linux_policy as i32 as usize)
|
||||
Ok(linux_policy as usize)
|
||||
}
|
||||
|
||||
/// Formats the syscall parameters for display/debug purposes
|
||||
///
|
||||
/// # Arguments
|
||||
/// * `args` - The raw syscall arguments
|
||||
///
|
||||
/// # Returns
|
||||
/// Vector of formatted parameters with descriptive names
|
||||
fn entry_format(&self, args: &[usize]) -> Vec<FormattedSyscallParam> {
|
||||
vec![FormattedSyscallParam::new(
|
||||
"pid",
|
||||
Self::pid(args).to_string(),
|
||||
(args[0] as i32).to_string(),
|
||||
)]
|
||||
}
|
||||
}
|
||||
|
||||
impl SysSchedGetscheduler {
|
||||
/// Extracts the process ID from syscall arguments
|
||||
fn pid(args: &[usize]) -> usize {
|
||||
args[0]
|
||||
}
|
||||
}
|
||||
|
||||
syscall_table_macros::declare_syscall!(SYS_SCHED_GETSCHEDULER, SysSchedGetscheduler);
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
use alloc::{string::ToString, vec::Vec};
|
||||
|
||||
use system_error::SystemError;
|
||||
|
||||
use crate::{
|
||||
arch::{interrupt::TrapFrame, syscall::nr::SYS_SCHED_SETSCHEDULER},
|
||||
process::{
|
||||
cred::{capable, CAPFlags},
|
||||
ProcessManager,
|
||||
},
|
||||
sched::SchedPolicy,
|
||||
syscall::{
|
||||
table::{FormattedSyscallParam, Syscall},
|
||||
user_access::UserBufferReader,
|
||||
},
|
||||
};
|
||||
|
||||
use super::{
|
||||
types::{
|
||||
KernelSchedParam, SCHED_BATCH, SCHED_DEADLINE, SCHED_FIFO, SCHED_IDLE, SCHED_OTHER,
|
||||
SCHED_RESET_ON_FORK, SCHED_RR,
|
||||
},
|
||||
util::{find_sched_target, same_sched_owner},
|
||||
};
|
||||
|
||||
struct SysSchedSetscheduler;
|
||||
|
||||
impl SysSchedSetscheduler {
|
||||
fn validate_policy(policy: i32, priority: i32) -> Result<bool, SystemError> {
|
||||
let base_policy = policy & !SCHED_RESET_ON_FORK;
|
||||
match base_policy {
|
||||
SCHED_OTHER => {
|
||||
if priority != 0 {
|
||||
return Err(SystemError::EINVAL);
|
||||
}
|
||||
Ok(true)
|
||||
}
|
||||
SCHED_FIFO | SCHED_RR => {
|
||||
if !(1..=99).contains(&priority) {
|
||||
return Err(SystemError::EINVAL);
|
||||
}
|
||||
Ok(false)
|
||||
}
|
||||
SCHED_BATCH | SCHED_IDLE => {
|
||||
if priority != 0 {
|
||||
return Err(SystemError::EINVAL);
|
||||
}
|
||||
Ok(false)
|
||||
}
|
||||
// The legacy ABI has no runtime/deadline/period fields, so it
|
||||
// cannot express a valid SCHED_DEADLINE request.
|
||||
SCHED_DEADLINE => Err(SystemError::EINVAL),
|
||||
_ => Err(SystemError::EINVAL),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Syscall for SysSchedSetscheduler {
|
||||
fn num_args(&self) -> usize {
|
||||
3
|
||||
}
|
||||
|
||||
fn handle(&self, args: &[usize], frame: &mut TrapFrame) -> Result<usize, SystemError> {
|
||||
let pid = args[0] as i32;
|
||||
let policy = args[1] as i32;
|
||||
let param = args[2] as *const KernelSchedParam;
|
||||
|
||||
// Linux rejects a negative policy before inspecting pid or userspace.
|
||||
if policy < 0 {
|
||||
return Err(SystemError::EINVAL);
|
||||
}
|
||||
if pid < 0 || param.is_null() {
|
||||
return Err(SystemError::EINVAL);
|
||||
}
|
||||
|
||||
// The legacy kernel ABI copies exactly one i32, before target lookup
|
||||
// and policy/priority validation.
|
||||
let reader = UserBufferReader::new(
|
||||
param,
|
||||
core::mem::size_of::<KernelSchedParam>(),
|
||||
frame.is_from_user(),
|
||||
)?;
|
||||
let sched_param: KernelSchedParam = reader.buffer_protected(0)?.read_one(0)?;
|
||||
|
||||
let target = find_sched_target(pid)?;
|
||||
let supported_fast_path = Self::validate_policy(policy, sched_param.sched_priority)?;
|
||||
if !supported_fast_path {
|
||||
return Err(SystemError::EOPNOTSUPP_OR_ENOTSUP);
|
||||
}
|
||||
|
||||
let reset_on_fork = policy & SCHED_RESET_ON_FORK != 0;
|
||||
let current = ProcessManager::current_pcb();
|
||||
let same_owner = same_sched_owner(¤t.cred(), &target.cred());
|
||||
let mut privileged = false;
|
||||
|
||||
loop {
|
||||
let mut pi_guard = target.sched_info().pi_lock_irqsave();
|
||||
if target.sched_info().policy() != SchedPolicy::CFS {
|
||||
return Err(SystemError::EOPNOTSUPP_OR_ENOTSUP);
|
||||
}
|
||||
|
||||
let clearing_protected_flag = pi_guard.sched_reset_on_fork() && !reset_on_fork;
|
||||
if (same_owner && !clearing_protected_flag) || privileged {
|
||||
pi_guard.set_sched_reset_on_fork(reset_on_fork);
|
||||
return Ok(0);
|
||||
}
|
||||
|
||||
// Capability lookup may take user-namespace locks. Never perform
|
||||
// it while holding a scheduler spin lock.
|
||||
drop(pi_guard);
|
||||
if !capable(CAPFlags::CAP_SYS_NICE) {
|
||||
return Err(SystemError::EPERM);
|
||||
}
|
||||
privileged = true;
|
||||
}
|
||||
}
|
||||
|
||||
fn entry_format(&self, args: &[usize]) -> Vec<FormattedSyscallParam> {
|
||||
vec![
|
||||
FormattedSyscallParam::new("pid", (args[0] as i32).to_string()),
|
||||
FormattedSyscallParam::new("policy", format!("{:#x}", args[1] as i32)),
|
||||
FormattedSyscallParam::new("param", format!("{:#x}", args[2])),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
syscall_table_macros::declare_syscall!(SYS_SCHED_SETSCHEDULER, SysSchedSetscheduler);
|
||||
@@ -0,0 +1,31 @@
|
||||
use crate::sched::SchedPolicy;
|
||||
|
||||
/// Legacy Linux scheduler parameter ABI.
|
||||
///
|
||||
/// The kernel ABI contains exactly one `int`, even when libc exposes a larger
|
||||
/// source-level structure with reserved fields.
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Clone, Copy, Default)]
|
||||
pub(super) struct KernelSchedParam {
|
||||
pub sched_priority: i32,
|
||||
}
|
||||
|
||||
const _: () = assert!(core::mem::size_of::<KernelSchedParam>() == 4);
|
||||
|
||||
pub(super) const SCHED_OTHER: i32 = 0;
|
||||
pub(super) const SCHED_FIFO: i32 = 1;
|
||||
pub(super) const SCHED_RR: i32 = 2;
|
||||
pub(super) const SCHED_BATCH: i32 = 3;
|
||||
pub(super) const SCHED_IDLE: i32 = 5;
|
||||
pub(super) const SCHED_DEADLINE: i32 = 6;
|
||||
pub(super) const SCHED_RESET_ON_FORK: i32 = 0x4000_0000;
|
||||
|
||||
#[inline]
|
||||
pub(super) fn policy_to_linux(policy: SchedPolicy) -> i32 {
|
||||
match policy {
|
||||
SchedPolicy::CFS => SCHED_OTHER,
|
||||
SchedPolicy::FIFO => SCHED_FIFO,
|
||||
SchedPolicy::RT => SCHED_RR,
|
||||
SchedPolicy::IDLE => SCHED_IDLE,
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,31 @@
|
||||
/// 调度系统调用相关的工具函数
|
||||
use crate::process::cred::CAPFlags;
|
||||
use crate::process::ProcessControlBlock;
|
||||
use alloc::sync::Arc;
|
||||
|
||||
use system_error::SystemError;
|
||||
|
||||
use crate::process::cred::{CAPFlags, Cred};
|
||||
use crate::process::{ProcessControlBlock, ProcessManager, RawPid};
|
||||
|
||||
/// Resolve a legacy scheduler syscall PID as a thread ID in the caller's
|
||||
/// active PID namespace.
|
||||
pub(super) fn find_sched_target(pid: i32) -> Result<Arc<ProcessControlBlock>, SystemError> {
|
||||
if pid < 0 {
|
||||
return Err(SystemError::EINVAL);
|
||||
}
|
||||
|
||||
if pid == 0 {
|
||||
Ok(ProcessManager::current_pcb())
|
||||
} else {
|
||||
ProcessManager::find_task_by_vpid(RawPid::from(pid as usize)).ok_or(SystemError::ESRCH)
|
||||
}
|
||||
}
|
||||
|
||||
/// Linux sched_setscheduler owner rule: the caller's effective UID must match
|
||||
/// either the target's real or effective UID.
|
||||
#[inline]
|
||||
pub(super) fn same_sched_owner(current: &Cred, target: &Cred) -> bool {
|
||||
current.euid == target.euid || current.euid == target.uid
|
||||
}
|
||||
|
||||
/// 检查当前进程是否有权限查询目标进程的调度信息
|
||||
///
|
||||
|
||||
@@ -0,0 +1,366 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <atomic>
|
||||
#include <cerrno>
|
||||
#include <climits>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <pthread.h>
|
||||
#include <poll.h>
|
||||
#include <sched.h>
|
||||
#include <signal.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/syscall.h>
|
||||
#include <sys/utsname.h>
|
||||
#include <sys/wait.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#ifndef SCHED_RESET_ON_FORK
|
||||
#define SCHED_RESET_ON_FORK 0x40000000
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
|
||||
struct alignas(4) RawSchedParam {
|
||||
int32_t sched_priority;
|
||||
};
|
||||
|
||||
struct RawSchedParamWithCanary {
|
||||
RawSchedParam param;
|
||||
uint8_t canary[60];
|
||||
};
|
||||
|
||||
static_assert(sizeof(RawSchedParam) == 4);
|
||||
static_assert(offsetof(RawSchedParamWithCanary, canary) == 4);
|
||||
|
||||
long RawGetParam(pid_t tid, RawSchedParam* param) {
|
||||
return syscall(SYS_sched_getparam, tid, param);
|
||||
}
|
||||
|
||||
long RawSetScheduler(pid_t tid, int policy, const RawSchedParam* param) {
|
||||
return syscall(SYS_sched_setscheduler, tid, policy, param);
|
||||
}
|
||||
|
||||
bool IsDragonOS() {
|
||||
struct utsname info {};
|
||||
return uname(&info) == 0 && strstr(info.release, "dragonos") != nullptr;
|
||||
}
|
||||
|
||||
int WaitForChild(pid_t child) {
|
||||
int status = 0;
|
||||
for (int attempt = 0; attempt < 500; ++attempt) {
|
||||
pid_t waited = waitpid(child, &status, WNOHANG);
|
||||
if (waited == child) {
|
||||
if (!WIFEXITED(status)) return 255;
|
||||
return WEXITSTATUS(status);
|
||||
}
|
||||
if (waited < 0 && errno != EINTR) return 254;
|
||||
usleep(10 * 1000);
|
||||
}
|
||||
|
||||
kill(child, SIGKILL);
|
||||
while (waitpid(child, nullptr, 0) < 0 && errno == EINTR) {
|
||||
}
|
||||
return 253;
|
||||
}
|
||||
|
||||
class ChildGuard {
|
||||
public:
|
||||
explicit ChildGuard(pid_t child) : child_(child) {}
|
||||
~ChildGuard() {
|
||||
if (child_ <= 0) return;
|
||||
kill(child_, SIGKILL);
|
||||
while (waitpid(child_, nullptr, 0) < 0 && errno == EINTR) {
|
||||
}
|
||||
}
|
||||
void Release() { child_ = -1; }
|
||||
|
||||
private:
|
||||
pid_t child_;
|
||||
};
|
||||
|
||||
void WriteByteOrExit(int fd, char value) {
|
||||
if (write(fd, &value, 1) != 1) _exit(120);
|
||||
}
|
||||
|
||||
bool ReadByte(int fd) {
|
||||
char value = 0;
|
||||
return read(fd, &value, 1) == 1;
|
||||
}
|
||||
|
||||
bool ReadByteWithTimeout(int fd, int timeout_ms) {
|
||||
struct pollfd poll_fd {fd, POLLIN, 0};
|
||||
int result;
|
||||
do {
|
||||
result = poll(&poll_fd, 1, timeout_ms);
|
||||
} while (result < 0 && errno == EINTR);
|
||||
return result == 1 && (poll_fd.revents & POLLIN) != 0 && ReadByte(fd);
|
||||
}
|
||||
|
||||
TEST(SchedParamAbi, GetParamWritesExactlyFourBytes) {
|
||||
RawSchedParamWithCanary value {};
|
||||
value.param.sched_priority = -1;
|
||||
memset(value.canary, 0xa5, sizeof(value.canary));
|
||||
|
||||
ASSERT_EQ(0, RawGetParam(0, &value.param)) << strerror(errno);
|
||||
EXPECT_EQ(0, value.param.sched_priority);
|
||||
for (uint8_t byte : value.canary) EXPECT_EQ(0xa5, byte);
|
||||
}
|
||||
|
||||
TEST(SchedParamAbi, SetParamReadsExactlyFourBytes) {
|
||||
const long page_size = sysconf(_SC_PAGESIZE);
|
||||
ASSERT_GT(page_size, 0);
|
||||
void* mapping = mmap(nullptr, static_cast<size_t>(page_size) * 2, PROT_READ | PROT_WRITE,
|
||||
MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
|
||||
ASSERT_NE(MAP_FAILED, mapping) << strerror(errno);
|
||||
ASSERT_EQ(0, mprotect(static_cast<char*>(mapping) + page_size, page_size, PROT_NONE));
|
||||
|
||||
auto* param = reinterpret_cast<RawSchedParam*>(static_cast<char*>(mapping) + page_size - 4);
|
||||
param->sched_priority = 0;
|
||||
|
||||
pid_t child = fork();
|
||||
ASSERT_GE(child, 0) << strerror(errno);
|
||||
if (child == 0) {
|
||||
_exit(RawSetScheduler(0, SCHED_OTHER | SCHED_RESET_ON_FORK, param) == 0 ? 0 : errno);
|
||||
}
|
||||
EXPECT_EQ(0, WaitForChild(child));
|
||||
EXPECT_EQ(0, munmap(mapping, static_cast<size_t>(page_size) * 2));
|
||||
}
|
||||
|
||||
TEST(SchedParamAbi, LibcWrapperInterop) {
|
||||
pid_t child = fork();
|
||||
ASSERT_GE(child, 0) << strerror(errno);
|
||||
if (child == 0) {
|
||||
struct sched_param param {};
|
||||
param.sched_priority = 0;
|
||||
if (sched_setscheduler(0, SCHED_OTHER | SCHED_RESET_ON_FORK, ¶m) != 0) _exit(10);
|
||||
struct sched_param out {};
|
||||
memset(&out, 0xa5, sizeof(out));
|
||||
if (sched_getparam(0, &out) != 0 || out.sched_priority != 0) _exit(11);
|
||||
if (sched_getscheduler(0) != (SCHED_OTHER | SCHED_RESET_ON_FORK)) _exit(12);
|
||||
_exit(0);
|
||||
}
|
||||
EXPECT_EQ(0, WaitForChild(child));
|
||||
}
|
||||
|
||||
TEST(SchedGetParam, CurrentAndErrorsMatchLinux) {
|
||||
RawSchedParam param {-1};
|
||||
EXPECT_EQ(0, RawGetParam(0, ¶m));
|
||||
EXPECT_EQ(0, param.sched_priority);
|
||||
|
||||
errno = 0;
|
||||
EXPECT_EQ(-1, RawGetParam(-1, ¶m));
|
||||
EXPECT_EQ(EINVAL, errno);
|
||||
errno = 0;
|
||||
EXPECT_EQ(-1, RawGetParam(0, nullptr));
|
||||
EXPECT_EQ(EINVAL, errno);
|
||||
errno = 0;
|
||||
EXPECT_EQ(-1, RawGetParam(INT_MAX, ¶m));
|
||||
EXPECT_EQ(ESRCH, errno);
|
||||
errno = 0;
|
||||
EXPECT_EQ(-1, RawGetParam(0, reinterpret_cast<RawSchedParam*>(1)));
|
||||
EXPECT_EQ(EFAULT, errno);
|
||||
}
|
||||
|
||||
TEST(SchedGetScheduler, CurrentAndErrorsMatchLinux) {
|
||||
EXPECT_EQ(SCHED_OTHER, sched_getscheduler(0));
|
||||
errno = 0;
|
||||
EXPECT_EQ(-1, sched_getscheduler(-1));
|
||||
EXPECT_EQ(EINVAL, errno);
|
||||
errno = 0;
|
||||
EXPECT_EQ(-1, sched_getscheduler(INT_MAX));
|
||||
EXPECT_EQ(ESRCH, errno);
|
||||
}
|
||||
|
||||
TEST(SchedSetScheduler, ErrorOrderingAndPolicyMatrix) {
|
||||
RawSchedParam zero {0};
|
||||
RawSchedParam one {1};
|
||||
RawSchedParam negative {-1};
|
||||
RawSchedParam maximum {INT_MAX};
|
||||
const auto* bad = reinterpret_cast<const RawSchedParam*>(1);
|
||||
|
||||
errno = 0;
|
||||
EXPECT_EQ(-1, RawSetScheduler(0, -1, bad));
|
||||
EXPECT_EQ(EINVAL, errno);
|
||||
errno = 0;
|
||||
EXPECT_EQ(-1, RawSetScheduler(-1, SCHED_OTHER, bad));
|
||||
EXPECT_EQ(EINVAL, errno);
|
||||
errno = 0;
|
||||
EXPECT_EQ(-1, RawSetScheduler(INT_MAX, SCHED_OTHER, bad));
|
||||
EXPECT_EQ(EFAULT, errno);
|
||||
errno = 0;
|
||||
EXPECT_EQ(-1, RawSetScheduler(INT_MAX, 0x20000000, &zero));
|
||||
EXPECT_EQ(ESRCH, errno);
|
||||
|
||||
for (const RawSchedParam* invalid : {&negative, &one, &maximum}) {
|
||||
errno = 0;
|
||||
EXPECT_EQ(-1, RawSetScheduler(0, SCHED_OTHER, invalid));
|
||||
EXPECT_EQ(EINVAL, errno);
|
||||
}
|
||||
errno = 0;
|
||||
EXPECT_EQ(-1, RawSetScheduler(0, 0x20000000, &zero));
|
||||
EXPECT_EQ(EINVAL, errno);
|
||||
errno = 0;
|
||||
EXPECT_EQ(-1, RawSetScheduler(0, SCHED_FIFO, &zero));
|
||||
EXPECT_EQ(EINVAL, errno);
|
||||
|
||||
// On Linux a valid FIFO request may really enter RT scheduling. Only the
|
||||
// DragonOS guest exercises its documented staging boundary.
|
||||
if (IsDragonOS()) {
|
||||
errno = 0;
|
||||
EXPECT_EQ(-1, RawSetScheduler(0, SCHED_FIFO, &one));
|
||||
EXPECT_EQ(EOPNOTSUPP, errno);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(SchedSetScheduler, SelfResetFlagRoundTripAndFork) {
|
||||
pid_t probe = fork();
|
||||
ASSERT_GE(probe, 0) << strerror(errno);
|
||||
if (probe == 0) {
|
||||
RawSchedParam zero {0};
|
||||
if (RawSetScheduler(0, SCHED_OTHER | SCHED_RESET_ON_FORK, &zero) != 0) _exit(10);
|
||||
if (sched_getscheduler(0) != (SCHED_OTHER | SCHED_RESET_ON_FORK)) _exit(11);
|
||||
pid_t child = fork();
|
||||
if (child < 0) _exit(12);
|
||||
if (child == 0) _exit(sched_getscheduler(0) == SCHED_OTHER ? 0 : 13);
|
||||
if (WaitForChild(child) != 0) _exit(14);
|
||||
if (sched_getscheduler(0) != (SCHED_OTHER | SCHED_RESET_ON_FORK)) _exit(15);
|
||||
_exit(0);
|
||||
}
|
||||
EXPECT_EQ(0, WaitForChild(probe));
|
||||
}
|
||||
|
||||
struct WorkerState {
|
||||
std::atomic<int> tid {0};
|
||||
std::atomic<int> proceed {0};
|
||||
int result = -1;
|
||||
};
|
||||
|
||||
void* NestedWorker(void* arg) {
|
||||
auto* result = static_cast<int*>(arg);
|
||||
*result = sched_getscheduler(0) == SCHED_OTHER ? 0 : 1;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void* FlagWorker(void* arg) {
|
||||
auto* state = static_cast<WorkerState*>(arg);
|
||||
state->tid.store(static_cast<int>(syscall(SYS_gettid)), std::memory_order_release);
|
||||
while (state->proceed.load(std::memory_order_acquire) == 0) sched_yield();
|
||||
if (sched_getscheduler(0) != (SCHED_OTHER | SCHED_RESET_ON_FORK)) {
|
||||
state->result = 1;
|
||||
return nullptr;
|
||||
}
|
||||
int nested_result = -1;
|
||||
pthread_t nested;
|
||||
if (pthread_create(&nested, nullptr, NestedWorker, &nested_result) != 0) {
|
||||
state->result = 2;
|
||||
return nullptr;
|
||||
}
|
||||
if (pthread_join(nested, nullptr) != 0 || nested_result != 0) {
|
||||
state->result = 3;
|
||||
return nullptr;
|
||||
}
|
||||
state->result = 0;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
TEST(SchedSetScheduler, OtherTidAndNestedCloneReset) {
|
||||
WorkerState state;
|
||||
pthread_t worker;
|
||||
ASSERT_EQ(0, pthread_create(&worker, nullptr, FlagWorker, &state));
|
||||
for (int attempt = 0;
|
||||
attempt < 500 && state.tid.load(std::memory_order_acquire) == 0; ++attempt) {
|
||||
usleep(10 * 1000);
|
||||
}
|
||||
if (state.tid.load(std::memory_order_acquire) == 0) {
|
||||
state.proceed.store(1, std::memory_order_release);
|
||||
pthread_join(worker, nullptr);
|
||||
FAIL() << "worker did not publish its TID within 5 seconds";
|
||||
}
|
||||
const pid_t tid = state.tid.load(std::memory_order_acquire);
|
||||
RawSchedParam zero {0};
|
||||
EXPECT_EQ(0, RawSetScheduler(tid, SCHED_OTHER | SCHED_RESET_ON_FORK, &zero)) << strerror(errno);
|
||||
EXPECT_EQ(SCHED_OTHER | SCHED_RESET_ON_FORK, sched_getscheduler(tid));
|
||||
state.proceed.store(1, std::memory_order_release);
|
||||
ASSERT_EQ(0, pthread_join(worker, nullptr));
|
||||
EXPECT_EQ(0, state.result);
|
||||
|
||||
errno = 0;
|
||||
EXPECT_EQ(-1, RawSetScheduler(tid, SCHED_OTHER | SCHED_RESET_ON_FORK, &zero));
|
||||
EXPECT_EQ(ESRCH, errno);
|
||||
}
|
||||
|
||||
int RunCredentialCaller(pid_t target, uid_t ruid, uid_t euid, bool set_flag,
|
||||
int expected_errno, bool expect_get_success) {
|
||||
pid_t caller = fork();
|
||||
if (caller < 0) return 200;
|
||||
if (caller == 0) {
|
||||
if (setresuid(ruid, euid, euid) != 0) _exit(100 + errno);
|
||||
if (expect_get_success) {
|
||||
if (sched_getscheduler(target) < 0) _exit(20);
|
||||
RawSchedParam query {-1};
|
||||
if (RawGetParam(target, &query) != 0 || query.sched_priority != 0) _exit(21);
|
||||
}
|
||||
RawSchedParam zero {0};
|
||||
errno = 0;
|
||||
long result = RawSetScheduler(
|
||||
target, set_flag ? SCHED_OTHER | SCHED_RESET_ON_FORK : SCHED_OTHER, &zero);
|
||||
if (expected_errno == 0) _exit(result == 0 ? 0 : 30 + errno);
|
||||
_exit(result == -1 && errno == expected_errno ? 0 : 60 + errno);
|
||||
}
|
||||
return WaitForChild(caller);
|
||||
}
|
||||
|
||||
TEST(SchedPermission, OwnerCapabilityAndProtectedClearMatrix) {
|
||||
if (geteuid() != 0) GTEST_SKIP() << "requires root to construct distinct credentials";
|
||||
|
||||
int hold[2];
|
||||
int ready[2];
|
||||
ASSERT_EQ(0, pipe(hold));
|
||||
ASSERT_EQ(0, pipe(ready));
|
||||
pid_t target = fork();
|
||||
ASSERT_GE(target, 0) << strerror(errno);
|
||||
if (target == 0) {
|
||||
close(hold[1]);
|
||||
close(ready[0]);
|
||||
if (setresuid(1001, 1001, 1001) != 0) _exit(100 + errno);
|
||||
WriteByteOrExit(ready[1], 'r');
|
||||
close(ready[1]);
|
||||
_exit(ReadByte(hold[0]) ? 0 : 121);
|
||||
}
|
||||
ChildGuard target_guard(target);
|
||||
close(hold[0]);
|
||||
close(ready[1]);
|
||||
ASSERT_TRUE(ReadByteWithTimeout(ready[0], 5000))
|
||||
<< "target did not publish credential readiness within 5 seconds";
|
||||
close(ready[0]);
|
||||
|
||||
// Cross-owner queries are unrestricted, while setters require owner or
|
||||
// CAP_SYS_NICE. Matching only current real UID must not authorize.
|
||||
EXPECT_EQ(0, RunCredentialCaller(target, 1002, 1002, true, EPERM, true));
|
||||
EXPECT_EQ(0, RunCredentialCaller(target, 1001, 1002, true, EPERM, true));
|
||||
|
||||
// Same effective UID may set the flag, but may not clear the protected flag.
|
||||
EXPECT_EQ(0, RunCredentialCaller(target, 1001, 1001, true, 0, true));
|
||||
EXPECT_EQ(SCHED_OTHER | SCHED_RESET_ON_FORK, sched_getscheduler(target));
|
||||
EXPECT_EQ(0, RunCredentialCaller(target, 1001, 1001, false, EPERM, true));
|
||||
EXPECT_EQ(SCHED_OTHER | SCHED_RESET_ON_FORK, sched_getscheduler(target));
|
||||
|
||||
// The root parent has CAP_SYS_NICE in the initial namespace and may clear.
|
||||
RawSchedParam zero {0};
|
||||
EXPECT_EQ(0, RawSetScheduler(target, SCHED_OTHER, &zero)) << strerror(errno);
|
||||
EXPECT_EQ(SCHED_OTHER, sched_getscheduler(target));
|
||||
|
||||
EXPECT_EQ(1, write(hold[1], "x", 1));
|
||||
close(hold[1]);
|
||||
EXPECT_EQ(0, WaitForChild(target));
|
||||
target_guard.Release();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
int main(int argc, char** argv) {
|
||||
::testing::InitGoogleTest(&argc, argv);
|
||||
return RUN_ALL_TESTS();
|
||||
}
|
||||
@@ -201,6 +201,9 @@ TEST(UdpBindToDevice, LocalFastPathDoesNotCrossBoundInterface) {
|
||||
ASSERT_GE(receiver.Get(), 0);
|
||||
ASSERT_GE(sender.Get(), 0);
|
||||
|
||||
timeval timeout = {.tv_sec = 0, .tv_usec = 100000};
|
||||
ASSERT_EQ(setsockopt(receiver.Get(), SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)), 0);
|
||||
|
||||
ASSERT_EQ(setsockopt(sender.Get(), SOL_SOCKET, SO_BINDTODEVICE, non_loopback.c_str(),
|
||||
non_loopback.size() + 1),
|
||||
0)
|
||||
@@ -222,8 +225,14 @@ TEST(UdpBindToDevice, LocalFastPathDoesNotCrossBoundInterface) {
|
||||
|
||||
char received[sizeof(payload)] = {};
|
||||
errno = 0;
|
||||
EXPECT_EQ(recv(receiver.Get(), received, sizeof(received), MSG_DONTWAIT), -1);
|
||||
EXPECT_EQ(recv(receiver.Get(), received, sizeof(received), 0), -1);
|
||||
EXPECT_TRUE(errno == EAGAIN || errno == EWOULDBLOCK) << strerror(errno);
|
||||
|
||||
ASSERT_EQ(sendto(sender.Get(), payload, sizeof(payload), 0,
|
||||
reinterpret_cast<sockaddr*>(&address), sizeof(address)),
|
||||
static_cast<ssize_t>(sizeof(payload)))
|
||||
<< "the bound interface must remain usable after processing ingress: "
|
||||
<< strerror(errno);
|
||||
}
|
||||
|
||||
TEST(UdpBindToDevice, ReuseOptionsAreReadAtBindConflictTime) {
|
||||
|
||||
@@ -35,6 +35,7 @@ normal/mlock_semantics
|
||||
normal/mmap_truncate_cow
|
||||
normal/sysv_shm_semantics
|
||||
normal/sched_affinity
|
||||
normal/sched_policy_semantics
|
||||
normal/sync_file_range
|
||||
normal/splice_concurrent_io
|
||||
normal/rcu_selftest
|
||||
|
||||
Reference in New Issue
Block a user