Compare commits

..

2 Commits

Author SHA1 Message Date
059019f458 Add readme 2026-02-15 17:17:30 +04:00
c44fc166ed Complete project 2026-02-15 17:02:10 +04:00
12 changed files with 379 additions and 25 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 (freestanding)
- **Виртуализация:** 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

@@ -2,10 +2,13 @@ CC = clang
OBJCOPY=llvm-objcopy OBJCOPY=llvm-objcopy
CFLAGS = -std=c11 -O2 -g3 -Wall -Wextra --target=riscv32 -ffreestanding -nostdlib CFLAGS = -std=c11 -O2 -g3 -Wall -Wextra --target=riscv32 -ffreestanding -nostdlib
kernel: shell kernel: shell disk.tar
$(CC) $(CFLAGS) -Wl,-Tkernel.ld -Wl,-Map=kernel.map -o kernel.elf kernel.c common.c shell.bin.o $(CC) $(CFLAGS) -Wl,-Tkernel.ld -Wl,-Map=kernel.map -o kernel.elf kernel.c common.c shell.bin.o
shell: shell:
$(CC) $(CFLAGS) -Wl,-Tuser.ld -Wl,-Map=shell.map -o shell.elf shell.c user.c common.c $(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) --set-section-flags .bss=alloc,contents -O binary shell.elf shell.bin
$(OBJCOPY) -Ibinary -Oelf32-littleriscv shell.bin shell.bin.o $(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

@@ -31,6 +31,13 @@ typedef uint32_t vaddr_t;
#define USER_BASE 0x1000000 #define USER_BASE 0x1000000
#define SSTATUS_SPIE (1 << 5) #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 *memset(void *buf, char c, size_t n);
void *memcpy(void *dst, const void *src, 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); } 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 ===== // ===== MEMORY ALLOCATION =====
paddr_t alloc_pages(uint32_t n) { paddr_t alloc_pages(uint32_t n) {
@@ -134,15 +138,22 @@ __attribute__((naked)) __attribute__((aligned(4))) void kernel_entry(void) {
"sret\n"); "sret\n");
} }
void handle_syscall(struct trap_frame *f);
void handle_trap(struct trap_frame *f) { void handle_trap(struct trap_frame *f) {
uint32_t scause = READ_CSR(scause); uint32_t scause = READ_CSR(scause);
uint32_t stval = READ_CSR(stval); uint32_t stval = READ_CSR(stval);
uint32_t user_pc = READ_CSR(sepc); 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, PANIC("unexpected trap scause=%x, stval=%x, sepc=%x\n", scause, stval,
user_pc); user_pc);
} }
WRITE_CSR(sepc, user_pc);
}
// ===== Multiprocessing ===== // ===== Multiprocessing =====
__attribute__((naked)) void switch_context(uint32_t *prev_sp, __attribute__((naked)) void switch_context(uint32_t *prev_sp,
uint32_t *next_sp) { uint32_t *next_sp) {
@@ -189,7 +200,9 @@ __attribute__((naked)) void user_entry(void) {
"csrw sstatus, %[sstatus]\n" "csrw sstatus, %[sstatus]\n"
"sret\n" "sret\n"
: :
: [sepc] "r"(USER_BASE), [sstatus] "r"(SSTATUS_SPIE)); : [sepc] "r"(USER_BASE),
[sstatus] "r"(SSTATUS_SPIE | SSTATUS_SUM) // updated
);
} }
struct process *create_process(const void *image, size_t image_size) { struct process *create_process(const void *image, size_t image_size) {
@@ -380,6 +393,148 @@ void read_write_disk(void *buf, unsigned sector, int is_write) {
memcpy(buf, blk_req->data, SECTOR_SIZE); 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 ===== // ===== ENTRY POINT =====
void kernel_main(void) { void kernel_main(void) {
memset(__bss, 0, (size_t)__bss_end - (size_t)__bss); memset(__bss, 0, (size_t)__bss_end - (size_t)__bss);
@@ -389,13 +544,7 @@ void kernel_main(void) {
WRITE_CSR(stvec, (uint32_t)kernel_entry); WRITE_CSR(stvec, (uint32_t)kernel_entry);
virtio_blk_init(); virtio_blk_init();
fs_init();
char buf[SECTOR_SIZE];
read_write_disk(buf, 0, false);
printf("first sector: %s\n", buf);
strcpy(buf, "hello from kernel!!!\n");
read_write_disk(buf, 0, true);
idle_proc = create_process(NULL, 0); idle_proc = create_process(NULL, 0);
idle_proc->pid = -1; idle_proc->pid = -1;

View File

@@ -55,6 +55,7 @@ struct trap_frame {
#define PROC_UNUSED 0 #define PROC_UNUSED 0
#define PROC_RUNNABLE 1 #define PROC_RUNNABLE 1
#define PROC_EXITED 2
struct process { struct process {
int pid; int pid;
@@ -148,6 +149,41 @@ struct virtio_blk_req *blk_req;
paddr_t blk_req_paddr; paddr_t blk_req_paddr;
unsigned blk_capacity; 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 ===== // ===== Macros =====
#define READ_CSR(reg) \ #define READ_CSR(reg) \
({ \ ({ \

Binary file not shown.

View File

@@ -1,6 +1,6 @@
@echo off @echo off
make kernel make kernel
cd /d "C:\Program Files\qemu" cd /d "C:\Program Files\qemu"
qemu-system-riscv32.exe -machine virt -bios default -nographic -serial mon:stdio --no-reboot -drive id=drive0,file=lorem.txt,format=raw,if=none -device virtio-blk-device,drive=drive0,bus=virtio-mmio-bus.0 -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 #!/bin/bash
set -xue set -xue
make kernel && qemu-system-riscv32 -machine virt -bios default -nographic -serial mon:stdio --no-reboot -drive id=drive0,file=lorem.txt,format=raw,if=none -device virtio-blk-device,drive=drive0,bus=virtio-mmio-bus.0 -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

View File

@@ -1,6 +1,37 @@
#include "user.h" #include "user.h"
void main(void) { void main(void) {
for (;;) 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);
}
} }

View File

@@ -2,17 +2,41 @@
extern char __stack_top[]; 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) { __attribute__((noreturn)) void exit(void) {
syscall(SYS_EXIT, 0, 0, 0);
for (;;) for (;;)
; ;
} }
void putchar(char c) { /* Доделать*/ }
__attribute__((section(".text.start"))) __attribute__((naked)) void __attribute__((section(".text.start"))) __attribute__((naked)) void
start(void) { start(void) {
__asm__ __volatile__( __asm__ __volatile__("mv sp, %[stack_top]\n"
"mv sp, %[stack_top] \n"
"call main\n" "call main\n"
"call exit\n" ::[stack_top] "r"(__stack_top)); "call exit\n" ::[stack_top] "r"(__stack_top));
} }

View File

@@ -2,5 +2,14 @@
#include "common.h" #include "common.h"
__attribute__((noreturn)) void exit(void); struct sysret {
int a0;
int a1;
int a2;
};
void putchar(char ch); 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);