Search     or:     and:
 LINUX 
 Language 
 Kernel 
 Package 
 Book 
 Test 
 OS 
 Forum 
iakovlev.org

Pramode C.E. : The Linux Kernel 0.01 Commentary

Оригинал статьи можно найти на http://pramode.net .
Для понимания работы ядра 0.01 необходимо разобраться в общих теоретитеских концепциях операционных систем, понять архитектуру 8086,работу ассемблера,компилятора,линкера. Можно скачать Intel 80386 Manual с адреса http://x86.ddj.com/intel.doc/386manuals.htm или посмотреть у меня на сайте в разделе /Languages/Asm/Intel 386 manuals .
Перед компиляцией необходимо отредактировать исходник . Современные компиляторы отличаются от того , которым компилировалось ядро 0.01 в 1991 г. Необходимо во всех ассемблерных файлах , а также в си-шных файлах там , где имеются inline-вставки , перед именами переменных убрать символ подчеркивания .
Символ комментария в файлах с расширением .as | нужно поменять на ; .
Необходимо модифицировать все Makefile . Изменения касаются опций ld , as, gcc .
Команде ld добавлена опция -r -T .
Команде as добавлена опция -b .
Был модифицирован файл build.c .Код в этом файле выполняет следующие вещи :
1. удаляет заголовки из обьектного файла , полученного путем компиляции boot.s
2. удаляет заголовка из файла 'system' и генерирует и добавляет в него загрузочный сектор .
build.c должен просто произвести загрузочный код размером 512 байт , который должен быть слеплен с 'system' командой 'cat' , и в результате должен получиться загрузочный файл 'Image'. Ядро 0.01 в качестве корневой файловой системы использует файловую систему minix . Поэтому винт должен быть на primary .C помощью команды fdisk нужно установить его тип как 80(Old Minix).
Для форматирования нужно использовать команду
mkfs.minix /dev/hda4 -n 14
14 - максимальная длина имени файла .
Далее нужно создать 2 каталога :
bin
dev
Нужно создать ноды , соответствующие корневой партиции (hda4) и консоли (tty0).Номера устройств можно найти в файле /include/linux/fs.h . Можно положить sh в каталог bin .
Необходимо откорректировать файл config.h . С помощью fdisk нужно найти число цилиндров ,секторов и заголовков на винте.Число заголовков не должно превышать 64 . Константу heads в этом файле нужно изменить с 16 на 64 . Оригинальный код в файле hd.c не работает с винтами обьемом более 10 гиг , но если закомментировать 1 строку, то все начинает работать . Этот код также позволяет работать и с secondary . Файл config.h - изменена константа ROOT_DEV на ту , которая у вас , например , для hda4 ROOT_DEV=304, для hda3 ROOT_DEV=303. В структуре LINUS_HD нужно поменять WPCOM,LANDZ на CTL 8 .
В файле /include/string.h многие переменные имеют префикс __res __asm__ , который был заменен на просто на __res .
В файле /include/asm/segment.h возвращаемое значение из функции имеет неопределенный спецификатор регистра (=r), которые все были заменены на (=a).
В файле /kernel/hd.c константе NR_HD было присвоено значение 2 .В функции hd_out() была закомментирована одна строка , из-за которой не работали винты более 10 гигабайт .
В этой же функции строка head>15 была заменена на head>63 .
Для того , чтобы работать и компилировать программы под запущенным ядром 0.01 , нужно помнить , что формат исполняемых файлов в 0.01 - a.out .Технология следующая : после компиляции используем ld для генерации raw binary output . Затем нужно специально написанной утилитой нужно прочитать размер полученого бинарника и приаттачить к нему заголовок . Этот код можно найти в linux-0.01/bin/shell/header/header.c.
Далее автор статьи пишет , что они тестили на 2-х машинах . Сначала они запустили на AMDK6 , и все заработало . Затем на Pentium 1 возник protection fault . Дамп показал , что затык произошел в /boot/head.s . Пришлось очищать флаг NT.

Сегментация в 386 .
Если взять к примеру архитектуру 8086 , то там каждый сегмент памяти имеет базовый адрес, который получается просто умножением содержимого сегментного регистра на 16 , после чего к результату добавляется смещение .
В архитектуре 386 содержимое сегментного регистра не имеет прямого отношения к базовым адресам . Здесь существует специальная map-таблица , которая преобразует содержимое сегментного регистра в базовый адрес. В этой таблице будут храниться базовые адреса сегментов , при этом
MAP_TABLE[0]=0x0,MAP_TABLE[1]=0x8,MAP_TABLE[2]=0x10,MAP_TABLE[3]=0x18,и т.д.
Сегментный регистр 16-разряден , и интерпретация его различна как в 8086 , так и в 386 .
В 386 :
биты 0,1,2 имеют специальное значение .
биты с 4 по 15 интерпретируются как индекс дескрипторной таблицы для получения базового адреса .
Дескрипторных таблиц может быть 2 :
LDT
GDT
В зависимости от того , в каком состоянии (0 или 1)находится 2-й бит сегментного регистра , мы будем обращаться или к LDT , или к GDT . Для инициализации этих таблиц существует команды LGDT , LLDT. В 386 имеются 2 специальных регистра для хранения базового адреса этого таблицы и её размера . Одна ячейка дескрипторной таблицы хранит 8 байт и называется дескриптором . Селектор отличается от дескриптора тем,что равен 16 байтам и хранится в сегментном регистре .
Механизм адресации в 386 состоит из 2-х блоков - сегментация плюс пэйджинг . Пэйджинг - необязательный блок, т.е. он может быть задисэблен . Если же он enable , то адрес , полученный в блоке сегментации , походит обработку в блоке пэйджинга .
Адресная шина в 386 - 32-битная.Поэтому максимальный размер памяти,который может быть приаттачен к такой шине, равен 2^32 , т.е. 4 гига . Вся доступная память разбита на 4-кб страницы .
Рассмотрим пример , когда у нас всего 16 метров памяти . Стартовые адреса первых наших страниц будут 0х0,0х1000,0х2000,0х3000,...,0хfff000 .Т.е. мы имеем 4кб страниц по 4 килобайта каждая. Предположим теперь , что виртуальные адреса , с которыми имеет дело наша программа , лежат в диапазоне 0xf0000000 до 0х000fefff . Если разбить эту область на 4 килобайта , получим 254 страницы , или 1016 кб . Теперь предположим , что программа загружается по физическому адресу 0х5000 . Это будет означать , что виртуальный адрес 0xf0000000 будет соответствовать физическому 0х5000 . Map table будет содержать физические адреса страниц нашей программы . Когда пользовательская программа будет генерировать адреса , они будут поставлены в соответствие адресам из мап-таблицы. Эти адреса имеют 32-битную длину.
Пэйджинг в 386 2-уровневый. Таблица , используемая на 1-м уровне индексации , называется page directory . Ее адрес хранится в регистре cr3 и размер ее 4 кб . В ней находятся адреса 1024 page tables для 2-го уровня индексации . В ядре 0.01 все процессы имеют одну и ту же page directory .

