Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 76 additions & 4 deletions os/StarryOS/kernel/src/syscall/task/schedule.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@ use ax_task::{
AxCpuMask, current,
future::{block_on, interruptible, sleep},
};
use bytemuck::{Pod, Zeroable};
use linux_raw_sys::general::{
__kernel_clockid_t, CLOCK_MONOTONIC, CLOCK_REALTIME, PRIO_PGRP, PRIO_PROCESS, PRIO_USER,
SCHED_RR, TIMER_ABSTIME, timespec,
SCHED_BATCH, SCHED_FIFO, SCHED_IDLE, SCHED_NORMAL, SCHED_RR, TIMER_ABSTIME, timespec,
};
use starry_vm::{VmMutPtr, VmPtr, vm_load, vm_write_slice};

Expand All @@ -20,6 +21,12 @@ use crate::{
time::TimeValueLike,
};

#[repr(C)]
#[derive(Copy, Clone, Pod, Zeroable)]
struct SchedParam {
sched_priority: i32,
}

pub fn sys_sched_yield() -> AxResult<isize> {
ax_task::yield_now();
Ok(0)
Expand Down Expand Up @@ -107,7 +114,27 @@ pub fn sys_sched_getaffinity(pid: i32, cpusetsize: usize, user_mask: *mut u8) ->
Ok(mask_bytes.len() as _)
}

pub fn check_sched_permission(pid: i32) -> AxResult<()> {
let caller = current().as_thread().cred();
let task = get_task_by_sched_pid(pid)?;
if task.id() == current().id() {
return Ok(());
}
let target_proc = get_process_data(pid as u32)?;
let target_cred = process_cred(&target_proc)?;
if caller.has_cap_sys_nice()
|| caller.euid == target_cred.uid
|| caller.euid == target_cred.euid
{
Ok(())
} else {
Err(AxError::OperationNotPermitted)
}
}

