Skip to content

Commit eb3f4dc

Browse files
committed
Import from the internal tree
1 parent 49d2df5 commit eb3f4dc

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

62 files changed

+8542
-0
lines changed

.clang-format

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
BasedOnStyle: Chromium
2+
Language: Cpp
3+
MaxEmptyLinesToKeep: 3
4+
IndentCaseLabels: false
5+
AllowShortIfStatementsOnASingleLine: false
6+
AllowShortCaseLabelsOnASingleLine: false
7+
AllowShortLoopsOnASingleLine: false
8+
DerivePointerAlignment: false
9+
PointerAlignment: Right
10+
SpaceAfterCStyleCast: true
11+
TabWidth: 4
12+
UseTab: Never
13+
IndentWidth: 4
14+
BreakBeforeBraces: Linux
15+
AccessModifierOffset: -4
16+
ForEachMacros:
17+
- foreach
18+
- Q_FOREACH
19+
- BOOST_FOREACH
20+
- list_for_each
21+
- list_for_each_safe
22+
- list_for_each_entry
23+
- list_for_each_entry_safe
24+
- hlist_for_each_entry
25+
- rb_list_foreach
26+
- rb_list_foreach_safe
27+
SpaceBeforeParens: ControlStatementsExceptForEachMacros

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
build/
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# HAL: Context Switching for RISC-V
2+
3+
## Context Switching
4+
5+
Context switching is essential to Linmo's preemptive multitasking kernel, facilitating smooth task transitions. In Linmo, this is managed through `setjmp` and `longjmp` functions, implemented in [arch/riscv/hal.c](../arch/riscv/hal.c) and declared in [arch/riscv/hal.h](../arch/riscv/hal.h). These functions enable the kernel to save and restore task states, supporting reliable multitasking. The process involves unique considerations due to the non-standard use of these functions, requiring careful handling to ensure system stability.
6+
7+
### Repurposed setjmp and longjmp
8+
9+
The `setjmp` and `longjmp` functions, typically used for exception handling, are repurposed in Linmo for context switching. They save additional states beyond standard registers, including CSRs like `mcause`, `mepc`, and `mstatus`, which may lead to unexpected behavior if not properly managed. During timer interrupts, `setjmp` must handle `mstatus.MIE` being cleared, relying on `MPIE` reconstruction, a departure from their usual role. Similarly, `longjmp` restores context without reinitializing local variables or the call stack, posing risks of resource leaks in tasks with dynamic memory. These deviations demand precise implementation to maintain task switching integrity.
10+
11+
### Context Switch Process
12+
13+
1. Save Current Task State: The `setjmp` function captures the current task's CPU state, storing it in a `jmp_buf` structure defined as `uint32_t jmp_buf[19]` in [arch/riscv/hal.h](../arch/riscv/hal.h). This includes callee-saved general-purpose registers (such as `s0` to `s11`), essential pointers (`gp`, `tp`, `sp`, `ra`), and CSRs (`mcause`, `mepc`, `mstatus`). The layout is defined by `CONTEXT_*` macros in [arch/riscv/hal.c](../arch/riscv/hal.c). Failing to reconstruct `mstatus` from `MPIE` during this step can cause incorrect interrupt settings, leading to missed timer interrupts or stalls, which is mitigated by ensuring proper `mstatus` reconstruction.
14+
15+
2. Select Next Task: The scheduler, invoked via `dispatcher()` during a machine timer interrupt, selects the next task using a priority-based round-robin algorithm or a user-defined scheduler. This evaluates task priorities and readiness, ensuring system responsiveness. The process relies on accurate `mstatus` restoration to avoid timing issues.
16+
17+
3. Restore Next Task State: The `longjmp` function restores the CPU state from the selected task’s `jmp_buf`, resuming execution where `setjmp` was called. For new tasks, `hal_context_init` initializes the `jmp_buf` with `sp`, `ra`, and `mstatus` set to `MSTATUS_MIE` and `MSTATUS_MPP_MACH`, while `hal_dispatch_init` launches the task. The `hal_interrupt_tick` function enables interrupts (`_ei()`) after the first task, ensuring a consistent environment. Premature restoration of `mstatus` can disrupt scheduling, addressed by prioritizing it before other registers.
18+
19+
## Machine Status Management
20+
21+
The machine status in RISC-V is managed through the `mstatus` CSR, which controls critical system states, such as the Machine Interrupt Enable (`MIE`) bit. Proper handling of `mstatus` during context switching is essential to maintain correct interrupt behavior and ensure tasks execute as expected.
22+
23+
### Role of `mstatus`
24+
25+
The `mstatus` register includes the `MIE` bit (bit 3), which enables or disables machine-mode interrupts globally, and the `MPIE` bit (bit 7), which preserves the previous interrupt enable state during a trap, allowing `MIE` to be reconstructed afterward. Other fields, such as those for privilege mode and memory protection, are less relevant in Linmo’s single-address-space model but must still be preserved. The `hal_interrupt_set` function in [arch/riscv/hal.h](../arch/riscv/hal.h) manipulates `MIE` using `read_csr(mstatus)` and `write_csr(mstatus, val)`, with convenience macros `_di()` (disable interrupts) and `_ei()` (enable interrupts).
26+
27+
### Saving and Restoring `mstatus`
28+
29+
1. Saving in `setjmp`: The `setjmp` function reads `mstatus` using the `csrr` instruction. Since `mstatus.MIE` is cleared during a trap, the previous interrupt state is reconstructed from `MPIE` by shifting bit 7 to bit 3, clearing the original `MIE`, and restoring it, then storing the result in `jmp_buf` at `CONTEXT_MSTATUS` (18). This ensures accurate interrupt context preservation, preventing issues from incorrect handling.
30+
31+
2. Restoring in `longjmp`: The `longjmp` function loads `mstatus` from `jmp_buf` and writes it back with `csrw` before other registers, ensuring early interrupt state establishment. This prevents premature interrupt handling and maintains consistency.
32+
33+
3. Timing and drift considerations: Incorrect `mstatus` restoration can disrupt scheduling by enabling interrupts at the wrong time, resolvable through testing with scheduler-stressing applications like message queues. Timer interrupt drift, if not handled carefully, is addressed by `do_trap` scheduling relative to the previous `mtimecmp` value, requiring `mstatus.MIE` to be enabled for effective reception.
34+
35+
### Best Practices for Machine Status
36+
37+
- Prioritize `mstatus` restoration: Restore `mstatus` before other registers in `longjmp` to establish the correct interrupt state early.
38+
39+
- Use safe CSR access: Use `read_csr` and `write_csr` macros in [arch/riscv/hal.h](../arch/riscv/hal.h) for consistent CSR manipulation.
40+
41+
- Initialize `mstatus` for new tasks: Set `MSTATUS_MIE` and `MSTATUS_MPP_MACH` in `hal_context_init` to ensure new tasks start with interrupts enabled.

