Видео смотреть бесплатно

Смотреть молодые видео

Официальный сайт physbook 24/7/365

Смотреть видео бесплатно

dementiy 03.11.2010 03:05

CodingЗагрузка ядра ОС Linux

Рассмотрим как происходит загрузка ядра (рассматривалось ядро версии 2.6.35 и архитектура x86_32), но работу BIOS и загрузчиков, таких как GRUB или GRUB2, мы рассматривать не будем.
Для начала я проиллюстрирую схему организации памяти, приведенную в документации к ядру (linux/Documentation/x86/boot.txt):



К этой схеме время от времени мы будем возвращаться. Итак...

Boot loader или загрузчик — это программа, которая вызывается BIOS для загрузки образа ядра в оперативную память. Образ ядра является точной копией файла расположенного на жестком диске (vmlinuz-version или bzImage). Образ ядра разделен на две части:
небольшой код, который работает в реальном режиме и загружается ниже барьера в 640K (0x0A0000);
часть ядра, которая работает в защищенном режиме, загружается после первого мегабайта памяти (0x100000).Для того, чтобы понять что происходит ниже барьера в 640К начнем с содержимого файла linux/arch/x86/boot/header.S и рассмотрим небольшой участок кода в самом начале файла:

 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
...
.code16
.section ".bstext", "ax"
.global bootsect_start
bootsect_start:
ljmp $BOOTSEG, $start2
start2:
movw %cs, %ax
movw %ax, %ds
movw %ax, %es
movw %ax, %ss
xorw %sp, %sp
sti
cld
movw $bugger_off_msg, %si
msg_loop:
lodsb
andb %al, %al
jz bs_die
movb $0xe, %ah
movw $7, %bx
int $0x10
jmp msg_loop
bs_die:
xorw %ax, %ax
int $0x16
int $0x19
ljmp $0xf000,$0xfff0
.section ".bsdata", "a"
bugger_off_msg:
.ascii "Direct booting from floppy is no longer supported.\r\n"
.ascii "Please use a boot loader program instead.\r\n"
.ascii "\n"
.ascii "Remove disk and press any key to reboot . . .\r\n"
.byte 0
.section ".header", "a"
.globl hdr
hdr:
setup_sects: .byte 0
...
boot_flag: .word 0xAA55
...


Этот участок кода является загрузочным сектором (boot sector), и именно он находится в первых 512 байтах вашего vmlinuz, вы можете это проверить, использовав команду dd if=/boot/vmlinuz-version of=vmlinuz.img bs=512 count=1:



Его задача довольно скромная — вывести пользователю сообщение о том, что загрузка с дискет больше не поддерживается и перезагрузить систему. Загрузочный сектор, в соответствии с приведенной выше схемой памяти, должен располагаться по адресу X, то есть зависит от того, где его расположит загрузчик (qemu расположил загрузочный сектор по адресу 0x10000, от этого адреса я в дальнейшем и буду отталкиваться).
Рассмотрим два поля setup_sects и boot_flag:
setup_sects — размер setup кода по количеству 512 байтных секторов. Код в реальном режиме состоит из загрузочного сектора (512 байт) и setup кода. Таким образом, размер всего кода в реальном режиме составляет (setup_sects+1)*512. В файле vmlinuz (bzImage) по этому адресу ((setup_sects+1)*512) располагается начало кода в защищенном режиме.
boot_flag — содержит значение 0xАА55 (магическое число), то есть является сигнатурой загрузочного сектора.Вернемся к файлу header.S (код, представленный ниже, следует сразу после «магического числа» 0xАА55 и выполняется в том случае, если загрузка происходит с жесткого диска):

 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
...
.globl _start
_start:
.byte 0xeb
.byte start_of_setup-1f
...
start_of_setup:
...
movw %ds, %ax
movw %ax, %es
cld
movw %ss, %dx
cmpw %ax, %dx
movw %sp, %dx
...
calll main

setup_bad:
...


