cargo build
This commit is contained in:
8
.cargo/config.toml
Normal file
8
.cargo/config.toml
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
[unstable]
|
||||||
|
build-std-features = ["compiler-builtins-mem"]
|
||||||
|
build-std = ["core", "compiler_builtins", "alloc"]
|
||||||
|
|
||||||
|
[build]
|
||||||
|
target = "x86_64-unknown-none"
|
||||||
|
|
||||||
|
# rustflags = ["-C", "link-arg=-T./linker.ld"]
|
6
.dockerignore
Normal file
6
.dockerignore
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
target/
|
||||||
|
.git/
|
||||||
|
*.log
|
||||||
|
.DS_Store
|
||||||
|
Dockerfile
|
||||||
|
.dockerignore
|
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
octox
|
||||||
|
target
|
||||||
|
.claude
|
||||||
|
*.log
|
||||||
|
*.lock
|
12
Bootloader.toml
Normal file
12
Bootloader.toml
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
# 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"
|
30
Cargo.toml
Normal file
30
Cargo.toml
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
[package]
|
||||||
|
name = "aios"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
bootloader = "0.9.23"
|
||||||
|
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]
|
||||||
|
panic = "abort"
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
panic = "abort"
|
12
README.md
12
README.md
@ -0,0 +1,12 @@
|
|||||||
|
# aios
|
||||||
|
|
||||||
|
create a unix-like os from scratch
|
||||||
|
|
||||||
|
## example
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# archlinux
|
||||||
|
cargo bootimage
|
||||||
|
qemu-system-x86_64 -drive format=raw,file=target/x86_64-unknown-none/debug/bootimage-kernel.bin -nographic
|
||||||
|
```
|
||||||
|
|
||||||
|
6
cfg/ai.conf
Normal file
6
cfg/ai.conf
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
# https://github.com/containers/shortnames
|
||||||
|
# ~/.config/containers/registries.conf.d/ai.conf
|
||||||
|
unqualified-search-registries = ['git.syui.ai', 'docker.io', 'ghcr.io']
|
||||||
|
|
||||||
|
[aliases]
|
||||||
|
"aios" = "git.syui.ai/ai/os"
|
72
cfg/gh-actions.yml
Normal file
72
cfg/gh-actions.yml
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
name: release
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
|
env:
|
||||||
|
DOCKER_TOKEN: ${{ secrets.DOCKER_TOKEN }}
|
||||||
|
IMAGE_NAME: ${{ github.repository }}
|
||||||
|
GITHUB_TOKEN: ${{ secrets.APP_TOKEN }}
|
||||||
|
REGISTRY: ghcr.io
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
release:
|
||||||
|
name: Release
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
container:
|
||||||
|
image: archlinux
|
||||||
|
options: --privileged
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
- name: Initialize
|
||||||
|
run: |
|
||||||
|
pacman -Syuu --noconfirm base-devel archiso docker git nodejs bc
|
||||||
|
git clone https://gitlab.archlinux.org/archlinux/archiso
|
||||||
|
cp -rf ./cfg/profiledef.sh /usr/share/archiso/configs/releng/
|
||||||
|
cp -rf ./cfg/profiledef.sh ./archiso/configs/releng/profiledef.sh
|
||||||
|
cp -rf ./cfg/profiledef.sh ./archiso/configs/baseline/profiledef.sh
|
||||||
|
cp -rf ./scpt/mkarchiso ./archiso/archiso/mkarchiso
|
||||||
|
./archiso/archiso/mkarchiso -v -o ./ ./archiso/configs/releng/
|
||||||
|
tar xf aios-bootstrap*.tar.gz
|
||||||
|
mkdir -p root.x86_64/var/lib/machines/arch
|
||||||
|
pacstrap -c root.x86_64/var/lib/machines/arch base
|
||||||
|
echo -e 'Server = http://mirrors.cat.net/archlinux/$repo/os/$arch
|
||||||
|
Server = https://geo.mirror.pkgbuild.com/$repo/os/$arch' >> ./root.x86_64/etc/pacman.d/mirrorlist
|
||||||
|
sed -i s/CheckSpace/#CheckeSpace/ root.x86_64/etc/pacman.conf
|
||||||
|
arch-chroot root.x86_64 /bin/sh -c 'pacman-key --init'
|
||||||
|
arch-chroot root.x86_64 /bin/sh -c 'pacman-key --populate archlinux'
|
||||||
|
arch-chroot root.x86_64 /bin/sh -c 'pacman -Syu --noconfirm base base-devel linux vim git zsh rust openssh openssl jq go nodejs docker podman bc'
|
||||||
|
arch-chroot root.x86_64 /bin/sh -c 'mkdir -p /etc/containers/registries.conf.d'
|
||||||
|
arch-chroot root.x86_64 /bin/sh -c 'curl -sL -o /etc/containers/registries.conf.d/ai.conf https://git.syui.ai/ai/os/raw/branch/main/cfg/ai.conf'
|
||||||
|
arch-chroot root.x86_64 /bin/sh -c 'chsh -s /bin/zsh'
|
||||||
|
arch-chroot root.x86_64 /bin/sh -c 'git clone https://git.syui.ai/ai/bot && cd bot && cargo build && cp -rf ./target/debug/ai /bin/ && ai ai'
|
||||||
|
tar -zcvf aios-bootstrap.tar.gz root.x86_64/
|
||||||
|
tar -C ./root.x86_64 -c . | docker import - ${{ env.IMAGE_NAME }}
|
||||||
|
echo "${{ env.DOCKER_TOKEN }}" | docker login -u syui --password-stdin
|
||||||
|
docker push ${{ env.IMAGE_NAME }}
|
||||||
|
|
||||||
|
- name: Log in to the Container registry
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ${{ env.REGISTRY }}
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ env.GITHUB_TOKEN }}
|
||||||
|
- name: github container registry
|
||||||
|
run: |
|
||||||
|
docker tag ${{ env.IMAGE_NAME }} ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||||
|
docker push ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
|
||||||
|
|
||||||
|
- name: Create new release
|
||||||
|
uses: softprops/action-gh-release@v1
|
||||||
|
with:
|
||||||
|
name: latest
|
||||||
|
tag_name: latest
|
||||||
|
files:
|
||||||
|
aios-bootstrap.tar.gz
|
||||||
|
|
1
cfg/hostname
Normal file
1
cfg/hostname
Normal file
@ -0,0 +1 @@
|
|||||||
|
aios
|
13
cfg/os-release
Normal file
13
cfg/os-release
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
BUILD_ID=rolling
|
||||||
|
ANSI_COLOR="38;2;23;147;209"
|
||||||
|
IMAGE_ID=aios
|
||||||
|
IMAGE_VERSION=2024.02.11
|
||||||
|
NAME=ai os
|
||||||
|
PRETTY_NAME=ai os
|
||||||
|
ID=ai
|
||||||
|
HOME_URL=https://git.syui.ai/ai/os
|
||||||
|
DOCUMENTATION_URL=https://git.syui.ai/ai/os/wiki
|
||||||
|
SUPPORT_URL=https://git.syui.ai/ai/os/issues
|
||||||
|
BUG_REPORT_URL=https://git.syui.ai/ai/os/issues
|
||||||
|
PRIVACY_POLICY_URL=https://git.syui.ai/ai/os
|
||||||
|
LOGO=ai-logo
|
27
cfg/profiledef.sh
Normal file
27
cfg/profiledef.sh
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# shellcheck disable=SC2034
|
||||||
|
|
||||||
|
iso_name="aios"
|
||||||
|
iso_label="AI_$(date --date="@${SOURCE_DATE_EPOCH:-$(date +%s)}" +%Y%m)"
|
||||||
|
iso_publisher="ai os <https://git.syui.ai/ai/os>"
|
||||||
|
iso_application="ai os Live/Rescue DVD"
|
||||||
|
iso_version="$(date --date="@${SOURCE_DATE_EPOCH:-$(date +%s)}" +%Y.%m.%d)"
|
||||||
|
install_dir="ai"
|
||||||
|
#buildmodes=('iso')
|
||||||
|
buildmodes=('bootstrap')
|
||||||
|
bootmodes=('bios.syslinux.mbr' 'bios.syslinux.eltorito'
|
||||||
|
'uefi-ia32.grub.esp' 'uefi-x64.grub.esp'
|
||||||
|
'uefi-ia32.grub.eltorito' 'uefi-x64.grub.eltorito')
|
||||||
|
arch="x86_64"
|
||||||
|
pacman_conf="pacman.conf"
|
||||||
|
airootfs_image_type="squashfs"
|
||||||
|
airootfs_image_tool_options=('-comp' 'xz' '-Xbcj' 'x86' '-b' '1M' '-Xdict-size' '1M')
|
||||||
|
file_permissions=(
|
||||||
|
["/etc/shadow"]="0:0:400"
|
||||||
|
["/root"]="0:0:750"
|
||||||
|
["/root/.automated_script.sh"]="0:0:755"
|
||||||
|
["/root/.gnupg"]="0:0:700"
|
||||||
|
["/usr/local/bin/choose-mirror"]="0:0:755"
|
||||||
|
["/usr/local/bin/Installation_guide"]="0:0:755"
|
||||||
|
["/usr/local/bin/livecd-sound"]="0:0:755"
|
||||||
|
)
|
50
claude.md
Normal file
50
claude.md
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
# osを作ろう
|
||||||
|
|
||||||
|
rust + os + unix
|
||||||
|
|
||||||
|
## archlinux
|
||||||
|
|
||||||
|
archlinuxのpacmanを組み込むことはできるだろうか。
|
||||||
|
|
||||||
|
archlinux-baseのosを作る方法を載せておく。これをarchlinux上で実行すると、`aios-bootstrap.tar.gz`ができ、それがlinux-imgになる。
|
||||||
|
|
||||||
|
```sh
|
||||||
|
#!/bin/zsh
|
||||||
|
pacman -Syuu --noconfirm base-devel archiso docker git nodejs bc
|
||||||
|
git clone https://gitlab.archlinux.org/archlinux/archiso
|
||||||
|
cp -rf ./cfg/profiledef.sh /usr/share/archiso/configs/releng/
|
||||||
|
cp -rf ./cfg/profiledef.sh ./archiso/configs/releng/profiledef.sh
|
||||||
|
cp -rf ./cfg/profiledef.sh ./archiso/configs/baseline/profiledef.sh
|
||||||
|
cp -rf ./scpt/mkarchiso ./archiso/archiso/mkarchiso
|
||||||
|
./archiso/archiso/mkarchiso -v -o ./ ./archiso/configs/releng/
|
||||||
|
tar xf aios-bootstrap*.tar.gz
|
||||||
|
mkdir -p root.x86_64/var/lib/machines/arch
|
||||||
|
pacstrap -c root.x86_64/var/lib/machines/arch base
|
||||||
|
echo -e 'Server = http://mirrors.cat.net/archlinux/$repo/os/$arch
|
||||||
|
Server = https://geo.mirror.pkgbuild.com/$repo/os/$arch' >> ./root.x86_64/etc/pacman.d/mirrorlist
|
||||||
|
sed -i s/CheckSpace/#CheckeSpace/ root.x86_64/etc/pacman.conf
|
||||||
|
arch-chroot root.x86_64 /bin/sh -c 'pacman-key --init'
|
||||||
|
arch-chroot root.x86_64 /bin/sh -c 'pacman-key --populate archlinux'
|
||||||
|
arch-chroot root.x86_64 /bin/sh -c 'pacman -Syu --noconfirm base base-devel linux vim git zsh rust openssh openssl jq go nodejs docker podman bc'
|
||||||
|
arch-chroot root.x86_64 /bin/sh -c 'mkdir -p /etc/containers/registries.conf.d'
|
||||||
|
arch-chroot root.x86_64 /bin/sh -c 'curl -sL -o /etc/containers/registries.conf.d/ai.conf https://git.syui.ai/ai/os/raw/branch/main/cfg/ai.conf'
|
||||||
|
arch-chroot root.x86_64 /bin/sh -c 'chsh -s /bin/zsh'
|
||||||
|
arch-chroot root.x86_64 /bin/sh -c 'git clone https://git.syui.ai/ai/bot && cd bot && cargo build && cp -rf ./target/debug/ai /bin/ && ai ai'
|
||||||
|
tar -zcvf aios-bootstrap.tar.gz root.x86_64/
|
||||||
|
```
|
||||||
|
|
||||||
|
## 当面の目標
|
||||||
|
|
||||||
|
1. claude codeを実行できるようにすること
|
||||||
|
2. package managerの作成
|
||||||
|
3. shellとAIの統合
|
||||||
|
4. shellはsystemd-nspawnのようなcontainerで実行
|
||||||
|
5. AIはcontainerを扱う
|
||||||
|
|
||||||
|
## ref
|
||||||
|
|
||||||
|
[o8vm/octox](https://github.com/o8vm/octox)が参考になるかもしれない。
|
||||||
|
|
||||||
|
```sh
|
||||||
|
git clone https://github.com/o8vm/octox
|
||||||
|
```
|
136
docs/ARCHITECTURE.md
Normal file
136
docs/ARCHITECTURE.md
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
# AIOS アーキテクチャ
|
||||||
|
|
||||||
|
## 概要
|
||||||
|
|
||||||
|
AIOSは、Rustで実装されたマイクロカーネルアーキテクチャを採用しています。最終的にClaude Codeを実行可能にするため、モジュラー設計を重視しています。
|
||||||
|
|
||||||
|
## システム構成
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────┐
|
||||||
|
│ ユーザーランド │
|
||||||
|
│ ┌─────────────┐ ┌─────────────────────┐│
|
||||||
|
│ │ Claude Code │ │ システムツール ││
|
||||||
|
│ │ │ │ (ls, cat, etc.) ││
|
||||||
|
│ └─────────────┘ └─────────────────────┘│
|
||||||
|
├─────────────────────────────────────────┤
|
||||||
|
│ システムコール │
|
||||||
|
├─────────────────────────────────────────┤
|
||||||
|
│ カーネル空間 │
|
||||||
|
│ ┌─────────┐ ┌─────────┐ ┌─────────────┐│
|
||||||
|
│ │ プロセス │ │ メモリ │ │ ファイル ││
|
||||||
|
│ │ 管理 │ │ 管理 │ │ システム ││
|
||||||
|
│ └─────────┘ └─────────┘ └─────────────┘│
|
||||||
|
│ ┌─────────────────────────────────────┐│
|
||||||
|
│ │ ハードウェア抽象化 ││
|
||||||
|
│ │ VGA・シリアル・キーボード・etc ││
|
||||||
|
│ └─────────────────────────────────────┘│
|
||||||
|
├─────────────────────────────────────────┤
|
||||||
|
│ ハードウェア │
|
||||||
|
│ CPU・メモリ・ストレージ │
|
||||||
|
└─────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
## 主要コンポーネント
|
||||||
|
|
||||||
|
### 1. カーネルコア (`src/main.rs`)
|
||||||
|
- システムの起動とメインループ
|
||||||
|
- パニックハンドラー
|
||||||
|
- テストフレームワーク統合
|
||||||
|
|
||||||
|
### 2. VGAドライバー (`src/vga_buffer.rs`)
|
||||||
|
- テキストモード画面制御
|
||||||
|
- 色指定とフォーマット
|
||||||
|
- スクロール機能
|
||||||
|
|
||||||
|
### 3. シリアル通信 (`src/serial.rs`)
|
||||||
|
- UART 16550制御
|
||||||
|
- デバッグ出力
|
||||||
|
- QEMU連携
|
||||||
|
|
||||||
|
### 4. メモリ管理 (予定)
|
||||||
|
- 仮想メモリ管理
|
||||||
|
- ヒープアロケーター
|
||||||
|
- ページテーブル管理
|
||||||
|
|
||||||
|
### 5. ファイルシステム (予定)
|
||||||
|
- VFS (Virtual File System)
|
||||||
|
- 基本的なファイル操作
|
||||||
|
- ディレクトリ構造
|
||||||
|
|
||||||
|
### 6. プロセス管理 (予定)
|
||||||
|
- タスクスケジューリング
|
||||||
|
- プロセス作成・終了
|
||||||
|
- IPC (Inter-Process Communication)
|
||||||
|
|
||||||
|
## メモリレイアウト
|
||||||
|
|
||||||
|
```
|
||||||
|
仮想アドレス空間:
|
||||||
|
0x0000_0000_0000_0000 - ユーザー空間開始
|
||||||
|
0x0000_7fff_ffff_ffff - ユーザー空間終了
|
||||||
|
0x8000_0000_0000_0000 - カーネル空間開始
|
||||||
|
0xffff_ffff_ffff_ffff - カーネル空間終了
|
||||||
|
|
||||||
|
物理メモリ:
|
||||||
|
0x0000_0000 - 0x0009_ffff : 実モード互換領域
|
||||||
|
0x0010_0000 - 0x7fff_ffff : 利用可能RAM
|
||||||
|
0x8000_0000 - 0xffff_ffff : デバイスメモリ
|
||||||
|
```
|
||||||
|
|
||||||
|
## ブートプロセス
|
||||||
|
|
||||||
|
1. **BIOS/UEFI** → ブートローダー読み込み
|
||||||
|
2. **ブートローダー** → カーネルロード
|
||||||
|
3. **カーネル初期化** → ハードウェア検出
|
||||||
|
4. **ドライバー初期化** → VGA・シリアル・etc
|
||||||
|
5. **メモリ管理開始** → ヒープ・ページング
|
||||||
|
6. **ファイルシステム** → ルートマウント
|
||||||
|
7. **プロセス管理** → initプロセス開始
|
||||||
|
8. **ユーザーランド** → Claude Code実行
|
||||||
|
|
||||||
|
## 設計原則
|
||||||
|
|
||||||
|
### 1. 安全性
|
||||||
|
- Rustの所有権システム活用
|
||||||
|
- バッファオーバーフローの防止
|
||||||
|
- 型安全性の保証
|
||||||
|
|
||||||
|
### 2. パフォーマンス
|
||||||
|
- ゼロコスト抽象化
|
||||||
|
- コンパイル時最適化
|
||||||
|
- 最小限のランタイムオーバーヘッド
|
||||||
|
|
||||||
|
### 3. 拡張性
|
||||||
|
- モジュラー設計
|
||||||
|
- プラグアーキテクチャ
|
||||||
|
- 標準インターフェース
|
||||||
|
|
||||||
|
### 4. 移植性
|
||||||
|
- ハードウェア抽象化レイヤー
|
||||||
|
- 設定可能なターゲット
|
||||||
|
- 条件付きコンパイル
|
||||||
|
|
||||||
|
## 今後の拡張計画
|
||||||
|
|
||||||
|
### フェーズ1: 基盤システム
|
||||||
|
- [ ] 動的メモリ管理
|
||||||
|
- [ ] 基本的なファイルシステム
|
||||||
|
- [ ] シンプルなプロセス管理
|
||||||
|
|
||||||
|
### フェーズ2: 高度な機能
|
||||||
|
- [ ] ネットワークスタック
|
||||||
|
- [ ] デバイスドライバーフレームワーク
|
||||||
|
- [ ] システムコール拡張
|
||||||
|
|
||||||
|
### フェーズ3: Claude Code統合
|
||||||
|
- [ ] Node.js/Rustランタイム
|
||||||
|
- [ ] パッケージマネージャー統合
|
||||||
|
- [ ] 開発ツール群
|
||||||
|
|
||||||
|
## 参考アーキテクチャ
|
||||||
|
|
||||||
|
- **xv6**: 教育用Unix系OS
|
||||||
|
- **Redox**: Rust製マイクロカーネルOS
|
||||||
|
- **seL4**: 形式検証されたマイクロカーネル
|
||||||
|
- **Plan 9**: 分散システム指向OS
|
68
docs/QUICKSTART.md
Normal file
68
docs/QUICKSTART.md
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
# AIOS クイックスタート
|
||||||
|
|
||||||
|
## 最短で試す方法
|
||||||
|
|
||||||
|
### 1. 必要なツールのインストール
|
||||||
|
|
||||||
|
**Mac:**
|
||||||
|
```bash
|
||||||
|
brew install qemu
|
||||||
|
cargo install bootimage
|
||||||
|
```
|
||||||
|
|
||||||
|
**Arch Linux:**
|
||||||
|
```bash
|
||||||
|
pacman -S qemu-full
|
||||||
|
cargo install bootimage
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. プロジェクトのセットアップ
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# リポジトリのクローン
|
||||||
|
git clone <repository-url>
|
||||||
|
cd aios
|
||||||
|
|
||||||
|
# 一発でビルド&実行
|
||||||
|
cargo bootimage && qemu-system-x86_64 -drive format=raw,file=target/x86_64-unknown-none/debug/bootimage-kernel.bin -serial stdio
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 動作確認
|
||||||
|
|
||||||
|
正常に動作している場合、以下のような出力が表示されます:
|
||||||
|
|
||||||
|
```
|
||||||
|
Hello World!
|
||||||
|
Welcome to AIOS - A simple OS written in Rust
|
||||||
|
```
|
||||||
|
|
||||||
|
QEMUを終了するには `Ctrl+A` を押してから `X` を押してください。
|
||||||
|
|
||||||
|
## 開発ワークフロー
|
||||||
|
|
||||||
|
### コードの変更
|
||||||
|
```bash
|
||||||
|
# コード変更後
|
||||||
|
cargo build
|
||||||
|
|
||||||
|
# 新しいブートイメージの作成
|
||||||
|
cargo bootimage
|
||||||
|
|
||||||
|
# 実行
|
||||||
|
qemu-system-x86_64 -drive format=raw,file=target/x86_64-unknown-none/debug/bootimage-kernel.bin -serial stdio
|
||||||
|
```
|
||||||
|
|
||||||
|
### デバッグ
|
||||||
|
```bash
|
||||||
|
# シリアル出力のみ表示
|
||||||
|
qemu-system-x86_64 -drive format=raw,file=target/x86_64-unknown-none/debug/bootimage-kernel.bin -serial stdio -nographic
|
||||||
|
|
||||||
|
# メモリ使用量を制限
|
||||||
|
qemu-system-x86_64 -drive format=raw,file=target/x86_64-unknown-none/debug/bootimage-kernel.bin -serial stdio -m 128M
|
||||||
|
```
|
||||||
|
|
||||||
|
## 次のステップ
|
||||||
|
|
||||||
|
1. `src/main.rs`の`println!`文を変更して、独自のメッセージを表示
|
||||||
|
2. `src/vga_buffer.rs`で色を変更
|
||||||
|
3. 新しいモジュールを追加して機能を拡張
|
147
docs/README.md
Normal file
147
docs/README.md
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
# AIOS - A Rust-based Operating System
|
||||||
|
|
||||||
|
AIOSは、Rustで一から作られたUnix系オペレーティングシステムです。最終的にClaude Codeを実行可能にすることを目標としています。
|
||||||
|
|
||||||
|
## 特徴
|
||||||
|
|
||||||
|
- **Pure Rust**: カーネルからユーザーランドまで、全てRustで実装
|
||||||
|
- **ベアメタル**: 標準ライブラリを使用せず、ハードウェアを直接制御
|
||||||
|
- **VGAテキストモード**: コンソール出力をサポート
|
||||||
|
- **シリアル通信**: デバッグとQEMU連携用
|
||||||
|
- **モジュラー設計**: 拡張可能なアーキテクチャ
|
||||||
|
|
||||||
|
## 必要な環境
|
||||||
|
|
||||||
|
### Mac (開発環境)
|
||||||
|
```bash
|
||||||
|
# Rust nightlyツールチェイン
|
||||||
|
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
|
||||||
|
rustup toolchain install nightly
|
||||||
|
rustup default nightly
|
||||||
|
rustup component add rust-src llvm-tools-preview
|
||||||
|
rustup target add x86_64-unknown-none
|
||||||
|
|
||||||
|
# QEMUエミュレーター
|
||||||
|
brew install qemu
|
||||||
|
|
||||||
|
# bootimageツール
|
||||||
|
cargo install bootimage
|
||||||
|
```
|
||||||
|
|
||||||
|
### Arch Linux (本格運用環境)
|
||||||
|
```bash
|
||||||
|
# 基本パッケージ
|
||||||
|
pacman -Syuu --noconfirm base-devel archiso docker git nodejs bc
|
||||||
|
|
||||||
|
# Rustツールチェイン
|
||||||
|
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
|
||||||
|
rustup toolchain install nightly
|
||||||
|
rustup component add rust-src llvm-tools-preview
|
||||||
|
rustup target add x86_64-unknown-none
|
||||||
|
|
||||||
|
# QEMUとbootimageツール
|
||||||
|
pacman -S qemu-full
|
||||||
|
cargo install bootimage
|
||||||
|
```
|
||||||
|
|
||||||
|
## ビルドと実行
|
||||||
|
|
||||||
|
### 1. プロジェクトのクローン
|
||||||
|
```bash
|
||||||
|
git clone <repository-url>
|
||||||
|
cd aios
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. ビルド
|
||||||
|
```bash
|
||||||
|
cargo build
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. ブートイメージの作成
|
||||||
|
```bash
|
||||||
|
cargo bootimage
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. QEMUでの実行
|
||||||
|
```bash
|
||||||
|
qemu-system-x86_64 -drive format=raw,file=target/x86_64-unknown-none/debug/bootimage-kernel.bin -serial stdio
|
||||||
|
```
|
||||||
|
|
||||||
|
## プロジェクト構造
|
||||||
|
|
||||||
|
```
|
||||||
|
aios/
|
||||||
|
├── src/
|
||||||
|
│ ├── main.rs # カーネルエントリーポイント
|
||||||
|
│ ├── vga_buffer.rs # VGAテキストモードドライバー
|
||||||
|
│ └── serial.rs # シリアルポート通信
|
||||||
|
├── .cargo/
|
||||||
|
│ └── config.toml # Cargo設定
|
||||||
|
├── Cargo.toml # プロジェクト設定
|
||||||
|
├── rust-toolchain.toml # Rustツールチェイン設定
|
||||||
|
└── docs/
|
||||||
|
└── README.md # このドキュメント
|
||||||
|
```
|
||||||
|
|
||||||
|
## 開発状況
|
||||||
|
|
||||||
|
### 完了済み
|
||||||
|
- [x] プロジェクト構造の設計と初期化
|
||||||
|
- [x] ベアメタル環境でのRustセットアップ
|
||||||
|
- [x] ブートローダーの実装
|
||||||
|
- [x] VGAテキストモードドライバーの実装
|
||||||
|
- [x] Hello World出力の実装
|
||||||
|
- [x] QEMUでの動作確認
|
||||||
|
|
||||||
|
### 進行中
|
||||||
|
- [ ] メモリ管理システムの実装
|
||||||
|
- [ ] ファイルシステムの実装
|
||||||
|
- [ ] プロセス管理システムの実装
|
||||||
|
- [ ] Claude Code実行環境の構築
|
||||||
|
|
||||||
|
## 目標
|
||||||
|
|
||||||
|
1. **Claude Codeを実行できるようにすること**
|
||||||
|
- Node.js/Rustランタイムの統合
|
||||||
|
- ファイルシステムとプロセス管理
|
||||||
|
- ネットワーク通信機能
|
||||||
|
|
||||||
|
2. **Package managerにpacmanを採用すること**
|
||||||
|
- Arch Linuxとの互換性
|
||||||
|
- パッケージビルドとサーバーの運用負荷軽減
|
||||||
|
|
||||||
|
## 参考資料
|
||||||
|
|
||||||
|
- [o8vm/octox](https://github.com/o8vm/octox) - xv6-riscvにインスパイアされたRust OS
|
||||||
|
- [The Rust Programming Language](https://doc.rust-lang.org/book/)
|
||||||
|
- [Writing an OS in Rust](https://os.phil-opp.com/)
|
||||||
|
|
||||||
|
## トラブルシューティング
|
||||||
|
|
||||||
|
### ビルドエラー
|
||||||
|
```bash
|
||||||
|
# 依存関係の更新
|
||||||
|
cargo update
|
||||||
|
|
||||||
|
# クリーンビルド
|
||||||
|
cargo clean
|
||||||
|
cargo build
|
||||||
|
```
|
||||||
|
|
||||||
|
### QEMUでの問題
|
||||||
|
```bash
|
||||||
|
# シリアル出力の確認
|
||||||
|
qemu-system-x86_64 -drive format=raw,file=target/x86_64-unknown-none/debug/bootimage-kernel.bin -serial stdio -nographic
|
||||||
|
```
|
||||||
|
|
||||||
|
## 貢献
|
||||||
|
|
||||||
|
このプロジェクトは教育目的で作成されていますが、貢献は歓迎します。Pull Requestを送る前に、以下を確認してください:
|
||||||
|
|
||||||
|
1. コードがRustのベストプラクティスに従っている
|
||||||
|
2. 適切なテストが含まれている
|
||||||
|
3. ドキュメントが更新されている
|
||||||
|
|
||||||
|
## ライセンス
|
||||||
|
|
||||||
|
このプロジェクトは MIT License の下で公開されています。
|
44
src/allocator.rs
Normal file
44
src/allocator.rs
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
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)
|
||||||
|
}
|
289
src/filesystem.rs
Normal file
289
src/filesystem.rs
Normal file
@ -0,0 +1,289 @@
|
|||||||
|
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"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
88
src/interrupts.rs
Normal file
88
src/interrupts.rs
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
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
Normal file
100
src/main.rs
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
#![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);
|
||||||
|
}
|
76
src/memory.rs
Normal file
76
src/memory.rs
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
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
Normal file
413
src/package.rs
Normal file
@ -0,0 +1,413 @@
|
|||||||
|
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
Normal file
203
src/process.rs
Normal file
@ -0,0 +1,203 @@
|
|||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
32
src/serial.rs
Normal file
32
src/serial.rs
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
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
Normal file
494
src/shell.rs
Normal file
@ -0,0 +1,494 @@
|
|||||||
|
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
Normal file
180
src/systemd.rs
Normal file
@ -0,0 +1,180 @@
|
|||||||
|
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"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
164
src/vga_buffer.rs
Normal file
164
src/vga_buffer.rs
Normal file
@ -0,0 +1,164 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
Reference in New Issue
Block a user