Прерывания : внешние устройства подсоединены к контроллеру - 8259 PIC или APIC . Когда внешнее устройство генерирует прерывание , PIC посылает сигнал . Затем один байт посылается на шину данных . Существует таблица прерываний - IDT - Interrupt Service Rutnes . Эта таблица содержит адреса процедур , вызываемых прерываниями .
При попытке записать данные в сегмент , который только на чтение , 386 сгенерирует FAULT . Каждый тип такого fault имеет идентификатор . Первые 32 строки в таблице IDT зарезервированы под исключения .

Unix - многозадачная система . Существует специальная task table , в которой сохраняется информация о всех выполняющихся процессах , в частности содержимое всех регистров , всех доступных портов . Сохранением этой информации должен быть озабочен программист , потому что процессор этим не занимается . Task Table называется Task State Structure (TSS) , и каждая задача должна иметь свою TSS . При создании нового процесса создается новая TSS . В текущей TSS всегда хранятся координаты задачи , на которую нужно переключиться . Переключение между задачами может происходить в результате вызова инструкций CALL,JMP,INT.

Ядро 0.01 использует только 2 уровня привилегий процессора из четырех - 0-й и 3-й . 0-й используется для кода ядра и 3-й для кода пользовательских программ .
В TSS имеется 4 пары стековых псевдо-регистров - ss0:esp0,ss1:esp1,ss2:esp2,ss3:esp3. И они соответствуют 4 уровням привилегий . Причем одна и та же задача будет иметь различное содержание в этих псевдо-регистрах для разных уровней привилегий .

Source Tree .
Код ядра состоит из 3-х главных компонентов :
1. файловая система
2. ядро - шедулер,system calls,signals и т.д.
3. память
Они представлены каталогами fs , kernel , mm .
В каталоге lib лежат системные вызовы , вызываемые из пользовательских программ. Они не нужны для загрузки , но необходимы например для работы шелла после загрузки .
Подкаталоги fs , kernel , mm имеют свои собственные makefile , которые производят соответственно обьектные файлы fs.o , kernel.o , mm.o . Они складываются с main.o , head.o , и в результате получается файл system в подкаталоге /tools , формат у этого файла raw binary . Далее из файла boot.s получаем raw binary файл boot . Затем к этому файлу прибавляется необходимое количество байт до размера 512 - эту задачу выполняет файл build.c. Полученный файл "лепится" с откомпилированным до этого обьектным ядром и в корне исходников получаем файл Image . Этот файл копируется на дискету , используя команду dd.

Boot Sector
BIOS при загрузке интересуют лишь первые 512 байт дискеты . При этом 511-й байт должен иметь значение 0х55, 512-й байт - 0хaa . После чего эти 512 байт копируются с дискеты в память по адресу 0х7с00 , с которого и происходит загрузка . Первое , что делает запускаемый код - он клонирует самоё себя по адресу 0х90000. Автор статьи далее описывает , что он попытался запустить код с адреса 0х7с00 , но код не заработал :-) После этого загруженный boot-сектор начинает читать оставшийся код ядра с дискеты и грузить его в память по адресу 0х10000 . После этого ядро копируется с этого адреса на адрес 0х0 . После чего управление переходит на адрес 0х0 , загрузчик больше не нужен и почивает в бозе . Кстати сказать , в современных ядрах boot-сектор превышает 512 байт и разбивается на 2 файла - boot.s и setup.s .

Kernel initialization
Далее начинает работать код , который лежит в /boot/head.s . С этого момента ядро работает в защищенном режиме. Активируется стек ядра , пэйджинг , таблицы IDT и GDT . Далее контроль передается в /init/main.c . Таблица IDT дополняется прерываниями . Ядро готово к обработке пользовательских программ . Функция init генерирует несколько других процессов, таких , как file system checker , шелл . После этого на экране появляется командное приглашение , и мы можем запускать свои собственные команды .
0.01 стал полностью "операбельным" ядром.
Сердцевиной любой операционки является работа таймера . Прерывание таймера - единственное , которое генерится всегда и постоянно , в отличие , скажем , от прерываний клавиатуры или харда . Операционка постоянно должна следить за переключением процессов .
Таймер увеличивает инкремент у процесса , тем самым мы знаем , как долго процесс выполняется . По этому счетчику делается вывод о том , надо ли переключаться на иной процесс . При переключении берется указателья из TR - task-регистра - который указывает на TSS процесса , на который надобно переключиться . Когда процессов пользователя нет совсем,выполняется т.н. idle task , который специально генерится ядром на случай простоя.
Когда при запуске очередного процесса может не хватить физической памяти , ядро займется своппингом - выделением памяти на харде , т.е. начнет сохранять выполняемые инструкции не в памяти , а на диске .

Рассмотрим более подробно , что происходит при загрузке ядра . Мы компилируем ядро 0.01 и получаем Image , который готов запуститься с адреса 0x0 . При включении PC загружается boot-сектор БИОС-а, который проверяет железо . Он читает дискету , на которой находится откомпилированный образ ядра 0.01 . Если 511-й и 512-й байтики на дискетке те самые заветные , биос грузит эти 510 байт в память , после чего загруженные байтики в памяти грузятся уже сами . Этот бут-лоадер - см. файл /boot/boot.s - начинает грузить ядро с дискеты в память по адресу 0х0 . После чего загруженный boot-сектор передает управление ядру , при этом происходит переключение с режима 8086 на защищенный режим .

