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

Пишем свою операционную систему. Работа с клавиатурой


Доброго времени суток!

До предыдущего выпуска пользователь не как не мог взаимодействовать с нашей операционной системой, лишь наблюдать её работу. Теперь она уже умеет как-то реагировать на нажатия клавиш, отображая текстовые сообщения, но этого мало для нормального ввода данных. Однако сначала приведу Makefile для предыдущего выпуска:

ifdef OS
	LDFLAGS = -mi386pe
else
	LDFLAGS = -melf_i386
endif

CFLAGS = -m32 -ffreestanding

all: script.ld startup.o stdlib.o main.o interrupts.o tty.o
	ld $(LDFLAGS) -T script.ld -o kernel.bin startup.o stdlib.o main.o interrupts.o tty.o
	objcopy kernel.bin -O binary
startup.o: startup.i386.asm
	fasm startup.i386.asm startup.o
stdlib.o: stdlib.c stdlib.h
	gcc -c $(CFLAGS) -o stdlib.o stdlib.c
main.o: main.c stdlib.h interrupts.h tty.h
	gcc -c $(CFLAGS) -o main.o main.c
interrupts.o: interrupts.c interrupts.h stdlib.h
	gcc -c $(CFLAGS) -o interrupts.o interrupts.c
tty.o: tty.c tty.h stdlib.h
	gcc -c $(CFLAGS) -o tty.o tty.c
clean:
	rm -v *.o kernel.bin

Буфер клавиатуры

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

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

 ...

#define KEY_BUFFER_SIZE 16
char key_buffer[KEY_BUFFER_SIZE];
unsigned int key_buffer_head = 0;
unsigned int key_buffer_tail = 0; 

В буфер помещаются 16 последних нажатых клавиш, если их не считывать самые старые будут теряться, всё так и должно быть. Для организации списка используется указатель на "голову" и на "хвост" списка кодов клавиш. При помещении очередного символа в буфер "хвост" увеличивается на 1. При чтении символа из буфера "голова" тоже сдвигается на 1. Если голова или хвост достигают конца массива, они становятся равными нулю. Таким образом получается так называемый кольцевой буфер. BIOS и DOS используют такой же, и в нашем случае нет смысла придумывать что-то сложнее. Помимо прочего такая структура устойчива к той ситуации, когда запись в буфер по прерыванию происходит в середине функции чтения.

Изменим обработчик прерывания IRQ1, убрав вывод сообщений и добавив запись символа в буфер:

IRQ_HANDLER(keyboard_int_handler) {
	uint8 key_code;
	if (key_buffer_tail >= KEY_BUFFER_SIZE) {
		key_buffer_tail = 0;
	}
	key_buffer_tail++;
	key_buffer[key_buffer_tail - 1] = key_code;
	uint8 status;
	inportb(0x61, status);
	status |= 1;
	outportb(0x61, status);
}

Чтение символов и строк 

Добавим в заголовочный файл tty.h пару новых функций:

#ifndef TTY_H
#define TTY_H

#include "stdlib.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, ...);
char in_char(bool wait);
void in_string(char *buffer, size_t buffer_size);

#endif

Первая функция позволяет читать одиночный символ с клавиатуры, вторая - считать целую строку символов. Замечу, что вторая функция имеет защиту от переполнение буфера с помощью ограничения длины вводимой строки.

Чтобы отделить преобразование символов от работы с буфером вынесем чтение скан-кода в отдельную функцию, которую добавим в конце tty.c:

uint8 in_scancode() {
	uint8 result;
	if (key_buffer_head != key_buffer_tail) {
		if (key_buffer_head >= KEY_BUFFER_SIZE) {
			key_buffer_head = 0;
		}
		result = key_buffer[key_buffer_head];
		key_buffer_head++;
	} else {
		result = 0;
	}
	return result;
} 

Эта функция возвращает скан-код нажатой клавиши или 0, если буфер пуст. 

Преобразовывать скан-код в символ придётся по специальной таблице, которую опишем в дополнительном файле scancodes.h:

#ifndef SCANCODES_H
#define SCANCODES_H

char scancodes[] = {
		0,
		0, // ESC
		'1','2','3','4','5','6','7','8','9','0', '-', '=',
		8, // BACKSPACE
		'\t', // TAB
		'q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '[', ']',
		'\n', // ENTER
		0, // CTRL
		'a', 's', 'f', 'g', 'h', 'j', 'k', 'l', ';', '\'', '`',
		0, // LEFT SHIFT
		'\\', 'z', 'x', 'c', 'v', 'b', 'n', 'm', ',', '.', '/',
		0, // RIGHT SHIFT
		'*', // NUMPAD
		0, // ALT
		' ', // SPACE
		0, // CAPSLOCK
		0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // F1 - F10
		0, // NUMLOCK
		0, // SCROLLLOCK
		0, // HOME
		0,
		0, // PAGE UP
		'-', // NUMPAD
		0, 0,
		0,
		'+', // NUMPAD
		0, // END
		0,
		0, // PAGE DOWN
		0, // INS
		0, // DEL
		0, // SYS RQ
		0,
		0, 0, // F11 - F12
		0,
		0, 0, 0, // F13 - F15
		0, 0, 0, 0, 0, 0, 0, 0, 0, // F16 - F24
		0, 0, 0, 0, 0, 0, 0, 0
	};
	