Makefile

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
ARCH := riscv
2+
3+
# in-tree build
4+
SRC_DIR := .
5+
6+
# Build directories
7+
BUILD_DIR := $(SRC_DIR)/build
8+
BUILD_APP_DIR := $(BUILD_DIR)/app
9+
BUILD_KERNEL_DIR := $(BUILD_DIR)/kernel
10+
BUILD_LIB_DIR := $(BUILD_DIR)/lib
11+
12+
include mk/common.mk
13+
# architecture-specific settings
14+
include arch/$(ARCH)/build.mk
15+
16+
# Include directories
17+
INC_DIRS += -I $(SRC_DIR)/include \
18+
-I $(SRC_DIR)/include/lib
19+
20+
KERNEL_OBJS := timer.o mqueue.o pipe.o semaphore.o mutex.o error.o syscall.o task.o main.o
21+
KERNEL_OBJS := $(addprefix $(BUILD_KERNEL_DIR)/,$(KERNEL_OBJS))
22+
deps += $(KERNEL_OBJS:%.o=%.o.d)
23+
24+
LIB_OBJS := stdio.o libc.o malloc.o queue.o
25+
LIB_OBJS := $(addprefix $(BUILD_LIB_DIR)/,$(LIB_OBJS))
26+
deps += $(LIB_OBJS:%.o=%.o.d)
27+
28+
# Applications
29+
APPS := coop echo hello mqueues semaphore mutex cond \
30+
pipes pipes_small pipes_struct prodcons progress \
31+
rtsched suspend test64 timer timer_kill \
32+
cpubench
33+
34+
# Output files for __link target
35+
IMAGE_BASE := $(BUILD_DIR)/image
36+
IMAGE_FILES := $(IMAGE_BASE).elf $(IMAGE_BASE).map $(IMAGE_BASE).lst \
37+
$(IMAGE_BASE).sec $(IMAGE_BASE).cnt $(IMAGE_BASE).bin \
38+
$(IMAGE_BASE).hex $(BUILD_DIR)/code.txt
39+
40+
# Default target
41+
.DEFAULT_GOAL := linmo
42+
43+
# Phony targets
44+
.PHONY: linmo rebuild clean distclean
45+
46+
# Create build directories
47+
$(BUILD_DIR):
48+
$(Q)mkdir -p $(BUILD_APP_DIR) $(BUILD_KERNEL_DIR) $(BUILD_LIB_DIR)
49+
50+
# Pattern rules for object files
51+
$(BUILD_DIR)/%.o: %.c | $(BUILD_DIR)
52+
$(VECHO) " CC\t$@\n"
53+
$(Q)$(CC) $(CFLAGS) -c -MMD -MF $@.d -o $@ $<
54+
55+
# Main library target
56+
linmo: $(BUILD_DIR)/liblinmo.a
57+
58+
$(BUILD_DIR)/liblinmo.a: $(HAL_OBJS) $(KERNEL_OBJS) $(LIB_OBJS)
59+
$(VECHO) " AR\t$@\n"
60+
$(Q)$(AR) $(ARFLAGS) $@ $^
61+
62+
# Application pattern rule
63+
$(APPS): %: rebuild $(BUILD_APP_DIR)/%.o linmo
64+
$(Q)$(MAKE) --no-print-directory __link
65+
66+
# Link target - creates all output files
67+
__link: $(IMAGE_FILES)
68+
69+
$(IMAGE_BASE).elf: $(BUILD_APP_DIR)/*.o $(BUILD_DIR)/liblinmo.a
70+
$(VECHO) " LD\t$@\n"
71+
$(Q)$(LD) $(LDFLAGS) -T$(LDSCRIPT) -Map $(IMAGE_BASE).map -o $@ $(BUILD_APP_DIR)/*.o -L$(BUILD_DIR) -llinmo
72+
73+
$(IMAGE_BASE).lst: $(IMAGE_BASE).elf
74+
$(VECHO) " DUMP\t$@\n"
75+
$(Q)$(DUMP) --disassemble --reloc $< > $@
76+
77+
$(IMAGE_BASE).sec: $(IMAGE_BASE).elf
78+
$(VECHO) " DUMP\t$@\n"
79+
$(Q)$(DUMP) -h $< > $@
80+
81+
$(IMAGE_BASE).cnt: $(IMAGE_BASE).elf
82+
$(VECHO) " DUMP\t$@\n"
83+
$(Q)$(DUMP) -s $< > $@
84+
85+
$(IMAGE_BASE).bin: $(IMAGE_BASE).elf
86+
$(VECHO) " COPY\t$@\n"
87+
$(Q)$(OBJ) -O binary $< $@
88+
89+
$(IMAGE_BASE).hex: $(IMAGE_BASE).elf
90+
$(VECHO) " COPY\t$@\n"
91+
$(Q)$(OBJ) -R .eeprom -O ihex $< $@
92+
93+
$(BUILD_DIR)/code.txt: $(IMAGE_BASE).bin
94+
$(VECHO) " DUMP\t$@\n"
95+
$(Q)hexdump -v -e '4/1 "%02x" "\n"' $< > $@
96+
@$(SIZE) $(IMAGE_BASE).elf
97+
98+
# Utility targets
99+
rebuild:
100+
$(Q)find '$(BUILD_APP_DIR)' -type f -name '*.o' -delete 2>/dev/null || true
101+
$(Q)mkdir -p $(BUILD_APP_DIR)
102+
103+
clean:
104+
$(VECHO) "Cleaning build artifacts...\n"
105+
$(Q)find '$(BUILD_APP_DIR)' '$(BUILD_KERNEL_DIR)' -type f -name '*.o' -delete 2>/dev/null || true
106+
$(Q)find '$(BUILD_DIR)' -type f \( -name '*.o' -o -name '*~' -o -name 'image.*' -o -name 'code.*' \) -delete 2>/dev/null || true
107+
$(Q)find '$(SRC_DIR)' -maxdepth 1 -type f -name '*.o' -delete 2>/dev/null || true
108+
@$(RM) $(deps)
109+
110+
distclean: clean
111+
$(VECHO) "Deep cleaning...\n"
112+
$(Q)find '$(BUILD_DIR)' -type f -name '*.a' -delete 2>/dev/null || true
113+
$(Q)rm -rf '$(BUILD_DIR)' 2>/dev/null || true
114+
115+
-include $(deps)

README.md

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
# Linmo: A Simple Multi-tasking Operating System Kernel
2+
```
3+
▅▅▅▅▅▅▅▅▅▅▅▅▅▅▅▖
4+
▐██████████████
5+
▐█████▜▆▆▛█████
6+
▗███▉▜██▟▀▀▙██▇███▆▖
7+
▗█████▜██▅▅▅▅██▀████▉
8+
▕███▙▇███▛▘▝████▆███▛ ▝▇▇▇▘ ▝▇▇▛▘▝▇▇▄ ▝▇▛▘ ▇▇▙ ▇▇▀ ▗▅▇▀▀▇▅▖
9+
▝████▛▀▔ ▔▀▜███▉ ▐█▋ ██▍ ▐██▙▖ ▐▌ ▐██▖ ▟██▎ ▗█▛ ██▖
10+
█▔█▍▝▀▀▀╸ ━▀▀▀ █▛▜▋ ▐█▋ ██▍ ▐▋▝▜█▃▐▌ █▎▜█▖▐▛▐█▌ ██▌ ▐█▋
11+
▜▖▜▌╺▄▃▄╸ ┓╺▄▄━ █▋▟▍ ▐█▋ ▅▏ ██▍ ▐▋ ▜██▌ █▏ ███ ▐█▋ ▐█▙ ▟█▘
12+
▐▛▀▊ ▝ ▕█▀█ ▗▟██▅▅▇▛ ▅██▙▖▗▟█▖ ▝█▌ ▅█▅ ▝█▘╶▟██▖ ▝▜▇▅▄▇▀▘
13+
▝▙▅█▖ ▀━┷▘ ▟█▅▛
14+
▗██▌█▖ ╺━▇▇━ ▄▊▜█▉ ▆▍ ▕▆
15+
▗██▛▗█▛▅▂▝▀▀▁▃▇██▝██▙ █▍▅▛ ▗▆▀▜▅ ▇▆▀▕▇┻▀▆ ▗▇▀▇▖▕█
16+
▄███▇██▎ ▀▀▀▀▀▔ ▐█████▙▁ █▛▜▄ ▜█▀▜▉ █▎ ▕█▏ █▎▜▛▀▜▉▕█
17+
▀▀▔▔▜▄▀▜▅▄▂▁▁▃▄▆▀▚▟▀▔▀▀▘ ▀▘ ▀▘ ▀▀▀▔ ▀ ▀ ▀ ▀▀▀▔ ▀
18+
▝▀▆▄▃████▃▄▆▀▔
19+
```
20+
21+
Linmo is a preemptive, multi-tasking operating system kernel built as an educational showcase for resource-constrained systems.
22+
It offers a lightweight environment where all tasks share a single address space,
23+
keeping the memory footprint to a minimum.
24+
25+
Target Platform:
26+
* RISC-V (32-bit): RV32I architecture, tested with QEMU.
27+
28+
Features:
29+
* Minimal kernel size.
30+
* Lightweight task model with a shared address space.
31+
* Preemptive and cooperative scheduling using a priority-based round-robin algorithm.
32+
* Support for a user-defined real-time scheduler.
33+
* Task synchronization and IPC primitives: semaphores, mutex / condition variable, pipes, and message queues.
34+
* Software timers with callback functionality.
35+
* Dynamic memory allocation.
36+
* A compact C library.
37+
38+
## Getting Started
39+
This section guides you through building the Linmo kernel and running example applications.
40+
41+
### Prerequisites
42+
* A RISC-V cross-compilation toolchain: `riscv-none-elf`, which can be download via [riscv-gnu-toolchain](https://github.com/riscv-collab/riscv-gnu-toolchain).
43+
* The QEMU emulator for RISC-V: `qemu-system-riscv32`.
44+
45+
### Building the Kernel
46+
To build the kernel, run the following command:
47+
48+
```bash
49+
make
50+
```
51+
52+
This command compiles the kernel into a static library (`liblinmo.a`) located in the `build/target` directory.
53+
This library can then be linked against user applications.
54+
55+
### Building and Running an Application
56+
To build and run an application (e.g., the `hello` example) on QEMU for 32-bit RISC-V:
57+
1. Build the application:
58+
```bash
59+
make hello
60+
```
61+
2. Run the application in QEMU:
62+
```bash
63+
make run
64+
```
65+
To exit QEMU, press `Ctrl+a` then `x`.
66+
67+
## Core Concepts
68+
69+
### Tasks
70+
Tasks are the fundamental units of execution in Linmo.
71+
Each task is a function that typically runs in an infinite loop.
72+
All tasks operate within a shared memory space.
73+
At startup, the kernel initializes all registered tasks and allocates a dedicated stack for each.
74+
75+
### Memory Management
76+
77+
#### Stack Allocation
78+
Each task is allocated a dedicated stack from the system heap.
79+
The heap is a shared memory region managed by Linmo's dynamic memory allocator and is available to both the kernel and the application.
80+
81+
A task's stack stores local variables, function call frames, and the CPU context during interrupts or context switches.
82+
The stack size is configurable per-task and must be specified upon task creation.
83+
Each target architecture defines a default stack size through the `DEFAULT_STACK_SIZE` macro in its HAL.
84+
It is crucial to tune the stack size for each task based on its memory requirements.
85+
86+
#### Dynamic Memory Allocation
87+
Linmo provides standard dynamic memory allocation functions (`malloc`, `calloc`, `realloc`, `free`) for both the kernel and applications.
88+
89+
### Scheduling
90+
Linmo supports both cooperative and preemptive multitasking.
91+
The scheduling mode is determined by the return value of the `app_main()` function at startup:
92+
* Cooperative Scheduling (return value `0`): Tasks retain control of the CPU until they explicitly yield it by calling `mo_task_yield()`.
93+
This mode gives the developer full control over when a context switch occurs.
94+
* Preemptive Scheduling (return value `1`): The kernel automatically schedules tasks based on a periodic interrupt,
95+
ensuring that each task gets a fair share of CPU time without needing to yield manually.
96+
97+
The default scheduler uses a priority-based round-robin algorithm.
98+
All tasks are initially assigned `TASK_PRIO_NORMAL`, and the scheduler allocates equal time slices to tasks of the same priority.
99+
Task priorities can be set during initialization in `app_main()` or dynamically at runtime using the `mo_task_priority()` function.
100+
101+
The available priority levels are:
102+
* `TASK_PRIO_CRIT` (Critical)
103+
* `TASK_PRIO_REALTIME` (Real-time)
104+
* `TASK_PRIO_HIGH` (High)
105+
* `TASK_PRIO_ABOVE` (Above Normal)
106+
* `TASK_PRIO_NORMAL` (Normal)
107+
* `TASK_BELOW_PRIO` (Below Normal)
108+
* `TASK_PRIO_LOW` (Low)
109+
* `TASK_PRIO_IDLE` (Lowest)
110+
111+
For more advanced scheduling needs, Linmo supports a user-defined real-time scheduler.
112+
If provided, this scheduler overrides the default round-robin policy for tasks designated as real-time.
113+
Real-time tasks are configured using the `mo_task_rt_priority()` function and linked to the custom scheduler via the kernel's control block.
114+
115+
### Inter-Task Communication (IPC)
116+
Linmo provides several primitives for task synchronization and data exchange, which are essential for building complex embedded applications:
117+
* Semaphores: Counting semaphores for mutual exclusion (mutex) and signaling between tasks.
118+
* Pipes: Unidirectional, byte-oriented channels for streaming data between tasks.
119+
* Message Queues and Event Queues: For structured message passing and event-based signaling (can be enabled in the configuration).
120+
121+
These IPC mechanisms ensure safe and coordinated interactions between concurrent tasks in the shared memory environment.
122+
123+
## Hardware Abstraction Layer (HAL)
124+
Linmo uses a Hardware Abstraction Layer (HAL) to separate the portable kernel code from the underlying hardware-specific details.
125+
This design allows applications to be compiled for different target architectures without modification.
126+
127+
## C Library Support (LibC)
128+
Linmo includes a minimal C library to reduce the overall footprint of the system.
129+
The provided functions are implemented as macros that alias internal library functions.
130+
For applications requiring more features or full standard compliance, these macros can be overridden or removed in the HAL to link against an external C library.
131+
132+
Functions like `printf()` are simplified implementations that provide only essential functionality, optimized for code size.
133+
134+
The following table lists the supported C library functions:
135+
136+
| LibC | | | | |
137+
| :----------- | :---------- | :---------- | :---------- | :---------- |
138+
| `strcpy()` | `strncpy()` | `strcat()` | `strncat()` | `strcmp()` |
139+
| `strncmp()` | `strstr()` | `strlen()` | `strchr()` | `strpbrk()` |
140+
| `strsep()` | `strtok()` | `strtok_r()`| `strtol()` | `atoi()` |
141+
| `itoa()` | `memcpy()` | `memmove()` | `memcmp()` | `memset()` |
142+
| `random_r()` | `random()` | `srand()` | `puts()` | `gets()` |
143+
| `fgets()` | `getline()` | `printf()` | `sprintf()` | `free()` |
144+
| `malloc()` | `calloc()` | `realloc()` | `abs()` | |
145+
146+
## Reference
147+
* [Operating System in 1000 Lines](https://operating-system-in-1000-lines.vercel.app/en/)
148+
* [egos-2000](https://github.com/yhzhang0128/egos-2000)

0 commit comments

Comments
 (0)