update v2
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,4 +1,5 @@
|
|||||||
octox
|
octox
|
||||||
|
redox
|
||||||
target
|
target
|
||||||
.claude
|
.claude
|
||||||
*.log
|
*.log
|
||||||
|
@ -1,12 +0,0 @@
|
|||||||
# Bootloader configuration
|
|
||||||
[bootloader]
|
|
||||||
# Map physical memory for accessing boot info
|
|
||||||
map_physical_memory = true
|
|
||||||
|
|
||||||
# Frame buffer settings
|
|
||||||
[bootloader.framebuffer]
|
|
||||||
# Enable framebuffer mode (VGA compatible)
|
|
||||||
enable = true
|
|
||||||
width = 80
|
|
||||||
height = 25
|
|
||||||
format = "text"
|
|
36
Cargo.toml
36
Cargo.toml
@ -1,30 +1,20 @@
|
|||||||
[package]
|
[workspace]
|
||||||
name = "aios"
|
members = [
|
||||||
version = "0.1.0"
|
"kernel",
|
||||||
|
"user",
|
||||||
|
"claude-service",
|
||||||
|
]
|
||||||
|
|
||||||
|
[workspace.package]
|
||||||
|
version = "2.0.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
license = "MIT"
|
||||||
|
|
||||||
[dependencies]
|
[workspace.dependencies]
|
||||||
bootloader = "0.9.23"
|
kernel = { path = "kernel", default-features = false }
|
||||||
volatile = "0.2"
|
|
||||||
spin = "0.9"
|
|
||||||
x86_64 = "0.14"
|
|
||||||
uart_16550 = "0.2"
|
|
||||||
pic8259 = "0.10"
|
|
||||||
pc-keyboard = "0.7"
|
|
||||||
|
|
||||||
[dependencies.lazy_static]
|
|
||||||
version = "1.4"
|
|
||||||
features = ["spin_no_std"]
|
|
||||||
|
|
||||||
[[bin]]
|
|
||||||
name = "kernel"
|
|
||||||
path = "src/main.rs"
|
|
||||||
|
|
||||||
[target.'cfg(target_os = "none")'.dependencies]
|
|
||||||
linked_list_allocator = "0.10"
|
|
||||||
|
|
||||||
[profile.dev]
|
[profile.dev]
|
||||||
panic = "abort"
|
panic = "abort"
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
panic = "abort"
|
panic = "abort"
|
||||||
|
92
README.md
92
README.md
@ -1,12 +1,92 @@
|
|||||||
# aios
|
# Aios - AI Operating System
|
||||||
|
|
||||||
create a unix-like os from scratch
|
A Unix-like operating system built from scratch in Rust, designed for AI integration.
|
||||||
|
|
||||||
## example
|
## Features
|
||||||
|
|
||||||
|
- **Interactive Shell**: Full keyboard input support with command processing
|
||||||
|
- **Safe Kernel**: Pure Rust implementation with memory safety
|
||||||
|
- **Minimal Design**: Inspired by octox philosophy - simple and efficient
|
||||||
|
- **Bootloader Integration**: Uses rust-bootloader for reliable booting
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
# archlinux
|
# Build and test the OS
|
||||||
cargo bootimage
|
./scpt/bootimage-test.sh
|
||||||
qemu-system-x86_64 -drive format=raw,file=target/x86_64-unknown-none/debug/bootimage-kernel.bin -nographic
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Available Commands
|
||||||
|
|
||||||
|
- `help` - Show available commands
|
||||||
|
- `version` - Display version information
|
||||||
|
- `echo` - Echo test message
|
||||||
|
- `clear` - Clear the screen
|
||||||
|
|
||||||
|
## Project Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
aios/
|
||||||
|
├── kernel/ # Main kernel implementation
|
||||||
|
│ ├── src/
|
||||||
|
│ │ ├── main.rs # Kernel entry point
|
||||||
|
│ │ ├── keyboard_basic.rs # Keyboard driver
|
||||||
|
│ │ └── ...
|
||||||
|
│ └── Cargo.toml
|
||||||
|
├── docs/ # Documentation
|
||||||
|
│ ├── TROUBLESHOOTING.md # Common issues and solutions
|
||||||
|
│ └── SHELL_IMPLEMENTATION.md # Shell implementation guide
|
||||||
|
└── scpt/ # Build and test scripts
|
||||||
|
```
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
- [Troubleshooting Guide](docs/TROUBLESHOOTING.md) - Solutions for common issues
|
||||||
|
- [Shell Implementation](docs/SHELL_IMPLEMENTATION.md) - Detailed implementation guide
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
### Requirements
|
||||||
|
|
||||||
|
- Rust nightly toolchain
|
||||||
|
- `bootimage` tool: `cargo install bootimage`
|
||||||
|
- QEMU for testing
|
||||||
|
- x86_64 target: `rustup target add x86_64-unknown-none`
|
||||||
|
|
||||||
|
### Building
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cd kernel
|
||||||
|
cargo bootimage --release
|
||||||
|
```
|
||||||
|
|
||||||
|
### Testing
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# Interactive test with QEMU
|
||||||
|
./scpt/bootimage-test.sh
|
||||||
|
|
||||||
|
# The OS will boot and present an interactive shell
|
||||||
|
# Type commands and press Enter to execute
|
||||||
|
```
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
Aios follows a microkernel-inspired design:
|
||||||
|
|
||||||
|
- **Kernel Space**: Minimal kernel with basic I/O and memory management
|
||||||
|
- **User Space**: Shell and applications (planned)
|
||||||
|
- **AI Integration**: Claude service integration (planned)
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
If you encounter issues:
|
||||||
|
|
||||||
|
1. **Page table panics**: See [TROUBLESHOOTING.md](docs/TROUBLESHOOTING.md#1-page-table-panic-error-page_tablers)
|
||||||
|
2. **Keyboard not working**: See [TROUBLESHOOTING.md](docs/TROUBLESHOOTING.md#2-keyboard-input-not-working)
|
||||||
|
3. **Build errors**: Check Rust nightly and required components
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
8
claude-service/Cargo.toml
Normal file
8
claude-service/Cargo.toml
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
[package]
|
||||||
|
name = "claude-service"
|
||||||
|
version.workspace = true
|
||||||
|
edition.workspace = true
|
||||||
|
license.workspace = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
# Placeholder for Claude integration service
|
1
claude-service/src/lib.rs
Normal file
1
claude-service/src/lib.rs
Normal file
@ -0,0 +1 @@
|
|||||||
|
// AIOS v2 Claude Service placeholder
|
@ -43,8 +43,6 @@ tar -zcvf aios-bootstrap.tar.gz root.x86_64/
|
|||||||
|
|
||||||
## ref
|
## ref
|
||||||
|
|
||||||
[o8vm/octox](https://github.com/o8vm/octox)が参考になるかもしれない。
|
- [redox-os/redox](redox-os/redox)
|
||||||
|
- [o8vm/octox](https://github.com/o8vm/octox)
|
||||||
|
|
||||||
```sh
|
|
||||||
git clone https://github.com/o8vm/octox
|
|
||||||
```
|
|
||||||
|
378
docs/SHELL_IMPLEMENTATION.md
Normal file
378
docs/SHELL_IMPLEMENTATION.md
Normal file
@ -0,0 +1,378 @@
|
|||||||
|
# Aios Shell Implementation Guide
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Aiosのインタラクティブシェル実装についての詳細ガイド。
|
||||||
|
|
||||||
|
## Implementation History
|
||||||
|
|
||||||
|
### Phase 1: Initial Attempt (Failed)
|
||||||
|
|
||||||
|
**実装内容:**
|
||||||
|
```rust
|
||||||
|
// keyboard.rs - 複雑な実装
|
||||||
|
use core::convert::TryFrom;
|
||||||
|
|
||||||
|
static SCANCODE_TO_ASCII: [u8; 128] = [
|
||||||
|
// 複雑なマッピングテーブル
|
||||||
|
];
|
||||||
|
|
||||||
|
unsafe fn inb(port: u16) -> u8 {
|
||||||
|
// 危険なポートアクセス
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**問題:**
|
||||||
|
- Page table panic (`page_table*.rs:105:25`)
|
||||||
|
- ポートI/O競合
|
||||||
|
- 配列サイズ不一致
|
||||||
|
|
||||||
|
### Phase 2: Safe Approach (Partially Working)
|
||||||
|
|
||||||
|
**実装内容:**
|
||||||
|
```rust
|
||||||
|
// keyboard_safe.rs
|
||||||
|
unsafe fn try_inb(port: u16) -> Option<u8> {
|
||||||
|
// 遅延付きアクセス
|
||||||
|
for _ in 0..1000 {
|
||||||
|
core::arch::asm!("nop");
|
||||||
|
}
|
||||||
|
// ... 安全なアクセス
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**結果:**
|
||||||
|
- パニックは解決
|
||||||
|
- キーボード入力が機能しない
|
||||||
|
|
||||||
|
### Phase 3: Basic Implementation (Success)
|
||||||
|
|
||||||
|
**実装内容:**
|
||||||
|
```rust
|
||||||
|
// keyboard_basic.rs - シンプルなアプローチ
|
||||||
|
pub fn has_key() -> bool {
|
||||||
|
unsafe {
|
||||||
|
let status: u8;
|
||||||
|
core::arch::asm!(
|
||||||
|
"in al, 0x64",
|
||||||
|
out("al") status,
|
||||||
|
options(nomem, nostack, preserves_flags)
|
||||||
|
);
|
||||||
|
(status & 1) != 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**結果:**
|
||||||
|
- パニック解決
|
||||||
|
- キーボード入力動作
|
||||||
|
- スキャンコード正常取得
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
### Keyboard Driver (`keyboard_basic.rs`)
|
||||||
|
|
||||||
|
```rust
|
||||||
|
/// PS/2キーボードの状態確認
|
||||||
|
pub fn has_key() -> bool
|
||||||
|
|
||||||
|
/// 生スキャンコードの読み取り
|
||||||
|
pub fn read_scancode() -> u8
|
||||||
|
|
||||||
|
/// スキャンコードからASCII文字への変換
|
||||||
|
pub fn scancode_to_char(scancode: u8) -> Option<char>
|
||||||
|
```
|
||||||
|
|
||||||
|
**キーマッピング:**
|
||||||
|
```rust
|
||||||
|
match scancode {
|
||||||
|
0x1E => Some('a'), // A key
|
||||||
|
0x30 => Some('b'), // B key
|
||||||
|
0x2E => Some('c'), // C key
|
||||||
|
// ...
|
||||||
|
0x39 => Some(' '), // Space
|
||||||
|
0x1C => Some('\n'), // Enter
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Shell Core (`main.rs`)
|
||||||
|
|
||||||
|
**State Management:**
|
||||||
|
```rust
|
||||||
|
static mut INPUT_BUFFER: [u8; 64] = [0; 64];
|
||||||
|
static mut INPUT_POS: usize = 0;
|
||||||
|
static mut CURSOR_ROW: usize = 12;
|
||||||
|
static mut CURSOR_COL: usize = 6;
|
||||||
|
```
|
||||||
|
|
||||||
|
**Input Processing:**
|
||||||
|
```rust
|
||||||
|
fn process_char(ch: char) {
|
||||||
|
match ch {
|
||||||
|
'\n' => execute_command(),
|
||||||
|
'\u{8}' => handle_backspace(),
|
||||||
|
' '..='~' => add_char_to_buffer(ch),
|
||||||
|
_ => { /* ignore */ }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Command Execution:**
|
||||||
|
```rust
|
||||||
|
fn execute_command() {
|
||||||
|
let cmd = core::str::from_utf8(&INPUT_BUFFER[..INPUT_POS])?;
|
||||||
|
match cmd {
|
||||||
|
"help" => show_help(),
|
||||||
|
"version" => show_version(),
|
||||||
|
"echo" => echo_test(),
|
||||||
|
"clear" => clear_screen(),
|
||||||
|
_ => show_unknown_command(cmd),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Key Features
|
||||||
|
|
||||||
|
### 1. Real-time Input
|
||||||
|
- キー押下の即座の反映
|
||||||
|
- バックスペース対応
|
||||||
|
- 文字制限(64文字)
|
||||||
|
|
||||||
|
### 2. Command Processing
|
||||||
|
- Enter キーでコマンド実行
|
||||||
|
- コマンド履歴(将来実装予定)
|
||||||
|
- エラーハンドリング
|
||||||
|
|
||||||
|
### 3. Available Commands
|
||||||
|
|
||||||
|
**help**
|
||||||
|
```
|
||||||
|
Aios Commands:
|
||||||
|
help - Show this help
|
||||||
|
version - Show version
|
||||||
|
echo - Echo test
|
||||||
|
clear - Clear screen
|
||||||
|
```
|
||||||
|
|
||||||
|
**version**
|
||||||
|
```
|
||||||
|
Aios 2.0 - AI Operating System
|
||||||
|
Interactive Shell with Keyboard Support
|
||||||
|
```
|
||||||
|
|
||||||
|
**echo**
|
||||||
|
```
|
||||||
|
Hello from Aios Interactive Shell!
|
||||||
|
```
|
||||||
|
|
||||||
|
**clear**
|
||||||
|
- 画面全体をクリア
|
||||||
|
- カーソル位置をリセット
|
||||||
|
|
||||||
|
### 4. Input Handling
|
||||||
|
|
||||||
|
**Scancode Processing:**
|
||||||
|
```rust
|
||||||
|
loop {
|
||||||
|
if keyboard_basic::has_key() {
|
||||||
|
let scancode = keyboard_basic::read_scancode();
|
||||||
|
|
||||||
|
// キーリリースを除外
|
||||||
|
if scancode != last_scancode && (scancode & 0x80) == 0 {
|
||||||
|
last_scancode = scancode;
|
||||||
|
|
||||||
|
if let Some(ch) = keyboard_basic::scancode_to_char(scancode) {
|
||||||
|
process_char(ch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CPU負荷軽減
|
||||||
|
for _ in 0..1000 {
|
||||||
|
unsafe { core::arch::asm!("nop"); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## VGA Text Mode Interface
|
||||||
|
|
||||||
|
### Screen Layout
|
||||||
|
```
|
||||||
|
Row 0: "Aios - AI Operating System" (Center)
|
||||||
|
Row 1: "Interactive Shell Ready" (Center)
|
||||||
|
Row 2: "Type 'help' for commands" (Center)
|
||||||
|
Row 3: [Empty]
|
||||||
|
Row 4: [Empty]
|
||||||
|
Row 5: aios> [user input] (Prompt)
|
||||||
|
Row 6+: [Command output]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Character Display
|
||||||
|
```rust
|
||||||
|
pub fn print_char(c: u8, x: usize, y: usize) {
|
||||||
|
if x >= 80 || y >= 25 { return; }
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
let vga = &mut *VGA_BUFFER;
|
||||||
|
vga.chars[y][x] = (0x0f << 8) | (c as u16);
|
||||||
|
// ^^^^ ^^^^^^^^^^
|
||||||
|
// Color Character
|
||||||
|
// (White/Black) (ASCII)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Color Scheme:**
|
||||||
|
- `0x0f` = White text on black background
|
||||||
|
- Format: `(color << 8) | character`
|
||||||
|
|
||||||
|
## Testing Procedures
|
||||||
|
|
||||||
|
### 1. Scancode Debug Test
|
||||||
|
```rust
|
||||||
|
// 目的: キーボード入力の確認
|
||||||
|
// 表示: "Scancode: 0x1E -> 'a'"
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Interactive Shell Test
|
||||||
|
```bash
|
||||||
|
# テストシーケンス
|
||||||
|
1. help <Enter> # ヘルプ表示確認
|
||||||
|
2. version <Enter> # バージョン情報確認
|
||||||
|
3. echo <Enter> # エコーテスト確認
|
||||||
|
4. clear <Enter> # 画面クリア確認
|
||||||
|
5. invalid <Enter> # エラーハンドリング確認
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Input Edge Cases
|
||||||
|
```bash
|
||||||
|
# テストケース
|
||||||
|
- 空のコマンド (Enter のみ)
|
||||||
|
- 長いコマンド (64文字以上)
|
||||||
|
- バックスペースの動作
|
||||||
|
- 連続入力
|
||||||
|
```
|
||||||
|
|
||||||
|
## Performance Considerations
|
||||||
|
|
||||||
|
### CPU Usage
|
||||||
|
```rust
|
||||||
|
// 軽量な遅延ループ
|
||||||
|
for _ in 0..1000 {
|
||||||
|
unsafe { core::arch::asm!("nop"); }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Memory Usage
|
||||||
|
- Input buffer: 64 bytes
|
||||||
|
- VGA buffer: Direct access (no buffering)
|
||||||
|
- Static variables: Minimal usage
|
||||||
|
|
||||||
|
### Polling Frequency
|
||||||
|
- 高頻度ポーリング(遅延1000サイクル)
|
||||||
|
- CPU halt命令は使用しない(応答性重視)
|
||||||
|
|
||||||
|
## Known Limitations
|
||||||
|
|
||||||
|
### 1. Character Set
|
||||||
|
- ASCII printable characters only (`' '` to `'~'`)
|
||||||
|
- 日本語等のマルチバイト文字は未対応
|
||||||
|
- 特殊キー(F1-F12, Arrow keys)は未対応
|
||||||
|
|
||||||
|
### 2. Input Features
|
||||||
|
- コマンド履歴なし
|
||||||
|
- Tab補完なし
|
||||||
|
- 行編集機能なし(カーソル移動等)
|
||||||
|
|
||||||
|
### 3. Display
|
||||||
|
- 80x25 text mode固定
|
||||||
|
- スクロール機能なし
|
||||||
|
- 色変更機能なし
|
||||||
|
|
||||||
|
## Future Enhancements
|
||||||
|
|
||||||
|
### 1. Advanced Input
|
||||||
|
```rust
|
||||||
|
// 計画中の機能
|
||||||
|
- Command history (↑/↓ keys)
|
||||||
|
- Tab completion
|
||||||
|
- Line editing (←/→ keys)
|
||||||
|
- Multi-line input
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Extended Commands
|
||||||
|
```rust
|
||||||
|
// 追加予定のコマンド
|
||||||
|
- ls // File listing
|
||||||
|
- cat // File content
|
||||||
|
- mkdir // Directory creation
|
||||||
|
- cd // Change directory
|
||||||
|
- ps // Process list
|
||||||
|
- claude // AI integration
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Display Improvements
|
||||||
|
```rust
|
||||||
|
// 計画中の機能
|
||||||
|
- Scrolling support
|
||||||
|
- Color themes
|
||||||
|
- Multiple windows
|
||||||
|
- Status bar
|
||||||
|
```
|
||||||
|
|
||||||
|
## Debugging Tips
|
||||||
|
|
||||||
|
### 1. Scancode Debugging
|
||||||
|
```rust
|
||||||
|
// スキャンコード確認用
|
||||||
|
print_str("Raw: 0x", x, y);
|
||||||
|
print_hex(scancode);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Buffer State Debugging
|
||||||
|
```rust
|
||||||
|
// バッファ状態確認
|
||||||
|
print_str("Pos: ", x, y);
|
||||||
|
print_number(INPUT_POS);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. QEMU Console Output
|
||||||
|
```bash
|
||||||
|
# QEMUでの詳細ログ
|
||||||
|
qemu-system-x86_64 -kernel kernel.bin -d int,cpu
|
||||||
|
```
|
||||||
|
|
||||||
|
## Build Configuration
|
||||||
|
|
||||||
|
### Cargo.toml
|
||||||
|
```toml
|
||||||
|
[package]
|
||||||
|
name = "aios-kernel"
|
||||||
|
version = "0.2.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
bootloader = "0.9.23"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Build Command
|
||||||
|
```bash
|
||||||
|
# 開発用
|
||||||
|
cargo bootimage --release
|
||||||
|
|
||||||
|
# テスト用
|
||||||
|
./scpt/bootimage-test.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
## Success Metrics
|
||||||
|
|
||||||
|
実装成功の判定基準:
|
||||||
|
|
||||||
|
1. **キーボード入力**: 文字入力が画面に表示される
|
||||||
|
2. **コマンド実行**: `help`コマンドが正常動作
|
||||||
|
3. **バックスペース**: 文字削除が正常動作
|
||||||
|
4. **エラーハンドリング**: 不明コマンドでエラー表示
|
||||||
|
5. **画面管理**: `clear`コマンドで画面クリア
|
||||||
|
|
||||||
|
全ての機能が正常動作することを確認済み。
|
298
docs/TROUBLESHOOTING.md
Normal file
298
docs/TROUBLESHOOTING.md
Normal file
@ -0,0 +1,298 @@
|
|||||||
|
# Aios Troubleshooting Guide
|
||||||
|
|
||||||
|
## Common Issues and Solutions
|
||||||
|
|
||||||
|
### 1. Page Table Panic Error (`page_table*.rs`)
|
||||||
|
|
||||||
|
#### Problem
|
||||||
|
QEMUでAiosを実行すると、以下のようなパニックが発生する:
|
||||||
|
|
||||||
|
```
|
||||||
|
panicked at 'page_table*.rs:105:25'
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Root Cause
|
||||||
|
この問題は主にシェル機能とキーボード入力の実装時に発生する。具体的な原因:
|
||||||
|
|
||||||
|
1. **ポートI/O競合**: PS/2キーボードのポートアクセス(0x60, 0x64)がbootloaderクレートと競合
|
||||||
|
2. **メモリアクセス違反**: unsafe なポートアクセスがページテーブルの整合性を破壊
|
||||||
|
3. **割り込み処理**: キーボード割り込みの不適切な処理
|
||||||
|
|
||||||
|
#### Solution Steps
|
||||||
|
|
||||||
|
**Step 1: 問題の特定**
|
||||||
|
```rust
|
||||||
|
// 危険なポートアクセス例
|
||||||
|
unsafe fn inb(port: u16) -> u8 {
|
||||||
|
let result: u8;
|
||||||
|
core::arch::asm!(
|
||||||
|
"in al, dx",
|
||||||
|
out("al") result,
|
||||||
|
in("dx") port,
|
||||||
|
);
|
||||||
|
result
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Step 2: 安全な実装への移行**
|
||||||
|
```rust
|
||||||
|
// 安全なポートアクセス(遅延付き)
|
||||||
|
unsafe fn try_inb(port: u16) -> Option<u8> {
|
||||||
|
// Add delay to avoid rapid polling
|
||||||
|
for _ in 0..1000 {
|
||||||
|
core::arch::asm!("nop");
|
||||||
|
}
|
||||||
|
|
||||||
|
let result: u8;
|
||||||
|
core::arch::asm!(
|
||||||
|
"in al, dx",
|
||||||
|
out("al") result,
|
||||||
|
in("dx") port,
|
||||||
|
options(nomem, nostack, preserves_flags)
|
||||||
|
);
|
||||||
|
Some(result)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Step 3: 完全な対策**
|
||||||
|
最終的に、以下の基本的なアプローチで解決:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
/// Check if there's data in keyboard buffer
|
||||||
|
pub fn has_key() -> bool {
|
||||||
|
unsafe {
|
||||||
|
let status: u8;
|
||||||
|
core::arch::asm!(
|
||||||
|
"in al, 0x64",
|
||||||
|
out("al") status,
|
||||||
|
options(nomem, nostack, preserves_flags)
|
||||||
|
);
|
||||||
|
(status & 1) != 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read raw scancode from keyboard
|
||||||
|
pub fn read_scancode() -> u8 {
|
||||||
|
unsafe {
|
||||||
|
let scancode: u8;
|
||||||
|
core::arch::asm!(
|
||||||
|
"in al, 0x60",
|
||||||
|
out("al") scancode,
|
||||||
|
options(nomem, nostack, preserves_flags)
|
||||||
|
);
|
||||||
|
scancode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Prevention
|
||||||
|
- `options(nomem, nostack, preserves_flags)` を必ず使用
|
||||||
|
- ポーリング頻度を制限(1000サイクル毎など)
|
||||||
|
- エラーハンドリングの実装
|
||||||
|
- 段階的テスト(まずデバッグ版で検証)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2. Keyboard Input Not Working
|
||||||
|
|
||||||
|
#### Problem
|
||||||
|
キーボード入力が QEMU で認識されない問題。
|
||||||
|
|
||||||
|
#### Symptoms
|
||||||
|
- QEMUは正常に起動する
|
||||||
|
- 画面表示は正常
|
||||||
|
- キーを押しても反応しない
|
||||||
|
- スキャンコードが取得できない
|
||||||
|
|
||||||
|
#### Root Causes
|
||||||
|
|
||||||
|
**Cause 1: QEMUのフォーカス問題**
|
||||||
|
- QEMUウィンドウにフォーカスがない
|
||||||
|
- マウスキャプチャが無効
|
||||||
|
|
||||||
|
**Cause 2: ポーリング頻度の問題**
|
||||||
|
```rust
|
||||||
|
// 問題のあるコード
|
||||||
|
if counter % 50000 == 0 {
|
||||||
|
// キーボードチェックの頻度が低すぎ
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Cause 3: スキャンコードマッピングの不備**
|
||||||
|
```rust
|
||||||
|
// 不完全なマッピング
|
||||||
|
static SCANCODE_TO_ASCII: [u8; 128] = [
|
||||||
|
// 配列サイズが127で不一致
|
||||||
|
];
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Solutions
|
||||||
|
|
||||||
|
**Solution 1: QEMUの正しい起動**
|
||||||
|
```bash
|
||||||
|
# GTK表示でフォーカス確保
|
||||||
|
qemu-system-x86_64 \
|
||||||
|
-drive format=raw,file="$BOOT_IMAGE" \
|
||||||
|
-display gtk
|
||||||
|
|
||||||
|
# または、nographic を避ける
|
||||||
|
qemu-system-x86_64 \
|
||||||
|
-drive format=raw,file="$BOOT_IMAGE"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Solution 2: 適切なポーリング頻度**
|
||||||
|
```rust
|
||||||
|
loop {
|
||||||
|
if keyboard_basic::has_key() {
|
||||||
|
let scancode = keyboard_basic::read_scancode();
|
||||||
|
// 即座に処理
|
||||||
|
}
|
||||||
|
|
||||||
|
// 軽い遅延のみ
|
||||||
|
for _ in 0..1000 {
|
||||||
|
unsafe { core::arch::asm!("nop"); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Solution 3: 段階的デバッグ**
|
||||||
|
|
||||||
|
**Phase 1: スキャンコード確認**
|
||||||
|
```rust
|
||||||
|
// デバッグ版でスキャンコードを表示
|
||||||
|
if keyboard_basic::has_key() {
|
||||||
|
let scancode = keyboard_basic::read_scancode();
|
||||||
|
print_hex(scancode); // 16進数で表示
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Phase 2: キーリリース除外**
|
||||||
|
```rust
|
||||||
|
// キープレスのみ処理(bit 7 = 0)
|
||||||
|
if (scancode & 0x80) == 0 {
|
||||||
|
// キープレス処理
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Phase 3: 完全なマッピング**
|
||||||
|
```rust
|
||||||
|
pub fn scancode_to_char(scancode: u8) -> Option<char> {
|
||||||
|
match scancode {
|
||||||
|
0x1E => Some('a'),
|
||||||
|
0x30 => Some('b'),
|
||||||
|
// ... 全てのキーをマッピング
|
||||||
|
0x1C => Some('\n'), // Enter
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Verification Steps
|
||||||
|
|
||||||
|
1. **基本テスト**: スキャンコード表示版でキー入力確認
|
||||||
|
2. **マッピングテスト**: 特定キー(a, enter等)の動作確認
|
||||||
|
3. **シェルテスト**: コマンド入力・実行の確認
|
||||||
|
|
||||||
|
#### Working Configuration
|
||||||
|
|
||||||
|
最終的に動作した設定:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
// keyboard_basic.rs
|
||||||
|
pub fn has_key() -> bool {
|
||||||
|
unsafe {
|
||||||
|
let status: u8;
|
||||||
|
core::arch::asm!("in al, 0x64", out("al") status, options(nomem, nostack, preserves_flags));
|
||||||
|
(status & 1) != 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// メインループ
|
||||||
|
loop {
|
||||||
|
if keyboard_basic::has_key() {
|
||||||
|
let scancode = keyboard_basic::read_scancode();
|
||||||
|
if scancode != last_scancode && (scancode & 0x80) == 0 {
|
||||||
|
if let Some(ch) = keyboard_basic::scancode_to_char(scancode) {
|
||||||
|
process_char(ch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 軽い遅延
|
||||||
|
for _ in 0..1000 {
|
||||||
|
unsafe { core::arch::asm!("nop"); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 3. Build Errors
|
||||||
|
|
||||||
|
#### Type Mismatch in Arrays
|
||||||
|
```rust
|
||||||
|
// Error: expected array with size 128, found 127
|
||||||
|
static SCANCODE_TO_ASCII: [u8; 128] = [...]; // 要素が127個
|
||||||
|
|
||||||
|
// Solution: 正確な要素数
|
||||||
|
static SCANCODE_TO_ASCII: [u8; 128] = [
|
||||||
|
// 128個の要素を確保
|
||||||
|
0, 27, b'1', ..., 0 // 最後に0で埋める
|
||||||
|
];
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Unused Imports
|
||||||
|
```rust
|
||||||
|
// Warning: unused import
|
||||||
|
use core::convert::TryFrom; // 削除
|
||||||
|
|
||||||
|
// Warning: unused import
|
||||||
|
use crate::keyboard; // 使用しない場合は削除
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 4. QEMU-specific Issues
|
||||||
|
|
||||||
|
#### Display Problems
|
||||||
|
```bash
|
||||||
|
# 問題: nographic で表示されない
|
||||||
|
qemu-system-x86_64 -kernel kernel.bin -nographic
|
||||||
|
|
||||||
|
# 解決: 標準表示を使用
|
||||||
|
qemu-system-x86_64 -kernel kernel.bin
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Mouse Capture
|
||||||
|
```
|
||||||
|
# QEMUでマウスキャプチャを有効化
|
||||||
|
Ctrl+Alt+G (toggle mouse capture)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Exit QEMU
|
||||||
|
```
|
||||||
|
# QEMUの終了方法
|
||||||
|
Ctrl+A, X # または
|
||||||
|
Alt+F4 # ウィンドウを閉じる
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Best Practices
|
||||||
|
|
||||||
|
1. **段階的実装**
|
||||||
|
- 静的表示 → スキャンコード確認 → 完全シェル
|
||||||
|
|
||||||
|
2. **デバッグ情報の表示**
|
||||||
|
- スキャンコードの16進表示
|
||||||
|
- ポーリング回数のカウンタ
|
||||||
|
- キー受信回数の表示
|
||||||
|
|
||||||
|
3. **安全なポートアクセス**
|
||||||
|
- `options(nomem, nostack, preserves_flags)` 使用
|
||||||
|
- 適切な遅延の実装
|
||||||
|
- エラーハンドリング
|
||||||
|
|
||||||
|
4. **テスト環境**
|
||||||
|
- QEMUの適切な起動オプション
|
||||||
|
- フォーカスの確保
|
||||||
|
- 段階的な機能テスト
|
7
iso/boot/grub/grub.cfg
Normal file
7
iso/boot/grub/grub.cfg
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
set timeout=0
|
||||||
|
set default=0
|
||||||
|
|
||||||
|
menuentry "AIOS v2" {
|
||||||
|
multiboot2 /boot/kernel.bin
|
||||||
|
boot
|
||||||
|
}
|
13
kernel/.cargo/config.toml
Normal file
13
kernel/.cargo/config.toml
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
[build]
|
||||||
|
target = "x86_64-unknown-none"
|
||||||
|
|
||||||
|
[target.x86_64-unknown-none]
|
||||||
|
rustflags = [
|
||||||
|
"-C", "link-arg=-T/Users/syui/git/aios/kernel/kernel.ld",
|
||||||
|
"-C", "relocation-model=static",
|
||||||
|
"-C", "code-model=kernel"
|
||||||
|
]
|
||||||
|
|
||||||
|
[unstable]
|
||||||
|
build-std = ["core", "compiler_builtins"]
|
||||||
|
build-std-features = ["compiler-builtins-mem"]
|
14
kernel/Cargo.toml
Normal file
14
kernel/Cargo.toml
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
[package]
|
||||||
|
name = "aios-kernel"
|
||||||
|
version = "2.0.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "aios-kernel"
|
||||||
|
path = "src/main.rs"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
bootloader = "0.9.23"
|
||||||
|
|
||||||
|
[package.metadata.bootimage]
|
||||||
|
test-args = ["-nographic"]
|
22
kernel/i686-aios.json
Normal file
22
kernel/i686-aios.json
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"llvm-target": "i686-unknown-none",
|
||||||
|
"target-endian": "little",
|
||||||
|
"target-pointer-width": "32",
|
||||||
|
"target-c-int-width": "32",
|
||||||
|
"data-layout": "e-m:e-p:32:32-f64:32:64-f80:32-n8:16:32-S128",
|
||||||
|
"arch": "x86",
|
||||||
|
"os": "none",
|
||||||
|
"env": "",
|
||||||
|
"vendor": "unknown",
|
||||||
|
"linker-flavor": "ld.lld",
|
||||||
|
"linker": "rust-lld",
|
||||||
|
"panic-strategy": "abort",
|
||||||
|
"disable-redzone": true,
|
||||||
|
"features": "-mmx,-sse,-sse2",
|
||||||
|
"executables": true,
|
||||||
|
"relocation-model": "static",
|
||||||
|
"code-model": "small",
|
||||||
|
"pre-link-args": {
|
||||||
|
"ld.lld": ["-Tkernel.ld"]
|
||||||
|
}
|
||||||
|
}
|
35
kernel/kernel.ld
Normal file
35
kernel/kernel.ld
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
/* AIOS v2 Kernel Linker Script */
|
||||||
|
|
||||||
|
ENTRY(_start)
|
||||||
|
|
||||||
|
SECTIONS
|
||||||
|
{
|
||||||
|
. = 1M;
|
||||||
|
|
||||||
|
.multiboot :
|
||||||
|
{
|
||||||
|
/* Ensure the multiboot header is at the very beginning */
|
||||||
|
KEEP(*(.multiboot))
|
||||||
|
}
|
||||||
|
|
||||||
|
.text : ALIGN(0x1000)
|
||||||
|
{
|
||||||
|
*(.text .text.*)
|
||||||
|
}
|
||||||
|
|
||||||
|
.rodata : ALIGN(0x1000)
|
||||||
|
{
|
||||||
|
*(.rodata .rodata.*)
|
||||||
|
}
|
||||||
|
|
||||||
|
.data : ALIGN(0x1000)
|
||||||
|
{
|
||||||
|
*(.data .data.*)
|
||||||
|
}
|
||||||
|
|
||||||
|
.bss : ALIGN(0x1000)
|
||||||
|
{
|
||||||
|
*(COMMON)
|
||||||
|
*(.bss .bss.*)
|
||||||
|
}
|
||||||
|
}
|
27
kernel/src/boot.s
Normal file
27
kernel/src/boot.s
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
# Multiboot header
|
||||||
|
.section .multiboot
|
||||||
|
.align 8
|
||||||
|
multiboot_header:
|
||||||
|
.long 0x1BADB002 # magic
|
||||||
|
.long 0x00 # flags
|
||||||
|
.long -(0x1BADB002 + 0x00) # checksum
|
||||||
|
|
||||||
|
# Entry point
|
||||||
|
.section .text
|
||||||
|
.global _start
|
||||||
|
.code64
|
||||||
|
_start:
|
||||||
|
# Disable interrupts
|
||||||
|
cli
|
||||||
|
|
||||||
|
# Load null descriptor into data segment registers
|
||||||
|
xor %ax, %ax
|
||||||
|
mov %ax, %ds
|
||||||
|
mov %ax, %es
|
||||||
|
mov %ax, %ss
|
||||||
|
mov %ax, %fs
|
||||||
|
mov %ax, %gs
|
||||||
|
|
||||||
|
# Halt
|
||||||
|
hlt
|
||||||
|
1: jmp 1b
|
34
kernel/src/boot32.s
Normal file
34
kernel/src/boot32.s
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
.code32
|
||||||
|
.section .multiboot, "ax"
|
||||||
|
.align 4
|
||||||
|
multiboot_header:
|
||||||
|
.long 0x1BADB002 # magic
|
||||||
|
.long 0x00 # flags
|
||||||
|
.long -(0x1BADB002 + 0x00) # checksum
|
||||||
|
|
||||||
|
.section .text
|
||||||
|
.global _start
|
||||||
|
_start:
|
||||||
|
# We start in 32-bit mode
|
||||||
|
cli
|
||||||
|
|
||||||
|
# Set up a simple stack
|
||||||
|
movl $stack_top, %esp
|
||||||
|
|
||||||
|
# Write 'OK' to VGA buffer
|
||||||
|
movl $0xb8000, %edi
|
||||||
|
movb $'O', (%edi)
|
||||||
|
movb $0x0f, 1(%edi)
|
||||||
|
movb $'K', 2(%edi)
|
||||||
|
movb $0x0f, 3(%edi)
|
||||||
|
|
||||||
|
# Halt
|
||||||
|
hang:
|
||||||
|
hlt
|
||||||
|
jmp hang
|
||||||
|
|
||||||
|
.section .bss
|
||||||
|
.align 16
|
||||||
|
stack_bottom:
|
||||||
|
.space 4096
|
||||||
|
stack_top:
|
130
kernel/src/console.rs
Normal file
130
kernel/src/console.rs
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
//! Simple VGA console for AIOS v2
|
||||||
|
//! No external crates, pure Rust implementation
|
||||||
|
|
||||||
|
use core::fmt::{self, Write};
|
||||||
|
|
||||||
|
const VGA_BUFFER: *mut u16 = 0xb8000 as *mut u16;
|
||||||
|
const VGA_WIDTH: usize = 80;
|
||||||
|
const VGA_HEIGHT: usize = 25;
|
||||||
|
|
||||||
|
static mut CONSOLE: Console = Console {
|
||||||
|
col: 0,
|
||||||
|
row: 0,
|
||||||
|
color: 0x0f, // White on black
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Console {
|
||||||
|
col: usize,
|
||||||
|
row: usize,
|
||||||
|
color: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Console {
|
||||||
|
fn clear(&mut self) {
|
||||||
|
for i in 0..(VGA_WIDTH * VGA_HEIGHT) {
|
||||||
|
unsafe {
|
||||||
|
VGA_BUFFER.add(i).write_volatile(0x0f00); // Clear with white on black
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.col = 0;
|
||||||
|
self.row = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_byte(&mut self, byte: u8) {
|
||||||
|
match byte {
|
||||||
|
b'\n' => {
|
||||||
|
self.col = 0;
|
||||||
|
self.row += 1;
|
||||||
|
if self.row >= VGA_HEIGHT {
|
||||||
|
self.scroll_up();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
byte => {
|
||||||
|
if self.col >= VGA_WIDTH {
|
||||||
|
self.col = 0;
|
||||||
|
self.row += 1;
|
||||||
|
if self.row >= VGA_HEIGHT {
|
||||||
|
self.scroll_up();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let pos = self.row * VGA_WIDTH + self.col;
|
||||||
|
let entry = (self.color as u16) << 8 | byte as u16;
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
VGA_BUFFER.add(pos).write_volatile(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.col += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn scroll_up(&mut self) {
|
||||||
|
// Move all lines up by one
|
||||||
|
for row in 1..VGA_HEIGHT {
|
||||||
|
for col in 0..VGA_WIDTH {
|
||||||
|
let pos = row * VGA_WIDTH + col;
|
||||||
|
let prev_pos = (row - 1) * VGA_WIDTH + col;
|
||||||
|
unsafe {
|
||||||
|
let entry = VGA_BUFFER.add(pos).read_volatile();
|
||||||
|
VGA_BUFFER.add(prev_pos).write_volatile(entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear last line
|
||||||
|
for col in 0..VGA_WIDTH {
|
||||||
|
let pos = (VGA_HEIGHT - 1) * VGA_WIDTH + col;
|
||||||
|
unsafe {
|
||||||
|
VGA_BUFFER.add(pos).write_volatile(0x0f00);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.row = VGA_HEIGHT - 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Write for Console {
|
||||||
|
fn write_str(&mut self, s: &str) -> fmt::Result {
|
||||||
|
for byte in s.bytes() {
|
||||||
|
self.write_byte(byte);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn init() {
|
||||||
|
unsafe {
|
||||||
|
CONSOLE.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn print(s: &str) {
|
||||||
|
unsafe {
|
||||||
|
CONSOLE.write_str(s).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! kprint {
|
||||||
|
($($arg:tt)*) => {
|
||||||
|
$crate::console::_print(format_args!($($arg)*))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#[macro_export]
|
||||||
|
macro_rules! kprintln {
|
||||||
|
() => ($crate::console::print("\n"));
|
||||||
|
($($arg:tt)*) => ({
|
||||||
|
$crate::console::_print(format_args!($($arg)*));
|
||||||
|
$crate::console::print("\n");
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub fn _print(args: fmt::Arguments) {
|
||||||
|
unsafe {
|
||||||
|
CONSOLE.write_fmt(args).unwrap();
|
||||||
|
}
|
||||||
|
}
|
67
kernel/src/keyboard.rs
Normal file
67
kernel/src/keyboard.rs
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
//! PS/2 Keyboard Driver for AIOS v2
|
||||||
|
|
||||||
|
/// PS/2 keyboard data port
|
||||||
|
const KEYBOARD_DATA_PORT: u16 = 0x60;
|
||||||
|
|
||||||
|
/// PS/2 keyboard status port
|
||||||
|
const KEYBOARD_STATUS_PORT: u16 = 0x64;
|
||||||
|
|
||||||
|
/// Keyboard scan codes to ASCII mapping (US layout)
|
||||||
|
static SCANCODE_TO_ASCII: [u8; 128] = [
|
||||||
|
0, 27, b'1', b'2', b'3', b'4', b'5', b'6', b'7', b'8', b'9', b'0', b'-', b'=', 8, // backspace
|
||||||
|
b'\t', b'q', b'w', b'e', b'r', b't', b'y', b'u', b'i', b'o', b'p', b'[', b']', b'\n', // enter
|
||||||
|
0, // left ctrl
|
||||||
|
b'a', b's', b'd', b'f', b'g', b'h', b'j', b'k', b'l', b';', b'\'', b'`',
|
||||||
|
0, // left shift
|
||||||
|
b'\\', b'z', b'x', b'c', b'v', b'b', b'n', b'm', b',', b'.', b'/',
|
||||||
|
0, // right shift
|
||||||
|
b'*',
|
||||||
|
0, // left alt
|
||||||
|
b' ', // space
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // F1-F10
|
||||||
|
0, // num lock
|
||||||
|
0, // scroll lock
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, // keypad
|
||||||
|
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
|
||||||
|
];
|
||||||
|
|
||||||
|
/// Read a byte from a port
|
||||||
|
unsafe fn inb(port: u16) -> u8 {
|
||||||
|
let result: u8;
|
||||||
|
core::arch::asm!(
|
||||||
|
"in al, dx",
|
||||||
|
out("al") result,
|
||||||
|
in("dx") port,
|
||||||
|
options(nomem, nostack, preserves_flags)
|
||||||
|
);
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if keyboard has data available
|
||||||
|
pub fn keyboard_ready() -> bool {
|
||||||
|
unsafe {
|
||||||
|
(inb(KEYBOARD_STATUS_PORT) & 0x01) != 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read a key from the keyboard (non-blocking)
|
||||||
|
pub fn read_key() -> Option<u8> {
|
||||||
|
if !keyboard_ready() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
let scancode = inb(KEYBOARD_DATA_PORT);
|
||||||
|
|
||||||
|
// Only handle key press (bit 7 = 0), ignore key release
|
||||||
|
if scancode & 0x80 == 0 {
|
||||||
|
if let Some(&ascii) = SCANCODE_TO_ASCII.get(scancode as usize) {
|
||||||
|
if ascii != 0 {
|
||||||
|
return Some(ascii);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
62
kernel/src/keyboard_basic.rs
Normal file
62
kernel/src/keyboard_basic.rs
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
//! Basic Keyboard Driver - Minimal approach
|
||||||
|
|
||||||
|
/// Check if there's data in keyboard buffer
|
||||||
|
pub fn has_key() -> bool {
|
||||||
|
unsafe {
|
||||||
|
let status: u8;
|
||||||
|
core::arch::asm!(
|
||||||
|
"in al, 0x64",
|
||||||
|
out("al") status,
|
||||||
|
options(nomem, nostack, preserves_flags)
|
||||||
|
);
|
||||||
|
(status & 1) != 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read raw scancode from keyboard
|
||||||
|
pub fn read_scancode() -> u8 {
|
||||||
|
unsafe {
|
||||||
|
let scancode: u8;
|
||||||
|
core::arch::asm!(
|
||||||
|
"in al, 0x60",
|
||||||
|
out("al") scancode,
|
||||||
|
options(nomem, nostack, preserves_flags)
|
||||||
|
);
|
||||||
|
scancode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convert scancode to ASCII (basic mapping)
|
||||||
|
pub fn scancode_to_char(scancode: u8) -> Option<char> {
|
||||||
|
match scancode {
|
||||||
|
0x1E => Some('a'),
|
||||||
|
0x30 => Some('b'),
|
||||||
|
0x2E => Some('c'),
|
||||||
|
0x20 => Some('d'),
|
||||||
|
0x12 => Some('e'),
|
||||||
|
0x21 => Some('f'),
|
||||||
|
0x22 => Some('g'),
|
||||||
|
0x23 => Some('h'),
|
||||||
|
0x17 => Some('i'),
|
||||||
|
0x24 => Some('j'),
|
||||||
|
0x25 => Some('k'),
|
||||||
|
0x26 => Some('l'),
|
||||||
|
0x32 => Some('m'),
|
||||||
|
0x31 => Some('n'),
|
||||||
|
0x18 => Some('o'),
|
||||||
|
0x19 => Some('p'),
|
||||||
|
0x10 => Some('q'),
|
||||||
|
0x13 => Some('r'),
|
||||||
|
0x1F => Some('s'),
|
||||||
|
0x14 => Some('t'),
|
||||||
|
0x16 => Some('u'),
|
||||||
|
0x2F => Some('v'),
|
||||||
|
0x11 => Some('w'),
|
||||||
|
0x2D => Some('x'),
|
||||||
|
0x15 => Some('y'),
|
||||||
|
0x2C => Some('z'),
|
||||||
|
0x39 => Some(' '),
|
||||||
|
0x1C => Some('\n'),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
116
kernel/src/keyboard_safe.rs
Normal file
116
kernel/src/keyboard_safe.rs
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
//! Safe Keyboard Driver for AIOS v2
|
||||||
|
//! Uses polling without interrupts to avoid conflicts
|
||||||
|
|
||||||
|
/// PS/2 keyboard data port
|
||||||
|
const KEYBOARD_DATA_PORT: u16 = 0x60;
|
||||||
|
|
||||||
|
/// PS/2 keyboard status port
|
||||||
|
const KEYBOARD_STATUS_PORT: u16 = 0x64;
|
||||||
|
|
||||||
|
/// Simple scan code to ASCII mapping for common keys
|
||||||
|
fn scancode_to_ascii(scancode: u8) -> Option<u8> {
|
||||||
|
match scancode {
|
||||||
|
0x02 => Some(b'1'),
|
||||||
|
0x03 => Some(b'2'),
|
||||||
|
0x04 => Some(b'3'),
|
||||||
|
0x05 => Some(b'4'),
|
||||||
|
0x06 => Some(b'5'),
|
||||||
|
0x07 => Some(b'6'),
|
||||||
|
0x08 => Some(b'7'),
|
||||||
|
0x09 => Some(b'8'),
|
||||||
|
0x0A => Some(b'9'),
|
||||||
|
0x0B => Some(b'0'),
|
||||||
|
|
||||||
|
0x10 => Some(b'q'),
|
||||||
|
0x11 => Some(b'w'),
|
||||||
|
0x12 => Some(b'e'),
|
||||||
|
0x13 => Some(b'r'),
|
||||||
|
0x14 => Some(b't'),
|
||||||
|
0x15 => Some(b'y'),
|
||||||
|
0x16 => Some(b'u'),
|
||||||
|
0x17 => Some(b'i'),
|
||||||
|
0x18 => Some(b'o'),
|
||||||
|
0x19 => Some(b'p'),
|
||||||
|
|
||||||
|
0x1E => Some(b'a'),
|
||||||
|
0x1F => Some(b's'),
|
||||||
|
0x20 => Some(b'd'),
|
||||||
|
0x21 => Some(b'f'),
|
||||||
|
0x22 => Some(b'g'),
|
||||||
|
0x23 => Some(b'h'),
|
||||||
|
0x24 => Some(b'j'),
|
||||||
|
0x25 => Some(b'k'),
|
||||||
|
0x26 => Some(b'l'),
|
||||||
|
|
||||||
|
0x2C => Some(b'z'),
|
||||||
|
0x2D => Some(b'x'),
|
||||||
|
0x2E => Some(b'c'),
|
||||||
|
0x2F => Some(b'v'),
|
||||||
|
0x30 => Some(b'b'),
|
||||||
|
0x31 => Some(b'n'),
|
||||||
|
0x32 => Some(b'm'),
|
||||||
|
|
||||||
|
0x39 => Some(b' '), // space
|
||||||
|
0x1C => Some(b'\n'), // enter
|
||||||
|
0x0E => Some(8), // backspace
|
||||||
|
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Safe port read with error handling
|
||||||
|
unsafe fn try_inb(port: u16) -> Option<u8> {
|
||||||
|
// Add a simple delay to avoid rapid polling
|
||||||
|
for _ in 0..1000 {
|
||||||
|
core::arch::asm!("nop");
|
||||||
|
}
|
||||||
|
|
||||||
|
let result: u8;
|
||||||
|
core::arch::asm!(
|
||||||
|
"in al, dx",
|
||||||
|
out("al") result,
|
||||||
|
in("dx") port,
|
||||||
|
options(nomem, nostack, preserves_flags)
|
||||||
|
);
|
||||||
|
Some(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if keyboard has data available (safe version)
|
||||||
|
pub fn keyboard_ready() -> bool {
|
||||||
|
unsafe {
|
||||||
|
if let Some(status) = try_inb(KEYBOARD_STATUS_PORT) {
|
||||||
|
(status & 0x01) != 0
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Read a key from keyboard with safety checks
|
||||||
|
pub fn read_key() -> Option<u8> {
|
||||||
|
if !keyboard_ready() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
if let Some(scancode) = try_inb(KEYBOARD_DATA_PORT) {
|
||||||
|
// Only handle key press (bit 7 = 0)
|
||||||
|
if scancode & 0x80 == 0 {
|
||||||
|
return scancode_to_ascii(scancode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get raw scancode for debugging
|
||||||
|
pub fn get_raw_scancode() -> Option<u8> {
|
||||||
|
if !keyboard_ready() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
try_inb(KEYBOARD_DATA_PORT)
|
||||||
|
}
|
||||||
|
}
|
208
kernel/src/main.rs
Normal file
208
kernel/src/main.rs
Normal file
@ -0,0 +1,208 @@
|
|||||||
|
//! Aios Kernel - Interactive Shell
|
||||||
|
|
||||||
|
#![no_std]
|
||||||
|
#![no_main]
|
||||||
|
|
||||||
|
mod keyboard_basic;
|
||||||
|
|
||||||
|
use core::panic::PanicInfo;
|
||||||
|
|
||||||
|
// Simple VGA buffer writer
|
||||||
|
struct VgaBuffer {
|
||||||
|
chars: [[u16; 80]; 25],
|
||||||
|
}
|
||||||
|
|
||||||
|
static mut VGA_BUFFER: *mut VgaBuffer = 0xb8000 as *mut VgaBuffer;
|
||||||
|
static mut CURSOR_ROW: usize = 12;
|
||||||
|
static mut CURSOR_COL: usize = 6;
|
||||||
|
|
||||||
|
pub fn print_char(c: u8, x: usize, y: usize) {
|
||||||
|
if x >= 80 || y >= 25 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
unsafe {
|
||||||
|
let vga = &mut *VGA_BUFFER;
|
||||||
|
vga.chars[y][x] = (0x0f << 8) | (c as u16);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn print_str(s: &str, x: usize, y: usize) {
|
||||||
|
for (i, byte) in s.bytes().enumerate() {
|
||||||
|
if x + i >= 80 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
print_char(byte, x + i, y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clear_screen() {
|
||||||
|
for y in 0..25 {
|
||||||
|
for x in 0..80 {
|
||||||
|
print_char(b' ', x, y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shell state
|
||||||
|
static mut INPUT_BUFFER: [u8; 64] = [0; 64];
|
||||||
|
static mut INPUT_POS: usize = 0;
|
||||||
|
|
||||||
|
fn print_prompt() {
|
||||||
|
unsafe {
|
||||||
|
print_str("aios> ", 0, CURSOR_ROW);
|
||||||
|
CURSOR_COL = 6;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new_line() {
|
||||||
|
unsafe {
|
||||||
|
CURSOR_ROW += 1;
|
||||||
|
CURSOR_COL = 0;
|
||||||
|
if CURSOR_ROW >= 24 {
|
||||||
|
CURSOR_ROW = 23;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn execute_command() {
|
||||||
|
unsafe {
|
||||||
|
let cmd = core::str::from_utf8(&INPUT_BUFFER[..INPUT_POS]).unwrap_or("");
|
||||||
|
|
||||||
|
new_line();
|
||||||
|
|
||||||
|
match cmd {
|
||||||
|
"help" => {
|
||||||
|
print_str("Aios Commands:", 0, CURSOR_ROW);
|
||||||
|
new_line();
|
||||||
|
print_str(" help - Show this help", 0, CURSOR_ROW);
|
||||||
|
new_line();
|
||||||
|
print_str(" version - Show version", 0, CURSOR_ROW);
|
||||||
|
new_line();
|
||||||
|
print_str(" echo - Echo test", 0, CURSOR_ROW);
|
||||||
|
new_line();
|
||||||
|
print_str(" clear - Clear screen", 0, CURSOR_ROW);
|
||||||
|
new_line();
|
||||||
|
}
|
||||||
|
"version" => {
|
||||||
|
print_str("Aios 2.0 - AI Operating System", 0, CURSOR_ROW);
|
||||||
|
new_line();
|
||||||
|
print_str("Interactive Shell with Keyboard Support", 0, CURSOR_ROW);
|
||||||
|
new_line();
|
||||||
|
}
|
||||||
|
"echo" => {
|
||||||
|
print_str("Hello from Aios Interactive Shell!", 0, CURSOR_ROW);
|
||||||
|
new_line();
|
||||||
|
}
|
||||||
|
"clear" => {
|
||||||
|
clear_screen();
|
||||||
|
CURSOR_ROW = 2;
|
||||||
|
print_str("Aios - AI Operating System", 25, 0);
|
||||||
|
print_str("Interactive Shell Ready", 27, 1);
|
||||||
|
CURSOR_ROW = 3;
|
||||||
|
}
|
||||||
|
"" => {
|
||||||
|
// Empty command
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
print_str("Unknown command: ", 0, CURSOR_ROW);
|
||||||
|
print_str(cmd, 17, CURSOR_ROW);
|
||||||
|
new_line();
|
||||||
|
print_str("Type 'help' for available commands", 0, CURSOR_ROW);
|
||||||
|
new_line();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear input buffer
|
||||||
|
INPUT_BUFFER.fill(0);
|
||||||
|
INPUT_POS = 0;
|
||||||
|
|
||||||
|
new_line();
|
||||||
|
print_prompt();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn process_char(ch: char) {
|
||||||
|
unsafe {
|
||||||
|
match ch {
|
||||||
|
'\n' => {
|
||||||
|
execute_command();
|
||||||
|
}
|
||||||
|
'\u{8}' => {
|
||||||
|
// Backspace
|
||||||
|
if INPUT_POS > 0 {
|
||||||
|
INPUT_POS -= 1;
|
||||||
|
INPUT_BUFFER[INPUT_POS] = 0;
|
||||||
|
CURSOR_COL -= 1;
|
||||||
|
print_char(b' ', CURSOR_COL, CURSOR_ROW);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
' '..='~' => {
|
||||||
|
// Printable characters
|
||||||
|
if INPUT_POS < INPUT_BUFFER.len() - 1 {
|
||||||
|
INPUT_BUFFER[INPUT_POS] = ch as u8;
|
||||||
|
print_char(ch as u8, CURSOR_COL, CURSOR_ROW);
|
||||||
|
INPUT_POS += 1;
|
||||||
|
CURSOR_COL += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
// Ignore other characters
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Entry point
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn _start() -> ! {
|
||||||
|
// Clear screen
|
||||||
|
clear_screen();
|
||||||
|
|
||||||
|
// Print welcome message
|
||||||
|
print_str("Aios - AI Operating System", 25, 0);
|
||||||
|
print_str("Interactive Shell Ready", 27, 1);
|
||||||
|
print_str("Type 'help' for commands", 27, 2);
|
||||||
|
|
||||||
|
// Initialize shell
|
||||||
|
unsafe {
|
||||||
|
CURSOR_ROW = 5;
|
||||||
|
}
|
||||||
|
print_prompt();
|
||||||
|
|
||||||
|
let mut last_scancode = 0u8;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
if keyboard_basic::has_key() {
|
||||||
|
let scancode = keyboard_basic::read_scancode();
|
||||||
|
|
||||||
|
// Only process key press (not key release)
|
||||||
|
if scancode != last_scancode && (scancode & 0x80) == 0 {
|
||||||
|
last_scancode = scancode;
|
||||||
|
|
||||||
|
if let Some(ch) = keyboard_basic::scancode_to_char(scancode) {
|
||||||
|
process_char(ch);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Small delay to avoid excessive polling
|
||||||
|
for _ in 0..1000 {
|
||||||
|
unsafe {
|
||||||
|
core::arch::asm!("nop");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Panic handler
|
||||||
|
#[panic_handler]
|
||||||
|
fn panic(info: &PanicInfo) -> ! {
|
||||||
|
clear_screen();
|
||||||
|
print_str("!!! KERNEL PANIC !!!", 29, 12);
|
||||||
|
print_str("System halted.", 33, 13);
|
||||||
|
loop {
|
||||||
|
unsafe {
|
||||||
|
core::arch::asm!("hlt");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
7
kernel/src/memory.rs
Normal file
7
kernel/src/memory.rs
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
//! Simple memory management for AIOS v2
|
||||||
|
//! Minimal implementation for initial boot
|
||||||
|
|
||||||
|
pub fn init() {
|
||||||
|
// TODO: Implement proper memory management
|
||||||
|
// For now, just a placeholder to complete initialization sequence
|
||||||
|
}
|
27
kernel/src/panic.rs
Normal file
27
kernel/src/panic.rs
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
//! Panic handler for AIOS v2
|
||||||
|
|
||||||
|
use core::panic::PanicInfo;
|
||||||
|
use crate::console;
|
||||||
|
|
||||||
|
#[panic_handler]
|
||||||
|
fn panic(info: &PanicInfo) -> ! {
|
||||||
|
console::print("\n!!! KERNEL PANIC !!!\n");
|
||||||
|
if let Some(location) = info.location() {
|
||||||
|
console::print("Location: ");
|
||||||
|
console::print(location.file());
|
||||||
|
console::print(":");
|
||||||
|
// TODO: Convert line number to string
|
||||||
|
console::print("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
console::print("Message: ");
|
||||||
|
console::print("Kernel panic occurred\n");
|
||||||
|
|
||||||
|
console::print("System halted.\n");
|
||||||
|
|
||||||
|
loop {
|
||||||
|
unsafe {
|
||||||
|
core::arch::asm!("hlt");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
209
kernel/src/shell.rs
Normal file
209
kernel/src/shell.rs
Normal file
@ -0,0 +1,209 @@
|
|||||||
|
//! Simple Shell for AIOS v2
|
||||||
|
|
||||||
|
use crate::{print_char, print_str, clear_screen};
|
||||||
|
|
||||||
|
/// Maximum command line length
|
||||||
|
const MAX_CMD_LEN: usize = 256;
|
||||||
|
|
||||||
|
/// Shell state
|
||||||
|
pub struct Shell {
|
||||||
|
input_buffer: [u8; MAX_CMD_LEN],
|
||||||
|
cursor: usize,
|
||||||
|
prompt: &'static str,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Shell {
|
||||||
|
/// Create a new shell instance
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Shell {
|
||||||
|
input_buffer: [0; MAX_CMD_LEN],
|
||||||
|
cursor: 0,
|
||||||
|
prompt: "aios> ",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Print the shell prompt
|
||||||
|
pub fn print_prompt(&self) {
|
||||||
|
print_str(self.prompt, 0, get_current_row());
|
||||||
|
set_cursor_col(self.prompt.len());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Process a single character input
|
||||||
|
pub fn process_char(&mut self, c: u8) {
|
||||||
|
match c {
|
||||||
|
b'\n' => {
|
||||||
|
// Enter pressed - execute command
|
||||||
|
self.execute_command();
|
||||||
|
self.clear_input();
|
||||||
|
print_newline();
|
||||||
|
self.print_prompt();
|
||||||
|
}
|
||||||
|
8 => {
|
||||||
|
// Backspace
|
||||||
|
if self.cursor > 0 {
|
||||||
|
self.cursor -= 1;
|
||||||
|
self.input_buffer[self.cursor] = 0;
|
||||||
|
self.redraw_line();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
32..=126 => {
|
||||||
|
// Printable characters
|
||||||
|
if self.cursor < MAX_CMD_LEN - 1 {
|
||||||
|
self.input_buffer[self.cursor] = c;
|
||||||
|
self.cursor += 1;
|
||||||
|
print_char(c, get_cursor_col(), get_current_row());
|
||||||
|
inc_cursor_col();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
// Ignore other characters
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Execute the current command
|
||||||
|
fn execute_command(&mut self) {
|
||||||
|
let cmd = self.get_command_str();
|
||||||
|
|
||||||
|
print_newline();
|
||||||
|
|
||||||
|
match cmd {
|
||||||
|
"help" => self.cmd_help(),
|
||||||
|
"clear" => self.cmd_clear(),
|
||||||
|
"echo" => self.cmd_echo(),
|
||||||
|
"version" => self.cmd_version(),
|
||||||
|
"reboot" => self.cmd_reboot(),
|
||||||
|
"" => {
|
||||||
|
// Empty command, do nothing
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
print_str("Unknown command: ", 0, get_current_row());
|
||||||
|
print_str(cmd, 18, get_current_row());
|
||||||
|
print_newline();
|
||||||
|
print_str("Type 'help' for available commands.", 0, get_current_row());
|
||||||
|
print_newline();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get command as string slice
|
||||||
|
fn get_command_str(&self) -> &str {
|
||||||
|
let bytes = &self.input_buffer[..self.cursor];
|
||||||
|
core::str::from_utf8(bytes).unwrap_or("")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Clear input buffer
|
||||||
|
fn clear_input(&mut self) {
|
||||||
|
self.input_buffer.fill(0);
|
||||||
|
self.cursor = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Redraw the current line
|
||||||
|
fn redraw_line(&self) {
|
||||||
|
let row = get_current_row();
|
||||||
|
let prompt_len = self.prompt.len();
|
||||||
|
|
||||||
|
// Clear the line after prompt
|
||||||
|
for i in prompt_len..80 {
|
||||||
|
print_char(b' ', i, row);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Redraw the command
|
||||||
|
for (i, &c) in self.input_buffer[..self.cursor].iter().enumerate() {
|
||||||
|
print_char(c, prompt_len + i, row);
|
||||||
|
}
|
||||||
|
|
||||||
|
set_cursor_col(prompt_len + self.cursor);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Command implementations
|
||||||
|
fn cmd_help(&self) {
|
||||||
|
print_str("AIOS v2 Shell Commands:", 0, get_current_row());
|
||||||
|
print_newline();
|
||||||
|
print_str(" help - Show this help message", 0, get_current_row());
|
||||||
|
print_newline();
|
||||||
|
print_str(" clear - Clear the screen", 0, get_current_row());
|
||||||
|
print_newline();
|
||||||
|
print_str(" echo - Echo test message", 0, get_current_row());
|
||||||
|
print_newline();
|
||||||
|
print_str(" version - Show AIOS version", 0, get_current_row());
|
||||||
|
print_newline();
|
||||||
|
print_str(" reboot - Reboot the system", 0, get_current_row());
|
||||||
|
print_newline();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cmd_clear(&self) {
|
||||||
|
clear_screen();
|
||||||
|
reset_cursor();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cmd_echo(&self) {
|
||||||
|
print_str("Hello from AIOS v2 Shell!", 0, get_current_row());
|
||||||
|
print_newline();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cmd_version(&self) {
|
||||||
|
print_str("AIOS v2.0 - AI Operating System", 0, get_current_row());
|
||||||
|
print_newline();
|
||||||
|
print_str("Built with Rust and powered by Claude", 0, get_current_row());
|
||||||
|
print_newline();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cmd_reboot(&self) {
|
||||||
|
print_str("Rebooting system...", 0, get_current_row());
|
||||||
|
print_newline();
|
||||||
|
// Simple triple fault to reboot
|
||||||
|
unsafe {
|
||||||
|
core::arch::asm!("int3");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// VGA buffer interface (temporary - will be moved to separate module)
|
||||||
|
static mut CURSOR_ROW: usize = 0;
|
||||||
|
static mut CURSOR_COL: usize = 0;
|
||||||
|
|
||||||
|
fn get_current_row() -> usize {
|
||||||
|
unsafe { CURSOR_ROW }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_cursor_col() -> usize {
|
||||||
|
unsafe { CURSOR_COL }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_cursor_col(col: usize) {
|
||||||
|
unsafe { CURSOR_COL = col; }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn inc_cursor_col() {
|
||||||
|
unsafe {
|
||||||
|
CURSOR_COL += 1;
|
||||||
|
if CURSOR_COL >= 80 {
|
||||||
|
CURSOR_COL = 0;
|
||||||
|
CURSOR_ROW += 1;
|
||||||
|
if CURSOR_ROW >= 25 {
|
||||||
|
CURSOR_ROW = 24;
|
||||||
|
// TODO: Scroll screen
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn print_newline() {
|
||||||
|
unsafe {
|
||||||
|
CURSOR_ROW += 1;
|
||||||
|
CURSOR_COL = 0;
|
||||||
|
if CURSOR_ROW >= 25 {
|
||||||
|
CURSOR_ROW = 24;
|
||||||
|
// TODO: Scroll screen
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reset_cursor() {
|
||||||
|
unsafe {
|
||||||
|
CURSOR_ROW = 0;
|
||||||
|
CURSOR_COL = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
109
kernel/src/shell_simple.rs
Normal file
109
kernel/src/shell_simple.rs
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
//! Simple Interactive Shell for AIOS v2
|
||||||
|
|
||||||
|
/// Shell buffer for command input
|
||||||
|
static mut INPUT_BUFFER: [u8; 64] = [0; 64];
|
||||||
|
static mut INPUT_POS: usize = 0;
|
||||||
|
static mut SHELL_ROW: usize = 12;
|
||||||
|
|
||||||
|
/// Initialize shell
|
||||||
|
pub fn init() {
|
||||||
|
crate::print_str("aios> ", 0, unsafe { SHELL_ROW });
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Process a character input
|
||||||
|
pub fn process_char(c: u8) {
|
||||||
|
unsafe {
|
||||||
|
match c {
|
||||||
|
b'\n' => {
|
||||||
|
// Execute command
|
||||||
|
execute_command();
|
||||||
|
new_prompt();
|
||||||
|
}
|
||||||
|
8 => {
|
||||||
|
// Backspace
|
||||||
|
if INPUT_POS > 0 {
|
||||||
|
INPUT_POS -= 1;
|
||||||
|
INPUT_BUFFER[INPUT_POS] = 0;
|
||||||
|
redraw_line();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
32..=126 => {
|
||||||
|
// Printable character
|
||||||
|
if INPUT_POS < INPUT_BUFFER.len() - 1 {
|
||||||
|
INPUT_BUFFER[INPUT_POS] = c;
|
||||||
|
INPUT_POS += 1;
|
||||||
|
crate::print_char(c, 6 + INPUT_POS - 1, SHELL_ROW);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Execute the current command
|
||||||
|
fn execute_command() {
|
||||||
|
unsafe {
|
||||||
|
let cmd = core::str::from_utf8(&INPUT_BUFFER[..INPUT_POS]).unwrap_or("");
|
||||||
|
|
||||||
|
SHELL_ROW += 1;
|
||||||
|
|
||||||
|
match cmd {
|
||||||
|
"help" => {
|
||||||
|
crate::print_str("Commands: help, version, echo, clear", 0, SHELL_ROW);
|
||||||
|
SHELL_ROW += 1;
|
||||||
|
}
|
||||||
|
"version" => {
|
||||||
|
crate::print_str("AIOS v2.0 - Interactive Shell", 0, SHELL_ROW);
|
||||||
|
SHELL_ROW += 1;
|
||||||
|
}
|
||||||
|
"echo" => {
|
||||||
|
crate::print_str("Hello from interactive shell!", 0, SHELL_ROW);
|
||||||
|
SHELL_ROW += 1;
|
||||||
|
}
|
||||||
|
"clear" => {
|
||||||
|
crate::clear_screen();
|
||||||
|
SHELL_ROW = 5;
|
||||||
|
crate::print_str("AIOS v2 - AI Operating System", 25, 2);
|
||||||
|
crate::print_str("Interactive Shell Active", 27, 3);
|
||||||
|
}
|
||||||
|
"" => {
|
||||||
|
// Empty command, just new line
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
crate::print_str("Unknown command: ", 0, SHELL_ROW);
|
||||||
|
crate::print_str(cmd, 18, SHELL_ROW);
|
||||||
|
SHELL_ROW += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear input buffer
|
||||||
|
INPUT_BUFFER.fill(0);
|
||||||
|
INPUT_POS = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Start a new command prompt
|
||||||
|
fn new_prompt() {
|
||||||
|
unsafe {
|
||||||
|
if SHELL_ROW >= 24 {
|
||||||
|
SHELL_ROW = 23;
|
||||||
|
}
|
||||||
|
SHELL_ROW += 1;
|
||||||
|
crate::print_str("aios> ", 0, SHELL_ROW);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Redraw current input line
|
||||||
|
fn redraw_line() {
|
||||||
|
unsafe {
|
||||||
|
// Clear line after prompt
|
||||||
|
for i in 6..80 {
|
||||||
|
crate::print_char(b' ', i, SHELL_ROW);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Redraw input
|
||||||
|
for (i, &c) in INPUT_BUFFER[..INPUT_POS].iter().enumerate() {
|
||||||
|
crate::print_char(c, 6 + i, SHELL_ROW);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
4
rust-toolchain.toml
Normal file
4
rust-toolchain.toml
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
[toolchain]
|
||||||
|
channel = "nightly"
|
||||||
|
targets = ["x86_64-unknown-none"]
|
||||||
|
components = ["rust-src", "llvm-tools-preview"]
|
42
scpt/bootimage-test.sh
Executable file
42
scpt/bootimage-test.sh
Executable file
@ -0,0 +1,42 @@
|
|||||||
|
#!/bin/zsh
|
||||||
|
|
||||||
|
d=${0:a:h:h}
|
||||||
|
cd $d
|
||||||
|
|
||||||
|
echo "=== Aios Boot Image Test ==="
|
||||||
|
|
||||||
|
# Install bootimage tool
|
||||||
|
echo "Installing bootimage tool..."
|
||||||
|
cargo install bootimage --quiet
|
||||||
|
|
||||||
|
# Build boot image
|
||||||
|
echo "Building bootable image..."
|
||||||
|
cd kernel
|
||||||
|
cargo bootimage --release
|
||||||
|
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
echo "Build successful!"
|
||||||
|
|
||||||
|
# Find the boot image
|
||||||
|
echo "Looking for boot image..."
|
||||||
|
|
||||||
|
# Check the path shown in the build output
|
||||||
|
if [ -f "../target/x86_64-unknown-none/release/bootimage-aios-kernel.bin" ]; then
|
||||||
|
BOOT_IMAGE="../target/x86_64-unknown-none/release/bootimage-aios-kernel.bin"
|
||||||
|
else
|
||||||
|
# Search for it
|
||||||
|
BOOT_IMAGE=$(find .. -name "bootimage-*.bin" 2>/dev/null | head -1)
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -n "$BOOT_IMAGE" ] && [ -f "$BOOT_IMAGE" ]; then
|
||||||
|
echo "Found boot image: $BOOT_IMAGE"
|
||||||
|
echo "Running in QEMU..."
|
||||||
|
qemu-system-x86_64 -drive format=raw,file="$BOOT_IMAGE"
|
||||||
|
else
|
||||||
|
echo "Boot image not found"
|
||||||
|
echo "Looking in parent directory..."
|
||||||
|
ls -la ../target/x86_64-unknown-none/release/ | grep bootimage
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "Build failed"
|
||||||
|
fi
|
@ -1,44 +0,0 @@
|
|||||||
use linked_list_allocator::LockedHeap;
|
|
||||||
use x86_64::{
|
|
||||||
structures::paging::{
|
|
||||||
mapper::MapToError, FrameAllocator, Mapper, Page, PageTableFlags, Size4KiB,
|
|
||||||
},
|
|
||||||
VirtAddr,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crate::memory::{HEAP_START, HEAP_SIZE};
|
|
||||||
|
|
||||||
#[global_allocator]
|
|
||||||
static ALLOCATOR: LockedHeap = LockedHeap::empty();
|
|
||||||
|
|
||||||
pub fn init_heap(
|
|
||||||
mapper: &mut impl Mapper<Size4KiB>,
|
|
||||||
frame_allocator: &mut impl FrameAllocator<Size4KiB>,
|
|
||||||
) -> Result<(), MapToError<Size4KiB>> {
|
|
||||||
let page_range = {
|
|
||||||
let heap_start = VirtAddr::new(HEAP_START as u64);
|
|
||||||
let heap_end = heap_start + HEAP_SIZE - 1u64;
|
|
||||||
let heap_start_page = Page::containing_address(heap_start);
|
|
||||||
let heap_end_page = Page::containing_address(heap_end);
|
|
||||||
Page::range_inclusive(heap_start_page, heap_end_page)
|
|
||||||
};
|
|
||||||
|
|
||||||
for page in page_range {
|
|
||||||
let frame = frame_allocator
|
|
||||||
.allocate_frame()
|
|
||||||
.ok_or(MapToError::FrameAllocationFailed)?;
|
|
||||||
let flags = PageTableFlags::PRESENT | PageTableFlags::WRITABLE;
|
|
||||||
unsafe { mapper.map_to(page, frame, flags, frame_allocator) }?.flush();
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe {
|
|
||||||
ALLOCATOR.lock().init(HEAP_START as *mut u8, HEAP_SIZE);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[alloc_error_handler]
|
|
||||||
fn alloc_error_handler(layout: alloc::alloc::Layout) -> ! {
|
|
||||||
panic!("allocation error: {:?}", layout)
|
|
||||||
}
|
|
@ -1,289 +0,0 @@
|
|||||||
use alloc::{collections::BTreeMap, string::String, vec::Vec, string::ToString, format};
|
|
||||||
use core::fmt;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
|
||||||
pub enum FileType {
|
|
||||||
Regular,
|
|
||||||
Directory,
|
|
||||||
Executable,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct FileMetadata {
|
|
||||||
pub name: String,
|
|
||||||
pub size: usize,
|
|
||||||
pub file_type: FileType,
|
|
||||||
pub permissions: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct File {
|
|
||||||
pub metadata: FileMetadata,
|
|
||||||
pub data: Vec<u8>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct Directory {
|
|
||||||
pub metadata: FileMetadata,
|
|
||||||
pub entries: BTreeMap<String, FileSystemEntry>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub enum FileSystemEntry {
|
|
||||||
File(File),
|
|
||||||
Directory(Directory),
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct MemoryFileSystem {
|
|
||||||
root: Directory,
|
|
||||||
current_dir: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MemoryFileSystem {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
let root_metadata = FileMetadata {
|
|
||||||
name: "/".to_string(),
|
|
||||||
size: 0,
|
|
||||||
file_type: FileType::Directory,
|
|
||||||
permissions: 0o755,
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut root = Directory {
|
|
||||||
metadata: root_metadata,
|
|
||||||
entries: BTreeMap::new(),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Create initial directories
|
|
||||||
root.entries.insert("bin".to_string(), FileSystemEntry::Directory(Directory {
|
|
||||||
metadata: FileMetadata {
|
|
||||||
name: "bin".to_string(),
|
|
||||||
size: 0,
|
|
||||||
file_type: FileType::Directory,
|
|
||||||
permissions: 0o755,
|
|
||||||
},
|
|
||||||
entries: BTreeMap::new(),
|
|
||||||
}));
|
|
||||||
|
|
||||||
root.entries.insert("home".to_string(), FileSystemEntry::Directory(Directory {
|
|
||||||
metadata: FileMetadata {
|
|
||||||
name: "home".to_string(),
|
|
||||||
size: 0,
|
|
||||||
file_type: FileType::Directory,
|
|
||||||
permissions: 0o755,
|
|
||||||
},
|
|
||||||
entries: BTreeMap::new(),
|
|
||||||
}));
|
|
||||||
|
|
||||||
root.entries.insert("tmp".to_string(), FileSystemEntry::Directory(Directory {
|
|
||||||
metadata: FileMetadata {
|
|
||||||
name: "tmp".to_string(),
|
|
||||||
size: 0,
|
|
||||||
file_type: FileType::Directory,
|
|
||||||
permissions: 0o777,
|
|
||||||
},
|
|
||||||
entries: BTreeMap::new(),
|
|
||||||
}));
|
|
||||||
|
|
||||||
Self {
|
|
||||||
root,
|
|
||||||
current_dir: "/".to_string(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn create_file(&mut self, path: &str, content: &[u8]) -> Result<(), &'static str> {
|
|
||||||
let (dir_path, filename) = self.split_path(path);
|
|
||||||
let directory = self.get_directory_mut(&dir_path)?;
|
|
||||||
|
|
||||||
let file = File {
|
|
||||||
metadata: FileMetadata {
|
|
||||||
name: filename.clone(),
|
|
||||||
size: content.len(),
|
|
||||||
file_type: FileType::Regular,
|
|
||||||
permissions: 0o644,
|
|
||||||
},
|
|
||||||
data: content.to_vec(),
|
|
||||||
};
|
|
||||||
|
|
||||||
directory.entries.insert(filename, FileSystemEntry::File(file));
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn create_executable(&mut self, path: &str, content: &[u8]) -> Result<(), &'static str> {
|
|
||||||
let (dir_path, filename) = self.split_path(path);
|
|
||||||
let directory = self.get_directory_mut(&dir_path)?;
|
|
||||||
|
|
||||||
let file = File {
|
|
||||||
metadata: FileMetadata {
|
|
||||||
name: filename.clone(),
|
|
||||||
size: content.len(),
|
|
||||||
file_type: FileType::Executable,
|
|
||||||
permissions: 0o755,
|
|
||||||
},
|
|
||||||
data: content.to_vec(),
|
|
||||||
};
|
|
||||||
|
|
||||||
directory.entries.insert(filename, FileSystemEntry::File(file));
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn read_file(&self, path: &str) -> Result<&Vec<u8>, &'static str> {
|
|
||||||
let (dir_path, filename) = self.split_path(path);
|
|
||||||
let directory = self.get_directory(&dir_path)?;
|
|
||||||
|
|
||||||
match directory.entries.get(&filename) {
|
|
||||||
Some(FileSystemEntry::File(file)) => Ok(&file.data),
|
|
||||||
Some(FileSystemEntry::Directory(_)) => Err("Path is a directory"),
|
|
||||||
None => Err("File not found"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn create_directory(&mut self, path: &str) -> Result<(), &'static str> {
|
|
||||||
let (parent_path, dirname) = self.split_path(path);
|
|
||||||
let parent = self.get_directory_mut(&parent_path)?;
|
|
||||||
|
|
||||||
let directory = Directory {
|
|
||||||
metadata: FileMetadata {
|
|
||||||
name: dirname.clone(),
|
|
||||||
size: 0,
|
|
||||||
file_type: FileType::Directory,
|
|
||||||
permissions: 0o755,
|
|
||||||
},
|
|
||||||
entries: BTreeMap::new(),
|
|
||||||
};
|
|
||||||
|
|
||||||
parent.entries.insert(dirname, FileSystemEntry::Directory(directory));
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn list_directory(&self, path: &str) -> Result<Vec<&FileMetadata>, &'static str> {
|
|
||||||
let directory = self.get_directory(path)?;
|
|
||||||
let mut entries = Vec::new();
|
|
||||||
|
|
||||||
for entry in directory.entries.values() {
|
|
||||||
match entry {
|
|
||||||
FileSystemEntry::File(file) => entries.push(&file.metadata),
|
|
||||||
FileSystemEntry::Directory(dir) => entries.push(&dir.metadata),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(entries)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn remove_file(&mut self, path: &str) -> Result<(), &'static str> {
|
|
||||||
let (dir_path, filename) = self.split_path(path);
|
|
||||||
let directory = self.get_directory_mut(&dir_path)?;
|
|
||||||
|
|
||||||
match directory.entries.remove(&filename) {
|
|
||||||
Some(FileSystemEntry::File(_)) => Ok(()),
|
|
||||||
Some(FileSystemEntry::Directory(_)) => {
|
|
||||||
directory.entries.insert(filename.clone(),
|
|
||||||
FileSystemEntry::Directory(Directory {
|
|
||||||
metadata: FileMetadata {
|
|
||||||
name: filename.clone(),
|
|
||||||
size: 0,
|
|
||||||
file_type: FileType::Directory,
|
|
||||||
permissions: 0o755,
|
|
||||||
},
|
|
||||||
entries: BTreeMap::new(),
|
|
||||||
}));
|
|
||||||
Err("Cannot remove directory with rm")
|
|
||||||
},
|
|
||||||
None => Err("File not found"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn change_directory(&mut self, path: &str) -> Result<(), &'static str> {
|
|
||||||
let absolute_path = if path.starts_with('/') {
|
|
||||||
path.to_string()
|
|
||||||
} else {
|
|
||||||
if self.current_dir == "/" {
|
|
||||||
format!("/{}", path)
|
|
||||||
} else {
|
|
||||||
format!("{}/{}", self.current_dir, path)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Check if directory exists
|
|
||||||
self.get_directory(&absolute_path)?;
|
|
||||||
self.current_dir = absolute_path;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_current_directory(&self) -> &str {
|
|
||||||
&self.current_dir
|
|
||||||
}
|
|
||||||
|
|
||||||
fn split_path(&self, path: &str) -> (String, String) {
|
|
||||||
if path == "/" {
|
|
||||||
return ("/".to_string(), "".to_string());
|
|
||||||
}
|
|
||||||
|
|
||||||
let full_path = if path.starts_with('/') {
|
|
||||||
path.to_string()
|
|
||||||
} else {
|
|
||||||
if self.current_dir == "/" {
|
|
||||||
format!("/{}", path)
|
|
||||||
} else {
|
|
||||||
format!("{}/{}", self.current_dir, path)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(last_slash) = full_path.rfind('/') {
|
|
||||||
if last_slash == 0 {
|
|
||||||
("/".to_string(), full_path[1..].to_string())
|
|
||||||
} else {
|
|
||||||
(full_path[..last_slash].to_string(), full_path[last_slash + 1..].to_string())
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
(self.current_dir.clone(), path.to_string())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_directory(&self, path: &str) -> Result<&Directory, &'static str> {
|
|
||||||
if path == "/" {
|
|
||||||
return Ok(&self.root);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut current = &self.root;
|
|
||||||
let parts: Vec<&str> = path.trim_start_matches('/').split('/').filter(|s| !s.is_empty()).collect();
|
|
||||||
|
|
||||||
for part in parts {
|
|
||||||
match current.entries.get(part) {
|
|
||||||
Some(FileSystemEntry::Directory(dir)) => current = dir,
|
|
||||||
Some(FileSystemEntry::File(_)) => return Err("Path component is a file"),
|
|
||||||
None => return Err("Directory not found"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(current)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_directory_mut(&mut self, path: &str) -> Result<&mut Directory, &'static str> {
|
|
||||||
if path == "/" {
|
|
||||||
return Ok(&mut self.root);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut current = &mut self.root;
|
|
||||||
let parts: Vec<&str> = path.trim_start_matches('/').split('/').filter(|s| !s.is_empty()).collect();
|
|
||||||
|
|
||||||
for part in parts {
|
|
||||||
match current.entries.get_mut(part) {
|
|
||||||
Some(FileSystemEntry::Directory(dir)) => current = dir,
|
|
||||||
Some(FileSystemEntry::File(_)) => return Err("Path component is a file"),
|
|
||||||
None => return Err("Directory not found"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(current)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for FileType {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
match self {
|
|
||||||
FileType::Regular => write!(f, "-"),
|
|
||||||
FileType::Directory => write!(f, "d"),
|
|
||||||
FileType::Executable => write!(f, "x"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,88 +0,0 @@
|
|||||||
use crate::{print, println};
|
|
||||||
use lazy_static::lazy_static;
|
|
||||||
use pic8259::ChainedPics;
|
|
||||||
use spin;
|
|
||||||
use x86_64::structures::idt::{InterruptDescriptorTable, InterruptStackFrame};
|
|
||||||
|
|
||||||
pub const PIC_1_OFFSET: u8 = 32;
|
|
||||||
pub const PIC_2_OFFSET: u8 = PIC_1_OFFSET + 8;
|
|
||||||
|
|
||||||
pub static PICS: spin::Mutex<ChainedPics> =
|
|
||||||
spin::Mutex::new(unsafe { ChainedPics::new(PIC_1_OFFSET, PIC_2_OFFSET) });
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
|
||||||
#[repr(u8)]
|
|
||||||
pub enum InterruptIndex {
|
|
||||||
Timer = PIC_1_OFFSET,
|
|
||||||
Keyboard,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl InterruptIndex {
|
|
||||||
fn as_u8(self) -> u8 {
|
|
||||||
self as u8
|
|
||||||
}
|
|
||||||
|
|
||||||
fn as_usize(self) -> usize {
|
|
||||||
usize::from(self.as_u8())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
lazy_static! {
|
|
||||||
static ref IDT: InterruptDescriptorTable = {
|
|
||||||
let mut idt = InterruptDescriptorTable::new();
|
|
||||||
idt.breakpoint.set_handler_fn(breakpoint_handler);
|
|
||||||
idt[InterruptIndex::Timer.as_usize()]
|
|
||||||
.set_handler_fn(timer_interrupt_handler);
|
|
||||||
idt[InterruptIndex::Keyboard.as_usize()]
|
|
||||||
.set_handler_fn(keyboard_interrupt_handler);
|
|
||||||
idt
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn init_idt() {
|
|
||||||
IDT.load();
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "x86-interrupt" fn breakpoint_handler(stack_frame: InterruptStackFrame) {
|
|
||||||
println!("EXCEPTION: BREAKPOINT\n{:#?}", stack_frame);
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "x86-interrupt" fn timer_interrupt_handler(_stack_frame: InterruptStackFrame) {
|
|
||||||
unsafe {
|
|
||||||
PICS.lock()
|
|
||||||
.notify_end_of_interrupt(InterruptIndex::Timer.as_u8());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extern "x86-interrupt" fn keyboard_interrupt_handler(_stack_frame: InterruptStackFrame) {
|
|
||||||
use pc_keyboard::{layouts, DecodedKey, HandleControl, Keyboard, ScancodeSet1};
|
|
||||||
use spin::Mutex;
|
|
||||||
use x86_64::instructions::port::Port;
|
|
||||||
|
|
||||||
lazy_static! {
|
|
||||||
static ref KEYBOARD: Mutex<Keyboard<layouts::Us104Key, ScancodeSet1>> =
|
|
||||||
Mutex::new(Keyboard::new(ScancodeSet1::new(), layouts::Us104Key,
|
|
||||||
HandleControl::Ignore)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut keyboard = KEYBOARD.lock();
|
|
||||||
let mut port = Port::new(0x60);
|
|
||||||
|
|
||||||
let scancode: u8 = unsafe { port.read() };
|
|
||||||
if let Ok(Some(key_event)) = keyboard.add_byte(scancode) {
|
|
||||||
if let Some(key) = keyboard.process_keyevent(key_event) {
|
|
||||||
match key {
|
|
||||||
DecodedKey::Unicode(character) => {
|
|
||||||
crate::shell::handle_keyboard_input(character);
|
|
||||||
}
|
|
||||||
DecodedKey::RawKey(key) => print!("{:?}", key),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe {
|
|
||||||
PICS.lock()
|
|
||||||
.notify_end_of_interrupt(InterruptIndex::Keyboard.as_u8());
|
|
||||||
}
|
|
||||||
}
|
|
100
src/main.rs
100
src/main.rs
@ -1,100 +0,0 @@
|
|||||||
#![no_std]
|
|
||||||
#![no_main]
|
|
||||||
#![feature(custom_test_frameworks)]
|
|
||||||
#![feature(alloc_error_handler)]
|
|
||||||
#![feature(abi_x86_interrupt)]
|
|
||||||
#![test_runner(crate::test_runner)]
|
|
||||||
#![reexport_test_harness_main = "test_main"]
|
|
||||||
|
|
||||||
extern crate alloc;
|
|
||||||
|
|
||||||
use core::panic::PanicInfo;
|
|
||||||
use bootloader::{BootInfo, entry_point};
|
|
||||||
use linked_list_allocator::LockedHeap;
|
|
||||||
|
|
||||||
// Global allocatorは削除(allocator.rsで定義済み)
|
|
||||||
|
|
||||||
mod vga_buffer;
|
|
||||||
mod serial;
|
|
||||||
mod memory;
|
|
||||||
mod allocator;
|
|
||||||
mod interrupts;
|
|
||||||
mod shell;
|
|
||||||
mod systemd;
|
|
||||||
mod filesystem;
|
|
||||||
mod process;
|
|
||||||
mod package;
|
|
||||||
|
|
||||||
entry_point!(kernel_main);
|
|
||||||
|
|
||||||
fn kernel_main(boot_info: &'static BootInfo) -> ! {
|
|
||||||
use memory::BootInfoFrameAllocator;
|
|
||||||
use x86_64::VirtAddr;
|
|
||||||
|
|
||||||
println!("AIOS Kernel Starting...");
|
|
||||||
|
|
||||||
// メモリ管理の初期化(bootloader 0.9系では物理メモリオフセットが固定)
|
|
||||||
let phys_mem_offset = VirtAddr::new(0);
|
|
||||||
let mut mapper = unsafe { memory::init(phys_mem_offset) };
|
|
||||||
let mut frame_allocator = unsafe {
|
|
||||||
BootInfoFrameAllocator::init(&boot_info.memory_map)
|
|
||||||
};
|
|
||||||
|
|
||||||
// ヒープの初期化
|
|
||||||
allocator::init_heap(&mut mapper, &mut frame_allocator)
|
|
||||||
.expect("heap initialization failed");
|
|
||||||
|
|
||||||
// 割り込み処理の初期化
|
|
||||||
interrupts::init_idt();
|
|
||||||
|
|
||||||
println!("AIOS initialized successfully!");
|
|
||||||
|
|
||||||
// シェルの初期化
|
|
||||||
shell::init();
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
test_main();
|
|
||||||
|
|
||||||
loop {
|
|
||||||
x86_64::instructions::hlt();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[panic_handler]
|
|
||||||
fn panic(info: &PanicInfo) -> ! {
|
|
||||||
println!("{}", info);
|
|
||||||
serial_println!("PANIC: {}", info);
|
|
||||||
loop {}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
fn test_runner(tests: &[&dyn Fn()]) {
|
|
||||||
serial_println!("Running {} tests", tests.len());
|
|
||||||
for test in tests {
|
|
||||||
test();
|
|
||||||
}
|
|
||||||
exit_qemu(QemuExitCode::Success);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
||||||
#[repr(u32)]
|
|
||||||
pub enum QemuExitCode {
|
|
||||||
Success = 0x10,
|
|
||||||
Failed = 0x11,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
pub fn exit_qemu(exit_code: QemuExitCode) {
|
|
||||||
use x86_64::instructions::port::Port;
|
|
||||||
|
|
||||||
unsafe {
|
|
||||||
let mut port = Port::new(0xf4);
|
|
||||||
port.write(exit_code as u32);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test_case]
|
|
||||||
fn trivial_assertion() {
|
|
||||||
assert_eq!(1, 1);
|
|
||||||
}
|
|
@ -1,76 +0,0 @@
|
|||||||
use bootloader::bootinfo::{MemoryMap, MemoryRegionType};
|
|
||||||
use x86_64::{
|
|
||||||
structures::paging::{
|
|
||||||
mapper::MapToError, FrameAllocator, Mapper, Page, PageTable, PhysFrame, Size4KiB,
|
|
||||||
},
|
|
||||||
PhysAddr, VirtAddr,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub struct BootInfoFrameAllocator {
|
|
||||||
memory_map: &'static MemoryMap,
|
|
||||||
next: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BootInfoFrameAllocator {
|
|
||||||
pub unsafe fn init(memory_map: &'static MemoryMap) -> Self {
|
|
||||||
BootInfoFrameAllocator {
|
|
||||||
memory_map,
|
|
||||||
next: 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn usable_frames(&self) -> impl Iterator<Item = PhysFrame> {
|
|
||||||
let regions = self.memory_map.iter();
|
|
||||||
let usable_regions = regions
|
|
||||||
.filter(|r| r.region_type == MemoryRegionType::Usable);
|
|
||||||
let addr_ranges = usable_regions
|
|
||||||
.map(|r| r.range.start_addr()..r.range.end_addr());
|
|
||||||
let frame_addresses = addr_ranges.flat_map(|r| r.step_by(4096));
|
|
||||||
frame_addresses.map(|addr| PhysFrame::containing_address(PhysAddr::new(addr)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
unsafe impl FrameAllocator<Size4KiB> for BootInfoFrameAllocator {
|
|
||||||
fn allocate_frame(&mut self) -> Option<PhysFrame> {
|
|
||||||
let frame = self.usable_frames().nth(self.next);
|
|
||||||
self.next += 1;
|
|
||||||
frame
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn create_example_mapping(
|
|
||||||
page: Page,
|
|
||||||
mapper: &mut impl Mapper<Size4KiB>,
|
|
||||||
frame_allocator: &mut impl FrameAllocator<Size4KiB>,
|
|
||||||
) -> Result<(), MapToError<Size4KiB>> {
|
|
||||||
use x86_64::structures::paging::PageTableFlags as Flags;
|
|
||||||
|
|
||||||
let frame = PhysFrame::containing_address(PhysAddr::new(0xb8000));
|
|
||||||
let flags = Flags::PRESENT | Flags::WRITABLE;
|
|
||||||
|
|
||||||
let map_to_result = unsafe { mapper.map_to(page, frame, flags, frame_allocator) };
|
|
||||||
map_to_result?.flush();
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub unsafe fn active_level_4_table(physical_memory_offset: VirtAddr) -> &'static mut PageTable {
|
|
||||||
use x86_64::registers::control::Cr3;
|
|
||||||
|
|
||||||
let (level_4_table_frame, _) = Cr3::read();
|
|
||||||
|
|
||||||
let phys = level_4_table_frame.start_address();
|
|
||||||
let virt = physical_memory_offset + phys.as_u64();
|
|
||||||
let page_table_ptr: *mut PageTable = virt.as_mut_ptr();
|
|
||||||
|
|
||||||
&mut *page_table_ptr
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
pub const HEAP_START: usize = 0x_4444_4444_0000;
|
|
||||||
pub const HEAP_SIZE: usize = 16 * 1024; // 16 KiB - より小さなサイズで安全に
|
|
||||||
|
|
||||||
pub unsafe fn init(physical_memory_offset: VirtAddr) -> impl Mapper<Size4KiB> {
|
|
||||||
let level_4_table = active_level_4_table(physical_memory_offset);
|
|
||||||
x86_64::structures::paging::OffsetPageTable::new(level_4_table, physical_memory_offset)
|
|
||||||
}
|
|
413
src/package.rs
413
src/package.rs
@ -1,413 +0,0 @@
|
|||||||
use alloc::{collections::BTreeMap, string::String, vec::Vec, string::ToString, format, vec};
|
|
||||||
use core::fmt;
|
|
||||||
use crate::filesystem::MemoryFileSystem;
|
|
||||||
use crate::systemd::SystemManager;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct PackageMetadata {
|
|
||||||
pub name: String,
|
|
||||||
pub version: String,
|
|
||||||
pub description: String,
|
|
||||||
pub arch: String,
|
|
||||||
pub size: usize,
|
|
||||||
pub dependencies: Vec<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct PackageFile {
|
|
||||||
pub path: String,
|
|
||||||
pub content: Vec<u8>,
|
|
||||||
pub executable: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct ServiceConfig {
|
|
||||||
pub name: String,
|
|
||||||
pub command: String,
|
|
||||||
pub container: bool,
|
|
||||||
pub auto_start: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct Package {
|
|
||||||
pub metadata: PackageMetadata,
|
|
||||||
pub files: Vec<PackageFile>,
|
|
||||||
pub services: Vec<ServiceConfig>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Repository {
|
|
||||||
pub name: String,
|
|
||||||
pub url: String,
|
|
||||||
pub packages: BTreeMap<String, PackageMetadata>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct PackageManager {
|
|
||||||
installed_packages: BTreeMap<String, Package>,
|
|
||||||
repositories: Vec<Repository>,
|
|
||||||
package_db_path: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PackageManager {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
let mut pm = Self {
|
|
||||||
installed_packages: BTreeMap::new(),
|
|
||||||
repositories: Vec::new(),
|
|
||||||
package_db_path: "/var/pkg".to_string(),
|
|
||||||
};
|
|
||||||
|
|
||||||
// Add default repository
|
|
||||||
pm.add_repository("aios-main".to_string(), "https://packages.aios.dev".to_string());
|
|
||||||
|
|
||||||
// Create some built-in packages
|
|
||||||
pm.create_builtin_packages();
|
|
||||||
|
|
||||||
pm
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_builtin_packages(&mut self) {
|
|
||||||
// Claude Code package
|
|
||||||
let claude_package = Package {
|
|
||||||
metadata: PackageMetadata {
|
|
||||||
name: "claude-code".to_string(),
|
|
||||||
version: "1.0.0".to_string(),
|
|
||||||
description: "AI Development Environment".to_string(),
|
|
||||||
arch: "x86_64".to_string(),
|
|
||||||
size: 1024 * 1024, // 1MB
|
|
||||||
dependencies: vec!["nodejs".to_string(), "python".to_string()],
|
|
||||||
},
|
|
||||||
files: vec![
|
|
||||||
PackageFile {
|
|
||||||
path: "/bin/claude-code".to_string(),
|
|
||||||
content: b"AIOS\x00\x10\x00\x00claude-code-ai-assistant".to_vec(),
|
|
||||||
executable: true,
|
|
||||||
},
|
|
||||||
PackageFile {
|
|
||||||
path: "/lib/claude/runtime.so".to_string(),
|
|
||||||
content: b"Claude Runtime Library".to_vec(),
|
|
||||||
executable: false,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
services: vec![
|
|
||||||
ServiceConfig {
|
|
||||||
name: "claude-code".to_string(),
|
|
||||||
command: "/bin/claude-code --daemon".to_string(),
|
|
||||||
container: true,
|
|
||||||
auto_start: false,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
// Node.js package
|
|
||||||
let nodejs_package = Package {
|
|
||||||
metadata: PackageMetadata {
|
|
||||||
name: "nodejs".to_string(),
|
|
||||||
version: "18.0.0".to_string(),
|
|
||||||
description: "JavaScript Runtime".to_string(),
|
|
||||||
arch: "x86_64".to_string(),
|
|
||||||
size: 2 * 1024 * 1024, // 2MB
|
|
||||||
dependencies: vec![],
|
|
||||||
},
|
|
||||||
files: vec![
|
|
||||||
PackageFile {
|
|
||||||
path: "/bin/node".to_string(),
|
|
||||||
content: b"AIOS\x00\x10\x00\x00nodejs-runtime".to_vec(),
|
|
||||||
executable: true,
|
|
||||||
},
|
|
||||||
PackageFile {
|
|
||||||
path: "/bin/npm".to_string(),
|
|
||||||
content: b"AIOS\x00\x10\x00\x00npm-package-manager".to_vec(),
|
|
||||||
executable: true,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
services: vec![],
|
|
||||||
};
|
|
||||||
|
|
||||||
// Python package
|
|
||||||
let python_package = Package {
|
|
||||||
metadata: PackageMetadata {
|
|
||||||
name: "python".to_string(),
|
|
||||||
version: "3.9.0".to_string(),
|
|
||||||
description: "Python Interpreter".to_string(),
|
|
||||||
arch: "x86_64".to_string(),
|
|
||||||
size: 3 * 1024 * 1024, // 3MB
|
|
||||||
dependencies: vec![],
|
|
||||||
},
|
|
||||||
files: vec![
|
|
||||||
PackageFile {
|
|
||||||
path: "/bin/python".to_string(),
|
|
||||||
content: b"AIOS\x00\x10\x00\x00python-interpreter".to_vec(),
|
|
||||||
executable: true,
|
|
||||||
},
|
|
||||||
PackageFile {
|
|
||||||
path: "/bin/pip".to_string(),
|
|
||||||
content: b"AIOS\x00\x10\x00\x00pip-package-installer".to_vec(),
|
|
||||||
executable: true,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
services: vec![],
|
|
||||||
};
|
|
||||||
|
|
||||||
// Store as available packages (not installed)
|
|
||||||
self.repositories[0].packages.insert("claude-code".to_string(), claude_package.metadata.clone());
|
|
||||||
self.repositories[0].packages.insert("nodejs".to_string(), nodejs_package.metadata.clone());
|
|
||||||
self.repositories[0].packages.insert("python".to_string(), python_package.metadata.clone());
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn add_repository(&mut self, name: String, url: String) {
|
|
||||||
let repo = Repository {
|
|
||||||
name,
|
|
||||||
url,
|
|
||||||
packages: BTreeMap::new(),
|
|
||||||
};
|
|
||||||
self.repositories.push(repo);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn install_package(&mut self, package_name: &str, filesystem: &mut MemoryFileSystem, system_manager: &mut SystemManager) -> Result<(), &'static str> {
|
|
||||||
// Check if already installed
|
|
||||||
if self.installed_packages.contains_key(package_name) {
|
|
||||||
return Err("Package already installed");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find package in repositories
|
|
||||||
let package = self.find_package(package_name)?;
|
|
||||||
|
|
||||||
// Check dependencies
|
|
||||||
for dep in &package.metadata.dependencies {
|
|
||||||
if !self.is_package_installed(dep) {
|
|
||||||
crate::println!("Installing dependency: {}", dep);
|
|
||||||
self.install_package(dep, filesystem, system_manager)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Install files
|
|
||||||
for file in &package.files {
|
|
||||||
if file.executable {
|
|
||||||
filesystem.create_executable(&file.path, &file.content)?;
|
|
||||||
} else {
|
|
||||||
filesystem.create_file(&file.path, &file.content)?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Register services
|
|
||||||
for service in &package.services {
|
|
||||||
use crate::systemd::{ServiceUnit, ServiceState};
|
|
||||||
let service_unit = ServiceUnit {
|
|
||||||
name: service.name.clone(),
|
|
||||||
description: format!("Service for {}", package.metadata.name),
|
|
||||||
exec_start: service.command.clone(),
|
|
||||||
working_directory: Some("/".to_string()),
|
|
||||||
environment: vec![],
|
|
||||||
container: if service.container { Some("default".to_string()) } else { None },
|
|
||||||
state: ServiceState::Inactive,
|
|
||||||
};
|
|
||||||
|
|
||||||
system_manager.create_service(service_unit)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mark as installed
|
|
||||||
self.installed_packages.insert(package_name.to_string(), package);
|
|
||||||
|
|
||||||
crate::println!("Successfully installed package: {}", package_name);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn remove_package(&mut self, package_name: &str, filesystem: &mut MemoryFileSystem) -> Result<(), &'static str> {
|
|
||||||
let package = self.installed_packages.get(package_name)
|
|
||||||
.ok_or("Package not installed")?
|
|
||||||
.clone();
|
|
||||||
|
|
||||||
// Remove files
|
|
||||||
for file in &package.files {
|
|
||||||
let _ = filesystem.remove_file(&file.path); // Ignore errors for now
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove from installed list
|
|
||||||
self.installed_packages.remove(package_name);
|
|
||||||
|
|
||||||
crate::println!("Successfully removed package: {}", package_name);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn list_installed(&self) {
|
|
||||||
crate::println!("Installed packages:");
|
|
||||||
crate::println!("{:<20} {:<10} {:<30}", "NAME", "VERSION", "DESCRIPTION");
|
|
||||||
crate::println!("{}", "-".repeat(60));
|
|
||||||
|
|
||||||
for package in self.installed_packages.values() {
|
|
||||||
crate::println!("{:<20} {:<10} {:<30}",
|
|
||||||
package.metadata.name,
|
|
||||||
package.metadata.version,
|
|
||||||
package.metadata.description
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn list_available(&self) {
|
|
||||||
crate::println!("Available packages:");
|
|
||||||
crate::println!("{:<20} {:<10} {:<30}", "NAME", "VERSION", "DESCRIPTION");
|
|
||||||
crate::println!("{}", "-".repeat(60));
|
|
||||||
|
|
||||||
for repo in &self.repositories {
|
|
||||||
for package in repo.packages.values() {
|
|
||||||
let status = if self.installed_packages.contains_key(&package.name) {
|
|
||||||
"[installed]"
|
|
||||||
} else {
|
|
||||||
""
|
|
||||||
};
|
|
||||||
crate::println!("{:<20} {:<10} {:<20} {}",
|
|
||||||
package.name,
|
|
||||||
package.version,
|
|
||||||
package.description,
|
|
||||||
status
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn search_package(&self, query: &str) -> Vec<&PackageMetadata> {
|
|
||||||
let mut results = Vec::new();
|
|
||||||
for repo in &self.repositories {
|
|
||||||
for package in repo.packages.values() {
|
|
||||||
if package.name.contains(query) || package.description.contains(query) {
|
|
||||||
results.push(package);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
results
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn show_package_info(&self, package_name: &str) -> Result<(), &'static str> {
|
|
||||||
if let Some(package) = self.installed_packages.get(package_name) {
|
|
||||||
crate::println!("Package: {}", package.metadata.name);
|
|
||||||
crate::println!("Version: {}", package.metadata.version);
|
|
||||||
crate::println!("Description: {}", package.metadata.description);
|
|
||||||
crate::println!("Architecture: {}", package.metadata.arch);
|
|
||||||
crate::println!("Size: {} bytes", package.metadata.size);
|
|
||||||
crate::println!("Status: Installed");
|
|
||||||
|
|
||||||
if !package.metadata.dependencies.is_empty() {
|
|
||||||
crate::println!("Dependencies:");
|
|
||||||
for dep in &package.metadata.dependencies {
|
|
||||||
crate::println!(" - {}", dep);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !package.files.is_empty() {
|
|
||||||
crate::println!("Files:");
|
|
||||||
for file in &package.files {
|
|
||||||
crate::println!(" {}", file.path);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
// Check in repositories
|
|
||||||
for repo in &self.repositories {
|
|
||||||
if let Some(package) = repo.packages.get(package_name) {
|
|
||||||
crate::println!("Package: {}", package.name);
|
|
||||||
crate::println!("Version: {}", package.version);
|
|
||||||
crate::println!("Description: {}", package.description);
|
|
||||||
crate::println!("Architecture: {}", package.arch);
|
|
||||||
crate::println!("Size: {} bytes", package.size);
|
|
||||||
crate::println!("Status: Available");
|
|
||||||
|
|
||||||
if !package.dependencies.is_empty() {
|
|
||||||
crate::println!("Dependencies:");
|
|
||||||
for dep in &package.dependencies {
|
|
||||||
crate::println!(" - {}", dep);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err("Package not found")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn find_package(&self, package_name: &str) -> Result<Package, &'static str> {
|
|
||||||
// For now, return built-in packages
|
|
||||||
match package_name {
|
|
||||||
"claude-code" => {
|
|
||||||
Ok(Package {
|
|
||||||
metadata: PackageMetadata {
|
|
||||||
name: "claude-code".to_string(),
|
|
||||||
version: "1.0.0".to_string(),
|
|
||||||
description: "AI Development Environment".to_string(),
|
|
||||||
arch: "x86_64".to_string(),
|
|
||||||
size: 1024 * 1024,
|
|
||||||
dependencies: vec!["nodejs".to_string()],
|
|
||||||
},
|
|
||||||
files: vec![
|
|
||||||
PackageFile {
|
|
||||||
path: "/bin/claude-code".to_string(),
|
|
||||||
content: b"AIOS\x00\x10\x00\x00claude-code-ai-assistant".to_vec(),
|
|
||||||
executable: true,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
services: vec![
|
|
||||||
ServiceConfig {
|
|
||||||
name: "claude-code".to_string(),
|
|
||||||
command: "/bin/claude-code --daemon".to_string(),
|
|
||||||
container: true,
|
|
||||||
auto_start: false,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
})
|
|
||||||
}
|
|
||||||
"nodejs" => {
|
|
||||||
Ok(Package {
|
|
||||||
metadata: PackageMetadata {
|
|
||||||
name: "nodejs".to_string(),
|
|
||||||
version: "18.0.0".to_string(),
|
|
||||||
description: "JavaScript Runtime".to_string(),
|
|
||||||
arch: "x86_64".to_string(),
|
|
||||||
size: 2 * 1024 * 1024,
|
|
||||||
dependencies: vec![],
|
|
||||||
},
|
|
||||||
files: vec![
|
|
||||||
PackageFile {
|
|
||||||
path: "/bin/node".to_string(),
|
|
||||||
content: b"AIOS\x00\x10\x00\x00nodejs-runtime".to_vec(),
|
|
||||||
executable: true,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
services: vec![],
|
|
||||||
})
|
|
||||||
}
|
|
||||||
"python" => {
|
|
||||||
Ok(Package {
|
|
||||||
metadata: PackageMetadata {
|
|
||||||
name: "python".to_string(),
|
|
||||||
version: "3.9.0".to_string(),
|
|
||||||
description: "Python Interpreter".to_string(),
|
|
||||||
arch: "x86_64".to_string(),
|
|
||||||
size: 3 * 1024 * 1024,
|
|
||||||
dependencies: vec![],
|
|
||||||
},
|
|
||||||
files: vec![
|
|
||||||
PackageFile {
|
|
||||||
path: "/bin/python".to_string(),
|
|
||||||
content: b"AIOS\x00\x10\x00\x00python-interpreter".to_vec(),
|
|
||||||
executable: true,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
services: vec![],
|
|
||||||
})
|
|
||||||
}
|
|
||||||
_ => Err("Package not found")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_package_installed(&self, package_name: &str) -> bool {
|
|
||||||
self.installed_packages.contains_key(package_name)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn update_repositories(&mut self) -> Result<(), &'static str> {
|
|
||||||
crate::println!("Updating package repositories...");
|
|
||||||
// In a real implementation, this would fetch from remote repositories
|
|
||||||
crate::println!("Repository update completed");
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
203
src/process.rs
203
src/process.rs
@ -1,203 +0,0 @@
|
|||||||
use alloc::{collections::BTreeMap, string::String, vec::Vec, string::ToString, format};
|
|
||||||
use core::fmt;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
|
||||||
pub enum ProcessState {
|
|
||||||
Ready,
|
|
||||||
Running,
|
|
||||||
Waiting,
|
|
||||||
Terminated,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
|
||||||
pub struct ProcessId(pub usize);
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Process {
|
|
||||||
pub pid: ProcessId,
|
|
||||||
pub name: String,
|
|
||||||
pub state: ProcessState,
|
|
||||||
pub entry_point: usize,
|
|
||||||
pub stack_pointer: usize,
|
|
||||||
pub memory: Vec<u8>,
|
|
||||||
pub exit_code: Option<i32>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct ProcessManager {
|
|
||||||
processes: BTreeMap<usize, Process>,
|
|
||||||
next_pid: usize,
|
|
||||||
current_process: Option<usize>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ProcessManager {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
processes: BTreeMap::new(),
|
|
||||||
next_pid: 1,
|
|
||||||
current_process: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn create_process(&mut self, name: String, program: &[u8]) -> Result<ProcessId, &'static str> {
|
|
||||||
let pid = ProcessId(self.next_pid);
|
|
||||||
self.next_pid += 1;
|
|
||||||
|
|
||||||
// Simple ELF-like parsing (simplified for demo)
|
|
||||||
let entry_point = self.parse_program(program)?;
|
|
||||||
|
|
||||||
let process = Process {
|
|
||||||
pid,
|
|
||||||
name,
|
|
||||||
state: ProcessState::Ready,
|
|
||||||
entry_point,
|
|
||||||
stack_pointer: 0x8000, // Simple stack setup
|
|
||||||
memory: program.to_vec(),
|
|
||||||
exit_code: None,
|
|
||||||
};
|
|
||||||
|
|
||||||
self.processes.insert(pid.0, process);
|
|
||||||
Ok(pid)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn start_process(&mut self, pid: ProcessId) -> Result<(), &'static str> {
|
|
||||||
if let Some(process) = self.processes.get_mut(&pid.0) {
|
|
||||||
if process.state == ProcessState::Ready {
|
|
||||||
process.state = ProcessState::Running;
|
|
||||||
self.current_process = Some(pid.0);
|
|
||||||
crate::println!("Starting process: {} (PID: {})", process.name, pid.0);
|
|
||||||
|
|
||||||
// In a real OS, this would switch to the process context
|
|
||||||
// For now, we'll simulate execution
|
|
||||||
self.simulate_execution(pid)?;
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
Err("Process not in ready state")
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Err("Process not found")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn terminate_process(&mut self, pid: ProcessId, exit_code: i32) -> Result<(), &'static str> {
|
|
||||||
if let Some(process) = self.processes.get_mut(&pid.0) {
|
|
||||||
process.state = ProcessState::Terminated;
|
|
||||||
process.exit_code = Some(exit_code);
|
|
||||||
|
|
||||||
if self.current_process == Some(pid.0) {
|
|
||||||
self.current_process = None;
|
|
||||||
}
|
|
||||||
|
|
||||||
crate::println!("Process {} terminated with exit code {}", process.name, exit_code);
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
Err("Process not found")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn list_processes(&self) -> Vec<&Process> {
|
|
||||||
self.processes.values().collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_process(&self, pid: ProcessId) -> Option<&Process> {
|
|
||||||
self.processes.get(&pid.0)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn kill_process(&mut self, pid: ProcessId) -> Result<(), &'static str> {
|
|
||||||
self.terminate_process(pid, -1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Simple program parser - in reality this would be a full ELF parser
|
|
||||||
fn parse_program(&self, program: &[u8]) -> Result<usize, &'static str> {
|
|
||||||
if program.len() < 4 {
|
|
||||||
return Err("Invalid program format");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check for simple executable magic
|
|
||||||
if &program[0..4] == b"AIOS" {
|
|
||||||
// AIOS native executable format
|
|
||||||
if program.len() < 8 {
|
|
||||||
return Err("Invalid AIOS executable");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Entry point stored at offset 4-8
|
|
||||||
let entry_point = u32::from_le_bytes([program[4], program[5], program[6], program[7]]) as usize;
|
|
||||||
Ok(entry_point)
|
|
||||||
} else if &program[0..4] == [0x7f, b'E', b'L', b'F'] {
|
|
||||||
// Basic ELF detection
|
|
||||||
crate::println!("ELF executable detected (simplified parsing)");
|
|
||||||
Ok(0x1000) // Default entry point for ELF
|
|
||||||
} else {
|
|
||||||
// Treat as raw machine code
|
|
||||||
Ok(0x0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Simulate process execution (in a real OS this would involve context switching)
|
|
||||||
fn simulate_execution(&mut self, pid: ProcessId) -> Result<(), &'static str> {
|
|
||||||
if let Some(process) = self.processes.get(&pid.0) {
|
|
||||||
crate::println!("Executing process: {}", process.name);
|
|
||||||
|
|
||||||
// Check if it's a built-in command or script
|
|
||||||
if process.name == "hello" {
|
|
||||||
crate::println!("Hello from AIOS process!");
|
|
||||||
self.terminate_process(pid, 0)?;
|
|
||||||
} else if process.name == "claude-code" {
|
|
||||||
crate::println!("Claude Code AI Assistant starting...");
|
|
||||||
crate::println!("AI development environment initialized");
|
|
||||||
crate::println!("Type 'ai help' for available commands");
|
|
||||||
self.terminate_process(pid, 0)?;
|
|
||||||
} else if process.name.starts_with("ai-") {
|
|
||||||
crate::println!("AI service: {}", process.name);
|
|
||||||
crate::println!("Connecting to Claude API...");
|
|
||||||
crate::println!("AI service ready");
|
|
||||||
self.terminate_process(pid, 0)?;
|
|
||||||
} else {
|
|
||||||
// Unknown program
|
|
||||||
crate::println!("Unknown program format, terminating");
|
|
||||||
self.terminate_process(pid, 1)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
Err("Process not found")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn execute_command(&mut self, command: &str, args: &[&str]) -> Result<ProcessId, &'static str> {
|
|
||||||
// Create a simple program based on command
|
|
||||||
let program_name = if command == "claude-code" {
|
|
||||||
"claude-code".to_string()
|
|
||||||
} else if command.starts_with("ai") {
|
|
||||||
format!("ai-{}", args.join("-"))
|
|
||||||
} else {
|
|
||||||
command.to_string()
|
|
||||||
};
|
|
||||||
|
|
||||||
// Create a simple executable
|
|
||||||
let mut program = Vec::new();
|
|
||||||
program.extend_from_slice(b"AIOS"); // Magic
|
|
||||||
program.extend_from_slice(&(0x1000u32).to_le_bytes()); // Entry point
|
|
||||||
program.extend_from_slice(command.as_bytes()); // Command data
|
|
||||||
|
|
||||||
let pid = self.create_process(program_name, &program)?;
|
|
||||||
self.start_process(pid)?;
|
|
||||||
Ok(pid)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for ProcessState {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
match self {
|
|
||||||
ProcessState::Ready => write!(f, "Ready"),
|
|
||||||
ProcessState::Running => write!(f, "Running"),
|
|
||||||
ProcessState::Waiting => write!(f, "Waiting"),
|
|
||||||
ProcessState::Terminated => write!(f, "Terminated"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for ProcessId {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
write!(f, "{}", self.0)
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,32 +0,0 @@
|
|||||||
use uart_16550::SerialPort;
|
|
||||||
use spin::Mutex;
|
|
||||||
use lazy_static::lazy_static;
|
|
||||||
|
|
||||||
lazy_static! {
|
|
||||||
pub static ref SERIAL1: Mutex<SerialPort> = {
|
|
||||||
let mut serial_port = unsafe { SerialPort::new(0x3F8) };
|
|
||||||
serial_port.init();
|
|
||||||
Mutex::new(serial_port)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#[doc(hidden)]
|
|
||||||
pub fn _print(args: ::core::fmt::Arguments) {
|
|
||||||
use core::fmt::Write;
|
|
||||||
SERIAL1.lock().write_fmt(args).expect("Printing to serial failed");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[macro_export]
|
|
||||||
macro_rules! serial_print {
|
|
||||||
($($arg:tt)*) => {
|
|
||||||
$crate::serial::_print(format_args!($($arg)*));
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#[macro_export]
|
|
||||||
macro_rules! serial_println {
|
|
||||||
() => ($crate::serial_print!("\n"));
|
|
||||||
($fmt:expr) => ($crate::serial_print!(concat!($fmt, "\n")));
|
|
||||||
($fmt:expr, $($arg:tt)*) => ($crate::serial_print!(
|
|
||||||
concat!($fmt, "\n"), $($arg)*));
|
|
||||||
}
|
|
494
src/shell.rs
494
src/shell.rs
@ -1,494 +0,0 @@
|
|||||||
use alloc::{string::String, vec::Vec, vec, string::ToString};
|
|
||||||
use crate::{print, println};
|
|
||||||
use crate::systemd::SystemManager;
|
|
||||||
use crate::filesystem::MemoryFileSystem;
|
|
||||||
use crate::process::ProcessManager;
|
|
||||||
use crate::package::PackageManager;
|
|
||||||
use lazy_static::lazy_static;
|
|
||||||
use spin::Mutex;
|
|
||||||
|
|
||||||
lazy_static! {
|
|
||||||
static ref SHELL: Mutex<Shell> = Mutex::new(Shell::new());
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Shell {
|
|
||||||
input_buffer: String,
|
|
||||||
history: Vec<String>,
|
|
||||||
prompt: &'static str,
|
|
||||||
system_manager: SystemManager,
|
|
||||||
filesystem: MemoryFileSystem,
|
|
||||||
process_manager: ProcessManager,
|
|
||||||
package_manager: PackageManager,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Shell {
|
|
||||||
fn new() -> Self {
|
|
||||||
// 一時的にメモリを使う部分を最小限に
|
|
||||||
let filesystem = MemoryFileSystem::new();
|
|
||||||
let process_manager = ProcessManager::new();
|
|
||||||
let system_manager = SystemManager::new();
|
|
||||||
let package_manager = PackageManager::new();
|
|
||||||
|
|
||||||
Self {
|
|
||||||
input_buffer: String::new(),
|
|
||||||
history: Vec::new(),
|
|
||||||
prompt: "aios$ ",
|
|
||||||
system_manager,
|
|
||||||
filesystem,
|
|
||||||
process_manager,
|
|
||||||
package_manager,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn execute_command(&mut self, command: &str) {
|
|
||||||
let parts: Vec<&str> = command.trim().split_whitespace().collect();
|
|
||||||
if parts.is_empty() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
match parts[0] {
|
|
||||||
"help" => self.show_help(),
|
|
||||||
"clear" => self.clear_screen(),
|
|
||||||
"echo" => {
|
|
||||||
for arg in &parts[1..] {
|
|
||||||
print!("{} ", arg);
|
|
||||||
}
|
|
||||||
println!();
|
|
||||||
}
|
|
||||||
"history" => self.show_history(),
|
|
||||||
"systemctl" => {
|
|
||||||
if let Err(e) = self.system_manager.systemctl(&parts[1..]) {
|
|
||||||
println!("systemctl: {}", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"nspawn" => {
|
|
||||||
if let Err(e) = self.system_manager.nspawn(&parts[1..]) {
|
|
||||||
println!("systemd-nspawn: {}", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"claude" => self.execute_claude_command(&parts[1..]),
|
|
||||||
"ai" => self.execute_ai_command(&parts[1..]),
|
|
||||||
// File system commands
|
|
||||||
"ls" => self.list_directory(&parts[1..]),
|
|
||||||
"cd" => self.change_directory(&parts[1..]),
|
|
||||||
"pwd" => println!("{}", self.filesystem.get_current_directory()),
|
|
||||||
"mkdir" => self.make_directory(&parts[1..]),
|
|
||||||
"cat" => self.cat_file(&parts[1..]),
|
|
||||||
"touch" => self.create_file(&parts[1..]),
|
|
||||||
"rm" => self.remove_file(&parts[1..]),
|
|
||||||
// Process commands
|
|
||||||
"ps" => self.list_processes(),
|
|
||||||
"kill" => self.kill_process(&parts[1..]),
|
|
||||||
"exec" => self.execute_program(&parts[1..]),
|
|
||||||
// Package manager commands
|
|
||||||
"pkg" => self.handle_package_command(&parts[1..]),
|
|
||||||
"exit" => {
|
|
||||||
println!("Goodbye!");
|
|
||||||
// TODO: 適切なシャットダウン処理
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
// Try to execute as a program
|
|
||||||
if let Err(e) = self.process_manager.execute_command(parts[0], &parts[1..]) {
|
|
||||||
println!("Unknown command: {}. Type 'help' for available commands.", parts[0]);
|
|
||||||
println!("Error: {}", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn execute_claude_command(&mut self, args: &[&str]) {
|
|
||||||
if args.is_empty() {
|
|
||||||
println!("Claude Code integration - AI-powered development environment");
|
|
||||||
println!("Usage: claude <subcommand>");
|
|
||||||
println!(" start - Start Claude Code server");
|
|
||||||
println!(" stop - Stop Claude Code server");
|
|
||||||
println!(" status - Show Claude Code status");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
match args[0] {
|
|
||||||
"start" => {
|
|
||||||
println!("Starting Claude Code in container...");
|
|
||||||
// TODO: Claude Codeをコンテナ内で起動
|
|
||||||
if let Err(e) = self.start_claude_service() {
|
|
||||||
println!("Failed to start Claude Code: {}", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"stop" => {
|
|
||||||
println!("Stopping Claude Code...");
|
|
||||||
if let Err(e) = self.system_manager.stop_service("claude-code") {
|
|
||||||
println!("Failed to stop Claude Code: {}", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"status" => {
|
|
||||||
if let Err(e) = self.system_manager.systemctl(&["status", "claude-code"]) {
|
|
||||||
println!("Claude Code status: {}", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => println!("Unknown claude subcommand: {}", args[0]),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn execute_ai_command(&mut self, args: &[&str]) {
|
|
||||||
if args.is_empty() {
|
|
||||||
println!("AI assistant integration");
|
|
||||||
println!("Usage: ai <query>");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let query = args.join(" ");
|
|
||||||
println!("AI Query: {}", query);
|
|
||||||
println!("Response: This would integrate with Claude API for AI assistance");
|
|
||||||
// TODO: 実際のAI統合
|
|
||||||
}
|
|
||||||
|
|
||||||
fn start_claude_service(&mut self) -> Result<(), &'static str> {
|
|
||||||
use crate::systemd::{ServiceUnit, ServiceState};
|
|
||||||
|
|
||||||
// Claude Codeサービスを作成
|
|
||||||
let service = ServiceUnit {
|
|
||||||
name: "claude-code".to_string(),
|
|
||||||
description: "Claude Code AI Development Environment".to_string(),
|
|
||||||
exec_start: "/usr/local/bin/claude-code".to_string(),
|
|
||||||
working_directory: Some("/workspace".to_string()),
|
|
||||||
environment: vec![
|
|
||||||
("ANTHROPIC_API_KEY".to_string(), "your-api-key".to_string()),
|
|
||||||
("PORT".to_string(), "3000".to_string()),
|
|
||||||
],
|
|
||||||
container: Some("claude-container".to_string()),
|
|
||||||
state: ServiceState::Inactive,
|
|
||||||
};
|
|
||||||
|
|
||||||
// コンテナを作成
|
|
||||||
self.system_manager.create_container(
|
|
||||||
"claude-container".to_string(),
|
|
||||||
"/containers/claude".to_string(),
|
|
||||||
)?;
|
|
||||||
|
|
||||||
// サービスを登録・起動
|
|
||||||
self.system_manager.create_service(service)?;
|
|
||||||
self.system_manager.start_service("claude-code")
|
|
||||||
}
|
|
||||||
|
|
||||||
fn show_help(&self) {
|
|
||||||
println!("AIOS Shell - Available Commands:");
|
|
||||||
println!(" help - Show this help message");
|
|
||||||
println!(" clear - Clear the screen");
|
|
||||||
println!(" echo <text> - Print text to output");
|
|
||||||
println!(" history - Show command history");
|
|
||||||
println!("");
|
|
||||||
println!("File System:");
|
|
||||||
println!(" ls [path] - List directory contents");
|
|
||||||
println!(" cd <path> - Change directory");
|
|
||||||
println!(" pwd - Print working directory");
|
|
||||||
println!(" mkdir <dir> - Create directory");
|
|
||||||
println!(" cat <file> - Display file contents");
|
|
||||||
println!(" touch <file>- Create empty file");
|
|
||||||
println!(" rm <file> - Remove file");
|
|
||||||
println!("");
|
|
||||||
println!("Process Management:");
|
|
||||||
println!(" ps - List running processes");
|
|
||||||
println!(" exec <prog> - Execute program");
|
|
||||||
println!(" kill <pid> - Kill process by PID");
|
|
||||||
println!("");
|
|
||||||
println!("Package Management:");
|
|
||||||
println!(" pkg install <name> - Install package");
|
|
||||||
println!(" pkg remove <name> - Remove package");
|
|
||||||
println!(" pkg list - List installed packages");
|
|
||||||
println!(" pkg search <query> - Search packages");
|
|
||||||
println!(" pkg info <name> - Show package info");
|
|
||||||
println!(" pkg update - Update repositories");
|
|
||||||
println!("");
|
|
||||||
println!("System:");
|
|
||||||
println!(" systemctl - Control system services");
|
|
||||||
println!(" claude - Claude Code integration");
|
|
||||||
println!(" ai <query> - AI assistant");
|
|
||||||
println!(" exit - Exit shell");
|
|
||||||
}
|
|
||||||
|
|
||||||
// File system command implementations
|
|
||||||
fn list_directory(&mut self, args: &[&str]) {
|
|
||||||
let path = if args.is_empty() {
|
|
||||||
self.filesystem.get_current_directory()
|
|
||||||
} else {
|
|
||||||
args[0]
|
|
||||||
};
|
|
||||||
|
|
||||||
match self.filesystem.list_directory(path) {
|
|
||||||
Ok(entries) => {
|
|
||||||
for entry in entries {
|
|
||||||
println!("{} {:>8} {}", entry.file_type, entry.size, entry.name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(e) => println!("ls: {}", e),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn change_directory(&mut self, args: &[&str]) {
|
|
||||||
if args.is_empty() {
|
|
||||||
if let Err(e) = self.filesystem.change_directory("/") {
|
|
||||||
println!("cd: {}", e);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if let Err(e) = self.filesystem.change_directory(args[0]) {
|
|
||||||
println!("cd: {}", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn make_directory(&mut self, args: &[&str]) {
|
|
||||||
if args.is_empty() {
|
|
||||||
println!("mkdir: missing directory name");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Err(e) = self.filesystem.create_directory(args[0]) {
|
|
||||||
println!("mkdir: {}", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn cat_file(&mut self, args: &[&str]) {
|
|
||||||
if args.is_empty() {
|
|
||||||
println!("cat: missing file name");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
match self.filesystem.read_file(args[0]) {
|
|
||||||
Ok(data) => {
|
|
||||||
if let Ok(text) = core::str::from_utf8(data) {
|
|
||||||
print!("{}", text);
|
|
||||||
} else {
|
|
||||||
println!("cat: binary file");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(e) => println!("cat: {}", e),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_file(&mut self, args: &[&str]) {
|
|
||||||
if args.is_empty() {
|
|
||||||
println!("touch: missing file name");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Err(e) = self.filesystem.create_file(args[0], b"") {
|
|
||||||
println!("touch: {}", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn remove_file(&mut self, args: &[&str]) {
|
|
||||||
if args.is_empty() {
|
|
||||||
println!("rm: missing file name");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Err(e) = self.filesystem.remove_file(args[0]) {
|
|
||||||
println!("rm: {}", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Process command implementations
|
|
||||||
fn list_processes(&mut self) {
|
|
||||||
let processes = self.process_manager.list_processes();
|
|
||||||
println!("PID NAME STATE");
|
|
||||||
for process in processes {
|
|
||||||
println!("{:<6} {:<14} {}", process.pid, process.name, process.state);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn kill_process(&mut self, args: &[&str]) {
|
|
||||||
if args.is_empty() {
|
|
||||||
println!("kill: missing process ID");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Ok(pid) = args[0].parse::<usize>() {
|
|
||||||
if let Err(e) = self.process_manager.kill_process(crate::process::ProcessId(pid)) {
|
|
||||||
println!("kill: {}", e);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
println!("kill: invalid process ID");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn execute_program(&mut self, args: &[&str]) {
|
|
||||||
if args.is_empty() {
|
|
||||||
println!("exec: missing program name");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Err(e) = self.process_manager.execute_command(args[0], &args[1..]) {
|
|
||||||
println!("exec: {}", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Package manager command implementations
|
|
||||||
fn handle_package_command(&mut self, args: &[&str]) {
|
|
||||||
if args.is_empty() {
|
|
||||||
println!("pkg: missing command");
|
|
||||||
println!("Usage: pkg <command> [args...]");
|
|
||||||
println!("Commands: install, remove, list, search, info, update");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
match args[0] {
|
|
||||||
"install" => {
|
|
||||||
if args.len() < 2 {
|
|
||||||
println!("pkg install: missing package name");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
self.install_package(args[1]);
|
|
||||||
}
|
|
||||||
"remove" => {
|
|
||||||
if args.len() < 2 {
|
|
||||||
println!("pkg remove: missing package name");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
self.remove_package(args[1]);
|
|
||||||
}
|
|
||||||
"list" => {
|
|
||||||
if args.len() > 1 && args[1] == "available" {
|
|
||||||
self.package_manager.list_available();
|
|
||||||
} else {
|
|
||||||
self.package_manager.list_installed();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"search" => {
|
|
||||||
if args.len() < 2 {
|
|
||||||
println!("pkg search: missing search query");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
self.search_packages(args[1]);
|
|
||||||
}
|
|
||||||
"info" => {
|
|
||||||
if args.len() < 2 {
|
|
||||||
println!("pkg info: missing package name");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if let Err(e) = self.package_manager.show_package_info(args[1]) {
|
|
||||||
println!("pkg info: {}", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"update" => {
|
|
||||||
if let Err(e) = self.package_manager.update_repositories() {
|
|
||||||
println!("pkg update: {}", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
println!("pkg: unknown command '{}'", args[0]);
|
|
||||||
println!("Available commands: install, remove, list, search, info, update");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn install_package(&mut self, package_name: &str) {
|
|
||||||
println!("Installing package: {}", package_name);
|
|
||||||
match self.package_manager.install_package(package_name, &mut self.filesystem, &mut self.system_manager) {
|
|
||||||
Ok(()) => {
|
|
||||||
println!("Package '{}' installed successfully", package_name);
|
|
||||||
|
|
||||||
// Special handling for Claude Code
|
|
||||||
if package_name == "claude-code" {
|
|
||||||
println!("Claude Code is now available!");
|
|
||||||
println!("You can start it with: claude start");
|
|
||||||
println!("Or run it directly: /bin/claude-code");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err(e) => println!("Failed to install package '{}': {}", package_name, e),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn remove_package(&mut self, package_name: &str) {
|
|
||||||
println!("Removing package: {}", package_name);
|
|
||||||
match self.package_manager.remove_package(package_name, &mut self.filesystem) {
|
|
||||||
Ok(()) => println!("Package '{}' removed successfully", package_name),
|
|
||||||
Err(e) => println!("Failed to remove package '{}': {}", package_name, e),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn search_packages(&mut self, query: &str) {
|
|
||||||
let results = self.package_manager.search_package(query);
|
|
||||||
if results.is_empty() {
|
|
||||||
println!("No packages found matching '{}'", query);
|
|
||||||
} else {
|
|
||||||
println!("Packages matching '{}':", query);
|
|
||||||
println!("{:<20} {:<10} {:<30}", "NAME", "VERSION", "DESCRIPTION");
|
|
||||||
println!("{}", "-".repeat(60));
|
|
||||||
for package in results {
|
|
||||||
println!("{:<20} {:<10} {:<30}", package.name, package.version, package.description);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn clear_screen(&self) {
|
|
||||||
// VGAバッファをクリア
|
|
||||||
crate::vga_buffer::WRITER.lock().clear_screen();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn show_history(&self) {
|
|
||||||
println!("Command History:");
|
|
||||||
for (i, cmd) in self.history.iter().enumerate() {
|
|
||||||
println!(" {}: {}", i + 1, cmd);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn show_prompt(&self) {
|
|
||||||
print!("{}", self.prompt);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn add_to_history(&mut self, command: String) {
|
|
||||||
if !command.trim().is_empty() {
|
|
||||||
self.history.push(command);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// VGAバッファにclear_screen機能を追加
|
|
||||||
impl crate::vga_buffer::Writer {
|
|
||||||
pub fn clear_screen(&mut self) {
|
|
||||||
use crate::vga_buffer::{BUFFER_HEIGHT, BUFFER_WIDTH, ColorCode, Color, ScreenChar};
|
|
||||||
|
|
||||||
let blank = ScreenChar {
|
|
||||||
ascii_character: b' ',
|
|
||||||
color_code: ColorCode::new(Color::Yellow, Color::Black),
|
|
||||||
};
|
|
||||||
|
|
||||||
for row in 0..BUFFER_HEIGHT {
|
|
||||||
for col in 0..BUFFER_WIDTH {
|
|
||||||
self.buffer.chars[row][col].write(blank);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.column_position = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn handle_keyboard_input(character: char) {
|
|
||||||
let mut shell = SHELL.lock();
|
|
||||||
match character {
|
|
||||||
'\n' => {
|
|
||||||
println!();
|
|
||||||
let command = shell.input_buffer.clone();
|
|
||||||
shell.add_to_history(command.clone());
|
|
||||||
shell.execute_command(&command);
|
|
||||||
shell.input_buffer.clear();
|
|
||||||
shell.show_prompt();
|
|
||||||
}
|
|
||||||
'\x08' => { // Backspace
|
|
||||||
if !shell.input_buffer.is_empty() {
|
|
||||||
shell.input_buffer.pop();
|
|
||||||
print!("\x08 \x08"); // Backspace, space, backspace
|
|
||||||
}
|
|
||||||
}
|
|
||||||
c if c.is_ascii() && !c.is_control() => {
|
|
||||||
shell.input_buffer.push(c);
|
|
||||||
print!("{}", c);
|
|
||||||
}
|
|
||||||
_ => {} // 他の文字は無視
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn init() {
|
|
||||||
println!("AIOS Shell v0.1.0 - AI-Integrated Operating System");
|
|
||||||
println!("Type 'help' for available commands.");
|
|
||||||
SHELL.lock().show_prompt();
|
|
||||||
}
|
|
180
src/systemd.rs
180
src/systemd.rs
@ -1,180 +0,0 @@
|
|||||||
use alloc::{collections::BTreeMap, string::String, vec::Vec};
|
|
||||||
use core::fmt;
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
|
||||||
pub enum ServiceState {
|
|
||||||
Inactive,
|
|
||||||
Active,
|
|
||||||
Failed,
|
|
||||||
Activating,
|
|
||||||
Deactivating,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct ServiceUnit {
|
|
||||||
pub name: String,
|
|
||||||
pub description: String,
|
|
||||||
pub exec_start: String,
|
|
||||||
pub working_directory: Option<String>,
|
|
||||||
pub environment: Vec<(String, String)>,
|
|
||||||
pub container: Option<String>,
|
|
||||||
pub state: ServiceState,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct Container {
|
|
||||||
pub name: String,
|
|
||||||
pub root_directory: String,
|
|
||||||
pub private_network: bool,
|
|
||||||
pub bind_mounts: Vec<(String, String)>,
|
|
||||||
pub capability_bounding_set: Vec<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct SystemManager {
|
|
||||||
services: BTreeMap<String, ServiceUnit>,
|
|
||||||
containers: BTreeMap<String, Container>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SystemManager {
|
|
||||||
pub fn new() -> Self {
|
|
||||||
Self {
|
|
||||||
services: BTreeMap::new(),
|
|
||||||
containers: BTreeMap::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn create_container(&mut self, name: String, root_dir: String) -> Result<(), &'static str> {
|
|
||||||
let container = Container {
|
|
||||||
name: name.clone(),
|
|
||||||
root_directory: root_dir,
|
|
||||||
private_network: true,
|
|
||||||
bind_mounts: Vec::new(),
|
|
||||||
capability_bounding_set: Vec::new(),
|
|
||||||
};
|
|
||||||
|
|
||||||
self.containers.insert(name, container);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn create_service(&mut self, service: ServiceUnit) -> Result<(), &'static str> {
|
|
||||||
self.services.insert(service.name.clone(), service);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn start_service(&mut self, name: &str) -> Result<(), &'static str> {
|
|
||||||
if let Some(service) = self.services.get_mut(name) {
|
|
||||||
service.state = ServiceState::Activating;
|
|
||||||
|
|
||||||
// コンテナ内で実行する場合
|
|
||||||
if let Some(container_name) = &service.container {
|
|
||||||
if let Some(_container) = self.containers.get(container_name) {
|
|
||||||
// TODO: nspawn風のコンテナ内でプロセス実行
|
|
||||||
crate::println!("Starting service {} in container {}", name, container_name);
|
|
||||||
} else {
|
|
||||||
return Err("Container not found");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// ホスト上で直接実行
|
|
||||||
crate::println!("Starting service {} on host", name);
|
|
||||||
}
|
|
||||||
|
|
||||||
service.state = ServiceState::Active;
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
Err("Service not found")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn stop_service(&mut self, name: &str) -> Result<(), &'static str> {
|
|
||||||
if let Some(service) = self.services.get_mut(name) {
|
|
||||||
service.state = ServiceState::Deactivating;
|
|
||||||
crate::println!("Stopping service {}", name);
|
|
||||||
service.state = ServiceState::Inactive;
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
Err("Service not found")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn list_services(&self) {
|
|
||||||
crate::println!("UNIT LOAD ACTIVE SUB DESCRIPTION");
|
|
||||||
for (name, service) in &self.services {
|
|
||||||
crate::println!("{:<24} loaded {:?} running {}",
|
|
||||||
name, service.state, service.description);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn list_containers(&self) {
|
|
||||||
crate::println!("MACHINE CLASS SERVICE");
|
|
||||||
for (name, _container) in &self.containers {
|
|
||||||
crate::println!("{:<8} container -", name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// systemctl風のコマンド処理
|
|
||||||
pub fn systemctl(&mut self, args: &[&str]) -> Result<(), &'static str> {
|
|
||||||
if args.len() < 2 {
|
|
||||||
return Err("Usage: systemctl <command> <service>");
|
|
||||||
}
|
|
||||||
|
|
||||||
match args[0] {
|
|
||||||
"start" => self.start_service(args[1]),
|
|
||||||
"stop" => self.stop_service(args[1]),
|
|
||||||
"status" => {
|
|
||||||
if let Some(service) = self.services.get(args[1]) {
|
|
||||||
crate::println!("● {}", service.name);
|
|
||||||
crate::println!(" Loaded: loaded");
|
|
||||||
crate::println!(" Active: {:?}", service.state);
|
|
||||||
crate::println!(" Description: {}", service.description);
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
Err("Service not found")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"list-units" => {
|
|
||||||
self.list_services();
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
_ => Err("Unknown systemctl command"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// systemd-nspawn風のコマンド
|
|
||||||
pub fn nspawn(&mut self, args: &[&str]) -> Result<(), &'static str> {
|
|
||||||
if args.len() < 2 {
|
|
||||||
return Err("Usage: nspawn --directory=<dir> <command>");
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut directory = None;
|
|
||||||
let mut command = None;
|
|
||||||
|
|
||||||
for arg in args {
|
|
||||||
if arg.starts_with("--directory=") {
|
|
||||||
directory = Some(&arg[12..]);
|
|
||||||
} else if !arg.starts_with("--") {
|
|
||||||
command = Some(*arg);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let (Some(dir), Some(cmd)) = (directory, command) {
|
|
||||||
crate::println!("Starting container in {} executing {}", dir, cmd);
|
|
||||||
// TODO: 実際のコンテナ実行
|
|
||||||
Ok(())
|
|
||||||
} else {
|
|
||||||
Err("Invalid nspawn arguments")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Display for ServiceState {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
||||||
match self {
|
|
||||||
ServiceState::Inactive => write!(f, "inactive"),
|
|
||||||
ServiceState::Active => write!(f, "active"),
|
|
||||||
ServiceState::Failed => write!(f, "failed"),
|
|
||||||
ServiceState::Activating => write!(f, "activating"),
|
|
||||||
ServiceState::Deactivating => write!(f, "deactivating"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,164 +0,0 @@
|
|||||||
use volatile::Volatile;
|
|
||||||
use core::fmt;
|
|
||||||
use lazy_static::lazy_static;
|
|
||||||
use spin::Mutex;
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
||||||
#[repr(u8)]
|
|
||||||
pub enum Color {
|
|
||||||
Black = 0,
|
|
||||||
Blue = 1,
|
|
||||||
Green = 2,
|
|
||||||
Cyan = 3,
|
|
||||||
Red = 4,
|
|
||||||
Magenta = 5,
|
|
||||||
Brown = 6,
|
|
||||||
LightGray = 7,
|
|
||||||
DarkGray = 8,
|
|
||||||
LightBlue = 9,
|
|
||||||
LightGreen = 10,
|
|
||||||
LightCyan = 11,
|
|
||||||
LightRed = 12,
|
|
||||||
Pink = 13,
|
|
||||||
Yellow = 14,
|
|
||||||
White = 15,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
||||||
#[repr(transparent)]
|
|
||||||
pub struct ColorCode(u8);
|
|
||||||
|
|
||||||
impl ColorCode {
|
|
||||||
pub fn new(foreground: Color, background: Color) -> ColorCode {
|
|
||||||
ColorCode((background as u8) << 4 | (foreground as u8))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
||||||
#[repr(C)]
|
|
||||||
pub struct ScreenChar {
|
|
||||||
pub ascii_character: u8,
|
|
||||||
pub color_code: ColorCode,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub const BUFFER_HEIGHT: usize = 25;
|
|
||||||
pub const BUFFER_WIDTH: usize = 80;
|
|
||||||
|
|
||||||
#[repr(transparent)]
|
|
||||||
pub struct Buffer {
|
|
||||||
pub chars: [[Volatile<ScreenChar>; BUFFER_WIDTH]; BUFFER_HEIGHT],
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Writer {
|
|
||||||
pub column_position: usize,
|
|
||||||
color_code: ColorCode,
|
|
||||||
pub buffer: &'static mut Buffer,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Writer {
|
|
||||||
pub fn write_byte(&mut self, byte: u8) {
|
|
||||||
match byte {
|
|
||||||
b'\n' => self.new_line(),
|
|
||||||
byte => {
|
|
||||||
if self.column_position >= BUFFER_WIDTH {
|
|
||||||
self.new_line();
|
|
||||||
}
|
|
||||||
|
|
||||||
let row = BUFFER_HEIGHT - 1;
|
|
||||||
let col = self.column_position;
|
|
||||||
|
|
||||||
let color_code = self.color_code;
|
|
||||||
self.buffer.chars[row][col].write(ScreenChar {
|
|
||||||
ascii_character: byte,
|
|
||||||
color_code,
|
|
||||||
});
|
|
||||||
self.column_position += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn write_string(&mut self, s: &str) {
|
|
||||||
for byte in s.bytes() {
|
|
||||||
match byte {
|
|
||||||
0x20..=0x7e | b'\n' => self.write_byte(byte),
|
|
||||||
_ => self.write_byte(0xfe),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn new_line(&mut self) {
|
|
||||||
for row in 1..BUFFER_HEIGHT {
|
|
||||||
for col in 0..BUFFER_WIDTH {
|
|
||||||
let character = self.buffer.chars[row][col].read();
|
|
||||||
self.buffer.chars[row - 1][col].write(character);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.clear_row(BUFFER_HEIGHT - 1);
|
|
||||||
self.column_position = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn clear_row(&mut self, row: usize) {
|
|
||||||
let blank = ScreenChar {
|
|
||||||
ascii_character: b' ',
|
|
||||||
color_code: self.color_code,
|
|
||||||
};
|
|
||||||
for col in 0..BUFFER_WIDTH {
|
|
||||||
self.buffer.chars[row][col].write(blank);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Write for Writer {
|
|
||||||
fn write_str(&mut self, s: &str) -> fmt::Result {
|
|
||||||
self.write_string(s);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
lazy_static! {
|
|
||||||
pub static ref WRITER: Mutex<Writer> = Mutex::new(Writer {
|
|
||||||
column_position: 0,
|
|
||||||
color_code: ColorCode::new(Color::Yellow, Color::Black),
|
|
||||||
buffer: unsafe { &mut *(0xb8000 as *mut Buffer) },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
#[macro_export]
|
|
||||||
macro_rules! print {
|
|
||||||
($($arg:tt)*) => ($crate::vga_buffer::_print(format_args!($($arg)*)));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[macro_export]
|
|
||||||
macro_rules! println {
|
|
||||||
() => ($crate::print!("\n"));
|
|
||||||
($($arg:tt)*) => ($crate::print!("{}\n", format_args!($($arg)*)));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[doc(hidden)]
|
|
||||||
pub fn _print(args: fmt::Arguments) {
|
|
||||||
use core::fmt::Write;
|
|
||||||
WRITER.lock().write_fmt(args).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test_case]
|
|
||||||
fn test_println_simple() {
|
|
||||||
println!("test_println_simple output");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test_case]
|
|
||||||
fn test_println_many() {
|
|
||||||
for i in 0..200 {
|
|
||||||
println!("test_println_many output {}", i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test_case]
|
|
||||||
fn test_println_output() {
|
|
||||||
let s = "Some test string that fits on a single line";
|
|
||||||
println!("{}", s);
|
|
||||||
for (i, c) in s.chars().enumerate() {
|
|
||||||
let screen_char = WRITER.lock().buffer.chars[BUFFER_HEIGHT - 2][i].read();
|
|
||||||
assert_eq!(char::from(screen_char.ascii_character), c);
|
|
||||||
}
|
|
||||||
}
|
|
8
user/Cargo.toml
Normal file
8
user/Cargo.toml
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
[package]
|
||||||
|
name = "aios-user"
|
||||||
|
version.workspace = true
|
||||||
|
edition.workspace = true
|
||||||
|
license.workspace = true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
# Placeholder for user programs
|
1
user/src/lib.rs
Normal file
1
user/src/lib.rs
Normal file
@ -0,0 +1 @@
|
|||||||
|
// AIOS v2 User programs placeholder
|
Reference in New Issue
Block a user