Ядро стартует с кода , который лежит в /boot/head.s . Первое , что нужно сделать - это проинициализировать таблицы IDT , GDT , LDT , проинициализировать page tables . После этих приготовлений мы переходим в /init/main.c . Здесь запускаются несколько основных процессов - init-процесс , процесс сканирования файловой системы , шелл .
- файл /kernel/system_call.s . Он обрабатывает прерывание 0х80 , которое используется для вызова system call . Кроме этого , файл включает в себя код управления signal , fork , exec , прерываниями таймера и прерываниями харда .
- файл /kernel/sys.c - включает код для обработки других system calls .
- файлы /kernel/asm.s , /kernel/traps.c - включает код для обработки исключений .
- файлы /kernel/console.c , /kernel/tty_io.c - включает код для обработки консоли .
- файлы /kernel/panic.c , /kernel/mktime.c , /kernel/vsprintf.c ,/kernel/printk.c- включает определение таких функций , как printf , printk .
- файлы /kernel/hd.c , /kernel/keyboard.s , /kernel/rs_io.s - управление устройствами - файлы /kernel/fork.c , /kernel/exit.c -
- файлы /kernel/shed.c - управление таймером , сердцевина ОС . Шедулер занимается переключением процессов .

Каталог /mm включает код для управления памятью .
- файл /mm/page.s - происходит обработка page fault , которая может произойти по 2 причинам - либо попытка записать в память , которая только на чтение , либо попытка открыть несуществующую страницу памяти - при обнаружении этих 2-х ошибок управление передается в memory.c .

Файловая система
- файл /fs/super.c - читает супер-блок , распознает файловую систему и инициализирует root inode .
- файл /fs/bufer.c - служит для оптимизации чтения-записи
- файл /fs/bitmap.c,/fs/inode.c , /fs/namei.c , /fs/truncate.c - ядро вначале монтирует корневую файловую систему и присваивает ей root inode . Доступ к файловой системе будет осуществляться через inode .
- файл /fs/block_dev.c , /fs/file_dev.c , /fs/char_dev.c - в 0.01 поддерживаются 3 типа устройств - блочные,файловые и символьные .
- файл /fs/file_table.c - просто массив файлов , открытых в данный момент
- файл /fs/open.c , /fs/read_write.c
- файл /fs/fcntl , /fs/ioctl.c , /fs/tty_ioctl.c , /fs/stat.c - операции , связанные с файловыми манипуляциями
- файл /fs/pipe.c
- файл /fs/exec.c - выполняет загрузку исполняемого файла с диска и его последующий запуск
- каталог /linux/lib/ - используются для работы с пользовательскими программами
- каталог /linux/tools/ - включает 1 файл build.c - это конструктор , который лепит заключительный файл Image .