Сразу за определением сигнатуры загрузочного сектора следует двухбайтовый прыжок (он явно указывается двумя байтами, иначе ассемблер может сгенерировать здесь 3-байтовый прыжок, который сдвинет все остальные инструкции на неправильное смещение) к адресу start_of_setup, где устанавливается стек и заполняется нулями bss (неинициализированные данные), после чего вызывается функция main.
Прежде чем перейти к функции main, обратим внимание на некоторые поля (между метками _start и start_of_setup), которые содержит header.S (в приведенном выше листинге они отсутствуют):
header — магическая сигнатура «HdrS» (Header Signature).
version — содержит версию используемого протокола (boot protocol):
1
2
(gdb) x/1xh 0x10206
0x10206: 0x020a # версия протокола соответственно 2.10


kernel_version — содержит указатель на версию ядра (задана строкой):
1
2
(gdb) x/7cb 0x133b0 
0x133b0: 50 '2' 46 '.' 54 '6' 46 '.' 51 '3' 53 '5' 32 ' ' # 2.6.35


type_of_loader — тип загрузчика (LILO, GRUB и т.д.):
1
2
(gdb) x/1xb 0x10210 
0x10210: 0xb0


Большинство загрузчиков имеют свой идентификатор (ID). Значение 0xTV трактуется следующим образом T — идентификатор загрузчика, V — версия загрузчика. Для данного примера 0xb0 следует: 0xb — загрузчик Qemu, 0x0 — версия 0 (все ID загрузчиков можно посмотреть в файле linux/Documentation/x86/boot.txt).
code32_start — адрес для перехода в защищенный режим:
1
2
(gdb) x/4xb 0x10214 
0x10214: 0x00100000


ramdisk_image — содержит 32-битный линейный адрес местоположения ramdisk или ramfs:
1
2
(gdb) x/1xw 0x10218
0x10218: 0x01f9c000


ramdisk_size — содержит размер ramdisk или ramfs:
1
2
3
4
(gdb) x/1xw 0x1021c
0x1021c: 0x00053a99 # соответственно 342681 байт
$ ls -l debug
rw-rw-r--. 1 dimm dimm 342681 Aug 7 21:29 debug


cmd_line_ptr — содержит 32-битный указатель на параметры командной строки:
1
2
3
4
5
(gdb) x/1xw 0x10228 
0x10228: 0x00020000
(gdb) x/15cb 0x20000
0x20000: 114 'r' 111 'o' 111 'o' 116 't' 61 '=' 47 '/' 100 'd' 101 'e'
0x20008: 118 'v' 47 '/' 115 's' 100 'd' 97 'a' 0 '\000' 0 '\000'


cmdline_size — максимальная длина аргументов командной строки:
1
2
(gdb) x/1xw 0x10238
0x10238: 0x000007ff # соответственно 2047 байт

Описание и принимаемые значения всех остальных параметров вы можете найти в файле linux/Documentation/x86/boot.txt.

Итак, была вызвана функция main. Это первая функция, написанная на языке С, которая встречается на пути загрузки ядра. Ее определение находится в файле linux/arch/x86/boot/main.c:

 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
void main(void) 
{
/* First, copy the boot header into the "zeropage" */
copy_boot_params();

/* End of heap check */
init_heap();

/* Make sure we have all the proper CPU support */
if (validate_cpu()) {
puts("Unable to boot - please use a kernel appropriate "
"for your CPU.\n");
die();
}

...
/* Detect memory layout */
detect_memory();

...
/* Set the video mode */
set_video();

/* Parse command line for 'quiet' and pass it to decompressor. */
if (cmdline_find_option_bool("quiet"))
boot_params.hdr.loadflags |= QUIET_FLAG;

/* Do the last things and invoke protected mode */
go_to_protected_mode();
}


Рассмотрим некоторые из функций:
copy_boot_params() — копирует параметры загрузки, определенные в файле header.S («структура» hdr) в структуру boot_params.hdr (определена в файле linux/arch/x86/include/asm/bootparam.h):

