Регулировка памяти
В предыдущем разделе мы увидели, что скомпилированная программа по умолчанию размещается начиная с позиции 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). Поэтому нам нужно настроить память программы и изменить её адрес ссылки.
Информация о памяти программы
Обычно программа делится на следующие сегменты:
Расположение этих сегментов определяет структуру памяти программы. Типичная структура памяти выглядит следующим образом:
Написание скрипта компоновщика
Мы используем скрипт компоновщика (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 )