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

Смотреть 365 видео

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

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

03.11.10 03:05 dementiy

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):

    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
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'ка с текстом поста. Во-вторых, если вы найдете смысловые ошибки, то пишите об этом в комментарии, разберемся и исправим, а если же это будут грамматические ошибки или просто очепятки, то пожалуйста пишите в ЛС. Спасибо за внимание, на этом все =)


Теги:

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
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.10 22:52 # +1
Спасибо. Я как раз сегодня пересматривал свежим взглядом и обнаружил эти вызовы, правда еще внутрь не заглядывал - выспаться хочу, а не ночами голову ламать, а какэто оно так тут все закручено :)
Еще раз спасибо, за такие интересные материалы.

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

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

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

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

извеняюсь,не внимателен сегодня)
grandse 10.12.10 18:41 # +0
Нашел кстати одну мелкую ошибочку: не boot/misc.c, а boot/compressed/misc.c в тексте, хотя ссылка правильная

Посты Комментарии
Последние посты
    Посты Комментарии
    Последние комментарии
      Посты Комментарии
      Изменения
        Посты Комментарии Изменения Черновики Избранное
        Черновики (все)
          Посты Комментарии Изменения Черновики Избранное
          Избранное (всё)
            Посты Комментарии Изменения Черновики Избранное
            Лучшие блоги (все 151)
            Топ пользователей Топ блогов
            Топ пользователей Топ блогов
            Элита (все 3057 из 226 городов)
            Топ пользователей Топ блогов
            welinux.ru

            Смотреть онлайн бесплатно

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


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

            Online video HD

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

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

            Full HD video online

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

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

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