sched: enforce thread state transitions

This commit is contained in:
Microindole 2026-05-12 23:23:17 +08:00
parent 7aaea3a4d3
commit b92453788b
No known key found for this signature in database
GPG Key ID: 22FB34CD2133ACA1
5 changed files with 113 additions and 5 deletions

View File

@ -93,3 +93,10 @@
- 不要直接返回 `-22` 这类负数字面量;使用 `-EINVAL` 这类符号化 errno
结构检查会拒绝裸负数返回。
- errno 常量放在 `include/tianole/errno.h`,只增加当前内核实际使用的值。
## 调度状态
- 线程状态转换必须通过 `kernel/sched/sched.h` 中的 helper 完成。
- 不要在调度实现或其他子系统里直接写 `thread->state = ...`;结构检查会拒绝
这种写法。
- 新增状态或转换规则时,必须同步更新状态转换 helper 和 scheduler selftest。

View File

@ -3,6 +3,7 @@
#include <stdint.h>
#include <tianole/early_log.h>
#include <tianole/sched.h>
#include <tianole/spinlock.h>
@ -42,34 +43,83 @@ static inline int thread_is_dead(const struct thread *thread)
return thread != 0 && thread->state == THREAD_DEAD;
}
static inline void thread_set_ready(struct thread *thread)
static inline int thread_state_transition_is_valid(
enum thread_state from, enum thread_state to)
{
if (from == to) {
return 1;
}
switch (from) {
case THREAD_READY:
return to == THREAD_RUNNING;
case THREAD_RUNNING:
return to == THREAD_READY || to == THREAD_SLEEPING ||
to == THREAD_WAITING || to == THREAD_DEAD;
case THREAD_SLEEPING:
case THREAD_WAITING:
return to == THREAD_READY || to == THREAD_DEAD;
case THREAD_DEAD:
return 0;
default:
return 0;
}
}
static inline void thread_validate_state_transition(
struct thread *thread, enum thread_state state)
{
if (thread == 0) {
panic("null thread state transition");
}
if (!thread_state_transition_is_valid(thread->state, state)) {
panic("invalid thread state transition");
}
}
static inline void thread_set_state(
struct thread *thread, enum thread_state state)
{
thread_validate_state_transition(thread, state);
thread->state = state;
}
static inline void thread_init_ready(struct thread *thread)
{
thread->wake_tick = 0;
thread->state = THREAD_READY;
}
static inline void thread_set_ready(struct thread *thread)
{
thread_set_state(thread, THREAD_READY);
thread->wake_tick = 0;
}
static inline void thread_set_running(struct thread *thread)
{
thread->state = THREAD_RUNNING;
thread_set_state(thread, THREAD_RUNNING);
}
static inline void thread_set_sleeping(
struct thread *thread, uint64_t wake_tick)
{
thread_validate_state_transition(thread, THREAD_SLEEPING);
thread->wake_tick = wake_tick;
thread->state = THREAD_SLEEPING;
}
static inline void thread_set_waiting(struct thread *thread)
{
thread_set_state(thread, THREAD_WAITING);
thread->wake_tick = 0;
thread->state = THREAD_WAITING;
}
static inline void thread_set_dead(struct thread *thread)
{
thread_set_state(thread, THREAD_DEAD);
thread->wake_tick = 0;
thread->state = THREAD_DEAD;
}
void enqueue_thread(struct thread *thread);

View File

@ -77,7 +77,7 @@ struct thread *kernel_thread_create(
stack_top = (uintptr_t)thread->stack_base + KERNEL_STACK_SIZE;
thread_set_ready(thread);
thread_init_ready(thread);
thread->entry = entry;
thread->arg = arg;
thread->stack_top = align_down_uintptr(stack_top, STACK_ALIGNMENT);

View File

@ -20,6 +20,35 @@ static int selftest_condition_false(void *arg)
return 0;
}
static void assert_thread_transition(
enum thread_state from, enum thread_state to, int expected)
{
if (thread_state_transition_is_valid(from, to) != expected) {
panic("thread state transition selftest failed");
}
}
static void sched_state_machine_selftest(void)
{
assert_thread_transition(THREAD_READY, THREAD_RUNNING, 1);
assert_thread_transition(THREAD_RUNNING, THREAD_READY, 1);
assert_thread_transition(THREAD_RUNNING, THREAD_SLEEPING, 1);
assert_thread_transition(THREAD_RUNNING, THREAD_WAITING, 1);
assert_thread_transition(THREAD_RUNNING, THREAD_DEAD, 1);
assert_thread_transition(THREAD_SLEEPING, THREAD_READY, 1);
assert_thread_transition(THREAD_WAITING, THREAD_READY, 1);
assert_thread_transition(THREAD_SLEEPING, THREAD_DEAD, 1);
assert_thread_transition(THREAD_WAITING, THREAD_DEAD, 1);
assert_thread_transition(THREAD_READY, THREAD_SLEEPING, 0);
assert_thread_transition(THREAD_READY, THREAD_WAITING, 0);
assert_thread_transition(THREAD_READY, THREAD_DEAD, 0);
assert_thread_transition(THREAD_SLEEPING, THREAD_RUNNING, 0);
assert_thread_transition(THREAD_WAITING, THREAD_RUNNING, 0);
assert_thread_transition(THREAD_DEAD, THREAD_READY, 0);
assert_thread_transition(THREAD_DEAD, THREAD_RUNNING, 0);
}
void sched_selftest(void)
{
struct spinlock test_lock;
@ -30,6 +59,8 @@ void sched_selftest(void)
kernel_thread_create("worker-b", thread_selftest_entry, 0);
uint64_t flags;
sched_state_machine_selftest();
test_lock.locked = 0;
if (first == 0 || second == 0 || first == second) {

View File

@ -124,6 +124,25 @@ def check_no_bare_negative_errno(root: Path, files: list[Path]) -> list[str]:
return errors
def check_scheduler_state_writes(root: Path, files: list[Path]) -> list[str]:
errors = []
state_write_re = re.compile(r"->state\s*=")
allowed_path = Path("kernel/sched/sched.h")
for path in files:
if path == allowed_path:
continue
for line_no, line in enumerate(read_text(root, path).splitlines(), 1):
if state_write_re.search(line):
errors.append(
f"{path}:{line_no}: thread state writes must use "
"kernel/sched/sched.h helpers"
)
return errors
def is_function_declaration_start(line: str) -> bool:
stripped = line.strip()
@ -424,6 +443,7 @@ def main() -> int:
errors = []
errors.extend(check_no_relative_parent_includes(root, source_files(files)))
errors.extend(check_no_bare_negative_errno(root, source_files(files)))
errors.extend(check_scheduler_state_writes(root, source_files(files)))
errors.extend(check_public_header_docs(root, public_headers(files)))
errors.extend(check_selftests_are_centralized(c_files(files)))
errors.extend(check_makefile_source_lists(root, files, all_mode))