1 В избранное 0 Ответвления 0

OSCHINA-MIRROR/rcore-os-rCore-Tutorial

Присоединиться к Gitlife
Откройте для себя и примите участие в публичных проектах с открытым исходным кодом с участием более 10 миллионов разработчиков. Приватные репозитории также полностью бесплатны :)
Присоединиться бесплатно
Клонировать/Скачать
part-6.md 12 КБ
Копировать Редактировать Web IDE Исходные данные Просмотреть построчно История
gitlife-traslator Отправлено 29.11.2024 21:14 7f8a4fd

Регулировка памяти

В предыдущем разделе мы увидели, что скомпилированная программа по умолчанию размещается начиная с позиции 0x11000:

start address: 0x0000000000011000
...
Program Header:
    PHDR off    0x0000000000000040 vaddr 0x0000000000010040 ...
    LOAD off    0x0000000000000000 vaddr 0x0000000000010000 ...
    LOAD off    0x0000000000001000 vaddr 0x0000000000011000 ...
   STACK off    0x0000000000000000 vaddr 0x0000000000000000 ...

Это связано с тем, что для обычных пользовательских программ код и данные обычно размещаются в нижнем адресном пространстве.

Для ОС-ядра обычно его адресное пространство размещается в верхнем адресе. И в QEMU-симуляции RISC-V DRAM-память имеет физический адрес, начинающийся с 0x80000000, размером 128 МБ (если хотите узнать больше, см. qemu/hw/riscv/virt.c файл, где определяется VIRT_DRAM, а также раздел «Экспериментальное руководство по обнаружению физической памяти» в части 2). Поэтому нам нужно настроить память программы и изменить её адрес ссылки.

Информация о памяти программы

Обычно программа делится на следующие сегменты:

  • .text — сегмент кода, содержащий ассемблерный код;
  • .rodata — сегмент только для чтения данных, который содержит только постоянные данные;
  • .data — сегмент инициализированных данных для чтения и записи, обычно используется для хранения глобальных переменных программы;
  • .bss — сегмент инициализированных нулём данных для чтения и записи. В отличие от сегмента .data, мы знаем, что он должен быть инициализирован нулём, поэтому в исполняемом файле достаточно записать размер этого сегмента и его местоположение, без необходимости записывать данные внутри него;
  • Stack — стек, используемый для хранения локальных переменных во время выполнения программы, а также для обработки вызовов функций; растёт от высокого адреса к низкому;
  • Heap — куча, используемая для поддержки динамического распределения памяти во время работы программы. Например, если вам нужно прочитать строку, вы не знаете её длину при написании программы, поэтому вы можете выделить память для этой строки только после того, как узнаете её длину во время выполнения.

Расположение этих сегментов определяет структуру памяти программы. Типичная структура памяти выглядит следующим образом:

Написание скрипта компоновщика

Мы используем скрипт компоновщика (Linker Script) для определения структуры памяти программы. Создаём файл os/src/linker.ld:

/* 有关 Linker Script 可以参考:https://sourceware.org/binutils/docs/ld/Scripts.html */

/* 目标架构 */
OUTPUT_ARCH(riscv)

/* 执行入口 */
ENTRY(_start)

/* 数据存放起始地址 */
BASE_ADDRESS = 0x80200000;

SECTIONS
{
    /* . 表示当前地址(location counter) */
    . = BASE_ADDRESS;

    /* start 符号表示全部的开始位置 */
    kernel_start = .;

    text_start = .;

    /* .text 字段 */
    .text : {
        /* 把 entry 函数放在最前面 */
        *(.text.entry)
        /* 要链接的文件的 .text 字段集中放在这里 */
        *(.text .text.*)
    }

    rodata_start = .;

    /* .rodata 字段 */
    .rodata : {
        /* 要链接的文件的 .rodata 字段集中放在这里 */
        *(.rodata .rodata.*)
    }

    data_start = .;

    /* .data 字段 */
    .data : {
        /* 要链接的文件的 .data 字段集中放在这里 */
        *(.data .data.*)
    }

    bss_start = .;

    /* .bss 字段 */
    .bss : {
        /* 要链接的文件的 .bss 字段集中放在这里 */
        *(.sbss .bss .bss.*)
    }

    /* 结束地址 */
    kernel_end = .;
}

