Отправляет email-рассылки с помощью сервиса Sendsay
  Все выпуски  

Пишем свою операционную систему. Bugfix и printf


Приветствую всех своих читателей!

Недавно заметил ошибку в написанном нами ещё давно startup.asm: аргументы передавались не в том порядке, в котором их принимает kernel_main. Это не проявлялось бы, пока мы не начали использовать значения этих аргументов. Вот правильная версия:

format ELF

public _start
extrn kernel_main

section ".text" executable

_start:
	movzx edx, dl
	push ebx
	push esi
	push edx
	lgdt [gdtr]
	call kernel_main
        add esp, 3 * 4
 @@:
	cli
	hlt
	jmp @b

section ".data" writable

gdt:
	dq 0                 
	dq 0x00CF9A000000FFFF
	dq 0x00CF92000000FFFF
gdtr:
	dw $ - gdt
	dd gdt 

Одного исправления ошибки маловато для нового выпуска, поэтому добавлю сюда и немного приятного для разработчиков функционала.

Во-первых, макросы для работы с портами: outportb, outportw, outportl, inportb, inportw, inportl. Они служат для чтения или записи значений в порты ввода-вывода и позволяет сделать основной код ядра понятнее, уменьшив количество ассемблерных вставок (хотя итоговый код будет тем же самым). Новый stdlib.h выглядит так:

#ifndef STDLIB_H
#define STDLIB_H

typedef enum {
	false = 0,
	true = 1
} bool;

#define NULL ((void*)0)

typedef unsigned char uint8;
typedef signed char int8;

typedef unsigned short uint16;
typedef signed short int16;

typedef unsigned long uint32;
typedef signed long int32;

typedef unsigned long long uint64;
typedef signed long long int64;

#ifdef __x86_64__
	typedef uint64 size_t;
#else
	typedef uint32 size_t;
#endif

#define min(a, b) (((a) > (b)) ? (b) : (a))
#define max(a, b) (((a) > (b)) ? (a) : (b))

#define outportb(port, value) asm("outb %b0, %w1"::"a"(value),"d"(port));
#define outportw(port, value) asm("outw %w0, %w1"::"a"(value),"d"(port));
#define outportl(port, value) asm("outl %0, %w1"::"a"(value),"d"(port));

#define inportb(port, out_value) asm("inb %w1, %b0":"=a"(value):"d"(port));
#define inportw(port, out_value) asm("inw %w1, %w0":"=a"(value):"d"(port));
#define inportl(port, out_value) asm("inl %w1, %0":"=a"(value):"d"(port));

void memset(void *mem, char value, size_t count);
void memset_word(void *mem, uint16 value, size_t count);
void memcpy(void *dest, void *src, size_t count);
int memcmp(void *mem1, void *mem2, size_t count);
void *memchr(void *mem, char value, size_t count);

size_t strlen(char *str);
void strcpy(char *dest, char *src);
void strncpy(char *dest, char*src, size_t max_count);
int strcmp(char *str1, char *str2);
char *strchr(char *str, char value);

#endif 

Код функции move_cursor из tty.c стал проще и понятнее:

void move_cursor(unsigned int pos) {
	cursor = pos;
	if (cursor >= tty_width * tty_height) {
		cursor = (tty_height - 1) * tty_width;
		memcpy(tty_buffer, tty_buffer + tty_width, tty_width * tty_height * sizeof(TtyChar));
		memset_word(tty_buffer + tty_width * (tty_height - 1), (text_attr << 8) + ' ', tty_width);
	}
	outportb(tty_io_port, 0x0E);
	outportb(tty_io_port + 1, cursor >> 8);
	outportb(tty_io_port, 0x0F);
	outportb(tty_io_port + 1, cursor & 0xFF);

Ну и наконец главное новшество этого выпуска - реализация ограниченной поддержки функции printf. До этого момента у нас не было способа выводить форматированный текст, лишь готовые строки, теперь у нас появится большая свобода действий.

Новый заголовочный файл tty.h:

#ifndef TTY_H
#define TTY_H

void init_tty();
void out_char(char chr);
void out_string(char *str);
void clear_screen();
void set_text_attr(char attr);
void move_cursor(unsigned int pos);
void printf(char *fmt, ...);

#endif 

Для написания функции с переменным числом аргументов потребуется включаемый файл stdarg.h. Пожалуй, это единственный файл стандартной библиотеки Си, который мы можем позволить себе включить в наше ядро. Он не требует наличия libc, а содержит лишь набор макросов. Начало tty.c немного изменяется:

#include <stdarg.h>
#include "stdlib.h"
#include "tty.h"

typedef struct {
	uint8 chr;
	uint8 attr;
} TtyChar;

unsigned int tty_width;
 ...

Для начала необходимо добавить в конец этого файла ряд вспомогательных определений для организации преобразования числа в строку перед выводом:

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);

А результат выполнения такого когда виден ниже:

Теперь вы получили больший простор для экспериментов с ядром. В следующем выпуске, как я и обещал, вы рассмотрим обработку прерываний и клавиатурный ввод. До встречи!


В избранное