| RSS



Меню

Bookmark and Share


Статистика
Ваш IP: 3.14.130.241
Вы используете: v





Сейчас на сайте:

Тех поддержка ->

Облако тэгов
ОС видио Tor Обратная сторона антенна 4.6 PHP Эксплоит Windows Server 2008 qip Virtual chroot kernel proc sysctl tune FreeBSD bridge Boot Disk Bluetooth GEO game directx Emulator Python Shell DDoS червь Conficker вирус троян Лаборатория Касперского пиратство apple iPhone ИТ-отрасль Щеголев Microsoft экономический кризис Twitter социальная сеть анонимность Лицензия Open Source ASP.NET MVC уязвимость MySQL база данных файлообмен закон франция пират Skype мобильный Deutsche Telekom Хакер киберпреступник Trend Micro кибератака Германия робот утечка данных персональные данные ноутбук интернет Китай цензура ядро Linux Торвальдс Windows Vista Acer Linux патент браузер Firefox Internet Explorer Opera Net Applications Safari Intel Linux Foundation Moblin Oracle патч банкомат кардер HSM IBM X-Force Cofee сша кибервойна Эстония Dell ИТ-специалист хакерские атаки Pirate Bay контроль кибербезопасность язык программирования The Pirate Bay Пиратская партия утечка информации приговор Mozilla Chrome безопасность Госдума СМИ Windows 8 Баллмер взлом Пентагон ботнет Украина Facebook Cisco cloud Windows XP нетбук торрент музыка биометрический nokia ФБР IP-адрес CIPAV Comcast sms RSA java Google CAPTCHA Symantec спам конфиденциальная информация инсайдер Perimetrix антивирус тест Anti-Malware Windows 7 операционная система Windows провайдер авторское право RapidShare UNIX свиной грипп шантаж дети EFF BluWiki копирайт экстремизм Panda Security cloud computing McAfee Cybercrime Response Unit Bottle Domains HTTPS ICANN студент шпионское ПО Норвегия школьник New York Times XSS YouTube Warner Music кибершпионаж КНДР Ubuntu свободное ПО AMD ATI касперский Россия РФ сервер хостинг фальшивый антивирус Comodo CA Wi-Fi D-Link суд пароль блог фишинг Одноклассники медведев контрафакт мошенник штраф Sony GPS по Gumblar JAVASCRIPT хакеры вредоносное ПО Yahoo ФАС компьютер Софт MPAA кибероружие PandaLabs Red Hat Минкомсвязи сбой ASUSTeK Computer мошенничество Доктор Веб ВКонтакте Cyber-Arc исходный код PCI DSS МВД фильтр порнография BREIN свобода слова Казахстан GEMA Autodesk сисадмин Gmail кредитная карта кибермошенник LiveJournal шифрование криптография Deep Purple банк нанотехнологии Wikipedia zero-day ColdFusion выборы кража данных DNS BIND Android BASIC атака Black Hat Mac OS X Click Forensics Clampi домен фсб Прокуратура Уголовное дело icq Barrelfish киберпреступность Sophos AT&T ошибка Electa Gamma Knife OpenBSD DARPA военные Сайт Visual Studio 2010 .NET Framework 4 Chrome OS электронная почта турция конференция спамер FTC полиция российская ОС Koobface Великобритания БЕЛОРУССИЯ грузия BSA Bittorrent облачные вычисления Azure Европа Dr.Web Билл Гейтс спецслужбы Cryzip Живой Журнал Royal Bank of Scotland смартфон Canonical Pwn2Own F-Secure Symbian Hotmail фильм

Главная » Статьи » Общие Статьи

Написание собственной Операционной Системы №2

Пояснения читателю