pub fn sys_sched_setaffinity(pid: i32, cpusetsize: usize, user_mask: *const u8) -> AxResult<isize> {
check_sched_permission(pid)?;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

check_sched_permission(pid)? 内部已经调用 get_task_by_sched_pid(pid) 获取 task 引用做权限校验,但该 task 引用在 check_sched_permission 返回后被丢弃。紧接着第 137 行又调用 get_task_by_sched_pid(pid) 重新获取同一个 task。

同样的问题存在于 sys_sched_setscheduler(第 174-175 行)。

建议:将 check_sched_permission 改为返回 (AxTaskRef, ()) 或单独封装一个 get_task_with_permission(pid) -> AxResult<AxTaskRef>,避免冗余查找。

let task = get_task_by_sched_pid(pid)?;
let size = cpusetsize.min(ax_hal::cpu_num().div_ceil(8));
let user_mask = vm_load(user_mask, size)?;
let mut cpu_mask = AxCpuMask::new();
Expand All @@ -121,8 +148,6 @@ pub fn sys_sched_setaffinity(pid: i32, cpusetsize: usize, user_mask: *const u8)
if cpu_mask.is_empty() {
return Err(AxError::InvalidInput);
}

let task = get_task_by_sched_pid(pid)?;
if task.id() == current().id() {
ax_task::set_current_affinity(cpu_mask);
} else {
Expand All @@ -141,14 +166,61 @@ fn get_task_by_sched_pid(pid: i32) -> AxResult<ax_task::AxTaskRef> {
}

pub fn sys_sched_getscheduler(_pid: i32) -> AxResult<isize> {
Ok(SCHED_RR as _)
let task = get_task_by_sched_pid(_pid)?;
Ok(task.sched_policy() as isize)
}

pub fn sys_sched_setscheduler(_pid: i32, _policy: i32, _param: *const ()) -> AxResult<isize> {
check_sched_permission(_pid)?;
let task = get_task_by_sched_pid(_pid)?;
if _param.is_null() {
return Err(AxError::InvalidInput);
}
let user_param = vm_load::<SchedParam>(_param.cast(), 1)?;
let user_param = user_param[0];
let mut policy = _policy as u32;
const SCHED_RESET_ON_FORK: u32 = 0x40000000;
let _reset_on_fork = (policy & SCHED_RESET_ON_FORK) != 0;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SCHED_RESET_ON_FORK 标志被正确解析和剥离,但 _reset_on_fork 变量以 _ 前缀标记为未使用。在 Linux 中,此标志意味着子进程应回退到 SCHED_NORMAL 而非继承父进程的实时调度策略。

当前实现接受此标志但不执行实际的 reset-on-fork 行为,即 fork/clone 时不会重置子进程的调度策略。建议在此处添加 // TODO: implement reset-on-fork behavior in fork/clone path 注释,明确标注这是已知未完成的语义。

policy &= !SCHED_RESET_ON_FORK;
let prio = user_param.sched_priority;
match policy {
SCHED_NORMAL | SCHED_FIFO | SCHED_RR | SCHED_BATCH | SCHED_IDLE => {}
_ => return Err(AxError::InvalidInput),
}
match policy {
SCHED_NORMAL | SCHED_BATCH | SCHED_IDLE => {
if prio != 0 {
return Err(AxError::InvalidInput);
}
}
SCHED_FIFO | SCHED_RR => {
if !(1..=99).contains(&prio) {
return Err(AxError::InvalidInput);
}
}
_ => unreachable!(),
}
task.set_sched_policy(policy as i32);
task.set_sched_priority(prio);
Ok(0)
}

pub fn sys_sched_getparam(_pid: i32, _param: *mut ()) -> AxResult<isize> {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

在 Linux 中,sched_getparam 对目标进程有权限检查:调用者需要 CAP_SYS_NICE 或调用者的 real/effective UID 匹配目标进程的 real/saved-set UID。

当前实现允许任意进程读取任意其它进程的调度参数,没有权限检查。虽然 sched_getparam 对安全性影响较低(只读操作),但若追求 Linux ABI 兼容性,建议添加与 sched_setparam 类似的权限检查。

let task = get_task_by_sched_pid(_pid)?;
if _param.is_null() {
return Err(AxError::InvalidInput);
}
let param = SchedParam {
sched_priority: task.sched_priority(),
};
let ptr = _param as *mut SchedParam;
unsafe {
let bytes = core::slice::from_raw_parts(
&param as *const SchedParam as *const u8,
core::mem::size_of::<SchedParam>(),
);
vm_write_slice(ptr as *mut u8, bytes)?;
}
Ok(0)
}

Expand Down
28 changes: 28 additions & 0 deletions os/arceos/modules/axtask/src/task.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,12 @@ pub struct TaskInner {
/// CPU affinity mask.
cpumask: SpinNoIrq<AxCpuMask>,

/// Scheduling policy of the task.
sched_policy: AtomicI32,

/// Scheduling priority of the task.
sched_priority: AtomicI32,

/// Mark whether the task is in the wait queue.
in_wait_queue: AtomicBool,

Expand Down Expand Up @@ -264,6 +270,26 @@ impl TaskInner {
*self.cpumask.lock() = cpumask
}

#[inline]
pub fn sched_policy(&self) -> i32 {
self.sched_policy.load(Ordering::Acquire)
}

#[inline]
pub fn set_sched_policy(&self, policy: i32) {
self.sched_policy.store(policy, Ordering::Release)
}

#[inline]
pub fn sched_priority(&self) -> i32 {
self.sched_priority.load(Ordering::Acquire)
}

#[inline]
pub fn set_sched_priority(&self, prio: i32) {
self.sched_priority.store(prio, Ordering::Release)
}

/// Polls whether the task has been interrupted.
#[inline]
pub fn poll_interrupt(&self, cx: &Context) -> Poll<()> {
Expand Down Expand Up @@ -324,6 +350,8 @@ impl TaskInner {
state: AtomicU8::new(TaskState::Ready as u8),
// By default, the task is allowed to run on all CPUs.
cpumask: SpinNoIrq::new(crate::api::cpu_mask_full()),
sched_policy: AtomicI32::new(0),
sched_priority: AtomicI32::new(0),
in_wait_queue: AtomicBool::new(false),
#[cfg(feature = "irq")]
timer_ticket_id: AtomicU64::new(0),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
cmake_minimum_required(VERSION 3.20)
project(test-sched-family C)
set(CMAKE_C_STANDARD 11)
set(CMAKE_C_STANDARD_REQUIRED ON)
set(CMAKE_C_EXTENSIONS OFF)
add_executable(test-sched-family src/main.c)
target_include_directories(test-sched-family PRIVATE src)
target_compile_options(test-sched-family PRIVATE -Wall -Wextra -Werror)
install(TARGETS test-sched-family RUNTIME DESTINATION usr/bin)
Loading