В предыдущей второй по счету статье мы с вами разработали необходимые функции для работы со строками из библиотеки С. В этом уроке мы реализуем полноценный отладочный вывод на экран — системный журнал ядра.
Перед тем как начать, нам понадобится ввести несколько полезных функций для работы с портами ввода-вывода. Порты ввода-вывода для программиста ничем не отличаются от обычных ячеек в памяти, за исключением того что для оперирования ими существуют отдельные команды. Устройства, оперирующие этими портами, подключены к шине памяти. Также для них существует выделенное адресное пространство. Нам потребуется две ассемблерные функции для работы с портами ввода-вывода, ведь как вы уже помните, я не терплю ассемблерных вставок.
Аналогично две команды для управления маскируемыми прерываниями процессора.
Ну и для экономии электроэнергии после неисправимых ошибок нужна команда остановки процессора.
Как ты помнишь, видеопамять начинается по адресу 0xB8000, но я предлагаю писать сообщения сначала в буфер в обычном текстовом формате. А потом просто этот буфер копировать в видеопамять с учетом аттрибутов цвета. Для этого я реализовал несколько утилит для работы с тамими буферами. Один буфер будет для системного журнала ядра, и остальные для виртуальных терминалов. Прокрутка экрана также будет выполняться над буфером. И только функция video_flush будет копировать буффер в видеопамять, расширяя его аттрибутами.
Теперь самое время ввести самые часто используемые функции. Две последних будут использоваться для отладки ядра, когда лень отлаживать дебаггером. Поверь, я не разу не юзал дебаггер когда писал это ядро.
Ну и собственно, функции для работы с системным журналом ядра. Для того чтобы управлять тем, что на экран выводится, системный журнал или пользовательская консоль я ввел функцию kmode. А для чтения системного журнала в буффер нужна будет функция klog, ибо у пользовательских процессов не будет доступа к ядру кроме как через системные вызовы.
Наиболее интересные функции привожу тут:
Подробный туториал смотри в видеоуроке.
> Видеоурок к этой статье
> Исходный код (тебе нужна ветка lesson3)
Оглавление
- Система сборки (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).
Системный журнал ядра
Перед тем как начать, нам понадобится ввести несколько полезных функций для работы с портами ввода-вывода. Порты ввода-вывода для программиста ничем не отличаются от обычных ячеек в памяти, за исключением того что для оперирования ими существуют отдельные команды. Устройства, оперирующие этими портами, подключены к шине памяти. Также для них существует выделенное адресное пространство. Нам потребуется две ассемблерные функции для работы с портами ввода-вывода, ведь как вы уже помните, я не терплю ассемблерных вставок.
extern u_char asm_read_port(u_char port);
extern void asm_write_port(u_int port, u_char data);
Аналогично две команды для управления маскируемыми прерываниями процессора.
extern void asm_lock();
extern void asm_unlock();
Ну и для экономии электроэнергии после неисправимых ошибок нужна команда остановки процессора.
extern void asm_hlt();
Как ты помнишь, видеопамять начинается по адресу 0xB8000, но я предлагаю писать сообщения сначала в буфер в обычном текстовом формате. А потом просто этот буфер копировать в видеопамять с учетом аттрибутов цвета. Для этого я реализовал несколько утилит для работы с тамими буферами. Один буфер будет для системного журнала ядра, и остальные для виртуальных терминалов. Прокрутка экрана также будет выполняться над буфером. И только функция video_flush будет копировать буффер в видеопамять, расширяя его аттрибутами.
extern void video_init();
extern void video_disable_cursor();
extern void* video_scroll(char const* video_buff, char* pos);
extern char* video_clear(char const* video_buff);
extern void video_flush(char const* video_buff);
Теперь самое время ввести самые часто используемые функции. Две последних будут использоваться для отладки ядра, когда лень отлаживать дебаггером. Поверь, я не разу не юзал дебаггер когда писал это ядро.
extern void kpanic(char* message, ...);
extern void kassert(const char* file, u_int line, bool expr);
extern void kunreachable(const char* file, u_int line);
Ну и собственно, функции для работы с системным журналом ядра. Для того чтобы управлять тем, что на экран выводится, системный журнал или пользовательская консоль я ввел функцию kmode. А для чтения системного журнала в буффер нужна будет функция klog, ибо у пользовательских процессов не будет доступа к ядру кроме как через системные вызовы.
extern void kclear();
extern void kprintf(const char* format, ...);
extern void kvprintf(const char* format, va_list list);
extern void kmode(bool is_early);
extern void klog(char* buf, u_int n);
Наиболее интересные функции привожу тут:
/*
* Api - Scroll video buffer up
* Returns new position
*/
extern void* video_scroll(char const* video_buff, char* pos)
{
char* ptr = (void*)video_buff;
/* scroll up */
for (int i = 1; i < VIDEO_SCREEN_HEIGHT; ++i) {
for (int j = 0; j < VIDEO_SCREEN_WIDTH; ++j) {
ptr[(i - 1) * VIDEO_SCREEN_WIDTH + j] = ptr[i * VIDEO_SCREEN_WIDTH + j];
}
}
/* empty last line */
for (int j = 0; j < VIDEO_SCREEN_WIDTH; ++j) {
ptr[(VIDEO_SCREEN_HEIGHT - 1) * VIDEO_SCREEN_WIDTH + j] = ' ';
}
/* move position up */
pos -= VIDEO_SCREEN_WIDTH;
return pos;
}
/*
* Api - Print kernel message
*/
extern void kvprintf(const char* format, va_list list)
{
char buff[VIDEO_SCREEN_WIDTH];
int len = vsprintf(buff, format, list);
for (int i = 0; i < len; ++i) {
if (buff[i] != '\n') {
kputc(buff[i]);
} else {
int line_pos = (syslog_pos - syslog) % VIDEO_SCREEN_WIDTH;
for (int j = 0; j < VIDEO_SCREEN_WIDTH - line_pos; ++j) {
kputc(' ');
}
}
}
kflush();
}
/*
* Put character to syslog
*/
static void kputc(char ch)
{
if ((size_t)syslog_pos - (size_t)syslog + 1 < VIDEO_SCREEN_SIZE) {
*syslog_pos++ = ch;
} else {
syslog_pos = video_scroll(syslog, syslog_pos);
kputc(ch);
}
}
Подробный туториал смотри в видеоуроке.
Ссылки
> Видеоурок к этой статье
> Исходный код (тебе нужна ветка lesson3)
Список литературы
- James Molloy. Roll your own toy UNIX-clone OS.
- Зубков. Ассемблер для DOS, Windows, Unix
- Калашников. Ассемблер — это просто!
- Таненбаум. Операционные системы. Реализация и разработка.
- Роберт Лав. Ядро Linux. Описание процесса разработки.
Комментарии (2)
AntonSazonov
10.09.2019 10:06В текстовом (3) режиме можно сделать плавный скроллинг, т.е. попиксельно. Точно не помню, но по-моему это осуществляется через порты 0x3d4 и 0x3d5.
Не думали о таком?
mwambanatanga
Не добавите ли в заголовок номер части?