Файл /boot/boot.s
  Первоначально биос грузит загрузчик c дискеты по адресу 0x7c00 ,
 после чего загрузчик сам клонирует себя по адресу 0x90000 .
 Процессор при этом находится в real mode . Почему загрузчик клонирует себя
 по адресу 0х90000 ? Потому что в более нижние адреса этого делать нежелательно
 из-за возможных конфликтов с биосом . Ядро будет загружено по адресу 0х10000 .
 Как известно БИОС оперирует в мервом мегабайте памяти . 
  Итак , после того как мы загрузили ядро , биос нам более не нужен .
 Переключение в защищенный режим происходит в boot.s . 
 
 
 .globl begtext, begdata, begbss, endtext, enddata, endbss
 .text
 begtext:
 .data
 begdata:
 .bss
 begbss:
 .text
 
 BOOTSEG = 0x07c0
 INITSEG = 0x9000
 SYSSEG  = 0x1000			| system loaded at 0x10000 (65536).
 ENDSEG	= SYSSEG + SYSSIZE
 
 entry start
 start:
 	mov	ax,#BOOTSEG
 	mov	ds,ax
 	mov	ax,#INITSEG
 	mov	es,ax
 	mov	cx,#256
 	sub	si,si
 	sub	di,di
 	rep
 	movw
 	jmpi	go,INITSEG
 
 В приведенном тексте происходит копирование 512 байт с адреса BOOTSEG в адрес INITSEG
  
  go:	mov	ax,cs
 	mov	ds,ax
 	mov	es,ax
 	mov	ss,ax
 	mov	sp,#0x400		| arbitrary value >>512
 
 	mov	ah,#0x03	| read cursor pos
 	xor	bh,bh
 	int	0x10
 	
 	mov	cx,#24
 	mov	bx,#0x0007	| page 0, attribute 7 (normal)
 	mov	bp,#msg1
 	mov	ax,#0x1301	| write string, move cursor
 	int	0x10
 
 | ok, we've written the message, now
 | we want to load the system (at 0x10000)
  
   А сейчас мы только-что распечатали сообщение на экране
 Следующий код читает ядро с дискеты и копирует его в память по адресу 0х10000 
   
   	mov	ax,#SYSSEG
 	mov	es,ax		| segment of 0x010000
 	call	read_it
 	call	kill_motor
 
  Переключение в protected mode :
  
       cli
  
  Теперь копируем ядро из памяти с адреса 0х10000 в адрес 0х0000 
  
  | first we move the system to it's rightful place
 
 	mov	ax,#0x0000
 	cld			| 'direction'=0, movs moves forward
 do_move:
 	mov	es,ax		| destination segment
 	add	ax,#0x1000
 	cmp	ax,#0x9000
 	jz	end_move
 	mov	ds,ax		| source segment
 	sub	di,di
 	sub	si,si
 	mov 	cx,#0x8000
 	rep
 	movsw
 	j	do_move
  
  Дальнейшая подготовка protected mode включает инициализацию таблиц GDT и IDT 
 Внутри кода boot.s можно найти 2 метки : 
 idt_48:
 gdt_48:
 которые и представляют из себя адреса этих таблиц в памяти .
  Следующий код с помощью контроллера клавиатуры получает доступ к 4 Gb памяти 
 (для данных)  
  
  | that was painless, now we enable A20
 
 	call	empty_8042
 	mov	al,#0xD1		| command write
 	out	#0x64,al
 	call	empty_8042
 	mov	al,#0xDF		| A20 on
 	out	#0x60,al
 	call	empty_8042
  
   Следующий код инициализирует контролер прерываний 8259 , у которого
 имеются свои регистры для чтения . Нужно помнить , что первые 32 прерывания 
 Интел зарезервировал для своих нужд , поэтому мы настраиваем контролер
 на стартовое прерывание по адресу 0х20 .  
  
  	mov	al,#0x11		| initialization sequence
 	out	#0x20,al		| send it to 8259A-1
 	.word	0x00eb,0x00eb		| jmp $+2, jmp $+2
 	out	#0xA0,al		| and to 8259A-2
 	.word	0x00eb,0x00eb
 	mov	al,#0x20		| start of hardware int's (0x20)
 	out	#0x21,al
 	.word	0x00eb,0x00eb
 	mov	al,#0x28		| start of hardware int's 2 (0x28)
 	out	#0xA1,al
 	.word	0x00eb,0x00eb
 	mov	al,#0x04		| 8259-1 is master
 	out	#0x21,al
 	.word	0x00eb,0x00eb
 	mov	al,#0x02		| 8259-2 is slave
 	out	#0xA1,al
 	.word	0x00eb,0x00eb
 	mov	al,#0x01		| 8086 mode for both
 	out	#0x21,al
 	.word	0x00eb,0x00eb
 	out	#0xA1,al
 	.word	0x00eb,0x00eb
 	mov	al,#0xFF		| mask off all interrupts for now
 	out	#0x21,al
 	.word	0x00eb,0x00eb
 	out	#0xA1,al
  
   А теперь само переключение в protected mode , и загрузчик прыгает 
 в начальный адрес памяти :
  
  	mov	ax,#0x0001	| protected mode (PE) bit
 	lmsw	ax		| This is it!
 	jmpi	0,8		| jmp offset 0 of segment 8 (cs)
  
  
  	.word	0x00eb,0x00eb
 	in	al,#0x64	| 8042 status port
 	test	al,#2		| is input buffer full?
 	jnz	empty_8042	| yes - loop
 	ret
  
   Что происходит дальше ?
 Мы копируем ядро с адреса 0х000 , используя индексную адресацию es:[bx].
 Вначале мы инициализируем :
 	es = 0x0   
 	bx = 0x0
 Дальше идет цикл инкремента bx до значения bx = 04ffff . 
 Затем мы прибавляем :
 	es + 0x1000
 и снова делаем 
 	bx = 0x0
 Адресация в x86 вычисляется по формуле :
 	es * 4 + bx
 Процедура read_track будет использовать БИОС для загрузки необходимого числа секторов :
 Вычисление размера сегмента ENDSEG
 
 sread:	.word 1			| sectors read of current track
 head:	.word 0			| current head
 track:	.word 0			| current track
 read_it:
 	mov ax,es
 	test ax,#0x0fff
 die:	jne die			| es must be at 64kB boundary
 	xor bx,bx		| bx is starting address within segment
 rp_read:
 	mov ax,es
 	cmp ax,#ENDSEG		| have we loaded all yet?
 	jb ok1_read
 	ret
 
    Умножаем cx на 512 - размер сектора :	
 
 ok1_read:
 	mov ax,#sectors
 	sub ax,sread
 	mov cx,ax
 	shl cx,#9
 
  Вычислим , сколько нам не хватает байт для текущего сегмента :
 
 	add cx,bx
 	jnc ok2_read
 	je ok2_read
 	xor ax,ax
 	sub ax,bx
 
  Таблица gdt создается в 2-х экземплярах - одна для кода и другая
  для данных каждая размером по 8 метров с соответствующими правами.
 
 gdt:
 	.word	0,0,0,0		| dummy
 
 	.word	0x07FF		| 8Mb - limit=2047 (2048*4096=8Mb)
 	.word	0x0000		| base address=0
 	.word	0x9A00		| code read/exec
 	.word	0x00C0		| granularity=4096, 386
 
 	.word	0x07FF		| 8Mb - limit=2047 (2048*4096=8Mb)
 	.word	0x0000		| base address=0
 	.word	0x9200		| data read/write
 	.word	0x00C0		| granularity=4096, 386
 
  Следующий код обнуляет таблицу прерываний - они нам не нужны :
 
 idt_48:
 	.word	0			| idt limit=0
 	.word	0,0			| idt base=0L
 
 
  Все - boot.s отработал свое .
 Посмотрим теперь на head.s 
 Следующий код нужен для инициализации page directory
 $0x10 - это адрес data segment :
 
 startup_32:
 	movl $0x10,%eax
 	mov %ax,%ds
 	mov %ax,%es
 	mov %ax,%fs
 	mov %ax,%gs
 Далее инициализация стека . 	
 stack_start - это базовая структура стека , которая прописана /kernel/shed.c
 
 	lss _stack_start,%esp
 
  Setup IDT и GDT :
 
 	call setup_idt
 	call setup_gdt
 
  Инициализируем все сегментные регистры :	
 
 	movl $0x10,%eax		# reload all the segment registers
 	mov %ax,%ds		# after changing gdt. CS was already
 	mov %ax,%es		# reloaded in 'setup_gdt'
 	mov %ax,%fs
 	mov %ax,%gs
 	lss _stack_start,%esp
  Прыгаем по адресу на метке after_page_tables . Вся память ниже этой метки
 уйдет на paging :  
 
 	xorl %eax,%eax
 1:	incl %eax		# check that A20 really IS enabled
 	movl %eax,0x000000
 	cmpl %eax,0x100000
 	je 1b
 	movl %cr0,%eax		# check math chip
 	andl $0x80000011,%eax	# Save PG,ET,PE
 	testl $0x10,%eax
 	jne 1f			# ET is set - 387 is present
 	orl $4,%eax		# else set emulate bit
 1:	movl %eax,%cr0
 	jmp after_page_tables
 
   Инициализируем таблицу IDT , состоящую из 256 адресов :
 
 setup_idt:
 	lea ignore_int,%edx
 	movl $0x00080000,%eax
 	movw %dx,%ax		/* selector = 0x0008 = cs */
 	movw $0x8E00,%dx	/* interrupt gate - dpl=0, present */
 
 	lea _idt,%edi
 	mov $256,%ecx
 rp_sidt:
 	movl %eax,(%edi)
 	movl %edx,4(%edi)
 	addl $8,%edi
 	dec %ecx
 	jne rp_sidt
 	lidt idt_descr
 	ret
 
   К следующей порции кода небольшой комментарий .
 Интел использует 2-уровневый paging - 
  1 уровень - это одна страница 1-го уровня ,называемая page directory
  2 уровень - 1024 страницы , называемые page tables
 Page directory начинается с адреса 0х0 и заканчивается 0х100 (4К) 
 Далее мы используем 2 page tables - первая по адресу 0х10000 (pg0)
 и 0х20000 (pg1). Итого памяти у этих 2 таблиц : 
 	2 * 1024 = 8 MB
 Есть еще одна страница - pg2 (0x30000-0x40000),но она не используется.
 Вся эта память ТОЛЬКО для ядра . Каждый пользовательский процесс 
 будет создавать свои собственные page directory/page tables :
 setup_gdt:
 	lgdt gdt_descr
 	ret
 
 .org 0x1000
 pg0:
 
 .org 0x2000
 pg1:
 
 .org 0x3000
 pg2:		# This is not used yet, but if you
 		# want to expand past 8 Mb, you'll have
 		# to use it.
 
 .org 0x4000
  После этого мы прыгаем в C-main-функцию :
 after_page_tables:
 	pushl $0		# These are the parameters to main :-)
 	pushl $0
 	pushl $0
 	pushl $L6		# return address for main, if it decides to.
 	pushl $_main
 	jmp setup_paging
 L6:
 	jmp L6			# main should never return here, but 
    Обнуляем pg0 , pg1 :	
 
 /* This is the default interrupt "handler" :-) */
 .align 2
 ignore_int:
 	incb 0xb8000+160		# put something on the screen
 	movb $2,0xb8000+161		# so that we know something
 	iret				# happened
 
 
 /*
  * Setup_paging
  *
  * This routine sets up paging by setting the page bit
  * in cr0. The page tables are set up, identity-mapping
  * the first 8MB. The pager assumes that no illegal
  * addresses are produced (ie >4Mb on a 4Mb machine).
  *
  * NOTE! Although all physical memory should be identity
  * mapped by this routine, only the kernel page functions
  * use the >1Mb addresses directly. All "normal" functions
  * use just the lower 1Mb, or the local data space, which
  * will be mapped to some other place - mm keeps track of
  * that.
  *
  * For those with more memory than 8 Mb - tough luck. I've
  * not got it, why should you :-) The source is here. Change
  * it. (Seriously - it shouldn't be too difficult. Mostly
  * change some constants etc. I left it at 8Mb, as my machine
  * even cannot be extended past that (ok, but it was cheap :-)
  * I've tried to show which constants to change by having
  * some kind of marker at them (search for "8Mb"), but I
  * won't guarantee that's all :-( )
  */
 .align 2
 setup_paging:
 	movl $1024*3,%ecx
 	xorl %eax,%eax
 	xorl %edi,%edi			/* pg_dir is at 0x000 */
 	cld;rep;stosl
 Заполним первые 2 строки в page directory указателями на pg0 и pg1 :	
 
 	movl $pg0+7,_pg_dir		/* set present bit/user r/w */
 	movl $pg1+7,_pg_dir+4		/*  --------- " " --------- */
   Теперь заполним page tables физическими адресами . Например 
 в последней строке pg1 будет адрес , соответствующий 8 М памяти.
 В адрес входят биты для прав :  
 
 	movl $pg1+4092,%edi
 	movl $0x7ff007,%eax		/*  8Mb - 4096 + 7 (r/w user,p) */
 	std
 1:	stosl			/* fill pages backwards - more efficient :-) */
 	subl $0x1000,%eax
 	jge 1b
  Установим page directory на адрес 0х0 :
 
 	xorl %eax,%eax		/* pg_dir is at 0x0000 */
 	movl %eax,%cr3		/* cr3 - page directory start */
 	movl %cr0,%eax
 	orl $0x80000000,%eax
 	movl %eax,%cr0		/* set paging (PG) bit */
 	ret			/* this also flushes prefetch-queue */
 	
 
 .align 2
 .word 0
 idt_descr:
 	.word 256*8-1		# idt contains 256 entries
 	.long _idt
 .align 2
 .word 0
 gdt_descr:
 	.word 256*8-1		# so does gdt (not that that's any
 	.long _gdt		# magic number, but it works for me :^)
 
 	.align 3
 _idt:	.fill 256,8,0		# idt is uninitialized
 
 
   Теперь переходим в каталог /kernel
 Рассмотрим файл /kernel/system_call.s
 
 /*
  *  system_call.s  contains the system-call low-level handling routines.
  * This also contains the timer-interrupt handler, as some of the code is
  * the same. The hd-interrupt is also here.
  *
  * NOTE: This code handles signal-recognition, which happens every time
  * after a timer-interrupt and after each system call. Ordinary interrupts
  * don't handle signal-recognition, as that would clutter them up totally
  * unnecessarily.
  *
  * Stack layout in 'ret_from_system_call':
  *
  *	 0(%esp) - %eax
  *	 4(%esp) - %ebx
  *	 8(%esp) - %ecx
  *	 C(%esp) - %edx
  *	10(%esp) - %fs
  *	14(%esp) - %es
  *	18(%esp) - %ds
  *	1C(%esp) - %eip
  *	20(%esp) - %cs
  *	24(%esp) - %eflags
  *	28(%esp) - %oldesp
  *	2C(%esp) - %oldss
  */
 
 Код этого файла выполняется в стеке . Причем у ядра и пользовательских
 процессов (в случае применения fork) будут разные стеки.
 При переключении уровней с 0 на 3 регистры ess,esp,eflags,cs
 также хранятся в кернел-стеке .    
 В метку _system_call мы приходим каждый раз , когда пользовательский
 процесс вызывает системный вызов -  int 0x86 . 
 
 SIG_CHLD	= 17
 EAX		= 0x00
 EBX		= 0x04
 ECX		= 0x08
 EDX		= 0x0C
 FS		= 0x10
 ES		= 0x14
 DS		= 0x18
 EIP		= 0x1C
 CS		= 0x20
 EFLAGS		= 0x24
 OLDESP		= 0x28
 OLDSS		= 0x2C
 
 state	= 0		# these are offsets into the task-struct.
 counter	= 4
 priority = 8
 signal	= 12
 restorer = 16		# address of info-restorer
 sig_fn	= 20		# table of 32 signal addresses
 
 nr_system_calls = 67
 
 .globl _system_call,_sys_fork,_timer_interrupt,_hd_interrupt,_sys_execve
 
 .align 2
 bad_sys_call:
 	movl $-1,%eax
 	iret
 .align 2
 reschedule:
 	pushl $ret_from_sys_call
 	jmp _schedule
 .align 2
 _system_call:
   При этом номер вызова передается в регистре eax :
 
 	cmpl $nr_system_calls-1,%eax
 	ja bad_sys_call
 	push %ds
 	push %es
 	push %fs
 	pushl %edx
 	pushl %ecx		# push %ebx,%ecx,%edx as parameters
 	pushl %ebx		# to the system call
    Регистры ds,es указывают на пространство ядра ,
 регистр fs - на пространство пользователя :
 
 	movl $0x10,%edx		# set up ds,es to kernel space
 	mov %dx,%ds
 	mov %dx,%es
 	movl $0x17,%edx		# fs points to local data space
    Далее настройка виртуального адреса системного вызова.
 Функция sys_call_table прописана в /include/linux/sys.h :   
 
 	mov %dx,%fs
 	call _sys_call_table(,%eax,4)
  Информация о пользовательском процессе хранится в специальной структуре
 Адрес этой структуры хранится в переменной current .
 В зависимости от содержимого этой структуры ядро принимает решение ,
 надо ли переключаться на другой процесс :	
 
 	pushl %eax
 	movl _current,%eax
 	cmpl $0,state(%eax)		# state
 	jne reschedule
 	cmpl $0,counter(%eax)		# counter
 	je reschedule
     После того , как системный вызов выполнен , процессу надо
 послать сигнал . Ядро при этом устанавливает нужный бит в специальном 
 массиве сигналов    
 
 ret_from_sys_call:
 	movl _current,%eax		# task[0] cannot have signals
 	cmpl _task,%eax
 	je 3f
 	movl CS(%esp),%ebx		# was old code segment supervisor
 	testl $3,%ebx			# mode? If so - don't check signals
 	je 3f
 	cmpw $0x17,OLDSS(%esp)		# was stack segment = 0x17 ?
 	jne 3f
  Как процесс возобновляет свою работу именно с того места ,
 откуда он сделал вызов system_call ? Просто зачение регистра
 EIP восстанавливается из стека 
 
 	xchgl %ebx,EIP(%esp)		# put new return address on stack
 	subl $28,OLDESP(%esp)
 	movl OLDESP(%esp),%edx		# push old return address on stack
    Следующий кусок кода проверяет т.н. page fault - т.е. идет проверка
 наличия вызываемой страницы памяти :   	
 
 	pushl %eax			# but first check that it's ok.
 	pushl %ecx
 	pushl $28
 	pushl %edx
 	call _verify_area
 	Следующий кусок прибивает процесс :
 
 default_signal:
 	incl %ecx
 	cmpl $SIG_CHLD,%ecx
 	je 2b
 	pushl %ecx
 	call _do_exit		# remember to set bit 7 when dumping core
 	addl $4,%esp
 	jmp 3b
 
 	
   Перейдем к файлу /kernel/fork.c		
 Функция verify_area проверяет , существует ли реальная физическая страница,
 соответствующая виртуальному в диапазоне от addr до addr+size .
 В каждом процессе есть линейный блок виртуальных адресов.
 Стартовый адрес процесса - nr . Возможное виртуальное адрессное
 пространство в 0.01 для одного процесса - 64 метра . Поэтому базовое 
 пространство nr = 0 * 0x4000000 . Все виртуальные адреса генерятся
 на этапе линковки пользовательской программы с указанием на 
 базовый адрес 0х0  . Функция write_verify создает реальную физическую
 страницу для соответственного виртуального адреса : 
 /*
  *  'fork.c' contains the help-routines for the 'fork' system call
  * (see also system_call.s), and some misc functions ('verify_area').
  * Fork is rather simple, once you get the hang of it, but the memory
  * management can be a bitch. See 'mm/mm.c': 'copy_page_tables()'
  */
 #include 
 
 #include < linux/sched.h>
 #include < linux/kernel.h>
 #include < asm/segment.h>
 #include < asm/system.h>
 
 extern void write_verify(unsigned long address);
 
 long last_pid=0;
 
 void verify_area(void * addr,int size)
 {
 	unsigned long start;
 
 	start = (unsigned long) addr;
 	size += start & 0xfff;
 	start &= 0xfffff000;
 	start += get_base(current->ldt[2]);
 	while (size>0) {
 		size -= 4096;
 		write_verify(start);
 		start += 4096;
 	}
 }
   Следующая функция - int copy_mem , она используется в другой 
 функции - copy_process . При вызове fork , потомок должен получить
 информацию о родителе . Юникс рассматривает адресное пространство
 одного процесса как единый блок виртуальных адресов .
 При вызове этой функции имеется ввиду , что в кодовом сегменте и сегменте
 данных должны быть одни и те же адреса . При создании нового процесса
 эти адреса генерятся как nr + 0x4000000 . Проверяется и добавляется
 строка в page directory :
 int copy_mem(int nr,struct task_struct * p)
 {
 	unsigned long old_data_base,new_data_base,data_limit;
 	unsigned long old_code_base,new_code_base,code_limit;
 
 	code_limit=get_limit(0x0f);
 	data_limit=get_limit(0x17);
 	old_code_base = get_base(current->ldt[1]);
 	old_data_base = get_base(current->ldt[2]);
 	if (old_data_base != old_code_base)
 		panic("We don't support separate I&D");
 	if (data_limit < code_limit)
 		panic("Bad data_limit");
 	new_data_base = new_code_base = nr * 0x4000000;
 	set_base(p->ldt[1],new_code_base);
 	set_base(p->ldt[2],new_data_base);
 	if (copy_page_tables(old_data_base,new_data_base,data_limit)) {
 		free_page_tables(new_data_base,data_limit);
 		return -ENOMEM;
 	}
 	return 0;
 }
 
   Следующая функция - copy_process , она вызывается из функции _sys_fork,
 которая лежит в system_call.s . В свою очередь , _sys_fork , являясь 
 элементом массива sys_call_table[] , вызывается из другой функции -
 system_call . 
   В функции int copy_process выделяется страница памяти для task structure 
 процесса . Затем копируем поля из родительской структуры в дочернюю -
 	*p = *current;
 В конце 2 адреса - TSS и LDT - сохраняются в gdt :	
 
 int copy_process(int nr,long ebp,long edi,long esi,long gs,long none,
 		long ebx,long ecx,long edx,
 		long fs,long es,long ds,
 		long eip,long cs,long eflags,long esp,long ss)
 {
 	struct task_struct *p;
 	int i;
 	struct file *f;
 
 	p = (struct task_struct *) get_free_page();
 	if (!p)
 		return -EAGAIN;
 	*p = *current;	/* NOTE! this doesn't copy the supervisor stack */
 	p->state = TASK_RUNNING;
 	p->pid = last_pid;
 	p->father = current->pid;
 	p->counter = p->priority;
 	p->signal = 0;
 	p->alarm = 0;
 	p->leader = 0;		/* process leadership doesn't inherit */
 	p->utime = p->stime = 0;
 	p->cutime = p->cstime = 0;
 	p->start_time = jiffies;
 	p->tss.back_link = 0;
 	p->tss.esp0 = PAGE_SIZE + (long) p;
 	p->tss.ss0 = 0x10;
 	p->tss.eip = eip;
 	p->tss.eflags = eflags;
 	p->tss.eax = 0;
 	p->tss.ecx = ecx;
 	p->tss.edx = edx;
 	p->tss.ebx = ebx;
 	p->tss.esp = esp;
 	p->tss.ebp = ebp;
 	p->tss.esi = esi;
 	p->tss.edi = edi;
 	p->tss.es = es & 0xffff;
 	p->tss.cs = cs & 0xffff;
 	p->tss.ss = ss & 0xffff;
 	p->tss.ds = ds & 0xffff;
 	p->tss.fs = fs & 0xffff;
 	p->tss.gs = gs & 0xffff;
 	p->tss.ldt = _LDT(nr);
 	p->tss.trace_bitmap = 0x80000000;
 	if (last_task_used_math == current)
 		__asm__("fnsave %0"::"m" (p->tss.i387));
 	if (copy_mem(nr,p)) {
 		free_page((long) p);
 		return -EAGAIN;
 	}
 	for (i=0; ifilp[i])
 			f->f_count++;
 	if (current->pwd)
 		current->pwd->i_count++;
 	if (current->root)
 		current->root->i_count++;
 	set_tss_desc(gdt+(nr<<1)+FIRST_TSS_ENTRY,&(p->tss));
 	set_ldt_desc(gdt+(nr<<1)+FIRST_LDT_ENTRY,&(p->ldt));
 	task[nr] = p;	/* do this last, just in case */
 	return last_pid;
 }
 
 
 
 Перейдем к /kernel/sched.c
 Функция schedule проверяет , насколько реальна причина для того , 
 чтобы пробудить тот или иной процесс . Когда процесс засыпает ,
 он может быть , а может и не быть пробужден .
 p->counter указывает на число тактов , в течение которых процесс
 может быть в работе перед тем , как заснуть . Когда этот счетчик
 становится равным нулю , процесс переходит в спячку .
 Существуют конечно и другие причины помимо этого каунтера ,
 по которым процесс может перейти в спячку . Функция ниже из всех
 процессов переводит в спячку тот , у которых каунтер минимален .
 Переключение на другой процесс происходит с помощью асм-функции
 switch_to :
 
 void schedule(void)
 {
 	int i,next,c;
 	struct task_struct ** p;
 
 /* check alarm, wake up any interruptible tasks that have got a signal */
 
 	for(p = &LAST_TASK ; p > &FIRST_TASK ; --p)
 		if (*p) {
 			if ((*p)->alarm && (*p)->alarm < jiffies) {
 					(*p)->signal |= (1<<(SIGALRM-1));
 					(*p)->alarm = 0;
 				}
 			if ((*p)->signal && (*p)->state==TASK_INTERRUPTIBLE)
 				(*p)->state=TASK_RUNNING;
 		}
 
 /* this is the scheduler proper: */
 
 	while (1) {
 		c = -1;
 		next = 0;
 		i = NR_TASKS;
 		p = &task[NR_TASKS];
 		while (--i) {
 			if (!*--p)
 				continue;
 			if ((*p)->state == TASK_RUNNING && (*p)->counter > c)
 				c = (*p)->counter, next = i;
 		}
 		if (c) break;
 		for(p = &LAST_TASK ; p > &FIRST_TASK ; --p)
 			if (*p)
 				(*p)->counter = ((*p)->counter >> 1) +
 						(*p)->priority;
 	}
 	switch_to(next);
 }
  Следующий системный вызов маркирует процесс как прерываемый  :
 
 int sys_pause(void)
 {
 	current->state = TASK_INTERRUPTIBLE;
 	schedule();
 	return 0;
 }
  Процесс переводится в спячку и выстраивается в очередь ,
 представляющую linked list . 
 Существует специальный буфер , в котором находится текущий процесс
 из очереди 
 
 void sleep_on(struct task_struct **p)
 {
 	struct task_struct *tmp;
 	if (!p)
 		return;
 	if (current == &(init_task.task))
 		panic("task[0] trying to sleep");
 	tmp = *p;
 	*p = current;
 	current->state = TASK_UNINTERRUPTIBLE;
 	schedule();
 	if (tmp)
 		tmp->state=0;
 }
 
 
  Перейдем к файлу /kernel/hd.c - он описывает работу с хардом.
 Все сделано через linked list . 
 В зависимости от того , каков запрос - на чтение или запись -
 соответственно указатель-функция do_hd будет установлен на функцию ,
 которая управляет прерыванием диска контроллера харда на чтение
 или запись соответственно . Если запрос на чтение , то контроллер
 сгенерит прерывание тогда , когда данные будут готовы к копированию
 в буфер . Далее вызывается функция do_hd . При записи все та же do_hd
 запишет данные из буфера на диск . Запросы выстраиваются в очередь :  
 static struct hd_i_struct{
 	int head,sect,cyl,wpcom,lzone,ctl;
 	} hd_info[]= { HD_TYPE };
 
 #define NR_HD ((sizeof (hd_info))/(sizeof (struct hd_i_struct)))
 
 static struct hd_struct {
 	long start_sect;
 	long nr_sects;
 } hd[5*MAX_HD]={{0,0},};
 
 static struct hd_request {
 	int hd;		/* -1 if no request */
 	int nsector;
 	int sector;
 	int head;
 	int cyl;
 	int cmd;
 	int errors;
 	struct buffer_head * bh;
 	struct hd_request * next;
 } request[NR_REQUEST];
 
 #define IN_ORDER(s1,s2) \
 ((s1)->hd<(s2)->hd || (s1)->hd==(s2)->hd && \
 ((s1)->cyl<(s2)->cyl || (s1)->cyl==(s2)->cyl && \
 ((s1)->head<(s2)->head || (s1)->head==(s2)->head && \
 ((s1)->sector<(s2)->sector))))
 
 static struct hd_request * this_request = NULL;
 
 static int sorting=0;
 
 static void do_request(void);
 static void reset_controller(void);
 static void rw_abs_hd(int rw,unsigned int nr,unsigned int sec,unsigned int head,
 	unsigned int cyl,struct buffer_head * bh);
 void hd_init(void);
 ...
 void (*do_hd)(void) = NULL;
 
 
 
 
  Перейдем к /mm/memory.c
 В основном этот код занимается распределением свободных виртуальных ресурсов
 в памяти . Напомню , что все что ниже 0х100000 - используется ядром.
 Обьем массива физических страниц зашит в самой системе . 
 Свободная физическая страница находится с помощью массива mem_map .
 После того как свободная страница найдена , ее индекс помещается в ecx ,
 поэтому физический адрес вычисляется как (4k * ecx) + 0x100000.
 Заполняем эту страницу нолями :
 unsigned long get_free_page(void)
 {
 register unsigned long __res asm("ax");
 
 __asm__("std ; repne ; scasw\n\t"
 	"jne 1f\n\t"
 	"movw $1,2(%%edi)\n\t"
 	"sall $12,%%ecx\n\t"
 	"movl %%ecx,%%edx\n\t"
 	"addl %2,%%edx\n\t"
 	"movl $1024,%%ecx\n\t"
 	"leal 4092(%%edx),%%edi\n\t"
 	"rep ; stosl\n\t"
 	"movl %%edx,%%eax\n"
 	"1:"
 	:"=a" (__res)
 	:"0" (0),"i" (LOW_MEM),"c" (PAGING_PAGES),
 	"D" (mem_map+PAGING_PAGES-1)
 	:"di","cx","dx");
 return __res;
 }
    Как я уже говорил , в 386 имеется иерархия :
    	page directory
   	     page table
 		 actual page
 Для того чтобы освободить одну actual page , нам нужно пройти
 всю эту цепочку . При освобождении одной позиции в page directory
 освобождается 4 метра , а одному процессу в данной версии
 доступно 64 метра : 					
 void free_page(unsigned long addr)
 {
 	if (addrHIGH_MEMORY)
 		panic("trying to free nonexistent page");
 	addr -= LOW_MEM;
 	addr >>= 12;
 	if (mem_map[addr]--) return;
 	mem_map[addr]=0;
 	panic("trying to free free page");
 }
  Для того чтобы вычислить нужную page directory , мы помним ,
 что в памяти они располагаются в самом низу - начиная с адреса
 0х0 :
 
 int free_page_tables(unsigned long from,unsigned long size)
 {
 	unsigned long *pg_table;
 	unsigned long * dir, nr;
 
 	if (from & 0x3fffff)
 		panic("free_page_tables called with wrong alignment");
 	if (!from)
 		panic("Trying to free up swapper memory space");
 	size = (size + 0x3fffff) >> 22;
 	dir = (unsigned long *) ((from>>20) & 0xffc); /* _pg_dir = 0 */
 	for ( ; size-->0 ; dir++) {
 		if (!(1 & *dir))
 			continue;
 		pg_table = (unsigned long *) (0xfffff000 & *dir);
 		for (nr=0 ; nr<1024 ; nr++) {
 			if (1 & *pg_table)
 				free_page(0xfffff000 & *pg_table);
 			*pg_table = 0;
 			pg_table++;
 		}
 		free_page(0xfffff000 & *dir);
 		*dir = 0;
 	}
 	invalidate();
 	return 0;
 }
    Следующая функция использует лишь один системный вызов - fork().
 Копирование происходит блоками по 4 метра .
 Происходит заполнение page tables адресами .
 Физические страницы при этом маркируются read-only :   
 
 int copy_page_tables(unsigned long from,unsigned long to,long size)
 {
 	unsigned long * from_page_table;
 	unsigned long * to_page_table;
 	unsigned long this_page;
 	unsigned long * from_dir, * to_dir;
 	unsigned long nr;
 
 	if ((from&0x3fffff) || (to&0x3fffff))
 		panic("copy_page_tables called with wrong alignment");
 	from_dir = (unsigned long *) ((from>>20) & 0xffc); /* _pg_dir = 0 */
 	to_dir = (unsigned long *) ((to>>20) & 0xffc);
 	size = ((unsigned) (size+0x3fffff)) >> 22;
 	for( ; size-->0 ; from_dir++,to_dir++) {
 		if (1 & *to_dir)
 			panic("copy_page_tables: already exist");
 		if (!(1 & *from_dir))
 			continue;
 		from_page_table = (unsigned long *) (0xfffff000 & *from_dir);
 		if (!(to_page_table = (unsigned long *) get_free_page()))
 			return -1;	/* Out of memory, see freeing */
 		*to_dir = ((unsigned long) to_page_table) | 7;
 		nr = (from==0)?0xA0:1024;
 		for ( ; nr-- > 0 ; from_page_table++,to_page_table++) {
 			this_page = *from_page_table;
 			if (!(1 & this_page))
 				continue;
 			this_page &= ~2;
 			*to_page_table = this_page;
 			if (this_page > LOW_MEM) {
 				*from_page_table = this_page;
 				this_page -= LOW_MEM;
 				this_page >>= 12;
 				mem_map[this_page]++;
 			}
 		}
 	}
 	invalidate();
 	return 0;
 }
   При копировании страницы памяти оба - и источник и получатель -
 маркируются на read only . Следующая функция позволяет использовать дочернему 
 процессу страницу , которая помечена на read only , для записи :  
 
 void un_wp_page(unsigned long * table_entry)
 {
 	unsigned long old_page,new_page;
 
 	old_page = 0xfffff000 & *table_entry;
 	if (old_page >= LOW_MEM && mem_map[MAP_NR(old_page)]==1) {
 		*table_entry |= 2;
 		return;
 	}
 	if (!(new_page=get_free_page()))
 		do_exit(SIGSEGV);
 	if (old_page >= LOW_MEM)
 		mem_map[MAP_NR(old_page)]--;
 	*table_entry = new_page | 7;
 	copy_page(old_page,new_page);
 }	
 
 
    Перейдем к /fs/exec.c
   Что происходит , когда мы что-то набираем в командной строке ?
 Шелл берет эти строки , ложит их в 2-мерный массив и передает в качестве
 параметров функции execve . 
  argv и envp - это адреса в сегменте данных пользователя , поэтому при
 обращении к ним ядро будет пользоваться LDT .     
   Следующая функция делает быстрое копирование из ds:si в es:di .
 Для пользователя в es копируется 0x17 , для ядра - 0x10 .
 Данные копируются из ядра в пользовательское пространство :    
 
 #define cp_block(from,to) \
 __asm__("pushl $0x10\n\t" \
 	"pushl $0x17\n\t" \
 	"pop %%es\n\t" \
 	"cld\n\t" \
 	"rep\n\t" \
 	"movsl\n\t" \
 	"pop %%es" \
 	::"c" (BLOCK_SIZE/4),"S" (from),"D" (to) \
 	:"cx","di","si")
 
Оставьте свой комментарий !

Ваше имя:
Комментарий:
Оба поля являются обязательными

 Автор  Комментарий к данной статье