pyOS-kernel 0.2.0__py3-none-any.whl → 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pyos/__init__.py +40 -12
- pyos/api/__init__.py +38 -0
- pyos/api/errors.py +35 -0
- pyos/api/fs.py +46 -0
- pyos/{memory → api}/gdt.py +8 -7
- pyos/api/idt.py +1 -0
- pyos/api/interrupts.py +97 -0
- pyos/api/kernel.py +271 -0
- pyos/api/keyboard.py +136 -0
- pyos/api/memory.py +178 -0
- pyos/api/process.py +42 -0
- pyos/api/screen.py +235 -0
- pyos/api/syscalls.py +82 -0
- pyos/boot/bootloader.asm +41 -18
- pyos/build/__init__.py +7 -0
- pyos/{builder.py → build/builder.py} +91 -86
- pyos/build/codegen.py +337 -0
- pyos/cli.py +5 -5
- pyos/emulator.py +1 -1
- pyos/kernel/__init__.py +1 -0
- pyos/kernel/arch/x86/gdt.c +79 -0
- pyos/{runtime/src → kernel/arch/x86}/idt.c +2 -1
- pyos/kernel/arch/x86/paging.c +45 -0
- pyos/{runtime/src → kernel/arch/x86}/start.S +4 -0
- pyos/kernel/arch/x86/syscall.c +84 -0
- pyos/kernel/arch/x86/usercopy.c +32 -0
- pyos/kernel/drivers/floppy.c +19 -0
- pyos/{runtime/src → kernel/drivers}/keyboard.c +37 -3
- pyos/kernel/drivers/pit.c +54 -0
- pyos/{runtime/src → kernel/drivers}/screen.c +34 -0
- pyos/kernel/drivers/shell.c +103 -0
- pyos/kernel/fs/fat12.c +34 -0
- pyos/kernel/fs/ramfs.c +2 -0
- pyos/kernel/fs/vfs.c +108 -0
- pyos/kernel/include/elf.h +8 -0
- pyos/kernel/include/fat12.h +9 -0
- pyos/kernel/include/floppy.h +12 -0
- pyos/kernel/include/gdt.h +9 -0
- pyos/{runtime → kernel}/include/heap.h +4 -0
- pyos/{runtime → kernel}/include/kernel.h +10 -2
- pyos/{runtime → kernel}/include/keyboard.h +3 -1
- pyos/kernel/include/paging.h +11 -0
- pyos/{runtime → kernel}/include/pic.h +2 -0
- pyos/kernel/include/pmm.h +10 -0
- pyos/{runtime → kernel}/include/screen.h +6 -2
- pyos/kernel/include/shell.h +11 -0
- pyos/kernel/include/string.h +12 -0
- pyos/kernel/include/syscall.h +22 -0
- pyos/kernel/include/task.h +35 -0
- pyos/kernel/include/timer.h +12 -0
- pyos/kernel/include/usercopy.h +10 -0
- pyos/kernel/include/vfs.h +26 -0
- pyos/kernel/kmain.c +78 -0
- pyos/{runtime/src → kernel/lib}/debug.c +3 -1
- pyos/kernel/lib/string.c +34 -0
- pyos/kernel/mm/heap.c +110 -0
- pyos/kernel/mm/pmm.c +40 -0
- pyos/kernel/proc/elf.c +11 -0
- pyos/kernel/proc/task.c +90 -0
- pyos_kernel-1.0.0.dist-info/METADATA +474 -0
- pyos_kernel-1.0.0.dist-info/RECORD +78 -0
- pyos_kernel-1.0.0.dist-info/licenses/LICENSE +21 -0
- pyos/compiler/__init__.py +0 -8
- pyos/compiler/codegen.py +0 -204
- pyos/drivers/__init__.py +0 -8
- pyos/drivers/keyboard.py +0 -319
- pyos/drivers/screen.py +0 -310
- pyos/interrupts/__init__.py +0 -8
- pyos/interrupts/handler.py +0 -237
- pyos/interrupts/idt.py +0 -165
- pyos/kernel.py +0 -295
- pyos/memory/__init__.py +0 -8
- pyos/memory/manager.py +0 -397
- pyos/runtime/include/syscall.h +0 -13
- pyos/runtime/src/heap.c +0 -48
- pyos/runtime/src/kmain.c +0 -50
- pyos/runtime/src/syscall.c +0 -43
- pyos/syscalls/__init__.py +0 -7
- pyos/syscalls/handler.py +0 -179
- pyos_kernel-0.2.0.dist-info/METADATA +0 -143
- pyos_kernel-0.2.0.dist-info/RECORD +0 -51
- /pyos/{compiler → build}/assembler.py +0 -0
- /pyos/{toolchain.py → build/toolchain.py} +0 -0
- /pyos/{runtime/src → kernel/arch/x86}/isr.S +0 -0
- /pyos/{runtime/src → kernel/arch/x86}/isr.asm +0 -0
- /pyos/{runtime/src → kernel/arch/x86}/pic.c +0 -0
- /pyos/{runtime/src → kernel/arch/x86}/start.asm +0 -0
- /pyos/{runtime → kernel}/include/debug.h +0 -0
- /pyos/{runtime → kernel}/include/idt.h +0 -0
- /pyos/{runtime → kernel}/include/io.h +0 -0
- /pyos/{runtime → kernel}/include/types.h +0 -0
- /pyos/{runtime → kernel}/linker.ld +0 -0
- {pyos_kernel-0.2.0.dist-info → pyos_kernel-1.0.0.dist-info}/WHEEL +0 -0
- {pyos_kernel-0.2.0.dist-info → pyos_kernel-1.0.0.dist-info}/entry_points.txt +0 -0
- {pyos_kernel-0.2.0.dist-info → pyos_kernel-1.0.0.dist-info}/top_level.txt +0 -0
pyos/__init__.py
CHANGED
|
@@ -1,22 +1,50 @@
|
|
|
1
1
|
"""
|
|
2
|
-
pyOS - Build
|
|
2
|
+
pyOS - Build real operating systems with a Python DSL
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
-
from .
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
5
|
+
from .api import (
|
|
6
|
+
GDT,
|
|
7
|
+
Architecture,
|
|
8
|
+
BuildTimeOnlyError,
|
|
9
|
+
COLOR_NAMES,
|
|
10
|
+
CapabilityError,
|
|
11
|
+
Color,
|
|
12
|
+
InterruptType,
|
|
13
|
+
Interrupts,
|
|
14
|
+
Kernel,
|
|
15
|
+
KernelConfig,
|
|
16
|
+
KeyCode,
|
|
17
|
+
KeyEvent,
|
|
18
|
+
Keyboard,
|
|
19
|
+
Memory,
|
|
20
|
+
Screen,
|
|
21
|
+
SysCall,
|
|
22
|
+
SysCallNumber,
|
|
23
|
+
UnsupportedOpError,
|
|
24
|
+
)
|
|
25
|
+
from .api.fs import File
|
|
26
|
+
from .api.process import Process
|
|
12
27
|
|
|
13
|
-
__version__ = "0.
|
|
28
|
+
__version__ = "1.0.0"
|
|
14
29
|
__all__ = [
|
|
30
|
+
"Architecture",
|
|
31
|
+
"BuildTimeOnlyError",
|
|
32
|
+
"COLOR_NAMES",
|
|
33
|
+
"CapabilityError",
|
|
34
|
+
"Color",
|
|
35
|
+
"File",
|
|
36
|
+
"GDT",
|
|
37
|
+
"InterruptType",
|
|
38
|
+
"Interrupts",
|
|
15
39
|
"Kernel",
|
|
16
|
-
"
|
|
40
|
+
"KernelConfig",
|
|
41
|
+
"KeyCode",
|
|
42
|
+
"KeyEvent",
|
|
17
43
|
"Keyboard",
|
|
18
44
|
"Memory",
|
|
19
|
-
"
|
|
20
|
-
"
|
|
45
|
+
"Process",
|
|
46
|
+
"Screen",
|
|
21
47
|
"SysCall",
|
|
48
|
+
"SysCallNumber",
|
|
49
|
+
"UnsupportedOpError",
|
|
22
50
|
]
|
pyos/api/__init__.py
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""Public Python DSL for building pyOS kernels."""
|
|
2
|
+
|
|
3
|
+
from .errors import BuildTimeOnlyError, CapabilityError, UnsupportedOpError
|
|
4
|
+
from .gdt import GDT, GDTEntry, PrivilegeLevel, SegmentType
|
|
5
|
+
from .interrupts import InterruptFrame, InterruptType, Interrupts
|
|
6
|
+
from .kernel import Architecture, Kernel, KernelConfig
|
|
7
|
+
from .keyboard import KeyCode, KeyEvent, Keyboard
|
|
8
|
+
from .memory import Memory, MemoryBlock, MemoryRegion
|
|
9
|
+
from .screen import COLOR_NAMES, Color, Screen
|
|
10
|
+
from .syscalls import SysCall, SysCallContext, SysCallNumber
|
|
11
|
+
|
|
12
|
+
__all__ = [
|
|
13
|
+
"Architecture",
|
|
14
|
+
"BuildTimeOnlyError",
|
|
15
|
+
"COLOR_NAMES",
|
|
16
|
+
"CapabilityError",
|
|
17
|
+
"Color",
|
|
18
|
+
"GDT",
|
|
19
|
+
"GDTEntry",
|
|
20
|
+
"InterruptFrame",
|
|
21
|
+
"InterruptType",
|
|
22
|
+
"Interrupts",
|
|
23
|
+
"Kernel",
|
|
24
|
+
"KernelConfig",
|
|
25
|
+
"KeyCode",
|
|
26
|
+
"KeyEvent",
|
|
27
|
+
"Keyboard",
|
|
28
|
+
"Memory",
|
|
29
|
+
"MemoryBlock",
|
|
30
|
+
"MemoryRegion",
|
|
31
|
+
"PrivilegeLevel",
|
|
32
|
+
"Screen",
|
|
33
|
+
"SegmentType",
|
|
34
|
+
"SysCall",
|
|
35
|
+
"SysCallContext",
|
|
36
|
+
"SysCallNumber",
|
|
37
|
+
"UnsupportedOpError",
|
|
38
|
+
]
|
pyos/api/errors.py
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""Honest API errors for pyOS — no silent stubs."""
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class CapabilityError(Exception):
|
|
5
|
+
"""Raised when a Python API feature is not available in the current kernel."""
|
|
6
|
+
|
|
7
|
+
def __init__(self, capability: str, hint: str = ""):
|
|
8
|
+
self.capability = capability
|
|
9
|
+
msg = f"Capability '{capability}' is not available in this pyOS build."
|
|
10
|
+
if hint:
|
|
11
|
+
msg = f"{msg}\n Hint: {hint}"
|
|
12
|
+
super().__init__(msg)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class BuildTimeOnlyError(Exception):
|
|
16
|
+
"""Raised when runtime Python logic is expected but only build-time recording exists."""
|
|
17
|
+
|
|
18
|
+
def __init__(self, api: str, hint: str = ""):
|
|
19
|
+
msg = (
|
|
20
|
+
f"'{api}' cannot run arbitrary Python inside the C kernel. "
|
|
21
|
+
"Only recorded build-time operations are emitted to C glue."
|
|
22
|
+
)
|
|
23
|
+
if hint:
|
|
24
|
+
msg = f"{msg}\n Hint: {hint}"
|
|
25
|
+
super().__init__(msg)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class UnsupportedOpError(Exception):
|
|
29
|
+
"""Raised at codegen time when an operation cannot be emitted."""
|
|
30
|
+
|
|
31
|
+
def __init__(self, op: str, hint: str = ""):
|
|
32
|
+
msg = f"Unsupported kernel operation at build time: {op}"
|
|
33
|
+
if hint:
|
|
34
|
+
msg = f"{msg}\n Hint: {hint}"
|
|
35
|
+
super().__init__(msg)
|
pyos/api/fs.py
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"""Filesystem API — available when Kernel capability 'filesystem' is enabled."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Dict, List, Union
|
|
6
|
+
|
|
7
|
+
from .errors import CapabilityError
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class File:
|
|
11
|
+
"""Seed files into the OS image at build time; runtime uses syscalls."""
|
|
12
|
+
|
|
13
|
+
_seed_files: Dict[str, bytes] = {}
|
|
14
|
+
_kernel_ref = None
|
|
15
|
+
|
|
16
|
+
@classmethod
|
|
17
|
+
def bind_kernel(cls, kernel) -> None:
|
|
18
|
+
cls._kernel_ref = kernel
|
|
19
|
+
|
|
20
|
+
@classmethod
|
|
21
|
+
def _require(cls) -> None:
|
|
22
|
+
k = cls._kernel_ref
|
|
23
|
+
if k is None or not k.capabilities.get("filesystem"):
|
|
24
|
+
raise CapabilityError(
|
|
25
|
+
"filesystem",
|
|
26
|
+
"Enable with Kernel(enable_filesystem=True) once Phase 5 runtime is linked",
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
@classmethod
|
|
30
|
+
def write_at_build(cls, path: str, data: Union[bytes, str]) -> None:
|
|
31
|
+
cls._require()
|
|
32
|
+
if isinstance(data, str):
|
|
33
|
+
data = data.encode("ascii", errors="replace")
|
|
34
|
+
cls._seed_files[path.lstrip("/")] = data
|
|
35
|
+
|
|
36
|
+
@classmethod
|
|
37
|
+
def _reset(cls) -> None:
|
|
38
|
+
cls._seed_files = {}
|
|
39
|
+
|
|
40
|
+
@classmethod
|
|
41
|
+
def _get_seeds(cls) -> Dict[str, bytes]:
|
|
42
|
+
return dict(cls._seed_files)
|
|
43
|
+
|
|
44
|
+
@classmethod
|
|
45
|
+
def list_seeds(cls) -> List[str]:
|
|
46
|
+
return list(cls._seed_files.keys())
|
pyos/{memory → api}/gdt.py
RENAMED
|
@@ -235,14 +235,15 @@ class GDT:
|
|
|
235
235
|
|
|
236
236
|
def install(self) -> None:
|
|
237
237
|
"""
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
This must be called after adding all segments.
|
|
241
|
-
|
|
242
|
-
Example:
|
|
243
|
-
gdt.install()
|
|
238
|
+
GDT is installed by the C kernel when enable_user_mode/enable_paging is set.
|
|
239
|
+
Calling this from Python documents intent only.
|
|
244
240
|
"""
|
|
245
|
-
|
|
241
|
+
from .errors import UnsupportedOpError
|
|
242
|
+
|
|
243
|
+
raise UnsupportedOpError(
|
|
244
|
+
"GDT.install",
|
|
245
|
+
"Pass Kernel(enable_user_mode=True) or enable_paging=True — C gdt_init() runs at boot",
|
|
246
|
+
)
|
|
246
247
|
|
|
247
248
|
def get_selector(self, index: int, ring: int = 0) -> int:
|
|
248
249
|
"""
|
pyos/api/idt.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# pyOS package
|
pyos/api/interrupts.py
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"""
|
|
2
|
+
pyOS Interrupt API — honest: enable/disable/mask emit to C; custom handlers unsupported.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from enum import Enum
|
|
9
|
+
from typing import Callable, Dict, Optional
|
|
10
|
+
|
|
11
|
+
from .errors import UnsupportedOpError
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class InterruptType(Enum):
|
|
15
|
+
DIVIDE_ERROR = 0
|
|
16
|
+
IRQ_TIMER = 32
|
|
17
|
+
IRQ_KEYBOARD = 33
|
|
18
|
+
SYSCALL = 0x80
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass
|
|
22
|
+
class InterruptFrame:
|
|
23
|
+
eip: int = 0
|
|
24
|
+
cs: int = 0
|
|
25
|
+
eflags: int = 0
|
|
26
|
+
esp: int = 0
|
|
27
|
+
ss: int = 0
|
|
28
|
+
error_code: int = 0
|
|
29
|
+
interrupt_number: int = 0
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class Interrupts:
|
|
33
|
+
_handlers: Dict[int, Callable] = {}
|
|
34
|
+
_enabled: bool = False
|
|
35
|
+
_operations: list = []
|
|
36
|
+
|
|
37
|
+
@classmethod
|
|
38
|
+
def handler(cls, interrupt: InterruptType):
|
|
39
|
+
def decorator(func: Callable) -> Callable:
|
|
40
|
+
raise UnsupportedOpError(
|
|
41
|
+
f"Interrupts.handler({interrupt.name})",
|
|
42
|
+
"Use @kernel.on_keypress / @kernel.on_timer; IDT is owned by the C kernel",
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
return decorator
|
|
46
|
+
|
|
47
|
+
@classmethod
|
|
48
|
+
def register(cls, interrupt_number: int, handler: Callable) -> None:
|
|
49
|
+
raise UnsupportedOpError("Interrupts.register")
|
|
50
|
+
|
|
51
|
+
@classmethod
|
|
52
|
+
def unregister(cls, interrupt_number: int) -> None:
|
|
53
|
+
raise UnsupportedOpError("Interrupts.unregister")
|
|
54
|
+
|
|
55
|
+
@classmethod
|
|
56
|
+
def enable(cls) -> None:
|
|
57
|
+
cls._enabled = True
|
|
58
|
+
cls._operations.append({"type": "irq_enable"})
|
|
59
|
+
|
|
60
|
+
@classmethod
|
|
61
|
+
def disable(cls) -> None:
|
|
62
|
+
cls._enabled = False
|
|
63
|
+
cls._operations.append({"type": "irq_disable"})
|
|
64
|
+
|
|
65
|
+
@classmethod
|
|
66
|
+
def is_enabled(cls) -> bool:
|
|
67
|
+
return cls._enabled
|
|
68
|
+
|
|
69
|
+
@classmethod
|
|
70
|
+
def send_eoi(cls, irq: int) -> None:
|
|
71
|
+
cls._operations.append({"type": "irq_eoi", "irq": int(irq)})
|
|
72
|
+
|
|
73
|
+
@classmethod
|
|
74
|
+
def mask_irq(cls, irq: int) -> None:
|
|
75
|
+
cls._operations.append({"type": "irq_mask", "irq": int(irq)})
|
|
76
|
+
|
|
77
|
+
@classmethod
|
|
78
|
+
def unmask_irq(cls, irq: int) -> None:
|
|
79
|
+
cls._operations.append({"type": "irq_unmask", "irq": int(irq)})
|
|
80
|
+
|
|
81
|
+
@classmethod
|
|
82
|
+
def trigger_software_interrupt(cls, interrupt_number: int) -> None:
|
|
83
|
+
raise UnsupportedOpError("Interrupts.trigger_software_interrupt")
|
|
84
|
+
|
|
85
|
+
@classmethod
|
|
86
|
+
def get_handler(cls, interrupt_number: int) -> Optional[Callable]:
|
|
87
|
+
return None
|
|
88
|
+
|
|
89
|
+
@classmethod
|
|
90
|
+
def _reset(cls) -> None:
|
|
91
|
+
cls._handlers = {}
|
|
92
|
+
cls._enabled = False
|
|
93
|
+
cls._operations = []
|
|
94
|
+
|
|
95
|
+
@classmethod
|
|
96
|
+
def _get_operations(cls) -> list:
|
|
97
|
+
return cls._operations.copy()
|
pyos/api/kernel.py
ADDED
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
"""
|
|
2
|
+
pyOS Kernel — Python DSL that builds a real freestanding C kernel.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
from dataclasses import dataclass, field
|
|
8
|
+
from enum import Enum
|
|
9
|
+
from typing import Any, Callable, Dict, List, Optional
|
|
10
|
+
|
|
11
|
+
from .errors import CapabilityError, UnsupportedOpError
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Architecture(Enum):
|
|
15
|
+
X86 = "x86"
|
|
16
|
+
X86_64 = "x86_64"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass
|
|
20
|
+
class KernelConfig:
|
|
21
|
+
arch: Architecture = Architecture.X86
|
|
22
|
+
stack_size: int = 16384
|
|
23
|
+
heap_size: int = 1048576
|
|
24
|
+
heap_start: int = 0x200000
|
|
25
|
+
stack_top: int = 0x90000
|
|
26
|
+
video_mode: str = "text"
|
|
27
|
+
enable_interrupts: bool = True
|
|
28
|
+
enable_gdt: bool = True
|
|
29
|
+
enable_paging: bool = False
|
|
30
|
+
enable_user_mode: bool = False
|
|
31
|
+
enable_processes: bool = False
|
|
32
|
+
enable_filesystem: bool = False
|
|
33
|
+
debug_level: str = "lab" # quiet | lab
|
|
34
|
+
keypress_mode: str = "echo" # echo | custom
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@dataclass
|
|
38
|
+
class KernelFunction:
|
|
39
|
+
name: str
|
|
40
|
+
func: Callable
|
|
41
|
+
event: str
|
|
42
|
+
priority: int = 0
|
|
43
|
+
key_filter: Optional[str] = None
|
|
44
|
+
interval_ms: int = 1000
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
# Active kernel for Memory/Screen/Process/File binding during boot recording
|
|
48
|
+
_ACTIVE_KERNEL: Optional["Kernel"] = None
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def get_active_kernel() -> Optional["Kernel"]:
|
|
52
|
+
return _ACTIVE_KERNEL
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class Kernel:
|
|
56
|
+
"""
|
|
57
|
+
Build-time OS definition. Boot handlers run on the host to record ops;
|
|
58
|
+
the C kernel executes the generated glue at runtime.
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
def __init__(
|
|
62
|
+
self,
|
|
63
|
+
arch: str = "x86",
|
|
64
|
+
stack_size: int = 16384,
|
|
65
|
+
heap_size: int = 1048576,
|
|
66
|
+
enable_interrupts: bool = True,
|
|
67
|
+
enable_gdt: bool = True,
|
|
68
|
+
enable_paging: bool = False,
|
|
69
|
+
enable_user_mode: bool = False,
|
|
70
|
+
enable_processes: bool = False,
|
|
71
|
+
enable_filesystem: bool = False,
|
|
72
|
+
debug_level: str = "lab",
|
|
73
|
+
keypress_mode: str = "echo",
|
|
74
|
+
):
|
|
75
|
+
if arch != "x86":
|
|
76
|
+
raise ValueError(
|
|
77
|
+
"Only arch='x86' is supported.\n"
|
|
78
|
+
" Why: the C kernel and bootloader are 32-bit protected mode.\n"
|
|
79
|
+
" Hint: use Kernel(arch='x86')"
|
|
80
|
+
)
|
|
81
|
+
if debug_level not in ("quiet", "lab"):
|
|
82
|
+
raise ValueError("debug_level must be 'quiet' or 'lab'")
|
|
83
|
+
if keypress_mode not in ("echo", "custom"):
|
|
84
|
+
raise ValueError("keypress_mode must be 'echo' or 'custom'")
|
|
85
|
+
|
|
86
|
+
# stack grows down from stack_top; keep classic low-memory top
|
|
87
|
+
stack_top = 0x90000
|
|
88
|
+
if stack_size < 4096 or stack_size > 0x80000:
|
|
89
|
+
raise ValueError("stack_size must be between 4096 and 524288")
|
|
90
|
+
|
|
91
|
+
self.config = KernelConfig(
|
|
92
|
+
arch=Architecture(arch),
|
|
93
|
+
stack_size=stack_size,
|
|
94
|
+
heap_size=heap_size,
|
|
95
|
+
stack_top=stack_top,
|
|
96
|
+
enable_interrupts=enable_interrupts,
|
|
97
|
+
enable_gdt=enable_gdt,
|
|
98
|
+
enable_paging=enable_paging,
|
|
99
|
+
enable_user_mode=enable_user_mode,
|
|
100
|
+
enable_processes=enable_processes,
|
|
101
|
+
enable_filesystem=enable_filesystem,
|
|
102
|
+
debug_level=debug_level,
|
|
103
|
+
keypress_mode=keypress_mode,
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
self.capabilities: Dict[str, bool] = {
|
|
107
|
+
"screen_text": True,
|
|
108
|
+
"heap_alloc": True,
|
|
109
|
+
"heap_free": True,
|
|
110
|
+
"keyboard_irq": True,
|
|
111
|
+
"timer": True,
|
|
112
|
+
"syscalls": True,
|
|
113
|
+
"paging": enable_paging,
|
|
114
|
+
"user_mode": enable_user_mode,
|
|
115
|
+
"processes": enable_processes,
|
|
116
|
+
"filesystem": enable_filesystem,
|
|
117
|
+
"gdt_runtime": enable_gdt,
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
self._boot_functions: List[KernelFunction] = []
|
|
121
|
+
self._interrupt_handlers: Dict[int, KernelFunction] = {}
|
|
122
|
+
self._syscall_handlers: Dict[int, KernelFunction] = {}
|
|
123
|
+
self._keypress_handlers: List[KernelFunction] = []
|
|
124
|
+
self._timer_handlers: List[KernelFunction] = []
|
|
125
|
+
self._custom_handlers: Dict[str, List[KernelFunction]] = {}
|
|
126
|
+
self._compiled_asm: Optional[str] = None
|
|
127
|
+
self._compiled_binary: Optional[bytes] = None
|
|
128
|
+
self._seed_files: Dict[str, bytes] = {}
|
|
129
|
+
|
|
130
|
+
from .fs import File
|
|
131
|
+
from .memory import Memory
|
|
132
|
+
from .process import Process
|
|
133
|
+
|
|
134
|
+
Memory.bind_kernel(self)
|
|
135
|
+
Process.bind_kernel(self)
|
|
136
|
+
File.bind_kernel(self)
|
|
137
|
+
|
|
138
|
+
def require_capability(self, name: str, hint: str = "") -> None:
|
|
139
|
+
if not self.capabilities.get(name):
|
|
140
|
+
raise CapabilityError(name, hint or f"Pass the matching Kernel(...) flag to enable '{name}'")
|
|
141
|
+
|
|
142
|
+
def on_boot(self, func: Callable = None, *, priority: int = 0):
|
|
143
|
+
def decorator(f: Callable) -> Callable:
|
|
144
|
+
self._boot_functions.append(
|
|
145
|
+
KernelFunction(name=f.__name__, func=f, event="boot", priority=priority)
|
|
146
|
+
)
|
|
147
|
+
self._boot_functions.sort(key=lambda x: x.priority)
|
|
148
|
+
return f
|
|
149
|
+
|
|
150
|
+
if func is not None:
|
|
151
|
+
return decorator(func)
|
|
152
|
+
return decorator
|
|
153
|
+
|
|
154
|
+
def on_keypress(self, func: Callable = None, *, key: str = None, mode: str = None):
|
|
155
|
+
def decorator(f: Callable) -> Callable:
|
|
156
|
+
handler = KernelFunction(name=f.__name__, func=f, event="keypress")
|
|
157
|
+
handler.key_filter = key
|
|
158
|
+
self._keypress_handlers.append(handler)
|
|
159
|
+
if mode:
|
|
160
|
+
self.config.keypress_mode = mode
|
|
161
|
+
return f
|
|
162
|
+
|
|
163
|
+
if func is not None:
|
|
164
|
+
return decorator(func)
|
|
165
|
+
return decorator
|
|
166
|
+
|
|
167
|
+
def on_interrupt(self, interrupt_number: int):
|
|
168
|
+
def decorator(func: Callable) -> Callable:
|
|
169
|
+
raise UnsupportedOpError(
|
|
170
|
+
f"on_interrupt({interrupt_number})",
|
|
171
|
+
"Custom IRQ handlers are reserved; use @on_keypress / @on_timer / built-in IDT",
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
return decorator
|
|
175
|
+
|
|
176
|
+
def on_syscall(self, syscall_number: int):
|
|
177
|
+
def decorator(func: Callable) -> Callable:
|
|
178
|
+
raise UnsupportedOpError(
|
|
179
|
+
f"on_syscall({syscall_number})",
|
|
180
|
+
"Syscall table is fixed in C (see SysCallNumber). Extend the C dispatcher to add numbers.",
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
return decorator
|
|
184
|
+
|
|
185
|
+
def on_timer(self, interval_ms: int = 1000):
|
|
186
|
+
def decorator(func: Callable) -> Callable:
|
|
187
|
+
self.require_capability("timer", "Timer requires enable_interrupts=True")
|
|
188
|
+
if not self.config.enable_interrupts:
|
|
189
|
+
raise CapabilityError("timer", "Set enable_interrupts=True")
|
|
190
|
+
handler = KernelFunction(name=func.__name__, func=func, event="timer")
|
|
191
|
+
handler.interval_ms = max(10, int(interval_ms))
|
|
192
|
+
self._timer_handlers.append(handler)
|
|
193
|
+
return func
|
|
194
|
+
|
|
195
|
+
return decorator
|
|
196
|
+
|
|
197
|
+
def on_event(self, event_name: str):
|
|
198
|
+
def decorator(func: Callable) -> Callable:
|
|
199
|
+
raise UnsupportedOpError(
|
|
200
|
+
f"on_event({event_name!r})",
|
|
201
|
+
"Custom event bus is not part of the C kernel; use on_boot/on_timer/on_keypress",
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
return decorator
|
|
205
|
+
|
|
206
|
+
def seed_file(self, path: str, data) -> None:
|
|
207
|
+
self.require_capability("filesystem")
|
|
208
|
+
if isinstance(data, str):
|
|
209
|
+
data = data.encode("ascii", errors="replace")
|
|
210
|
+
self._seed_files[path.lstrip("/")] = data
|
|
211
|
+
|
|
212
|
+
def compile(self) -> str:
|
|
213
|
+
global _ACTIVE_KERNEL
|
|
214
|
+
from ..build.codegen import CodeGenerator
|
|
215
|
+
|
|
216
|
+
_ACTIVE_KERNEL = self
|
|
217
|
+
try:
|
|
218
|
+
generator = CodeGenerator(self)
|
|
219
|
+
self._compiled_asm = generator.generate()
|
|
220
|
+
return self._compiled_asm
|
|
221
|
+
finally:
|
|
222
|
+
_ACTIVE_KERNEL = None
|
|
223
|
+
|
|
224
|
+
def assemble(self) -> bytes:
|
|
225
|
+
from pathlib import Path
|
|
226
|
+
import tempfile
|
|
227
|
+
|
|
228
|
+
from ..build.builder import OSBuilder
|
|
229
|
+
|
|
230
|
+
builder = OSBuilder(self)
|
|
231
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
232
|
+
path = Path(tmp) / "kernel.bin"
|
|
233
|
+
builder.build_bin(str(path))
|
|
234
|
+
self._compiled_binary = path.read_bytes()
|
|
235
|
+
return self._compiled_binary
|
|
236
|
+
|
|
237
|
+
def build(self, output: str, format: str = "bin") -> str:
|
|
238
|
+
from ..build.builder import OSBuilder
|
|
239
|
+
|
|
240
|
+
builder = OSBuilder(self)
|
|
241
|
+
if format == "iso":
|
|
242
|
+
return builder.build_iso(output)
|
|
243
|
+
if format == "bin":
|
|
244
|
+
return builder.build_bin(output)
|
|
245
|
+
raise ValueError(f"Unknown format: {format}")
|
|
246
|
+
|
|
247
|
+
def run(self, image_path: str = None, debug: bool = False):
|
|
248
|
+
from ..emulator import QEMURunner
|
|
249
|
+
|
|
250
|
+
if image_path is None:
|
|
251
|
+
image_path = self.build("temp_os.bin")
|
|
252
|
+
runner = QEMURunner(self.config.arch)
|
|
253
|
+
runner.run(image_path, debug=debug)
|
|
254
|
+
|
|
255
|
+
def get_info(self) -> Dict[str, Any]:
|
|
256
|
+
return {
|
|
257
|
+
"architecture": self.config.arch.value,
|
|
258
|
+
"stack_size": self.config.stack_size,
|
|
259
|
+
"stack_top": self.config.stack_top,
|
|
260
|
+
"heap_size": self.config.heap_size,
|
|
261
|
+
"interrupts_enabled": self.config.enable_interrupts,
|
|
262
|
+
"gdt_enabled": self.config.enable_gdt,
|
|
263
|
+
"paging": self.config.enable_paging,
|
|
264
|
+
"user_mode": self.config.enable_user_mode,
|
|
265
|
+
"processes": self.config.enable_processes,
|
|
266
|
+
"filesystem": self.config.enable_filesystem,
|
|
267
|
+
"boot_functions": len(self._boot_functions),
|
|
268
|
+
"keypress_handlers": len(self._keypress_handlers),
|
|
269
|
+
"timer_handlers": len(self._timer_handlers),
|
|
270
|
+
"capabilities": dict(self.capabilities),
|
|
271
|
+
}
|