1
memcpy(&boot;_params.hdr, &hdr;, sizeof hdr);


init_heap() — устанавливает конец «кучи» равным: heap_end = stack_end = esp — 0x200
validate_cpu() — данная функция проверяет возможности процессора для работы с данным ядром;
detect_memory() — функция detect_memory() использует прерывание int 15 и e820 (e801, 88) в качестве значения регистра ax для того, чтобы получить карту адресов памяти (System Adderss Map). В эту карту входит весь RAM (Random Access Memory) и диапазоны адресов физической памяти, зарезервированные BIOS.
Просмотреть эту карту можно при помощи команды dmesg:



Более детальное описание адресов находится в файле /proc/iomem.
set_video() — устанавливается видео режим. Функция set_video() определена в файле linux/arch/x86/boot/video.c:

 1
2
3
4
5
6
7
8
9
10
11
12
void set_video(void)
{
...
for (;;) {
if (mode == ASK_VGA)
mode = mode_menu();
if (!set_mode(mode))
break;
printf("Undefined video mode number: %x\n", mode);
mode = ASK_VGA;
...
}


Проверяется, был ли установлен режим опроса (vga=«ask» в качестве параметра командной строки), если да, то выводится меню со списком видео режимов (см. рис. ниже), затем устанавливается выбранный режим. Если в командной строке режим не был задан, то будет использован стандартный 80x25 (vga=«normal»).


go_to_protected_mode() — функция определена в файле linux/arch/x86/boot/pm.c и производит заключительные настройки перед переходом в защищенный режим:

 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
void go_to_protected_mode(void)
{
/* Hook before leaving real mode, also disables interrupts */
realmode_switch_hook();

/* Enable the A20 gate */
if (enable_a20()) {
puts("A20 gate not responding, unable to boot...\n");
die();
}

/* Reset coprocessor (IGNNE#) */
reset_coprocessor();

/* Mask all interrupts in the PIC */
mask_all_interrupts();

/* Actual transition to protected mode... */
setup_idt();
setup_gdt();
protected_mode_jump(boot_params.hdr.code32_start,
(u32)&boot;_params + (ds() << 4));
}


enable_a20() — включает адресную линию A20 для полноценной 32-битной адресации;
setup_idt()/setup_gdt() — устанавливаются Interrupt Descriptor Table (для реального режима таблица расположена по адресу 0x0, для защищенного режима этот адрес определяется регистром idtr) и Global Descriptor Table.
protected_mode_jump() — определена в файле linux/arch/x86/boot/pmjump.S и осуществляет переход по адресу, указанному в boot_params.hdr.code32_start (0x100000).Итак, мы перешли в защищенный режим по адресу 0x100000. Этот адрес служит точкой входя для функции startup_32, которая определена в файле linux/arch/x86/boot/compressed/head_32.S:

 1
2
3
4
5
6
7
8
9
10
11
12
ENTRY(startup_32)
...
call decompress_kernel
...

/*
* Jump to the decompressed kernel.
*/
xorl %ebx, %ebx
jmp *%ebp
...
ENDPROC(startup_32)


В функции startup_32 распаковывается (decompress) ядро, а затем осуществляется к нему переход. За распаковку ядра отвечает функция decompress_kernel() (linux/arch/x86/boot/compressed/misc.c). Если во время распаковки ядра не возникло ошибок, то на экране появится надпись «Decompressing Linux... Booting the kernel». Ядро может быть распаковано или по адресу 0x100000, или, в случае, если ядро было собрано с опцией «CONFIG_RELOCATABLE=y», по любому другому адресу выше 1MB (начиная с адреса 0x100000 и выше).
В любом случае осуществляется переход к распакованному ядру, а именно к функции start_kernel(), которая определена в файле linux/init/main.c:

 1
2
3
4
5
6
7
8
9
10
11
asmlinkage void __init start_kernel(void)
{
char * command_line;
extern struct kernel_param __start___param
, __stop___param
;

...
/* Do the rest non-__init'ed, we're now alive */
rest_init();
}


Если вы посмотрите на содержимое функции start_kernel(), то можете заметить, что она вызывает целую серию других функций, которые инициализируют различные подсистемы ядра и структуры данных. Часть функций с коротким описанием представлена в схеме (см. рисунок ниже), поэтому мы сразу перейдем к рассмотрению заключительного этапа — функции rest_init().



Функция rest_init() создает новый поток ядра, который, в конечном итоге вызывает программу пространства пользователя /sbin/init:

 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
static noinline void __init_refok rest_init(void)
__releases(kernel_lock)
{
int pid;

...
kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);
|
+--> static noinline int init_post(void) {
...
run_init_process("/sbin/init");
run_init_process("/etc/init");
run_init_process("/bin/init");
run_init_process("/bin/sh");
...
}
...
pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);

