sched: enforce thread state transitions
This commit is contained in:
parent
7aaea3a4d3
commit
b92453788b
@ -93,3 +93,10 @@
|
|||||||
- 不要直接返回 `-22` 这类负数字面量;使用 `-EINVAL` 这类符号化 errno,
|
- 不要直接返回 `-22` 这类负数字面量;使用 `-EINVAL` 这类符号化 errno,
|
||||||
结构检查会拒绝裸负数返回。
|
结构检查会拒绝裸负数返回。
|
||||||
- errno 常量放在 `include/tianole/errno.h`,只增加当前内核实际使用的值。
|
- errno 常量放在 `include/tianole/errno.h`,只增加当前内核实际使用的值。
|
||||||
|
|
||||||
|
## 调度状态
|
||||||
|
|
||||||
|
- 线程状态转换必须通过 `kernel/sched/sched.h` 中的 helper 完成。
|
||||||
|
- 不要在调度实现或其他子系统里直接写 `thread->state = ...`;结构检查会拒绝
|
||||||
|
这种写法。
|
||||||
|
- 新增状态或转换规则时,必须同步更新状态转换 helper 和 scheduler selftest。
|
||||||
|
|||||||
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include <tianole/early_log.h>
|
||||||
#include <tianole/sched.h>
|
#include <tianole/sched.h>
|
||||||
#include <tianole/spinlock.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;
|
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->wake_tick = 0;
|
||||||
thread->state = THREAD_READY;
|
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)
|
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(
|
static inline void thread_set_sleeping(
|
||||||
struct thread *thread, uint64_t wake_tick)
|
struct thread *thread, uint64_t wake_tick)
|
||||||
{
|
{
|
||||||
|
thread_validate_state_transition(thread, THREAD_SLEEPING);
|
||||||
thread->wake_tick = wake_tick;
|
thread->wake_tick = wake_tick;
|
||||||
thread->state = THREAD_SLEEPING;
|
thread->state = THREAD_SLEEPING;
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline void thread_set_waiting(struct thread *thread)
|
static inline void thread_set_waiting(struct thread *thread)
|
||||||
{
|
{
|
||||||
|
thread_set_state(thread, THREAD_WAITING);
|
||||||
thread->wake_tick = 0;
|
thread->wake_tick = 0;
|
||||||
thread->state = THREAD_WAITING;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline void thread_set_dead(struct thread *thread)
|
static inline void thread_set_dead(struct thread *thread)
|
||||||
{
|
{
|
||||||
|
thread_set_state(thread, THREAD_DEAD);
|
||||||
thread->wake_tick = 0;
|
thread->wake_tick = 0;
|
||||||
thread->state = THREAD_DEAD;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void enqueue_thread(struct thread *thread);
|
void enqueue_thread(struct thread *thread);
|
||||||
|
|||||||
@ -77,7 +77,7 @@ struct thread *kernel_thread_create(
|
|||||||
|
|
||||||
stack_top = (uintptr_t)thread->stack_base + KERNEL_STACK_SIZE;
|
stack_top = (uintptr_t)thread->stack_base + KERNEL_STACK_SIZE;
|
||||||
|
|
||||||
thread_set_ready(thread);
|
thread_init_ready(thread);
|
||||||
thread->entry = entry;
|
thread->entry = entry;
|
||||||
thread->arg = arg;
|
thread->arg = arg;
|
||||||
thread->stack_top = align_down_uintptr(stack_top, STACK_ALIGNMENT);
|
thread->stack_top = align_down_uintptr(stack_top, STACK_ALIGNMENT);
|
||||||
|
|||||||
@ -20,6 +20,35 @@ static int selftest_condition_false(void *arg)
|
|||||||
return 0;
|
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)
|
void sched_selftest(void)
|
||||||
{
|
{
|
||||||
struct spinlock test_lock;
|
struct spinlock test_lock;
|
||||||
@ -30,6 +59,8 @@ void sched_selftest(void)
|
|||||||
kernel_thread_create("worker-b", thread_selftest_entry, 0);
|
kernel_thread_create("worker-b", thread_selftest_entry, 0);
|
||||||
uint64_t flags;
|
uint64_t flags;
|
||||||
|
|
||||||
|
sched_state_machine_selftest();
|
||||||
|
|
||||||
test_lock.locked = 0;
|
test_lock.locked = 0;
|
||||||
|
|
||||||
if (first == 0 || second == 0 || first == second) {
|
if (first == 0 || second == 0 || first == second) {
|
||||||
|
|||||||
@ -124,6 +124,25 @@ def check_no_bare_negative_errno(root: Path, files: list[Path]) -> list[str]:
|
|||||||
return errors
|
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:
|
def is_function_declaration_start(line: str) -> bool:
|
||||||
stripped = line.strip()
|
stripped = line.strip()
|
||||||
|
|
||||||
@ -424,6 +443,7 @@ def main() -> int:
|
|||||||
errors = []
|
errors = []
|
||||||
errors.extend(check_no_relative_parent_includes(root, source_files(files)))
|
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_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_public_header_docs(root, public_headers(files)))
|
||||||
errors.extend(check_selftests_are_centralized(c_files(files)))
|
errors.extend(check_selftests_are_centralized(c_files(files)))
|
||||||
errors.extend(check_makefile_source_lists(root, files, all_mode))
|
errors.extend(check_makefile_source_lists(root, files, all_mode))
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user