Добрый день, уважаемые читатели. Некоторое время я был занят, но теперь снова возвращаюсь к продолжению хождений по мукам разработки операционной системы. Эта статья посвящена работе с курсором. Курсор в текстовом режиме, как показала практика, обладает аппаратной поддержкой.

Порты ввода/вывода

Порты ввода/вывода — это решение для взаимодействия процессора и внешних устройств. В процессорах x86 порты не связаны с основной оперативной памятью. Для взаимодействия с портами в ассемблере используются команды in — для чтения из порта и out — для записи в порт. Увы, прямой возможности работать на языке Си с портами не получится.

От ассемблера не уйти

Я старался! Правда! Но действительно не получится. Однако есть небольшое удобство. Команды ассемблера можно писать прямо в исходном коде на Си. Для этого предназначена команда:

__asm__

Встроенный ассемблер в GCC использует синтаксис AT&T. Для меня это открытие было очень болезненным. Конечно, можно перевести GCC в режим использования формат Intel, но такой синтаксис, хоть и похож на синтаксис nasm, но имеет отличия. Поэтому я решил писать на ассемблере AT&T. Ниже структура инлайновой ассемблерной строки:

__asm__( Инструкции ассемблера

: выходные операнды

: входные операнды

: список изменяемых регистров

Я не планирую развивать тему далее, так как она требует минимум отдельной статьи. Больше можно прочитать на HOWTO-странице по ассемблеру GCC или в переводе. Вернемся к нашим задачам.

Рефакторинг библиотеки

Курсор в текстовом режиме - рефакторинг библиотеки

В прошлой статье мы начали создавать библиотеку. Теперь продолжаем улучшать. Для начала возьмусь за типизацию. Точнее сделаю ее понятнее. Для этого создадим файл с наименованием types.h, в котором определим несколько новых типов:

#ifndef TYPES_H
#define TYPES_H

typedef unsigned char uint8;
typedef unsigned short uint16;
typedef char int8;
typedef short int16;
typedef unsigned int uint32;

#endif

Также я вынес глобальные константы в единый файл — constants.h

#ifndef CONSTANTS_H
#define CONSTANTS_H
 
#define VIDEO_MEMORY_ADDRESS 0xB8000 
 
#define SPACE_CHAR ' '
 
#define BACKGROUND_COLOR_DEFALT 0x07 
 
#define MAX_ROWS 25
 
#define MAX_COLS 80
 
#define REG_SCREEN_CTRL 0x3d4
#define REG_SCREEN_DATA 0x3d5
 
#endif

Взаимодействие с портами

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

#include "ports.h"
#include "types.h"

uint8 inb (uint16 port) {

    uint8 result;

    __asm__("in %%dx, %%al" : "=a" (result) : "d" (port));
    return result;
}
void outb (uint16 port, uint8 data) {
    __asm__("out %%al, %%dx" : : "a" (data), "d" (port));
}

uint16 inw (uint16 port) {
    uint16 result;
    __asm__("in %%dx, %%ax" : "=a" (result) : "d" (port));
    return result;
}

void outw (uint16 port, uint16 data) {
    __asm__("out %%ax, %%dx" : : "a" (data), "d" (port));
}

Эта часть кода чрезвычайно проста. Здесь содержится четыре функции 2 из которых работают с одним байтом:

  • outb — выводит байт в указанный порт
  • inb — получает байт из порта

Две другие — со словом (2 байта):

  • outw — выводит слово
  • inw — получает слово

Курсор

Курсор в текстовом режиме - курсор

Теперь можно начинать работу с курсором. Условно всю работу с курсором можно разделить на три части:

  • Включение курсора
  • Изменение местоположения
  • Отключение курсора

Пока мы реализуем только две части. В исходном коде ниже представлены функции для работы с экраном в текстовом режиме.

#include "screen.h"
#include "ports.h"
#include "constants.h"
#include "types.h"

void screen_init() {

    clear_screen();
	init_cursor(14, 15);
    set_cursor(0, 0);
}

void clear_screen() {

    uint8* video_memory = (uint8*)VIDEO_MEMORY_ADDRESS;
	uint16 i = 0;
	while(i < MAX_COLS * MAX_ROWS * 2) {
		video_memory[i] = SPACE_CHAR;
		video_memory[i+1] = BACKGROUND_COLOR_DEFALT; 		
		i = i + 2;
	}
}

void init_cursor(uint8 cursor_start, uint8 cursor_end) {

	outb(0x3D4, 0x0A);
	outb(0x3D5, (inb(0x3D5) & 0xC0) | cursor_start);
	outb(0x3D4, 0x0B);
	outb(0x3D5, (inb(0x3D5) & 0xE0) | cursor_end);
}

void set_cursor(uint8 row, uint8 col) {

	uint16 pos = row * MAX_COLS + col;

	outb(0x3D4, 0x0F);
	outb(0x3D5, (uint8) (pos & 0xFF));
	outb(0x3D4, 0x0E);
	outb(0x3D5, (uint8) ((pos >> 8) & 0xFF));
}

Функция init_cursor занимается настройкой внешнего вида курсора и, самое главное, его включением. Другая функция — set_cursor — занимается установкой курсора в нужную позицию на экране. Также я перенес сюда функцию очистки экрана, что на мой взгляд естественно. Вызов инициализации экрана осуществляется в основном файле ядра — kernel.c. Сейчас он немного изменился:

#include "library/string.h"
#include "library/screen.h"

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

	screen_init();

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

Все изменения внесены в репозиторий: Simple OS

В результате получился вот такой курсор.

Курсор в текстовом режиме

P.S. Запущен сайт с документацией, формируемой с помощью утилиты doxygen. Документация доступна по двум адресам: github и здесь, на моем сайте.

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

Статья в Яндекс.Дзен