Добрый день, уважаемые читатели. Пришла пора изучить кое-что новенькое и исправить что-то старенькое. В предыдущей статье я переписал ассемблерный вывод строки, используя язык Си. Но если хорошо посмотреть на полученный код возникнет куча вопросов. Что делать, если понадобится еще вывести текст на экран? Писать то же самое, но в другом месте? Так, конечно, делать нельзя. Дублирование кода может завести в тупик. Вот тут-то и нужна работа над ошибками.

Прежде чем начать, хочу порекомендовать пару очень хороших книг по вопросу красоты кода.

роберт мартин - чистый код

Книга Роберта Мартина, или как его часто называют uncle Bob (дядюшка Боб), Чистый код: создание, анализ и рефакторинг. Этот бестселлер в среде разработчиков трудно переоценить. На страницах разворачивается бой с «дурно пахнущим» кодом.

работа над ошибками - совершенный код

Я не скажу, что Совершенный код Стива Макконнелла нужно читать новичку. В книге мало примеров кода и жизненный цикл разработки ПО расписан до мелочей. Однако произведение must read для понимания процесса.

Библиотека стандартных функций

В связи с вышеописанным я решил начать разрабатывать некое жалкое подобие стандартной библиотеки для Simple OS. Но я не расстраиваюсь.

Из малого рождается великое.

Публий Сир

Итак, начнем. Сначала создадим директорию, которую назовем: library. Постараюсь разрабатывать модульно, для более удобного чтения и поиска в исходниках. Для каждого отдельного модуля библиотеки нужно создавать два файла:

  • исходный код — файл с расширением .c
  • заголовочный файл, имеющий расширение .h

В нашем случае это: string.c и string.h. Также подготовим файл constants.h, в котором поместим требуемые нам глобальные константы, наподобие адреса начала видеопамяти.

Заголовочные файлы

Цель заголовочного файла удобно сгруппировать функций в одном файле. Точнее сказать, интерфейсы функций, которые реализуются в исходниках. С помощью директивы препроцессора #include «filename.h» реализация разворачивается в месте использования директивы.

Еще нужно добавить директивную связку #ifndef MODULE_NAME, #define MODULE_NAME и #endif MODULE_NAME. Комплекс поможет при компоновке не дублировать функции в разных исходниках.

Наш заголовочный файл будет выглядеть следующим образом:

#ifndef STRING_H
#define STRING_H

/*
*   Печать строки с началом в определенной строке и колонке
*/
void print_string(char col, char row, char *str);

/*
*   Очистка экрана
*/
void clear_screen();

#endif // STRING_H

Как можно увидеть в файле, код реализации в нем отсутствует. Однако есть все требуемые для взаимодействия с интерфейсом параметры. Сигнатура интерфейса состоит из трех частей:

  • Имя функции
  • Перечень формальных параметров с их типами
  • Тип возвращаемого значения
работа над ошибками - сигнатура функции

Теперь нужно реализовать объявленные интерфейсы. В исходнике подключаем заголовки директивой: #include «string.h». Код реализации не сильно трудный.

/*
*  string.c
*/
#include "string.h"
#include "constants.h"

char* video_memory = (char*)VIDEO_MEMORY_ADDRESS;

void print_string(char row, char col, char* str) {

    unsigned int i = (row * 80 + col) * 2;
    unsigned int j = 0;
    while(str[j] != '\0') {
		video_memory[i] = str[j];
		video_memory[i+1] = BACKGROUND_COLOR_DEFALT;
		++j;
		i = i + 2;
	}
}

void clear_screen() {

	unsigned int i = 0;
	while(i < 80 * 25 * 2) {
		video_memory[i] = SPACE_CHAR;
		video_memory[i+1] = BACKGROUND_COLOR_DEFALT; 		
		i = i + 2;
	}
}

Константы

Как уже было сказано выше, я выделю константы в отдельный файл. Сейчас их очень мало.

#ifndef CONSTANTS_H
#define CONSTANTS_H