...
schedule();
...
cpu_idle();
}


Следует иметь ввиду, что выполняются не все вызовы run_init_process() в функции init_post(), так как, если вызов был удачным, то мы из него не возвращаемся. Если все вызовы провалились, то будет выведено сообщение «No init found. Try passing init= option to kernel. See Linux Documentation/init.txt for guidance» функцией panic(). Функция panic() останавливает инициализацию системы. Процесс init получает идентификатор равный 1 (pid = 1). Но init не всегда первый процесс в системе, как было сказано, возможно его не удастся запустить или, что более вероятно в качестве параметра командной строки был задан «init=/bin/sh», то sh получит идентификатор равный 1:



Затем создается еще один поток ядра — khtreadd (он имеет pid = 2) — «...Опосредованно взаимодействуя с помощью определенных API с данным потоком, различные части ядра могут ставить в очередь на создание новые потоки, которые и создает kthreadd...» (о потоках ядра можно прочитать на rflinux.blogspot.com).
Далее вызывается планировщик (с управлением процессов можно ознакомиться на http://welinux.ru/) и функция cpu_idle(). cpu_idle() является «idle» потоком для ядра (cpu_idle() это нулевой процесс, то есть pid = 0) и всегда находится в системе. Она передает управление планировщику, а если нет задач для выполнения, то сама занимает процессор в бесконечном поиске новой задачи.
На этом инициализация ядра заканчивается. Если был успешно запущен процесс init, то он продолжает работу по загрузке системы.

Некоторые ссылки:
Подробности процесса загрузки Linux
Исследуем процесс загрузки Linux
Перевод Linux-Init-HOWTO
P.S. Во-первых традиционная pdf'ка с текстом поста. Во-вторых, если вы найдете смысловые ошибки, то пишите об этом в комментарии, разберемся и исправим, а если же это будут грамматические ошибки или просто очепятки, то пожалуйста пишите в ЛС. Спасибо за внимание, на этом все =)


Тэги: boot kernel
+ 42 -
Похожие Поделиться

silent 03.11.2010 03:33 #
+ 6 -
Вот это да, спасибо!
Данил Гребень 03.11.2010 08:33 #
+ 6 -
WeLinux торт!
Sylar 03.11.2010 10:00 #
+ 2 -
Мясо. Спасибо
grandse 03.11.2010 10:48 #
+ 2 -
Спасибо большое. Очень классный материал - не знаю как удержать свое внимание на работе, а не на копании в приведенных Вами ссылках и указанных исходниках. Еще раз большое Спасибо!
total 03.11.2010 15:03 #
+ 0 -
Единственный годный тред в последнее время.

Спасибо.
m0nhawk 03.11.2010 17:02 #
+ 1 -
Хм... Надо будет предложить ввести "Жирный плюс" :)
razum2um 03.11.2010 17:13 #
+ -1 -
тов. dementiy - это просто как джекпот, аншлаг. какой там ресурс... я в рунете не встречал такого обилия и involve`мента в ассемблер на линукс и ядерный С.

Вам бы в Intel....
razum2um 03.11.2010 17:17 #
+ -1 -
это при том, что не находится людей (по комметам уже 5й(?) статьи сужу) способных по делу ответить и поговорить... ну нет слов. одни "торты" и эмоции.
dementiy 03.11.2010 18:44 #
+ 0 -
Вот. Спасибо Вам и всем комментаторам за приятные слова, но, как Вы подметили нет конструктивной критики. Конечно я всегда стараюсь проверять материал, который пишу (на это уходит большая часть времени). Во-первых, сам материал складывается из двух половинок: моих личных исследований и той информации, которую я могу подчерпнуть из блогов (в основном англо-язычных). Во-вторых я не так много знаю, поэтому могу ошибиться и конечно хотелось бы, чтобы кто-то в таких случаях мог меня поправлять и указывать на эти ошибки.
razum2um 03.11.2010 19:36 #
+ 0 -
а если не секрет, откуда интерес?
embedd`ите? портируете на еще одну архитектуру?
dementiy 03.11.2010 19:55 #
+ 2 -
Если честно, то я некоторое время пытался заниматься web'ом, но меня никогда не покидало чувство... абстракции наверное. Мне просто ближе что-то системное, для меня это интересней. А интерес просто есть, мне хочется знать - как это работает.
grandse 09.12.2010 04:16 #
+ 0 -
Только дошли руки прочитать статью более-менее вдумчиво. Огрмное спасибо за проделанную работу. Так шерстить код ядра Linux, просто слов нет...
Пытался копнуть кое-где поглубже чем описано в статье (сподвигли на это дело ссылки, приведенные Вами в конце - Вы некоторые детали опустили, а там вробе и описано, но версия ядра менее актуальна, никаких setup.S, video.S уже нет, а есть setup.ld, отвечающий за конечное компоновку функций в бинарник, да video.c, котрый вызывается в совсем другом месте, чем в той же статье на OpenNet и т.д. и т.п.). Вызвало еще большее уважение.
А Вы не расскопали процесс получения параметров жесткого диска, MC, APM, PS/2 на этапе работы в реальном режиме? Хочется еще больше подробностей и детализации, а то понял отсилы 20% кода в boot :) Хотя уже через пару версий ядра все может быть переписано до неузнаваемости - они как я посмотрю ребята шустрые :)
dementiy 09.12.2010 20:00 #
+ 0 -
Спасибо за комментарий. На счет параметров (MCA, APM), если посмотрите в функцию main, то возможно найдете то, что искали (я в эту сторону особо не копал):

