Регістри sBPF та модель пам'яті
Коли ваша програма sBPF виконується, вона працює з двома основними механізмами зберігання: 11 високошвидкісними регістрами, вбудованими в процесор, та організованими областями пам'яті, які містять все інше.
Registers
Регістри — це ніби 11 пронумерованих комірок зберігання (від r0
до r10
), вбудованих безпосередньо в процесор. Кожен містить одне 64-бітне значення і забезпечує миттєвий доступ без затримок. Коли ви виконуєте add64 r1, r2
, процесор негайно отримує значення з обох регістрів і виконує обчислення.
Компроміс простий: у вас є лише 11 комірок загалом, тому вам потрібно використовувати їх стратегічно для значень, з якими ви активно працюєте.
sBPF призначає кожному регістру певну роль:
Регістр | Роль | Зберігається викликаним? | Примітки щодо використання |
---|---|---|---|
r0 | Значення, що повертається | Ні | Результати функцій, значення, що повертаються системними викликами |
r1 | Вказівник на вхідний буфер | Ні | Вказує на 0x400000000 при вході |
r2-r5 | Тимчасові/аргументи | Ні | Аргументи допоміжних функцій, тимчасові значення |
r6-r9 | Загального призначення | Так | Повинні зберігатися між викликами |
r10 | Вказівник кадру | Так, тільки для читання | Основа стеку, не можна змінювати напряму |
Регістри від r6
до r9
є збереженими викликаним, що означає, що їхні значення зберігаються під час викликів функцій. Використовуйте їх для важливих даних, які повинні зберігатися під час викликів функцій.
Решта регістрів (r0
-r5
) перезаписуються під час системних викликів та викликів функцій.
Регістр r10
є "особливим", оскільки він служить вказівником кадру, вказуючи на основу вашого стекового простору і залишаючись доступним лише для читання. Ви отримуєте доступ до змінних стеку, використовуючи від'ємні зміщення, такі як [r10 - 8]
та [r10 - 16]
.
Пам'ять
У той час як регістри зберігають активно використовувані значення, пам'ять зберігає все інше. sBPF організовує пам'ять у фіксовані регіони з однаковими макетами для всіх програм:
Регіон | Початкова адреса | Призначення | Розмір/Примітки |
---|---|---|---|
Text | 0x100000000 | Код та дані тільки для читання | Бінарний файл програми |
Stack | 0x200000000 | Локальні змінні | 4 КіБ на кожен кадр стеку, з максимальною глибиною виклику 64 |
Heap | 0x300000000 | Динамічне виділення | 32 КіБ |
Input | 0x400000000 | Параметри програми | Серіалізовані облікові записи та дані інструкцій |
- Текстовий регіон містить ваш виконуваний код та дані тільки для читання, такі як рядкові константи. Дані, визначені з директивами
.quad
, зазвичай розміщуються тут. - Регіон стеку містить локальні змінні. З
r10
, що вказує на базу стеку, ви отримуєте доступ до локальних змінних, використовуючи від'ємні зміщення:[r10 - 16]
,[r10 - 24]
тощо. - Вхідний регіон містить параметри вашої програми. При вході
r1
вказує на цей регіон (0x400000000
), дозволяючи вам читати серіалізовані дані облікових записів та параметри інструкцій, передані вашій програмі.
Цей фіксований макет забезпечує три ключові переваги: безпеку через ізоляцію пам'яті між програмами, детермінізм з однаковими адресами на всіх валідаторах та продуктивність завдяки оптимізаціям компілятора для відомих розташувань.
Використання регістрів і пам'яті
Ось як регістри та пам'ять працюють разом на практиці:
.globl entrypoint
entrypoint:
// On entry: r1 points to input data at 0x400000000
ldxdw r0, [r1 + 0] // Load first 8 bytes from input into r0
mov64 r2, 42 // Put 42 in register r2
add64 r0, r2 // Add them: r0 = r0 + r2
stxdw [r10 - 8], r0 // Store result on stack
mov64 r0, 0 // Return success
exit
Ця програма демонструє типовий шаблон:
- Використовує регістр
r1
(вказівник на вхідні дані) для читання з пам'яті - Використовує регістри
r0
таr2
для обчислень - Використовує регістр
r10
(вказівник на кадр) для доступу до пам'яті стеку - Повертає результат у регістрі
r0
Робочий процес слідує послідовному шаблону: завантаження даних з пам'яті в регістри, виконання обчислень з регістрами, а потім збереження результатів назад у пам'ять за потреби.
Використання стеку
Стек працює з r10
як вказівником кадру, що вказує на базу вашого поточного кадру стеку (найвища адреса). Локальні змінні використовують від'ємні зміщення:
// Store a value on the stack
mov64 r0, 42
stxdw [r10 - 8], r0 // Store at first stack slot
// Load it back
ldxdw r1, [r10 - 8] // Load from first stack slot
Слоти стеку зазвичай мають ширину 8 байтів, тому послідовні змінні використовують зміщення, такі як [r10 - 8]
, [r10 - 16]
, [r10 - 24]
і так далі.
Вхід та вихід програми
Виконання вашої програми починається з символу, позначеного як .globl
(зазвичай точка входу). Початковий стан регістрів є мінімальним і передбачуваним:
.globl entrypoint
entrypoint:
// On entry:
// r1 = 0x400000000 (input buffer pointer)
// r10 = 0x200001000 (0x200000000 stack start + 0x1000 4KiB frame size)
// r0, r2-r9 = 0 (all other registers zeroed)
// Your program logic here
mov64 r0, 0 // Success code (0 = SUCCESS)
exit // Return to runtime
Поведінка виходу залежить від глибини виклику:
- На глибині виклику 0, вихід завершує програму з
r0
як кодом результату. - На глибших рівнях виклику, вихід діє як оператор повернення, відновлюючи регістри, що зберігаються викликаючою стороною (
r6
-r9
) та вказівник кадру викликаючої сторони (r10
) перед продовженням виконання за адресою повернення.
Середовище виконання автоматично обробляє налаштування та завершення через неявний код прологу та епілогу, включаючи конфігурацію початкового стану регістрів та кінцеву обробку повернення.