В предыдущей статье мы научились запускать Hello World ядро и написали пару функций для работы со строками. Теперь пришло время расширить библиотеку С чтобы можно было реализовать kprintf и другие необходимые функции. Поехали!
Оглавление
- Система сборки (make, gcc, gas). Первоначальная загрузка (multiboot). Запуск (qemu). Библиотека C (strcpy, memcpy, strext).
- Библиотека C (sprintf, strcpy, strcmp, strtok, va_list ...). Сборка библиотеки в режиме ядра и в режиме пользовательского приложения.
- Системный журнал ядра. Видеопамять. Вывод на терминал (kprintf, kpanic, kassert).
- Динамическая память, куча (kmalloc, kfree).
- Организация памяти и обработка прерываний (GDT, IDT, PIC, syscall). Исключения.
- Виртуальная память (каталог страниц и таблица страниц).
- Процесс. Планировщик. Многозадачность. Системные вызовы (kill, exit, ps).
- Файловая система ядра (initrd), elf и его внутренности. Системные вызовы (exec).
- Драйверы символьных устройств. Системные вызовы (ioctl, fopen, fread, fwrite). Библиотека C (fopen, fclose, fprintf, fscanf).
- Оболочка как полноценная программа для ядра.
- Пользовательский режим защиты (ring3). Сегмент состояния задачи (tss).
Библиотека С
Сначала необходимо реализовать типы с явным указанием размерности.
Поскольку мы будем собирать под одну платформу, пока наши определения и реализации будут верны. Это не универсально, но именно поэтому читаемо и просто. Я придерживаюсь подхода, что иногда лучше сделать что-то не универсальное, но удовлетворяющее текущим требованиям, поскольку поддерживать универсальные решения крайне тяжело.
typedef unsigned char u8;
typedef unsigned short u16;
typedef unsigned int u32;
typedef unsigned char u_char;
typedef unsigned short u_short;
typedef unsigned int u_int;
typedef unsigned int u_long;
Не помешает и ввести Булевский тип.
#pragma once
/* types */
typedef int bool;
#define true 1
#define false 0
#define null 0
Также не помешает нам и парочка макросов для работы с байтами.
typedef unsigned long size_t;
#define HIGH_WORD(addr) ((addr & 0xffff0000) >> 16)
#define LOW_WORD(addr) ((addr & 0xffff))
#define LOW_BYTE(addr) ((addr & 0x00ff))
#define HIGH_BYTE(addr) ((addr & 0xff00) >> 8)
Для работы с переменным количеством аргументов нужны следующие макросы. Этот подход работает только в том случае если функция следует соглашению вызова языка Си, в котором аргументы функции передаются через стек начиная с последнего.
typedef size_t* va_list;
#define va_start(l, a) (l = (void*)((size_t)&a) + sizeof(a))
#define va_end(l) (l = (void*)0)
#define va_arg(l, s) (*(s*)(l++))
Конечно можно было пойти и другим путем. Вместо определения собственных функций постараться использовать встроенную библиотеку и заменять функции которые будут обращаться к ядру через LD_PRELOAD. Но я люблю контролировать процесс полностью, поэтому оставим этот вариант в качестве идеи для тех кто начинает писать свою ОС по этому туториалу.
Далее, в видеоуроке мы рассмотрим реализацию следующих библиотечных функций. Реализация не претендует на оптимальность и полноту, но на простоту и читаемость думаю претендует. Отмечу только что мы используем потокобезопасную реализацию функции strtok, которая называется strtok_r. А функции strinv и strext мы придумали сами в прошлом уроке. Если ты знаком с языком С, думаю тебе будут знакомы почти все перечисленные ниже функции.
extern int strlen(const char* s);
extern char* strcpy(char* s1, const char* s2);
extern char* strncpy(char* s1, const char* s2, u_int n);
extern void* memcpy(void* buf1, const void* buf2, u_int bytes);
extern void* memset(void* buf1, u8 value, u_int bytes);
extern int strcmp(const char* s1, const char* s2);
extern int strncmp(const char* s1, const char* s2, u_int n);
extern char* strcat(char* s1, const char* s2);
extern char* strext(char* buf, const char* str, char sym);
extern int strspn(char* str, const char* accept);
extern int strcspn(char* str, const char* rejected);
char* strchr(const char* str, char ch);
extern char* strtok_r(char* str, const char* delims, char** save_ptr);
extern char* memext(void* buff_dst, u_int n, const void* buff_src, char sym);
extern char* itoa(unsigned int value, char* str, unsigned int base);
extern unsigned int atou(char* str);
extern char* strinv(char* str);
extern unsigned int sprintf(char* s1, const char* s2, ...);
extern unsigned int snprintf(char* s1, u_int n, const char* s2, ...);
extern unsigned int vsprintf(char* s1, const char* s2, va_list list);
extern unsigned int vsnprintf(char* s1, unsigned int n, const char* s2, va_list list);
С рутиной покончили. Конец шаблонного кода. Следующий урок будет намного сложнее и интереснее. Если хочешь поконтрибьютить проект, можешь предложить свои оптимальные реализации библиотечных функций.
Ссылки
Разработка монолитной Unix подобной OS — Начало
Видеоурок к этой статье
Исходный код (тебе нужна ветка lesson2)
Список литературы
- James Molloy. Roll your own toy UNIX-clone OS.
- Зубков. Ассемблер для DOS, Windows, Unix
- Калашников. Ассемблер — это просто!
- Таненбаум. Операционные системы. Реализация и разработка.
- Роберт Лав. Ядро Linux. Описание процесса разработки.
Автор: arsboretskiy