Недавно заметил ошибку в написанном нами ещё давно startup.asm: аргументы передавались не в том порядке, в котором их принимает kernel_main. Это не проявлялось бы, пока мы не начали использовать значения этих аргументов. Вот правильная версия:
Одного исправления ошибки маловато для нового выпуска, поэтому добавлю сюда и немного приятного для разработчиков функционала.
Во-первых, макросы для работы с портами: outportb, outportw, outportl, inportb, inportw, inportl. Они служат для чтения или записи значений в
порты ввода-вывода и позволяет сделать основной код ядра понятнее, уменьшив количество ассемблерных вставок (хотя итоговый код будет тем же самым). Новый stdlib.h выглядит так:
Ну и наконец главное новшество этого выпуска - реализация ограниченной поддержки функции printf. До этого момента у нас не было способа выводить форматированный текст, лишь готовые строки, теперь у нас появится большая свобода действий.
Для написания функции с переменным числом аргументов потребуется включаемый файл stdarg.h. Пожалуй, это единственный файл стандартной библиотеки Си, который мы можем позволить себе включить в наше ядро. Он не требует наличия libc, а содержит лишь набор макросов. Начало tty.c немного изменяется:
Для начала необходимо добавить в конец этого файла ряд вспомогательных определений для организации преобразования числа в строку перед выводом:
const char digits[] = "0123456789ABCDEF";
char num_buffer[65];
char *int_to_str(size_t value, unsigned char base) {
size_t i = sizeof(num_buffer) - 1;
num_buffer[i--] = '\0';
do {
num_buffer[i--] = digits[value % base];
value = value / base;
} while (value);
return &num_buffer[i + 1];
}
Эта функция позволяет преобразовать число value в строковое представление в любой системе счисления от 2 до 16. Она возвращает указатель на строку с числом. Следует использовать её или же скопировать в другое место до следующего вызова int_to_str, потому что этот адрес находится внутри глобальной переменной.
Ну и, наконец, самая главная функция - printf (её следует описать после int_to_str):
void printf(char *fmt, ...) {
va_list args;
va_start(args, fmt);
while (*fmt) {
if (*fmt == '%') {
fmt++;
size_t arg = va_arg(args, size_t);
switch (*fmt) {
case '%':
out_char('%');
break;
case 'c':
out_char(arg);
break;
case 's':
out_string((char*)arg);
break;
case 'b':
out_string(int_to_str(arg, 2));
break;
case 'o':
out_string(int_to_str(arg, 8));
break;
case 'd':
out_string(int_to_str(arg, 10));
break;
case 'x':
out_string(int_to_str(arg, 16));
break;
}
} else {
out_char(*fmt);
}
fmt++;
}
va_end(args);
}
Наш printf понимает следующие форматы: %s - Вывод строки. %c - Вывод символа с указанным кодом %b - Вывод числа в двоичном представлении %o - Вывод числа в восьмеричном представлении %d - Вывод числа в десятичном представлении %x - Вывод числа в шестнадцатеричном представлении %% - Вывод символа процента
Вывод вещественных чисел, точность, выравнивание не поддерживаются, потому что кода писать придётся много, а функционал в итоге всё равно нам не пригодится.
Функция kernel_main из main.c теперь может выглядеть так:
void kernel_main(uint8 boot_disk_id,
void *memory_map, BootModuleInfo *boot_module_list) {
init_tty();
set_text_attr(15);
printf("Welcome to MyOS!\n");
printf("Boot disk id is %d\n", boot_disk_id);
printf("Memory map at 0x%x\n", memory_map);
printf("Boot module list at 0x%x\n", boot_module_list);
printf("String is %s, char is %c, number is %d, hex number is 0x%x", __DATE__, 'A', 1234, 0x1234);
}
А результат выполнения такого когда виден ниже:
Теперь вы получили больший простор для экспериментов с ядром. В следующем выпуске, как я и обещал, вы рассмотрим обработку прерываний и клавиатурный ввод. До встречи!