Для начала я бы хотел устроить небольшой разбор полетов. В прошлой части я забыл написать, что исходные коды программ на ассемблере я предполагаю компилировать в Nasm в виде бинарных файлов, а запись полученного результата отправлять на отформатированную дискету с помощью, скажем, программы RawWrite. При том записаны они должны быть сразу друг за другом, поэтому в первый загрузчик можно включить в самом конце incbin 'name2.bin' (name2 - название второго загрузчика), соответственно тогда он включит в себя текст второго. И сразу отвечу на вопрос читателей, который мне после этой статьи задавали. Я понимаю, что описать написание ОС в нескольких статьях невозможно, потому я бы предложил читателю для начала прочитать следующие книги(помимо Зубкова, указанного ранее):

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

Тем не менее сейчас я дополнительно опишу минимум теории, необходимый для прошлой статьи, который я из нее убрал. Процессор может работать в двух режимах - реальный и защищенный (их гибрид я не учитываю). Защищенный режим предназначен для того, чтобы можно было использовать такие возможности как многозадачность, виртуальная память и пр. Основная структура данных PM (protected mode) - дескриптор (8 байт), необходимый для контроля сегментации, аппаратной многозадачности, обработки прерываний. В этом режиме память делится на защищенные части - сегменты. В дескрипторе сегмента содержатся данные о его типе, базе (адресе), размере, уровне привилегий и пр. Сегментная память используется для того, чтобы защитить программы при их исполнении от других (благодаря привилегиям).
Для перевода процессора в PM используется глобальная таблица дескрипторов, в которой хранится для каждого сегмента отдельный дескриптор. Его вид:

0,1 байты - размер
2-4 байты - база
5 байт - бит наличия сегмента, уровень привилегий, тип сегмента (0010 - данные, 1010 - код)
6 байт - 1, разрядность (0 - 16, 1 - 32), 0, 0, размер
7 байт - база

Итак, задача для перехода в PM такова: построить GDT с дескрипторами для кода и данных (база - 0, размер - 4 Гб), включить адресную линию A20 (она отключена для совместимости со старыми процессорами), изменить в регистре CR0 один бит в 1 (флаг того, что мы в PM), после чего прыгнуть в 32-битный сегмент.

Введение

Итак, в прошлой части мы подготовили плацдарм для наших действий. Теперь перед нами обширные горизонты, от которых надо постараться не потерять голову. Давайте немного подумаем, какие первоочередные задачи сейчас перед нами стоят? Во-первых, мы умеем только загружать нашу ОС, у нас нету так ядра, которому бы передавалось управление. Далее наша ОС должна иметь минимальный набор функция для контакта с пользователем, а потому перед нами встает еще задача обеспечения функций работы с железом. Для этого нам будет необходимо написать таблицу прерываний, а так же научить наше детище работать, скажем, для начала с клавиатурой и жестким диском. Начнем с ядра. Кстати, немного о его концепции. Оно бывает нескольких видов: микроядро, монолитное, экзоядро. Различаются они по делению исполняемых задач по привилегиям. Так в микроядре на правах супервизора работает крайне малая часть функций, остальное - подключаемые библиотеки. Монолитное же ядро очень велико, все основные функции ОС сосредоточены именно в нем. Микроядерная архитектура имеет огромные преимущества над традиционной монолитной и только один серьезный недостаток - пониженное быстродействие. Экзоядерная архитектура превосходит все другие архитектуры по производительности в несколько раз, однако она слишком сложна. Она отличается тем, что если микро и монолитные ядра обеспечивают задачи распределения ресурсов и создания виртуальной машины, то экзоядра эти задачи разделают. В каком смысле? Например, дать возможность пользовательским программам обращаться к конкретной ячейке памяти, конкретному сектору диска, конкретным портам других внешних устройств. Мы будем рассматривать микроядро, т.е. в ядре будет сосредоточен базовый минимум функций, всю остальную работу будет обеспечивать набор драйверов. Хотя надо заметить, что в ближайших двух статьях ссылки на тип ядра у нас не будет.