arch/x86/boot/main.c:
void main(void)
{
...
/* Query MCA information */
query_mca();
...
/* Query APM information */
#if defined(CONFIG_APM) || defined(CONFIG_APM_MODULE)
query_apm_bios();
#endif
}
grandse 09.12.2010 22:52 #
+ 1 -
Спасибо. Я как раз сегодня пересматривал свежим взглядом и обнаружил эти вызовы, правда еще внутрь не заглядывал - выспаться хочу, а не ночами голову ламать, а какэто оно так тут все закручено :)
Еще раз спасибо, за такие интересные материалы.

А Вы не пробовали свои знания приложить к каким-то практическим задчам? М.б. патчики какие-то к ядру пытались писать, или еще как-нибуть его покрутить?
dementiy 09.12.2010 23:52 #
+ 0 -
Нее, не пробовал, я же не программист =) Как я говорил чуть выше, мне просто интересно, как что-то работает.
grandse 10.12.2010 12:26 #
+ 0 -
А чем же занимаетесь, если не секрет? По-моему, для "непрограммиста" у Вас очень хорошие познания в программировании :)
le087 03.11.2010 20:28 #
+ 1 -
Нереально здорово!

Не беспокойтесь за отсутствие критики. Я думаю со временем все будет. В том числе и лица, способные вести хорошие, аргументированные споры на Вашем уровне.
Lemures 04.11.2010 01:30 #
+ 0 -
Сейчас всё идёт к упрощению, и освоению линукса виндовс-заточенными пользователями, а вы с ассемблером:)

