В предыдущей статье мы научились запускать Hello World ядро и написали пару функций для работы со строками. Теперь пришло время расширить библиотеку С чтобы можно было реализовать kprintf и другие необходимые функции. Поехали!

Оглавление


  1. Система сборки (make, gcc, gas). Первоначальная загрузка (multiboot). Запуск (qemu). Библиотека C (strcpy, memcpy, strext).
  2. Библиотека C (sprintf, strcpy, strcmp, strtok, va_list ...). Сборка библиотеки в режиме ядра и в режиме пользовательского приложения.
  3. Системный журнал ядра. Видеопамять. Вывод на терминал (kprintf, kpanic, kassert).
  4. Динамическая память, куча (kmalloc, kfree).
  5. Организация памяти и обработка прерываний (GDT, IDT, PIC, syscall). Исключения.
  6. Виртуальная память (каталог страниц и таблица страниц).
  7. Процесс. Планировщик. Многозадачность. Системные вызовы (kill, exit, ps).
  8. Файловая система ядра (initrd), elf и его внутренности. Системные вызовы (exec).
  9. Драйверы символьных устройств. Системные вызовы (ioctl, fopen, fread, fwrite). Библиотека C (fopen, fclose, fprintf, fscanf).
  10. Оболочка как полноценная программа для ядра.
  11. Пользовательский режим защиты (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)

Список литературы


  1. James Molloy. Roll your own toy UNIX-clone OS.
  2. Зубков. Ассемблер для DOS, Windows, Unix
  3. Калашников. Ассемблер — это просто!
  4. Таненбаум. Операционные системы. Реализация и разработка.
  5. Роберт Лав. Ядро Linux. Описание процесса разработки.

Комментарии (3)


  1. a-tk
    09.09.2019 13:17
    +1

    typedef unsigned int u_int;
    typedef unsigned int u_long;

    Это точно не потенциальная уязвимость?


    1. arsboretskiy Автор
      09.09.2019 13:40

      Кстати тут надо было long поставить, не заметил) В попыхах кожу.


  1. aaprelev
    10.09.2019 08:38
    +1

    Подскажите, возможно я что-то не понял, но зачем вместо стандартных типов из stdint.h и stdbool.h вы определяете типы с собственными именами? Нельзя ли ваши типы называть точно так же?