Replies: 1 comment
-
|
为了支持后续的 POSIX 规范要求
目前 StarryOS 中的 具体的设计是在 pub struct ProcessSignalManager {
...
/// Signal event flag, keep track of un-consumed stop/continue event by
/// `wait`
signal_events: AtomicU8,
/// The signal stops the process most recently
last_stop_signal: SpinNoIrq<Option<Signo>>,
}前者 需要注意的是,这里信号与事件的记录、清除等过程与进程的状态切换相互独立,均由 pub(crate) fn do_stop(stop_signal: Signo) {
...
// record the stop signal in the `ProcessSignalManager`
curr_thread.proc_data.signal.set_stop_signal(stop_signal);
// change the state of current process to `STOPPED`
curr_process.transition_to_stopped();
...
}
pub(crate) fn do_continue() {
...
// record the continue event in the `ProcessSignalManager`
curr_thread.proc_data.signal.set_cont_signal();
// change the state of current process to `RUNNING`
curr_proc.transition_to_running();
...
}结合进程停止/继续过程中对父进程 child_exit_event 的唤醒,整体结构采用了单发布者-单订阅者的发布/订阅模式。由于 POSIX 标准中对于停止/继续事件的要求是:
对于连续的、未被消费的同类事件,POSIX 未作记录要求(新事件会覆盖旧事件),因此无需额外设计消息队列,仅保留最新的事件状态即可。 相关 commit: |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
-
目前,StarryOS 中进程存在两个状态,Running 与 Zombie,并且尚未支持通过 SIGSTOP 与 SIGCONT 进行动态的停止与继续。这里希望通过借助已经存在的信号基础设施 starry-signal 与 PollSet 等异步机制实现相关功能,为后续支持更完善的
waitpid乃至ptrace提供基础。这里拟定了三个仍处在 Draft 阶段的 PR,希望能够提供一个可靠的解决方案。
starry-process
首先,是关于 starry-process 的 PR:Starry-OS/starry-process#5
这个 PR 中主要增加了一个状态管理的标志位,包括
RUNNING,STOPPED, 和ZOMBIE三个状态,并且围绕这三个状态设置了is_[state]的状态判断函数与transition_to_[state]的状态转换函数,除此之外没有更改。之所以添加这三个状态是为了更好的向内核中进程管理提供接口,辅助更加上层的实际执行进程状态切换的do_stop与do_continue的实现。starry-signal
然后,是关于 starry-signal 的 PR:Starry-OS/starry-signal#6
在这个 PR 中,首先针对就绪信号队列
pending.rs进行了功能的扩充,提供了判断当前队列中是否存在某一信号的has_signal函数与从队列中移除某一个具体信号的函数remove_signal。基于此,分别针对进程级别信号队列和线程级别信号队列进行功能扩充,添加进程和线程级别的has_signal判断函数,remove_signal移除某一信号的函数和更加细化的flush_stop_signals移除所有可能导致进程停止的信号的函数。之所以进行这部分的更改是为了满足 POSIX 标准中的要求:使得更上层的内核的进程管理能够根据收到的信号来处理队列中就绪但仍未处理的其他信号。
在原有的 starry-signal 中,如果一个线程/进程
SIGCONTSIGCONT那么在该线程所在的进程被停止后,
SIGCONT信号将在负责信号传送的
send_signal函数中直接被丢弃在被
handle_signal过滤掉从而无法被返回处理,进程不能正常继续,违背了 POSIX 标准的要求:因此需要针对
SIGCONT进行额外处理。针对
SIGCONT与SIGKILL这种无论进程信号设定如何,都有可能存在副作用的信号添加Signo的工具函数:这是为了结合当前
starry-signal的信号传送逻辑进行更改:这里通过引入
has_side_effect()方法,我们将信号分为两类:普通信号:
has_side_effect() == false特殊信号:
has_side_effect() == true(仅SIGCONT和SIGKILL)这个设计不破坏现有行为,普通信号的处理逻辑完全不变。
这解决了上文的情况 b,即在
SIGCONT被设置为Ignore时未能送达直接被丢弃的问题,保证后续会对其进行处理。此外,针对线程层面的信号检查函数
check_signals_slow,这个 PR 中也进行了部分接口与功能的更改,将原有的函数接口更改为:
主要是更改了返回值类型,从
SignalOSAction变为Option<SignalOSAction>。这一返回值类型更改需要结合该函数逻辑部分的扩充来看:原本的
check_signal_slow会根据当前线程的信号设定 mask 与 sigaction 进行筛选,过滤掉被线程阻塞的信号。这将导致原本的check_signal_slow会根据当前线程的信号设定 mask 与 sigaction 进行筛选,过滤掉被线程阻塞的信号,但对于SIGCONT这种即使被阻塞或忽略也必须产生副作用的信号,需要特殊处理。因此,在这里检查过滤信号时增加额外的判断逻辑:
这里主要解决的是情况 b:线程在设置信号处理的 sigaction 的时候设置为忽略(ignore)
SIGCONT。如果被处理的信号的是SIGCONT并且该信号被设置为SignalDisposition::Ignore,那么返回的结果是Some((sig, None))。这允许我们在更加上层的信号处理逻辑中结合进程的具体状态进行判断处理。StarryOS
最后是关于 StarryOS 本身修改的 PR: #65
为了实现进程的暂停与继续,首先实现状态转换的函数
do_stop与do_continue,它们都是调用 starry-process 中的函数判断、更新进程状态,调用 starry-signal 函数移除根据 POSIX 标准需要移除的信号,如do_stop的实现逻辑为:ProcessData中记录停止信号(为了之后的 waitpid 服务)。STOPPED。类似的,
do_continue负责的主要是:RUNNING。在此基础之上可以实现进程的停止与继续机制。在
new_user_task之中的let reason = uctx.run();运行退出用户空间之后进行信号检查会调用api/src/signal.rs中的check_signals函数。信号检查与处理流程
check_signals函数是整个信号处理流程的核心入口,它负责检查待处理的信号并执行相应的 OS 级操作。该函数的处理流程如下:1. SIGCONT 预检查机制
在进行常规信号处理之前,首先对
SIGCONT进行特殊检查:这里使用
has_signal()而不是dequeue_signal()的原因是:即使 SIGCONT 被阻塞,也必须触发进程继续的副作用。信号本身会保留在队列中,等解除阻塞后再递送给处理器。这个预检查确保了 POSIX 标准的要求:无论 SIGCONT 是否被阻塞或忽略,只要进程处于停止状态,就必须立即继续。
2. 常规信号处理
调用底层的
ThreadSignalManager::check_signals()获取一个未被阻塞的待处理信号:如果没有信号待处理,直接返回
false。3. SIGCONT 的 Ignore 处理
针对
SIGCONT被设置为SIG_IGN的特殊情况:因为在步骤 1 中已经调用了
do_continue(),这里直接返回true表示信号已处理,无需进一步操作。4. OS 级操作处理
根据信号的
SignalOSAction执行相应的内核操作:进程停止机制的实现
进程停止不仅仅是状态的改变,还需要实际阻塞进程中的所有线程。这是通过
handle_stopped_state函数实现的。异步阻塞机制
handle_stopped_state函数在每次系统调用返回用户态前被调用(位于api/src/task.rs:245-330):这里使用了 Rust 的 async 机制:
双层检查保障:
STOPPED。在多线程环境下,如果某个线程已经处理了SIGCONT并恢复了进程运行,其他线程需要能够检测到这一状态变化并立即退出阻塞。STOPPED,则检查是否有SIGCONT或SIGKILLpending唤醒机制:
stop_eventSIGCONT或SIGKILL被发送时,core/src/task.rs:512会调用stop_event.wake()唤醒所有等待的任务Ready唤醒触发点
在
core/src/task.rs的send_signal_to_process函数中:这确保了在当前信号的目标进程可能已经由于
SIGSTOP信号被阻塞,无法按照正常步骤进行信号的检查的情况下,stop_event也会被触发,唤醒所有阻塞的线程来进行检查。SIGCONT 三种情况的完整处理
综合以上机制,
SIGCONT在不同情况下的处理如下:情况 1:SIGCONT 未被 blocked,未被 ignored
这是最正常的情况,只需要直接进行处理,恢复进程执行,并调用用户处理函数(如果配置):
send_signal()将信号加入队列,返回true(需要 interrupt)stop_event.wake()唤醒阻塞的线程task.interrupt()中断当前任务check_signals()被调用SIGCONT→ 调用do_continue()dequeue_signal()取出信号情况 2:SIGCONT 被 blocked
根据 POSIX 标准的要求:
我们在确保目标进程继续运行的同时应当将这个
SIGCONT保存在阻塞的信号队列中直到不再被阻塞:send_signal()将信号加入队列(通过has_side_effect()保证),返回false(blocked,不 interrupt)stop_event.wake()无条件唤醒handle_stopped_state中的 Future 被唤醒poll_fn检查has_signal(SIGCONT)为true→ 返回Readycheck_signals()被调用进行实际的信号处理SIGCONT→ 调用do_continue()dequeue_signal()不会取出信号(因为 blocked)情况 3:SIGCONT 被设置为 Ignore
即使信号已经被忽略,根据前文提及的修改后的
send_signal仍会将其加入目标进程的信号队列中,后续的信号处理逻辑会针对其特殊检查并处理:send_signal()中signal_ignored()返回truehas_side_effect()也返回truestop_event.wake()被调用handle_stopped_state被唤醒,has_signal(SIGCONT)返回truecheck_signals()被调用SIGCONT→ 调用do_continue()dequeue_signal()取出信号os_action.is_none()(因为 ignored) → 直接返回true总结
本次重构和修复实现了完整的 POSIX 兼容的进程停止与继续机制:
has_signal,remove_signal,flush_stop_signals)SIGCONT被 ignore 时副作用无法执行的问题(引入has_side_effect())SIGCONT被 block 的情况(返回Option<SignalOSAction>)do_stop和do_continue状态转换函数check_signals信号检查和分发逻辑handle_stopped_state异步阻塞机制SIGCONT的副作用一定发生整个设计通过以下机制协同工作:
stop_event.wake()+has_signal()检查相关 PR:
Beta Was this translation helpful? Give feedback.
All reactions