Compare commits

..

5 Commits

Author SHA1 Message Date
5a4b12d976 Update README.md 2026-02-15 13:39:01 +00:00
059019f458 Add readme 2026-02-15 17:17:30 +04:00
c44fc166ed Complete project 2026-02-15 17:02:10 +04:00
2c70ec98d8 Disk I/O 2026-02-15 16:14:57 +04:00
c94866f688 User mode 2026-02-15 15:34:42 +04:00
12 changed files with 664 additions and 41 deletions

95
README.md Normal file
View File

@@ -0,0 +1,95 @@
# 🖥️ Операционная система в 1000 строках кода на С
> Минимальная операционная система для архитектуры RISC-V 32-bit, реализованная по книге [Operating System in 1,000 Lines](https://operating-system-in-1000-lines.vercel.app/en/)
![](./screenshot.png)
---
## 📋 Стек
- **Архитектура:** RISC-V 32-bit
- **Компилятор:** Clang с поддержкой RISC-V
- **Язык:** C11 (no std)
- **Виртуализация:** QEMU (для эмуляции)
---
## 🚀 О проекте
- **Минимальная операционная система** с полным набором базовых функций
- **Управление памятью** с поддержкой виртуальной адресации и страничной организации памяти (SV32)
- Выделение страниц памяти
- Маппинг виртуальных адресов на физические
- Изоляция адресных пространств процессов
- **Многозадачность** с поддержкой до 8 процессов одновременно
- Переключение контекста между процессами
- Планировщик задач с round-robin алгоритмом
- Изоляция процессов через отдельные page tables
- **Системные вызовы** для взаимодействия пользовательских программ с ядром
- `SYS_PUTCHAR` — вывод символа в консоль
- `SYS_GETCHAR` — чтение символа из консоли (с блокировкой)
- `SYS_EXIT` — завершение процесса
- `SYS_READFILE` — чтение файла
- `SYS_WRITEFILE` — запись в файл
- **Файловая система** на основе формата tar (ustar)
- Поддержка чтения и записи файлов
- Автоматическая загрузка файлов из образа диска при старте
- Сохранение изменений на диск через VirtIO
- **Работа с диском** через интерфейс VirtIO Block Device
- Чтение и запись секторов диска
- Поддержка виртуальных очередей VirtIO
- **Обработка исключений и прерываний** через trap handler
- Обработка системных вызовов через инструкцию `ecall`
- Переключение между режимами supervisor и user
- **Интерактивная оболочка (shell)** в пользовательском пространстве
- Команда `hello` — приветствие
- Команда `exit` — выход из shell
- Команда `readfile` — чтение файла
- Команда `writefile` — запись в файл
- **Разделение на kernel и user space**
- Ядро работает в режиме supervisor
- Пользовательские программы работают в режиме user с ограниченными правами
- Защита памяти через page tables
---
## 📝 Структура проекта
```
OSIn1000Lines/
├── src/
│ ├── kernel.c # Основной код ядра (управление процессами, системные вызовы)
│ ├── kernel.h # Заголовочные файлы ядра
│ ├── kernel.ld # Скрипт линковки ядра
│ ├── user.c # Пользовательское пространство (системные вызовы)
│ ├── user.h # Заголовочные файлы пользовательского пространства
│ ├── user.ld # Скрипт линковки пользовательских программ
│ ├── shell.c # Код интерактивной оболочки
│ ├── common.c # Общие функции (printf, memset, memcpy, strcmp)
│ ├── common.h # Общие определения
│ ├── Makefile # Сборка проекта
│ ├── disk/ # Файлы для файловой системы
│ │ ├── hello.txt
│ │ └── meow.txt
│ └── run.sh # Скрипт запуска в QEMU для Linux
│ └── run.bat # Скрипт запуска в QEMU для Windows
└── README.md # Этот файл
```
---
## 🎯 Сборка и запуск
### Требования
- **Clang** с поддержкой RISC-V target
- **LLVM objcopy** (llvm-objcopy)
- **QEMU** с поддержкой RISC-V 32-bit
### Запуск
```bash
cd src
./run.sh
```

BIN
screenshot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

View File

@@ -1,5 +1,14 @@
CC = clang
OBJCOPY=llvm-objcopy
CFLAGS = -std=c11 -O2 -g3 -Wall -Wextra --target=riscv32 -ffreestanding -nostdlib
kernel:
$(CC) $(CFLAGS) -Wl,-Tkernel.ld -Wl,-Map=kernel.map -o kernel.elf kernel.c common.c
kernel: shell disk.tar
$(CC) $(CFLAGS) -Wl,-Tkernel.ld -Wl,-Map=kernel.map -o kernel.elf kernel.c common.c shell.bin.o
shell:
$(CC) $(CFLAGS) -Wl,-Tuser.ld -Wl,-Map=shell.map -o shell.elf shell.c user.c common.c
$(OBJCOPY) --set-section-flags .bss=alloc,contents -O binary shell.elf shell.bin
$(OBJCOPY) -Ibinary -Oelf32-littleriscv shell.bin shell.bin.o
disk.tar: $(wildcard disk/*.txt)
cd disk && tar cf ../disk.tar --format=ustar ./*.txt

View File

@@ -28,6 +28,17 @@ typedef uint32_t vaddr_t;
#define PAGE_SIZE 4096
#define USER_BASE 0x1000000
#define SSTATUS_SPIE (1 << 5)
#define SSTATUS_SUM (1 << 18)
#define SYS_PUTCHAR 1
#define SYS_GETCHAR 2
#define SYS_EXIT 3
#define SYS_READFILE 4
#define SYS_WRITEFILE 5
void *memset(void *buf, char c, size_t n);
void *memcpy(void *dst, const void *src, size_t n);

View File

@@ -21,6 +21,10 @@ struct sbiret sbi_call(long arg0, long arg1, long arg2, long arg3, long arg4,
}
void putchar(char ch) { sbi_call(ch, 0, 0, 0, 0, 0, 0, 1); }
long getchar(void) {
struct sbiret ret = sbi_call(0, 0, 0, 0, 0, 0, 0, 2);
return ret.error;
}
// ===== MEMORY ALLOCATION =====
paddr_t alloc_pages(uint32_t n) {
@@ -54,7 +58,7 @@ void map_page(uint32_t *table1, uint32_t vaddr, paddr_t paddr, uint32_t flags) {
}
// ===== Exception handler =====
void kernel_entry(void) {
__attribute__((naked)) __attribute__((aligned(4))) void kernel_entry(void) {
__asm__ __volatile__("csrrw sp, sscratch, sp\n"
"addi sp, sp, -4 * 31\n"
@@ -89,9 +93,11 @@ void kernel_entry(void) {
"sw s10, 4 * 28(sp)\n"
"sw s11, 4 * 29(sp)\n"
// Извлечение и сохранение sp в момент исключения.
"csrr a0, sscratch\n"
"sw a0, 4 * 30(sp)\n"
// Сброс стека ядра.
"addi a0, sp, 4 * 31\n"
"csrw sscratch, a0\n"
@@ -132,13 +138,20 @@ void kernel_entry(void) {
"sret\n");
}
void handle_syscall(struct trap_frame *f);
void handle_trap(struct trap_frame *f) {
uint32_t scause = READ_CSR(scause);
uint32_t stval = READ_CSR(stval);
uint32_t user_pc = READ_CSR(sepc);
if (scause == SCAUSE_ECALL) {
handle_syscall(f);
user_pc += 4;
} else {
PANIC("unexpected trap scause=%x, stval=%x, sepc=%x\n", scause, stval,
user_pc);
}
WRITE_CSR(sepc, user_pc);
}
// ===== Multiprocessing =====
@@ -182,7 +195,17 @@ __attribute__((naked)) void switch_context(uint32_t *prev_sp,
"ret\n");
}
struct process *create_process(uint32_t pc) {
__attribute__((naked)) void user_entry(void) {
__asm__ __volatile__("csrw sepc, %[sepc]\n"
"csrw sstatus, %[sstatus]\n"
"sret\n"
:
: [sepc] "r"(USER_BASE),
[sstatus] "r"(SSTATUS_SPIE | SSTATUS_SUM) // updated
);
}
struct process *create_process(const void *image, size_t image_size) {
struct process *proc = NULL;
int i;
for (i = 0; i < PROCS_MAX; i++) {
@@ -208,13 +231,26 @@ struct process *create_process(uint32_t pc) {
*--sp = 0; // s2
*--sp = 0; // s1
*--sp = 0; // s0
*--sp = (uint32_t)pc; // ra
*--sp = (uint32_t)user_entry; // ra
uint32_t *page_table = (uint32_t *)alloc_pages(1);
for (paddr_t paddr = (paddr_t)__kernel_base;
paddr < (paddr_t)__free_ram_end; paddr += PAGE_SIZE)
map_page(page_table, paddr, paddr, PAGE_R | PAGE_W | PAGE_X);
map_page(page_table, VIRTIO_BLK_PADDR, VIRTIO_BLK_PADDR, PAGE_R | PAGE_W);
for (uint32_t off = 0; off < image_size; off += PAGE_SIZE) {
paddr_t page = alloc_pages(1);
size_t remaining = image_size - off;
size_t copy_size = PAGE_SIZE <= remaining ? PAGE_SIZE : remaining;
memcpy((void *)page, image + off, copy_size);
map_page(page_table, USER_BASE + off, page,
PAGE_U | PAGE_R | PAGE_W | PAGE_X);
}
proc->pid = i + 1;
proc->state = PROC_RUNNABLE;
proc->sp = (uint32_t)sp;
@@ -248,35 +284,273 @@ void yield(void) {
switch_context(&prev->sp, &next->sp);
}
// ===== Disk I/O =====
uint32_t virtio_reg_read32(unsigned offset) {
return *((volatile uint32_t *)(VIRTIO_BLK_PADDR + offset));
}
uint64_t virtio_reg_read64(unsigned offset) {
return *((volatile uint64_t *)(VIRTIO_BLK_PADDR + offset));
}
void virtio_reg_write32(unsigned offset, uint32_t value) {
*((volatile uint32_t *)(VIRTIO_BLK_PADDR + offset)) = value;
}
void virtio_reg_fetch_and_or32(unsigned offset, uint32_t value) {
virtio_reg_write32(offset, virtio_reg_read32(offset) | value);
}
struct virtio_virtq *virtq_init(unsigned index) {
paddr_t virtq_paddr = alloc_pages(
align_up(sizeof(struct virtio_virtq), PAGE_SIZE) / PAGE_SIZE);
struct virtio_virtq *vq = (struct virtio_virtq *)virtq_paddr;
vq->queue_index = index;
vq->used_index = (volatile uint16_t *)&vq->used.index;
virtio_reg_write32(VIRTIO_REG_QUEUE_SEL, index);
virtio_reg_write32(VIRTIO_REG_QUEUE_NUM, VIRTQ_ENTRY_NUM);
virtio_reg_write32(VIRTIO_REG_QUEUE_ALIGN, 0);
virtio_reg_write32(VIRTIO_REG_QUEUE_PFN, virtq_paddr);
return vq;
}
void virtio_blk_init(void) {
if (virtio_reg_read32(VIRTIO_REG_MAGIC) != 0x74726976)
PANIC("virtio: invalid magic value");
if (virtio_reg_read32(VIRTIO_REG_VERSION) != 1)
PANIC("virtio: invalid version");
if (virtio_reg_read32(VIRTIO_REG_DEVICE_ID) != VIRTIO_DEVICE_BLK)
PANIC("virtio: invalid device id");
virtio_reg_write32(VIRTIO_REG_DEVICE_STATUS, 0);
virtio_reg_fetch_and_or32(VIRTIO_REG_DEVICE_STATUS, VIRTIO_STATUS_ACK);
virtio_reg_fetch_and_or32(VIRTIO_REG_DEVICE_STATUS, VIRTIO_STATUS_DRIVER);
virtio_reg_fetch_and_or32(VIRTIO_REG_DEVICE_STATUS, VIRTIO_STATUS_FEAT_OK);
blk_request_vq = virtq_init(0);
virtio_reg_write32(VIRTIO_REG_DEVICE_STATUS, VIRTIO_STATUS_DRIVER_OK);
blk_capacity =
virtio_reg_read64(VIRTIO_REG_DEVICE_CONFIG + 0) * SECTOR_SIZE;
printf("virtio-blk: capacity is %d bytes\n", blk_capacity);
blk_req_paddr =
alloc_pages(align_up(sizeof(*blk_req), PAGE_SIZE) / PAGE_SIZE);
blk_req = (struct virtio_blk_req *)blk_req_paddr;
}
void virtq_kick(struct virtio_virtq *vq, int desc_index) {
vq->avail.ring[vq->avail.index % VIRTQ_ENTRY_NUM] = desc_index;
vq->avail.index++;
__sync_synchronize();
virtio_reg_write32(VIRTIO_REG_QUEUE_NOTIFY, vq->queue_index);
vq->last_used_index++;
}
bool virtq_is_busy(struct virtio_virtq *vq) {
return vq->last_used_index != *vq->used_index;
}
void read_write_disk(void *buf, unsigned sector, int is_write) {
if (sector >= blk_capacity / SECTOR_SIZE) {
printf("virtio: tried to read/write sector=%d, but capacity is %d\n",
sector, blk_capacity / SECTOR_SIZE);
return;
}
blk_req->sector = sector;
blk_req->type = is_write ? VIRTIO_BLK_T_OUT : VIRTIO_BLK_T_IN;
if (is_write)
memcpy(blk_req->data, buf, SECTOR_SIZE);
struct virtio_virtq *vq = blk_request_vq;
vq->descs[0].addr = blk_req_paddr;
vq->descs[0].len = sizeof(uint32_t) * 2 + sizeof(uint64_t);
vq->descs[0].flags = VIRTQ_DESC_F_NEXT;
vq->descs[0].next = 1;
vq->descs[1].addr = blk_req_paddr + offsetof(struct virtio_blk_req, data);
vq->descs[1].len = SECTOR_SIZE;
vq->descs[1].flags =
VIRTQ_DESC_F_NEXT | (is_write ? 0 : VIRTQ_DESC_F_WRITE);
vq->descs[1].next = 2;
vq->descs[2].addr = blk_req_paddr + offsetof(struct virtio_blk_req, status);
vq->descs[2].len = sizeof(uint8_t);
vq->descs[2].flags = VIRTQ_DESC_F_WRITE;
virtq_kick(vq, 0);
while (virtq_is_busy(vq))
;
if (blk_req->status != 0) {
printf("virtio: warn: failed to read/write sector=%d status=%d\n",
sector, blk_req->status);
return;
}
if (!is_write)
memcpy(buf, blk_req->data, SECTOR_SIZE);
}
// ===== Filesystem =====
struct file files[FILES_MAX];
uint8_t disk[DISK_MAX_SIZE];
int oct2int(char *oct, int len) {
int dec = 0;
for (int i = 0; i < len; i++) {
if (oct[i] < '0' || oct[i] > '7')
break;
dec = dec * 8 + (oct[i] - '0');
}
return dec;
}
void fs_init(void) {
for (unsigned sector = 0; sector < sizeof(disk) / SECTOR_SIZE; sector++)
read_write_disk(&disk[sector * SECTOR_SIZE], sector, false);
unsigned off = 0;
for (int i = 0; i < FILES_MAX; i++) {
struct tar_header *header = (struct tar_header *)&disk[off];
if (header->name[0] == '\0')
break;
if (strcmp(header->magic, "ustar") != 0)
PANIC("invalid tar header: magic=\"%s\"", header->magic);
int filesz = oct2int(header->size, sizeof(header->size));
struct file *file = &files[i];
file->in_use = true;
strcpy(file->name, header->name);
memcpy(file->data, header->data, filesz);
file->size = filesz;
printf("file: %s, size=%d\n", file->name, file->size);
off += align_up(sizeof(struct tar_header) + filesz, SECTOR_SIZE);
}
}
void fs_flush(void) {
memset(disk, 0, sizeof(disk));
unsigned off = 0;
for (int file_i = 0; file_i < FILES_MAX; file_i++) {
struct file *file = &files[file_i];
if (!file->in_use)
continue;
struct tar_header *header = (struct tar_header *)&disk[off];
memset(header, 0, sizeof(*header));
strcpy(header->name, file->name);
strcpy(header->mode, "000644");
strcpy(header->magic, "ustar");
strcpy(header->version, "00");
header->type = '0';
int filesz = file->size;
for (int i = sizeof(header->size); i > 0; i--) {
header->size[i - 1] = (filesz % 8) + '0';
filesz /= 8;
}
int checksum = ' ' * sizeof(header->checksum);
for (unsigned i = 0; i < sizeof(struct tar_header); i++)
checksum += (unsigned char)disk[off + i];
for (int i = 5; i >= 0; i--) {
header->checksum[i] = (checksum % 8) + '0';
checksum /= 8;
}
memcpy(header->data, file->data, file->size);
off += align_up(sizeof(struct tar_header) + file->size, SECTOR_SIZE);
}
for (unsigned sector = 0; sector < sizeof(disk) / SECTOR_SIZE; sector++)
read_write_disk(&disk[sector * SECTOR_SIZE], sector, true);
printf("wrote %d bytes to disk\n", sizeof(disk));
}
struct file *fs_lookup(const char *filename) {
for (int i = 0; i < FILES_MAX; i++) {
struct file *file = &files[i];
if (!strcmp(file->name, filename))
return file;
}
return NULL;
}
// ===== System calls =====
void handle_syscall(struct trap_frame *f) {
switch (f->a3) {
case SYS_PUTCHAR:
putchar(f->a0);
break;
case SYS_GETCHAR:
while (1) {
long ch = getchar();
if (ch >= 0) {
f->a0 = ch;
break;
}
yield();
}
break;
case SYS_EXIT:
printf("process %d exited\n", current_proc->pid);
current_proc->state = PROC_EXITED;
yield();
PANIC("unreachable");
case SYS_READFILE:
case SYS_WRITEFILE: {
const char *filename = (const char *)f->a0;
char *buf = (char *)f->a1;
int len = f->a2;
struct file *file = fs_lookup(filename);
if (!file) {
printf("file not found: %s\n", filename);
f->a0 = -1;
break;
}
if (len > (int)sizeof(file->data))
len = file->size;
if (f->a3 == SYS_WRITEFILE) {
memcpy(file->data, buf, len);
file->size = len;
fs_flush();
} else {
memcpy(buf, file->data, len);
}
f->a0 = len;
break;
}
default:
PANIC("unexpected syscall a3=%x\n", f->a3);
}
}
// ===== ENTRY POINT =====
struct process *proc_a;
struct process *proc_b;
void proc_a_entry(void) {
while (1) {
putchar('A');
yield();
}
}
void proc_b_entry(void) {
while (1) {
putchar('B');
yield();
}
}
void kernel_main(void) {
memset(__bss, 0, (size_t)__bss_end - (size_t)__bss);
printf("\n\n");
WRITE_CSR(stvec, (uint32_t)kernel_entry);
idle_proc = create_process((uint32_t)NULL);
virtio_blk_init();
fs_init();
idle_proc = create_process(NULL, 0);
idle_proc->pid = -1;
current_proc = idle_proc;
proc_a = create_process((uint32_t)proc_a_entry);
proc_b = create_process((uint32_t)proc_b_entry);
create_process(_binary_shell_bin_start, (size_t)_binary_shell_bin_size);
yield();
PANIC("switched to idle process");

View File

@@ -7,6 +7,7 @@ extern char __bss[], __bss_end[];
extern char __stack_top[];
extern char __free_ram[], __free_ram_end[];
extern char __kernel_base[];
extern char _binary_shell_bin_start[], _binary_shell_bin_size[];
// ===== SBI data ======
struct sbiret {
@@ -54,6 +55,7 @@ struct trap_frame {
#define PROC_UNUSED 0
#define PROC_RUNNABLE 1
#define PROC_EXITED 2
struct process {
int pid;
@@ -74,6 +76,114 @@ struct process *idle_proc;
#define PAGE_X (1 << 3)
#define PAGE_U (1 << 4)
// ===== Disk I/O =====
#define SECTOR_SIZE 512
#define VIRTQ_ENTRY_NUM 16
#define VIRTIO_DEVICE_BLK 2
#define VIRTIO_BLK_PADDR 0x10001000
#define VIRTIO_REG_MAGIC 0x00
#define VIRTIO_REG_VERSION 0x04
#define VIRTIO_REG_DEVICE_ID 0x08
#define VIRTIO_REG_QUEUE_SEL 0x30
#define VIRTIO_REG_QUEUE_NUM_MAX 0x34
#define VIRTIO_REG_QUEUE_NUM 0x38
#define VIRTIO_REG_QUEUE_ALIGN 0x3c
#define VIRTIO_REG_QUEUE_PFN 0x40
#define VIRTIO_REG_QUEUE_READY 0x44
#define VIRTIO_REG_QUEUE_NOTIFY 0x50
#define VIRTIO_REG_DEVICE_STATUS 0x70
#define VIRTIO_REG_DEVICE_CONFIG 0x100
#define VIRTIO_STATUS_ACK 1
#define VIRTIO_STATUS_DRIVER 2
#define VIRTIO_STATUS_DRIVER_OK 4
#define VIRTIO_STATUS_FEAT_OK 8
#define VIRTQ_DESC_F_NEXT 1
#define VIRTQ_DESC_F_WRITE 2
#define VIRTQ_AVAIL_F_NO_INTERRUPT 1
#define VIRTIO_BLK_T_IN 0
#define VIRTIO_BLK_T_OUT 1
struct virtq_desc {
uint64_t addr;
uint32_t len;
uint16_t flags;
uint16_t next;
} __attribute__((packed));
struct virtq_avail {
uint16_t flags;
uint16_t index;
uint16_t ring[VIRTQ_ENTRY_NUM];
} __attribute__((packed));
struct virtq_used_elem {
uint32_t id;
uint32_t len;
} __attribute__((packed));
struct virtq_used {
uint16_t flags;
uint16_t index;
struct virtq_used_elem ring[VIRTQ_ENTRY_NUM];
} __attribute__((packed));
struct virtio_virtq {
struct virtq_desc descs[VIRTQ_ENTRY_NUM];
struct virtq_avail avail;
struct virtq_used used __attribute__((aligned(PAGE_SIZE)));
int queue_index;
volatile uint16_t *used_index;
uint16_t last_used_index;
} __attribute__((packed));
struct virtio_blk_req {
uint32_t type;
uint32_t reserved;
uint64_t sector;
uint8_t data[512];
uint8_t status;
} __attribute__((packed));
struct virtio_virtq *blk_request_vq;
struct virtio_blk_req *blk_req;
paddr_t blk_req_paddr;
unsigned blk_capacity;
// ===== Filesystem =====
#define FILES_MAX 2
#define DISK_MAX_SIZE align_up(sizeof(struct file) * FILES_MAX, SECTOR_SIZE)
struct tar_header {
char name[100];
char mode[8];
char uid[8];
char gid[8];
char size[12];
char mtime[12];
char checksum[8];
char type;
char linkname[100];
char magic[6];
char version[2];
char uname[32];
char gname[32];
char devmajor[8];
char devminor[8];
char prefix[155];
char padding[12];
char data[];
} __attribute__((packed));
struct file {
bool in_use;
char name[100];
char data[1024];
size_t size;
};
// ===== System calls =====
#define SCAUSE_ECALL 8
// ===== Macros =====
#define READ_CSR(reg) \
({ \

View File

@@ -1,4 +1,6 @@
@echo off
make kernel
cd /d "C:\Program Files\qemu"
qemu-system-riscv32.exe -machine virt -bios default -nographic -serial mon:stdio --no-reboot -kernel %~dp0kernel.elf
qemu-system-riscv32.exe -machine virt -bios default -nographic -serial mon:stdio --no-reboot -drive id=drive0,file=disk.tar,format=raw,if=none -device virtio-blk-device,drive=drive0,bus=virtio-mmio-bus.0 -kernel %~dp0kernel.elf

View File

@@ -1,4 +1,4 @@
#!/bin/bash
set -xue
make kernel && qemu-system-riscv32 -machine virt -bios default -nographic -serial mon:stdio --no-reboot -kernel kernel.elf
make kernel && qemu-system-riscv32 -machine virt -bios default -nographic -serial mon:stdio --no-reboot -drive id=drive0,file=disk.tar,format=raw,if=none -device virtio-blk-device,drive=drive0,bus=virtio-mmio-bus.0 -kernel kernel.elf

37
src/shell.c Normal file
View File

@@ -0,0 +1,37 @@
#include "user.h"
void main(void) {
while (1) {
prompt:
printf("> ");
char cmdline[128];
for (int i = 0;; i++) {
char ch = getchar();
putchar(ch);
if (i == sizeof(cmdline) - 1) {
printf("command line too long\n");
goto prompt;
} else if (ch == '\r') {
printf("\n");
cmdline[i] = '\0';
break;
} else {
cmdline[i] = ch;
}
}
if (strcmp(cmdline, "hello") == 0)
printf("Hello world from shell!\n");
else if (strcmp(cmdline, "exit") == 0)
exit();
else if (strcmp(cmdline, "readfile") == 0) {
char buf[128];
int len = readfile("./hello.txt", buf, sizeof(buf));
buf[len] = '\0';
printf("%s\n", buf);
} else if (strcmp(cmdline, "writefile") == 0)
writefile("./hello.txt", "Hello from shell!\n", 19);
else
printf("unknown command: %s\n", cmdline);
}
}

42
src/user.c Normal file
View File

@@ -0,0 +1,42 @@
#include "user.h"
extern char __stack_top[];
int syscall(int sysno, int arg0, int arg1, int arg2) {
register int a0 __asm__("a0") = arg0;
register int a1 __asm__("a1") = arg1;
register int a2 __asm__("a2") = arg2;
register int a3 __asm__("a3") = sysno;
__asm__ __volatile__("ecall"
: "=r"(a0)
: "r"(a0), "r"(a1), "r"(a2), "r"(a3)
: "memory");
return a0;
}
void putchar(char ch) { syscall(SYS_PUTCHAR, ch, 0, 0); }
int getchar(void) { return syscall(SYS_GETCHAR, 0, 0, 0); }
int readfile(const char *filename, char *buf, int len) {
return syscall(SYS_READFILE, (int)filename, (int)buf, len);
}
int writefile(const char *filename, const char *buf, int len) {
return syscall(SYS_WRITEFILE, (int)filename, (int)buf, len);
}
__attribute__((noreturn)) void exit(void) {
syscall(SYS_EXIT, 0, 0, 0);
for (;;)
;
}
__attribute__((section(".text.start"))) __attribute__((naked)) void
start(void) {
__asm__ __volatile__("mv sp, %[stack_top]\n"
"call main\n"
"call exit\n" ::[stack_top] "r"(__stack_top));
}

15
src/user.h Normal file
View File

@@ -0,0 +1,15 @@
#pragma once
#include "common.h"
struct sysret {
int a0;
int a1;
int a2;
};
void putchar(char ch);
int getchar(void);
int readfile(const char *filename, char *buf, int len);
int writefile(const char *filename, const char *buf, int len);
__attribute__((noreturn)) void exit(void);

28
src/user.ld Normal file
View File

@@ -0,0 +1,28 @@
ENTRY(start)
SECTIONS {
. = 0x1000000;
.text :{
KEEP(*(.text.start));
*(.text .text.*);
}
.rodata : ALIGN(4) {
*(.rodata .rodata.*);
}
.data : ALIGN(4) {
*(.data .data.*);
}
.bss : ALIGN(4) {
*(.bss .bss.* .sbss .sbss.*);
. = ALIGN(16);
. += 64 * 1024; /* 64KB */
__stack_top = .;
ASSERT(. < 0x1800000, "too large executable");
}
}