Search
Write a publication
Pull to refresh

Низкоуровневый АД: пишем ОС. Часть 2 — модули и ввод

Level of difficultyEasy
Reading time5 min
Views1.1K

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

Шаг 1. Модули

Для более читабельного кода я решил сделать папочку kernel/lib, в которой у нас будет три файла: stdio.h, types.h, string.h. В первом уже понятно — ввод-вывод, во втором типы, в третьем работа со строками.

В stdio.h мы пишем сначала 2 функции - printc() и printl(), которые выводят символ и строку соответственно

void printc(char* video_memory, char symb, char* color, int charpos) {
    video_memory[charpos * 2] = symb;
    video_memory[charpos * 2 + 1] = color;
}
int printl(char* video_memory, const char* text, int startpos) {
    int nextpos;
    for (int i = 0;; i++) {
        if (text[i] == '\0') {break;}
        printc(video_memory, text[i], (char *)0x07, startpos);
        startpos++;
        nextpos = startpos;
    }
    return nextpos;
}

Первая функция — это как строки с выводом с ядре — в видеопамять (которая как аргумент ведь адрес может отличаться) мы записываем по индексу номер символа (порядковый) 2 сам символ, а в номер2+1 - цвет. В printl() же всего три аргумента — адрес видеопамяти, текст и... начальный символ. Ведь текст может быть после предыдущего вывода. Кстати указатель на следующий после текста символ возвращается. Тут все просто - просто в цикле вызываем printc().

Дальше - интересней. Пишем ввод

static inline unsigned char inb(unsigned short port) {
    unsigned char data;
    __asm__ volatile ("inb %1, %0" : "=a" (data) : "Nd" (port));
    return data;
}

unsigned char getchar(void) {
    unsigned char scancode;
    do {
        scancode = inb(0x64);
        scancode &= 1;
    } while (!scancode);

    return inb(0x60);
}

В первой функции мы получаем данные с порта через ASM и возвращаем их. Во второй - получаем данные с порта состояния PS/2 (да-да, старый добрый разъем, не USB, там надо писать ого-го!), смотрит на бит готовности, и так по циклу пока не будет бит равен 1; дальше возвращается сама клавиша.

На заметку: код нажатой клавишы (и соответственно символ) отличается от того, что нарисован на самой клавише

В types.h почти ничего нет — просто пишем один typedef

typedef unsinged int u32

В string.h тож ничего особенного — просто функция для подсчета символов в строке

int strlen(constchar* text) {
    return sizeof(text) / sizeof(char);
}

Шаг 2. Пишем ввод в ядре