/*
*   Константы для библиотечного модуля string
*/
#define VIDEO_MEMORY_ADDRESS 0xB8000 
#define SPACE_CHAR ' '
#define BACKGROUND_COLOR_DEFALT 0x07  

#endif

Предпоследнее что нужно сделать, исправить файл kernel.c убрав оттуда реализации и сделать вызов сначала функции очистки экрана, а потом вывода текста. Естественно не стоит забывать подключить нужный заголовочный файл новоявленной библиотеки.

/*
*  kernel.c
*/
#include "library/string.h"

void kmain(void)
{
	const char *str = "Simple OS, version 0.0.1";

	/* Очистка экрана */
	clear_screen();

	/* Вывод наименования ОС и версии ядра */
	print_string(0, 0, str);
	
	return;
}

Сборка библиотеки

Выходим на финишную прямую. Осталось всего-ничего пересечь финишную черту и порвать ленточку. А это мы сможем сделать в два счета.

Подготавливаем новый Makefile в папке с библиотекой. Эта конфигурация сейчас будет содержать всего две цели. Нужно скомпилировать в объектный файл наш исходник - string.c. Вторая цель, так сказать задел на будущее. Цель all, к тому же, позволяет использовать утилиту make без указания цели. Позже, когда исходников стандартной библиотеки станет больше одного, я задействую эту цель в полном объеме.

BUILD_DIR=../build
CC=/usr/bin/gcc
CC_FLAGS=-m32 -c

string: string.c
	${CC} ${CC_FLAGS} string.c -o ${BUILD_DIR}/library.o
all: string

Основной Makefile теперь изменим так. Внесем дополнительную цель для сборки библиотеки. И поправим компоновку с учетом нового объектного файла - library.o.

BUILD_DIR=./build
CC=/usr/bin/gcc
CC_FLAGS=-m32 -c
ASM=/usr/bin/nasm
ASM_FLAGS=-f elf32
LD=/usr/bin/ld
MKDIR=mkdir -p
RM=rm -f -r

project_structure:
	${MKDIR} ${BUILD_DIR}
library: project_structure
	make -C ./library
boot: project_structure boot.asm
	${ASM} ${ASM_FLAGS} boot.asm -o ${BUILD_DIR}/boot.o
kernel: project_structure kernel.c
	${CC} ${CC_FLAGS} kernel.c -o ${BUILD_DIR}/kernel.o
link: boot kernel library link.ld
	${LD} -m elf_i386 -T link.ld -o ${BUILD_DIR}/kernel.bin ${BUILD_DIR}/boot.o ${BUILD_DIR}/library.o ${BUILD_DIR}/kernel.o
iso: link
	${MKDIR} ${BUILD_DIR}/isofiles/boot/grub
	cp grub.cfg ${BUILD_DIR}/isofiles/boot/grub
	cp ${BUILD_DIR}/kernel.bin ${BUILD_DIR}/isofiles/boot
	grub-mkrescue -o ${BUILD_DIR}/simpleos.iso ${BUILD_DIR}/isofiles
qemu: iso
	qemu-system-i386 build/simpleos.iso
qemu_cd: iso
	qemu-system-i386 -cdrom build/simpleos.iso
clean:
	${RM} ${BUILD_DIR}/*

Запускаем сборку и....

Ничего не изменилось. По прежнему на экран выводится наименование ОС и версия. Но, ведь, так и должно было быть. Это и есть наша работа над ошибками, которая, кстати, называется модным словом - рефакторинг. Оно означает равносильное преобразование алгоритмов — процесс изменения внутренней структуры программы, не затрагивающий её внешнего поведения и имеющий целью облегчить понимание её работы.

Весь код, представленный в статье, вы сможете найти в репозитории Simple OS на github.com. А сейчас пора прощаться, надеюсь вам было интересно. До свидания и до новых статей.

Канал Яндекс.Дзен

Картинки найдены на просторах интернета.