mirror of
https://github.com/StepanovPlaton/OSin1000Lines.git
synced 2026-04-03 12:20:46 +04:00
Compare commits
5 Commits
10c456438b
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 5a4b12d976 | |||
| 059019f458 | |||
| c44fc166ed | |||
| 2c70ec98d8 | |||
| c94866f688 |
95
README.md
Normal file
95
README.md
Normal 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/)
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
## 📋 Стек
|
||||
|
||||
- **Архитектура:** 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
BIN
screenshot.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 38 KiB |
13
src/Makefile
13
src/Makefile
@@ -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
|
||||
|
||||
11
src/common.h
11
src/common.h
@@ -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);
|
||||
|
||||
|
||||
348
src/kernel.c
348
src/kernel.c
@@ -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);
|
||||
}
|
||||
|
||||
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++) {
|
||||
@@ -196,25 +219,38 @@ struct process *create_process(uint32_t pc) {
|
||||
PANIC("no free process slots");
|
||||
|
||||
uint32_t *sp = (uint32_t *)&proc->stack[sizeof(proc->stack)];
|
||||
*--sp = 0; // s11
|
||||
*--sp = 0; // s10
|
||||
*--sp = 0; // s9
|
||||
*--sp = 0; // s8
|
||||
*--sp = 0; // s7
|
||||
*--sp = 0; // s6
|
||||
*--sp = 0; // s5
|
||||
*--sp = 0; // s4
|
||||
*--sp = 0; // s3
|
||||
*--sp = 0; // s2
|
||||
*--sp = 0; // s1
|
||||
*--sp = 0; // s0
|
||||
*--sp = (uint32_t)pc; // ra
|
||||
*--sp = 0; // s11
|
||||
*--sp = 0; // s10
|
||||
*--sp = 0; // s9
|
||||
*--sp = 0; // s8
|
||||
*--sp = 0; // s7
|
||||
*--sp = 0; // s6
|
||||
*--sp = 0; // s5
|
||||
*--sp = 0; // s4
|
||||
*--sp = 0; // s3
|
||||
*--sp = 0; // s2
|
||||
*--sp = 0; // s1
|
||||
*--sp = 0; // s0
|
||||
*--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");
|
||||
|
||||
110
src/kernel.h
110
src/kernel.h
@@ -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) \
|
||||
({ \
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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
37
src/shell.c
Normal 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
42
src/user.c
Normal 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
15
src/user.h
Normal 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
28
src/user.ld
Normal 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");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user