Ближе к делу. Как вы помните, написанный в предыдущем выпуске загрузчик обладает возможностью загружать и выполнять код, находящийся в файле kernel.bin и скомпонованный по адресу 0x200000. Теперь мы уже можем работать на С. Первой же проблемой, с которой мы столкнемся при создании kernal.bin, будет то, что содержимое функции main() вынесется компилятором на самый верх программы, т.е. мы будем из загрузчика передавать управление в никуда. Для того, чтобы от этого избавиться, надо передавать управление ядру через переходник (c.asm):

[BITS 32]
[EXTERN k_main]
[GLOBAL _go]
_go:
mov esp, 0x200000-4
call k_main

Само ядро у нас будет выглядеть вот так (kernel.c):

void k_main()
{
for(;;);
}


Большое, не правда ли? Перейдем к работе с железом. Для начала нам потребуется написать несколько функций, позволяющих работать с портами и прерываниями, после чего можно будет уже самостоятельно описывать прерывания (что мы рассмотрим на примере клавиатуры).

При возникновении прерывания, процессору известен только номер прерывания. Поскольку сам по себе этот номер ничего не говорит о том, где именно находится процедура-обработчик, эту информацию процессор должен почерпнуть из специальной таблицы (таблица прерываний). В режиме реальных адресов таблица прерываний находится по абсолютному адресу 0:0 - 0:0x400 и представляет из себя 256 абсолютных четырехбайтных адресов процедур-обработчиков. Процессору остается только взять из этой таблицы адрес соответствующего номеру прерывания обработчика, и выполнить переход на этот адрес. В защищенном режиме таблица прерываний называется таблицей дескрипторов прерываний IDT (Interrupt Descriptors Table) и на ее местонахождение указывает регистр IDTR (interrupt descriptors table register). Уже по названию можно судить, что в ней находятся не просто адреса обработчиков, а дескрипторы обработчиков прерывания. В качестве таких дескрипторов могут выступать дескрипторы трех типов: шлюз прерывания, шлюз ловушки и шлюз задачи. На данном этапе нас интересуют первые два. Они отличаются тем, что при выполнении обработчика прерывания все прерывания сразу запрещаются до завершения обработчика, а флаг трассировки сбрасывается.

Дескриптор представляет собой следующую запись:

0,1 байты:
2,3 байты: селектор сегмента
4: пустой
5:
0-3 содержит тип шлюза
(0110 - 16 битный шлюз прерывания, 0111 - 16 битный шлюз ловушки, 1110 - 32 битный шлюз прерывания, 1111 - 32 битный шлюз ловушки)
4 - тип дескриптора
5,6 - уровень привилегий сегмента
7 - наличие сегмента
6,7: биты смещения процедуры-обработчика

Итак, в таблице должно быть 256 дескрипторов (по количеству возможных прерываний), каждый из которых будет иметь свой личный номер, использующийся для его вызова при соответствующем прерывании. При вызове обработчика прерываний процессор помещает в стек регистр флагов и адрес возврата

Начнем мы с функций (inter.c), которые позволят нам устанавливать обработчики прерываний, а также запрещать и разрешать обработку прерываний:

#define IT 0x100000
#define IR 0x100800
#define SCS 0x8

//
В inst() some является обработчиком intr, тип шлюза задаем параметром kind; де-факто мы создаем/редактируем дескриптор в таблице.
void inst(unsigned char int, void (*some)(), unsigned char kind)
{
char * it=IT;
unsigned char i;
unsigned char a[8];
a[0]= (unsigned int)some & 0x000000FF;
a[1]=( (unsigned int)some & 0x0000FF00) >> 8;
a[2]=SCS;
a[3]=0;
a[4]=0;
a[5]=kind;
a[6]=( (unsigned int)some & 0x00FF0000) >> 16;
a[7]=( (unsigned int)some & 0xFF000000) >> 24;
for(k=0;k<8;k++){
*(it+int*8+k)=a[k];
}
}
//
Загружаем IDTR
void int_l()
{
unsigned short *limit = IR;
unsigned int *place = IR+2;
*limit = 256*8 - 1;
*place = IT;

asm("lidt 0(,%0,)"::"a"(IR));
asm("sti");
}
//
Разрешаем прерывания
void int_e()
{
asm("sti");
}