Сегодня мы уже не можем ожидать, что весь код будет написан в одном файле. Во время компиляции наш компилятор и компоновщик автоматически генерируют структуру памяти для каждого файла. Здесь наша задача компоновщика — объединить структуры памяти отдельных файлов в окончательную структуру памяти ядра.

Сначала мы используем OUTPUT_ARCH для указания архитектуры, затем используем ENTRY для указания точки входа в _start функцию, то есть первой инструкции, которая будет выполнена программой. Мы не видим _start в этом скрипте компоновщика, но помним, что мы переписали точку входа _start для удаления зависимостей во время выполнения в предыдущей главе. Таким образом, скрипт компоновщика объявляет, что вся программа будет запускаться отсюда.

Весь скрипт компоновщика находится в блоке SECTION{ }, который содержит несколько операторов вида output section: { input section list }, каждый из которых описывает выходной сегмент всей программы, состоящий из входных сегментов различных файлов.

Мы можем использовать ( ) для обозначения того, что все входные сегменты, соответствующие требованиям в скобках, помещаются в текущую позицию. Внутри скобок мы можем напрямую использовать имена сегментов или включать подстановочные знаки *.

Отдельный . представляет собой текущий адрес (Location Counter), который можно присвоить для продолжения размещения различных сегментов с этого адреса. Если не присвоено значение, сегменты будут размещены рядом друг с другом с высоким адресом. Присвоение значения символу записывает адрес этого символа.

Здесь мы примерно понимаем, что делает этот скрипт компоновщика. Сначала он размещает различные сегменты, начиная с BASE_ADDRESS, равного 0x80200000, в порядке .text, .rodata, .data, .stack и .bss. Кроме того, мы записываем начальные и конечные адреса каждого сегмента, такие как начальный и конечный адреса .text сегмента, которые являются значениями символов stext и etext соответственно. Они будут использоваться позже.

[info] Почему это 0x80200000?

OpenSBI (если вы хотите узнать больше, обратитесь к разделу «Переписывание точки входа программы» этой главы) разместит себя по адресу 0x80000000 и после завершения инициализации перейдёт к 0x80200000. Поэтому _start должен находиться по этому адресу. .text обозначает сегмент кода и первым размещает _start (то есть .text.entry).

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

Использование скрипта компоновки

Чтобы использовать этот скрипт компоновки во время компиляции, мы добавляем следующую конфигурацию в файл .cargo/config:

# 使用我们的 linker script 来进行链接
[target.riscv64imac-unknown-none-elf]
rustflags = [
    "-C", "link-arg=-Tsrc/linker.ld",
]

Его функция заключается в передаче параметра -T во время компоновки для указания используемого скрипта компоновки.

Перекомпилируем и снова проверим сгенерированный исполняемый файл:

$ cargo build
...
    Finished dev [unoptimized + debuginfo] target(s) in 0.23s
$ rust-objdump target/riscv64imac-unknown-none-elf/debug/os -h --arch-name=riscv64

target/riscv64imac-unknown-none-elf/debug/os:   file format ELF64-riscv

Sections:
Idx Name          Size     VMA          Type
  0               00000000 0000000000000000 
  1 .text         0000000c 0000000080200000 TEXT 
  2 .rodata       00000000 ```
.data         00000000 000000008020000c TEXT
.bss          00000000 000000008020000c BSS
...

Формат файла — ELF64-riscv.

Дизассемблирование секции .text:

0000000080200000 stext: 80200000: 41 11 addi sp, sp, -16 80200002: 06 e4 sd ra, 8(sp) 80200004: 22 e0 sd s0, 0(sp) 80200006: 00 08 addi s0, sp, 16 80200008: 09 a0 j 2 8020000a: 01 a0 j 0

Программа правильно размещена по указанному адресу.

Теперь мы знаем, как будет выглядеть окончательная компоновка программы в памяти. В следующем разделе мы дополним сегмент, который не был определён в этом скрипте, и завершим компиляцию.

Опубликовать ( 0 )

Вы можете оставить комментарий после Вход в систему

1
https://api.gitlife.ru/oschina-mirror/rcore-os-rCore-Tutorial.git
git@api.gitlife.ru:oschina-mirror/rcore-os-rCore-Tutorial.git
oschina-mirror
rcore-os-rCore-Tutorial
rcore-os-rCore-Tutorial
master