А сейчас, реализовав вывод и ввод, мы можем включить это в наше ядро. Но для этого нам потребуется проанализировать, при нажатии какой клавиша какой символ будет. Поэтому:

  • Меняем код вывода на этот (тут вывод и ввод смивола) (а также не забудьте подключить нашу библиотеку (#include "stdio.h")

const char* message = "HabrOS 0.0.2                                                                    >>> ";
int nextpos = printl(video_memory, message, 0);
unsigned char ch = getchar();
printc(video_memory, ch, (char *)0x07, nextpos+1);

Сообщение можете взять мое, а можете свое

  • Вставляем ниже код для отображения сканкода:

char hex_chars[] = "0123456789ABCDEF";
printc(video_memory, hex_chars[(ch >> 4) & 0xF], (char*)0x07, nextpos++);
printc(video_memory, hex_chars[ch & 0xF], (char*)0x07, nextpos++);
  • Компилируем (скрипт есть, но не забудьте к i686-elf-gcc -o ... прибавить -I kernel/lib)

  • Запускаем и пробуем нажать клавишу

  • Идем на OSDev Wiki и ищем там наш код

  • Если клавиша которую вы нажали сходится с клавишей которая указана в таблице - значит у вас этот code scan set (у меня первый набор)

После этого можно идти и писать обработчик.

Мы создадим специальную функцию (в stdio.h) которая будет превращать символы из сканкодов в символы которые написаны на клавиатуре

unsigned char transform(unsigned char ch) {
    unsigned char codes[] = {
    (unsigned char)0x01, (unsigned char)0x02, (unsigned char)0x03,
    (unsigned char)0x04, (unsigned char)0x05, (unsigned char)0x06, 
    (unsigned char)0x07, (unsigned char)0x08, (unsigned char)0x09,
    (unsigned char)0x0A, (unsigned char)0x0B, (unsigned char)0x0C,
    (unsigned char)0x0D, (unsigned char)0x0E, (unsigned char)0x0F,
    (unsigned char)0x10, (unsigned char)0x11, (unsigned char)0x12,
    (unsigned char)0x13, (unsigned char)0x14, (unsigned char)0x15,
    (unsigned char)0x16, (unsigned char)0x17, (unsigned char)0x18,
    (unsigned char)0x19, (unsigned char)0x1A, (unsigned char)0x1B,
    (unsigned char)0x1C, (unsigned char)0x1D, (unsigned char)0x1E,
    (unsigned char)0x1F, (unsigned char)0x20, (unsigned char)0x21,
    (unsigned char)0x22, (unsigned char)0x23, (unsigned char)0x24,
    (unsigned char)0x25, (unsigned char)0x26, (unsigned char)0x27,
    (unsigned char)0x28, (unsigned char)0x29, (unsigned char)0x2A,
    (unsigned char)0x2B, (unsigned char)0x2C, (unsigned char)0x2D,
    (unsigned char)0x2E, (unsigned char)0x2F, (unsigned char)0x30,
    (unsigned char)0x31, (unsigned char)0x32, (unsigned char)0x33,
    (unsigned char)0x34, (unsigned char)0x35, (unsigned char)0x36,
    (unsigned char)0x37, (unsigned char)0x38, (unsigned char)0x39,
    (unsigned char)0x3A, (unsigned char)0x3B, (unsigned char)0x3C,
    (unsigned char)0x3D, (unsigned char)0x3E, (unsigned char)0x3F,
    (unsigned char)0x40, (unsigned char)0x41, (unsigned char)0x42,
    (unsigned char)0x43, (unsigned char)0x44, (unsigned char)0x45,
    (unsigned char)0x46, (unsigned char)0x47, (unsigned char)0x48,
    (unsigned char)0x49, (unsigned char)0x4A, (unsigned char)0x4B,
    (unsigned char)0x4C, (unsigned char)0x4D, (unsigned char)0x4E,
    (unsigned char)0x4F, (unsigned char)0x50, (unsigned char)0x51,
    (unsigned char)0x52, (unsigned char)0x53, (unsigned char)0x57,
    (unsigned char)0x58
    };
    
    unsigned char symbols_standart[] = {
    (unsigned char)0x00, '1', '2', // 0x00 - null (not bind)
    '3', '4', '5',
    '6', '7', '8',
    '9', '0', '-',
    '=', (unsigned char)0x08, (unsigned char)0x09,
    'q', 'w', 'e',
    'r', 't', 'y',
    'u', 'i', 'o',
    'p', '[', ']',
    (unsigned char)0x0A, (unsigned char)0x01, 'a', //0x01 - l ctrl
    's', 'd', 'f',
    'g', 'h', 'j',
    'k', 'l', ';',
    '\'', '`', (unsigned char)0x02, //0x02 - l shift
    '\\', 'z', 'x',
    'c', 'v', 'b',
    'n', 'm', ',',
    '.', '/', (unsigned char)0x00,
    '*', (unsigned char)0x00, ' ',
    (unsigned char)0x03, (unsigned char)0x00, (unsigned char)0x00, //0x03 - caps
    (unsigned char)0x00, (unsigned char)0x00, (unsigned char)0x00,
    (unsigned char)0x00, (unsigned char)0x00, (unsigned char)0x00,
    (unsigned char)0x00, (unsigned char)0x00, (unsigned char)0x00,
    (unsigned char)0x00, '7', '8',
    '9', '-', '4',
    '5', '6', '+',
    '1', '2', '3',
    '0', '.', (unsigned char)0x00,
    (unsigned char)0x00
    };
    int status = 0;
    for (int i = 0; i != (sizeof(codes) / sizeof(char)); i++) {
        if (ch == codes[i]) {
            status = 1;
            return symbols_standart[i];
        }
    }
    if (status == 0) {
        return ch;
    }
}

Функция как возвращает, так и принимает символ (который из сканкода получается), дальше по кругу проверяет со символами из 1-го массива, если равен, возвращает соответственный по позиции символ из 2-го массива (нажатая клавиша). А если не нашло в 1-м массиве наш символ, то мы его же и возвращаем. Обработчик сам-то несложный, но массивы получаются достаточно большими, а это еще мы не обрабатывали нажатие с CapsLock и Shift, поэтому я привел более упрощенный вариант.

Меняем в ядре строчки вывода и пустой цикл на

while(1) {
    unsigned char ch = getchar();
    unsigned char ch_descaned = transform(ch);
    printc(video_memory, ch_descaned, (char *)0x07, nextpos+1); 
    nextpos++;
}

А теперь компилируем, и смотрим что получается

Эм...... Мы же не этого хотели?
Эм...... Мы же не этого хотели?

Странно как-то. Вроде клавишы обрабатываются, но за ними потом идет еще символ. Странно, да? А теперь попробуйте зажать. Нету того символа, да? Так вот в чем проблема: мы обрабатывали прерывание с отпусканием клавишы, и оно посылало сканкод 0x80 + сканкод клавишы. Давайте вернемся в stdio.h и вместо return inb(0x60); напишем данный код:

unsigned char scan = inb(0x60);
if (scan & 0x80) {
    return 0;
}
return scan;

Он игнорирует отпускание клавишы. А теперь в kernel.c после получение символа с клавиатуры:

if (ch == 0) continue;

Чтобы пробел не выводился после каждого символа мы добавляем этот код (все равно сканкода 0x00 нету)

Запускаем откомпилированный и собранный в ISO файл — и вуаля

Ура, мы сделали это
Ура, мы сделали это

Ну надеюсь вы сможете дописать обработку всех клавиш. А давайте пока составим план на следующие части:

  1. Сделать командную оболочку

  2. Реализовать там функции echo, shutdown и version

Ссылка на Github


Спасибо что прочитали! Буду дальше писать ОС.

Tags:
Hubs:
+5
Comments23

Articles