//
Запрещаем прерывания
void int_d()
{
asm("cli");
}


Необходимо так же помнить и о работе с портами, иначе прерывания нам становятся не нужны. Создадим port.c, главными строками в котором будут:

asm("outb %b0,%w1":: "a"(value), "d"(port));
asm("inb %w1, %b0": "=a"(value): "d"(port));


(value - простое число, я надеюсь, что скелет вы напишете сами, раз читаете эту статью)

Собственно, теперь мы готовы заниматься самими прерываниями. Как я и говорил, начнем мы с клавиатуры, однако не стоит забывать и о таймере, который тикает у нас каждую 1/18,3 секунды. Перед выполнением задачи обработчик прерывания должен сохранить регистры, а по завершении обработки - послать байт 0x20 в порт 0x20 (сигнал "конец прерывания" контроллеру прерываний), после чего восстановить регистры. Дабы не использовать в тексте каждый раз загромождений кодом из asm, я введу отдельный макрос:

#define IRQ_HANDLER(func) void func (void);\
asm(#func ": pusha \n call _" #func " \n movb $0x20, %al \n outb %al, $0x20 \n popa \n iret \n");\
void _ ## func(void)


(можно не пытаться в него вникнуть, главное понимайте зачем он нужен)

Возьмем code.h (содержит таблицу символов):

char codes[] = {0, 0, '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '-', '=', 8,'\t','q', 'w', 'e', 'r', 't', 'y', 'u', 'i', 'o', 'p', '[', ']','\n', 0,'a', 's', 'd', 'f', 'g', 'h', 'j', 'k', 'l', ';', '\'', '`',0,'\\', 'z', 'x', 'c', 'v','b', 'n', 'm', ',', '.', '/',0,'*',0,' ',0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,'-', 0, 0, 0,'+', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
char codes_sh[] = {0, 0, '!', '@', '#', '$', '%', '^', '&', '*', '(', ')','_', '+', 8, '\t', 'Q', 'W', 'E', 'R', 'T', 'Y', 'U', 'I', 'O', 'P', '{', '}', '\n', 0, 'A', 'S', 'D', 'F', 'G', 'H', 'J', 'K', 'L', ':', '"', '~', 0, '|', 'Z', 'X', 'C', 'V', 'B', 'N', 'M', '<', '>', '?', 0, '*', 0, ' ', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, '-', 0, 0, 0, '+', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
//
8 - backspace, на ctrl, alt и пр. стоят заглушки - 0.

Теперь напишем следующий finter.c:

#include "code.h"
//
Я говорил вам о таймере, здесь пока будет простая заглушка, можно потом будет ввести сюда более серьезные функции. Например, подсчет времени.
IRQ_HANDLER(irq_time)
{

}
//
Работа с клавиатурой довольно проста - мы должны считывать с нее коды клавиш, при этом не забывая, что у нас может быть два состояния - с shift и без него.

char sh = 0;
IRQ_HANDLER(irq_kb)
{
unsigned char code, asc;
unsigned char reg;

//
С 60-го порта вы получаем данные
code = ipb(0x60);
//
ipb, opb - мои названия функций из port.c
switch(code) {
//
С shift
case 0x36:
case 0x2A:
sh = 1;
break;

//
Без shift
case 0x36 + 0x80:
case 0x2A + 0x80:
sh = 0;
break;
default:

//
Если клавишу отпустили, то не делать ничего, а если нажали, то в зависимости от значения shift преобразовать скан-код в в разные символы ASCII
if(code >= 0x80) {
} else {
if(sh){
asc = codes_sh[code];
} else {
asc = codes[code];
}
if(asc != 0) {
//
pc - выводит на экран символ, эту функцию я опишу ниже.
pc(asc);
}
}
break;
}

//
После чего надо показать, что работа по считыванию символа проделана. Для этого считываем состояние клавиатуры (61 порт), преобразуем в нем старший бит в 1, после чего запишем назад.
reg = ipb(0x61);
reg |= 1;
opb(0x61, creg);
}


//
Теперь установка обработчиков прерываний
void iint()
{
inst(0x20, &irq_time, 0x8e);
inst(0x21, &irq_kb, 0x8e);
int_l();
int_e();
}

Вот, теперь у нас есть два прерывания. Надо бы проверить их работоспособность, да и вообще хотелось бы, чтобы наша ОС умела нам хоть что-то показывать, потому необходимо описать функции для работы с монитором. Это делать мы будет напрямую через видеопамять (video.c).

#define VW 80 //
Ширина экрана
#define VH 25 //
Его высота
#define RAM 0xb8000 //
Видеопамять

int cur; //
Где находится курсор
int param; //
Параметры

//
Идея проста - "бегаем" по экрану при прочтении очередного символа, заносим этот символ в видеопамять, меняем параметры символа.
void initial()
{
cur = 0;
param = 7;
}


void color(char c)
{
param = c;
}

void clear()
{
char *vid = RAM;
int k;
for (k = 0; k < VH*VW; k++) {
*(vid + k*2) = ' ';
}

cur = 0;
}

//
Единственная хоть чем-то интересная для нас функция, здесь мы как раз и записываем символ, меняем положение курсора.
void pc(char c)
{
char *vid = RAM;
int k;

switch (c) {
case '\n':
cur+=VW;
cur-=cur%VW;
break;

default:
*(vid + cur*2) = c;
*(vid + cur*2+1) = param;
cur++;
break;
}

//
Здесь необходимо описать что делать, если положение курсора вышло за пределы экрана. Решение простое - сдвиг экрана на одну строку.
if(cur>VW*VH){
for(i=VW*2;i<=VW*VH*2+VW*2;i++){
*(vid+i-VW*2)=*(vid+i);
}
cur-=VW;
}
}
}


На сегодня остался лишь один вопрос. У нас много кода (я описал все за исключением функций работы с портами), но его же надо как-то заставить работать. Как это сделать? Стоит добавить в k_main вызовы функций: initial(), iint(), а так же clear(). Компилируется все это довольно просто, достаточно следующей последовательности команд:

gcc -ffreestanding -c -o keyboard.o keyboard.c
gcc -ffreestanding -c -o inter.o inter.c
gcc -ffreestanding -c -o finter.o finter.c
gcc -ffreestanding -c -o port.o port.c
gcc -ffreestanding -c -o video.o video.c
gcc -ffreestanding -c -o kernel.o kernel.c
nasm -felf -o c.o c.asm
ld --oformat binary -Ttext 0x200000 -o kernel.bin c.o video.o inter.o port.o finter.o kernel.o
nasm -fbin -o boot2.bin boot2.asm
nasm -fbin -o all_image.bin boot1.asm

(В текст второго загрузчика в конце теперь надо добавить kernel_binary: incbin 'kernel.bin')

Итак, с этой частью статьи все. Мы научились работать с железом на простейшем уровне, смогли наконец-то заставить работать набор всех этих кодов. В следующей (уже более объемной и серьезной) части будет рассматриваться работа с жестким диском и файловой системой.
Категория: Общие Статьи | Добавил: aka_kludge (12.09.2008) | Автор: makarov и Codeworld
Просмотров: 1573 | Рейтинг: 0.0/0
Всего комментариев: 0
Добавлять комментарии могут только зарегистрированные пользователи.
[ Регистрация | Вход ]
    Главная      
...
На службе : дней

12:24
Обновить


Пользователи
aka_kludge
qwerty
LeadyTOR
aka_Atlantis
AdHErENt
mAss
Sissutr
hiss
DrBio
tHick

Поиск


Copyright tHR - TeAM 2024 г. admin: aka_kludge (ICQ:334449009) Moderator's: LeadyTOR, ... Яндекс.Метрика