Search     or:     and:
 LINUX 
 Language 
 Kernel 
 Package 
 Book 
 Test 
 OS 
 Forum 
 iakovlev.org 
 Kernels
 Boot 
 Memory 
 File system
 0.01
 1.0 
 2.0 
 2.4 
 2.6 
 3.x 
 4.x 
 5.x 
 6.x 
 Интервью 
 Kernel
 HOW-TO 1
 Ptrace
 Kernel-Rebuild-HOWTO
 Runlevel
 Linux daemons
 FAQ
NEWS
Последние статьи :
  Тренажёр 16.01   
  Эльбрус 05.12   
  Алгоритмы 12.04   
  Rust 07.11   
  Go 25.12   
  EXT4 10.11   
  FS benchmark 15.09   
  Сетунь 23.07   
  Trees 25.06   
  Apache 03.02   
 
TOP 20
 Linux Kernel 2.6...5170 
 Trees...940 
 Максвелл 3...871 
 Go Web ...823 
 William Gropp...803 
 Ethreal 3...787 
 Gary V.Vaughan-> Libtool...774 
 Ethreal 4...771 
 Rodriguez 6...766 
 Ext4 FS...755 
 Clickhouse...755 
 Steve Pate 1...755 
 Ethreal 1...742 
 Secure Programming for Li...732 
 C++ Patterns 3...716 
 Ulrich Drepper...698 
 Assembler...695 
 DevFS...662 
 Стивенс 9...651 
 MySQL & PosgreSQL...632 
 
  01.01.2024 : 3621733 посещений 

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")
 
Оставьте свой комментарий !

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

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