Статья очень занимательная, понравилась. Прочитав её, в голову приходит только одно - у Торвальдса котелок очень хорошо варил.
grandse 09.12.2010 04:20 #
+ 0 -
Не просто варил - они продолжают все дальше и дальше писать, допиливать.

В принципе не такая большая сложность написать простенький загрузчик, который будет выполнять похожие действия: проверил несколько параметров, подгрузил сжатое ядро, распаковал его, но на практике учесть все то, что учитывается в этом коде очень и очень сложно. Вон у детища Таненбаума до сих пор насколько я понимаю нет поддержки многопроцессорности, потому-что там привычные академические методы управления ресурсами не действуют :)
false 04.11.2010 02:14 #
+ 1 -
уточните,о какой именно функции идет речь,т.к startup_32() две штуки ) одна находится в arch/x86/boot/compressed/head.S вторая в arch/x86/kernel/head.S и обе они связанны с загрузкой,только первая работает до распаковки ядра,вторая - после)
false 04.11.2010 02:16 #
+ 0 -
Итак, мы перешли в защищенный режим по адресу 0x100000. Этот адрес служит точкой входя для функции startup_32, которая определена в файле linux/arch/x86/boot/compressed/head_32.S:

извеняюсь,не внимателен сегодня)
grandse 10.12.2010 18:41 #
+ 0 -
Нашел кстати одну мелкую ошибочку: не boot/misc.c, а boot/compressed/misc.c в тексте, хотя ссылка правильная
Eugeny 10.03.2011 19:19 #
+ 0 -
Только начинаю копать исходники Linux и возник вопрос.
...
.code16
.section ".bstext", "ax"
.global bootsect_start
bootsect_start:
ljmp $BOOTSEG, $start2
start2:
movw %cs, %ax
movw %ax, %ds
movw %ax, %es
movw %ax, %ss
xorw %sp, %sp
sti
cld
movw $bugger_off_msg, %si
msg_loop:
lodsb
andb %al, %al
jz bs_die
movb $0xe, %ah
movw $7, %bx
int $0x10
jmp msg_loop
bs_die:
xorw %ax, %ax
int $0x16
int $0x19
ljmp $0xf000,$0xfff0
.section ".bsdata", "a"
bugger_off_msg:
.ascii "Direct booting from floppy is no longer supported.\r\n"
.ascii "Please use a boot loader program instead.\r\n"
.ascii "\n"
.ascii "Remove disk and press any key to reboot . . .\r\n"
.byte 0
.section ".header", "a"
.globl hdr
hdr:
setup_sects: .byte 0
...
boot_flag: .word 0xAA55
...

На сколько я понимаю эта часть файла header.S является загрузчиком который находится в первом секторе HDD и которую считывает BIOS и запускает по адресу 7С00, и вроде этот код всегда будет перезапускаться и вызываться снова, а как управление передается на метку _start?
dementiy 10.03.2011 21:12 #
+ 0 -
Ох, девичья моя память =) Итак, Вы верно подметили, что это часть файла header.S, но данная часть кода выполняется только в том случае, если загрузка происходит с дискеты (поэтому данный код при загрузке вызываться не будет), если же загрузка происходит с диска, то код выполняется с метки _start. Где именно передается управление этой метке я не могу вспомнить (вроде как определяется загрузчиком, но.... могу только посоветовать взять Qemu + GDB и попробовать это выяснить самостоятельно). И в посте отсутствуют изображения (видимо с fastpic они пропали, надо будет их перезалить), поэтому дополнительно узурпируйте pdf.

В хорошем качестве hd видео

Онлайн видео бесплатно


Смотреть русское с разговорами видео

Online video HD

Видео скачать на телефон

Русские фильмы бесплатно

Full HD video online

Смотреть видео онлайн

Смотреть HD видео бесплатно

School смотреть онлайн