char scancodes_shifted[] = {
		0,
		0, // ESC
		'!','@','#','$','%','^','&','*','(',')', '_', '+',
		8, // BACKSPACE
		'\t', // TAB
		'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P', '{', '}',
		'\n', // ENTER
		0, // CTRL
		'A', 'S', 'F', 'G', 'H', 'J', 'K', 'L', ':', '"', '~',
		0, // LEFT SHIFT
		'|', 'Z', 'X', 'C', 'V', 'B', 'N', 'M', '<', '>', '?',
		0, // RIGHT SHIFT
		'*', // NUMPAD
		0, // ALT
		' ', // SPACE
		0, // CAPSLOCK
		0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // F1 - F10
		0, // NUMLOCK
		0, // SCROLLLOCK
		0, // HOME
		0,
		0, // PAGE UP
		'-', // NUMPAD
		0, 0,
		0,
		'+', // NUMPAD
		0, // END
		0,
		0, // PAGE DOWN
		0, // INS
		0, // DEL
		0, // SYS RQ
		0,
		0, 0, // F11 - F12
		0,
		0, 0, 0, // F13 - F15
		0, 0, 0, 0, 0, 0, 0, 0, 0, // F16 - F24
		0, 0, 0, 0, 0, 0, 0, 0
	};

#endif 

Эту таблицу с небольшими изменениями я позаимствовал отсюда: http://sysbin.com/files/lowlevel/osdev11.htm#keyboard. По сути у нас две таблицы - одна обычная, другая для символов, при вводе которых был зажат Shift. Теперь мы можем написать и in_char:

char in_char(bool wait) {
	static bool shift = false;
	uint8 chr;
	do {
		chr = in_scancode();
		switch (chr) {
			case 0x2A:
			case 0x36:
				shift = true;
				break;
			case 0x2A + 0x80:
			case 0x36 + 0x80:
				shift = false;
				break;
		}
		if (chr & 0x80) {
			chr = 0;
		}
		if (shift) {
			chr = scancodes_shifted[chr];
		} else {
			chr = scancodes[chr];
		}
	} while (wait && (!chr));
	return chr;
} 

Эта функция уже умеет ждать нажатий при необходимости, а также преобразовывать скан-коды в символы с учётом нажатости клавиши Shift.

Сделать функцию in_string не составит особого труда:

void in_string(char *buffer, size_t buffer_size) {
	char chr;
	size_t position = 0;
	do {
		chr = in_char(true);
		switch (chr) {
			case 0:
				break;
			case 8:
				if (position > 0) {
					position--;
					out_char(8);
				}
				break;
			case '\n':
				out_char('\n');
				break;
			default:
				if (position < buffer_size - 1) {
					buffer[position] = chr;
					position++;
					out_char(chr);
				}
		}
	} while (chr != '\n');
	buffer[position] = 0;
} 

Ну вот и готова поддержка ввода текста, теперь можно написать в kernel_main что-нибудь вроде этого:

void kernel_main(uint8 boot_disk_id, void *memory_map, BootModuleInfo *boot_module_list) {
	init_interrupts();
	init_tty();
	set_text_attr(15);
	printf("Welcome to MyOS!\n");
	while (true) {
		char buffer[256];
		out_string("Enter string: ");
		in_string(buffer, sizeof(buffer));
		printf("You typed: %s\n", buffer);
	}

При условии, что в stdlib.c у нас есть strcmp, ничто не мешает реализовать что-нибудь вроде командного интерпретатора и сделать ОС немного интерактивной и полезной.

Заключение

Теперь у нас есть достаточные возможности для поддержания диалога с пользователем, во всяком случае вы наглядно видите результат своей работы. Далее пойдут более теоретические части. В первую очередь - менеджер памяти. Кстати, дальше того, что мы сделали сейчас, ни одна рассылка не продвинулась (разве что в одной была сделана на скорую руку многозадачность), так что теперь мы не будем никого повторять и идти по своему собственному уникальному пути. До встречи!

P.S.: Пожалуйста, пишите свои замечания и предложения по поводу постройки архитектуры системы. Также было бы не плохо выбрать более красивое название. Мой адрес, как обычно, kiv.apple@gmail.com.


В избранное