Тема |
Описание |
Доп. |
|---|---|---|
no_std Для написание прошивки, ядра или кода загрузчика |
||
Компилятор вставляет импорты основных примитивов по умолчанию. Что бы отключить стандартные импорты используйте #![no_implicit_prelude] |
|
|
Effective Rust |
no_stdЭто имеет смысл только в том случае, если все ящики, от которых вы зависите также no_std. В std есть две большие части:
Но когда мы отключаем std т.е. используем no_std то alloc нам нужно явно подключить (core остается и так) Rust поставляется со стандартной библиотекой, которая называется Атрибут Rust поддерживает конфигурации без полной стандартной библиотеки std, что позволяет использовать его в системах, не имеющих полноценной операционной системы (например, без файловой системы, без сети). Rust также поддерживает сборку кода для сред, где невозможно предоставить полную стандартную библиотеку, таких как загрузчики, прошивки и встраиваемые платформы в целом. При std компилятор вставляет импорты основных примитивов по умолчанию.
Что бы отключить стандартные импорты используйте
Но для no_std необходимо явно включать нужные импорты. Проверка зависимостей на no_stdТак как компилятор не сообщает что std а что no_std мы можем явно задать цель сборки в которой отсутвует std и тогда подтягиваемые зависимсоти с std сломают компиляцию, что даст нам понимание от чего избавится. Мы собираем проект под таргет, в котором вообще нет std, например: thumbv6m-none-eabi (ARM Cortex-M0, популярный no_std target)
Если у вас библиотека (lib.rs):
Если у вас бинарник, то лучше отключить std через features: Cargo features аддитивные — они только добавляют возможности, но никогда не должны выключать. Поэтому опция — добавляет std, а не отключает его. (Cargo объединяет features, выполняя логическое ИЛИ поэтому если один выключит а другой включи то по итогу получится features включена) По умолчанию включена фича std
Очень важно: Фича std только добавляет функциональность, а не отключает. Cargo.toml:
И в коде:
Сборка:
Вариант через cargo tree:
Нельзя так:
Потому что один пользователь включает no_std, другой нет → итог = включено → ломается. coreБиблиотека core содержит код который не выделяет память в heap. Поэтому структур данных Vec, Map, Sets там нет. Даже при разработке для самых ограниченных платформ многие фундаментальные типы из стандартной библиотеки остаются доступными. Например, Option и Result. Они по-прежнему доступны через Типы из core доступны для всех программ Rust автоматически. Однако, как правило, их необходимо явно allocДля использования heap в среде no_std должен быть аллокатор (например linked_list_allocator, buddy_system_allocator, jemalloc, ваш собственный…). Нужно включить crate alloc и тогда будут доступны структуры данных для работы с памятью heap
Тип std::vec::Vec на самом деле это alloc::vec::Vec Отсутствуют коллекции HashMap и HashSet так как для генерации хешей нужны возможности ОС. Но есть BTreeMap и BTreeSet. Отсутствует структура синхронизации std::sync::Mutex. Для многопоточного кода в no_std используют crate spin Если вы это сделали — у вас появляется heap и выделение памяти в no_std
Каждый вызов Vec::push() может вызвать аллокацию. Но Rust не предполагает что выделение памяти в heap может дать ошибку, т.е. Rust предполагает, что аллокатор не может провалиться. Нет способа обработать failure и продолжить выполнение, как в C (malloc → NULL). И это поведение Rust не подходит для embedded, kernel, или ограниченных систем. Rust начал добавлять альтернативы, которые возвращают Result:
Пока нет полноценного Vec::try_push(), поэтому приходится сначала резервировать память, а потом делать push().
Или можно не использовать heap и отключить выделение памяти. В некоторых системах (например Linux kernel, embedded): Можно отключить глобальную обработку OOM через Тогда любая попытка infallible аллокации станет ошибкой компиляции/сборки, если она случайно появится. Проблема сборки под no_stdRust должен знать, на какой процессор будет компилировать, для этого нужно выбрать цель, по умолчанию используется x86_64-unknown-linux-gnu в Linux. Но когда мы отключаем std #![no_std] то цель нужно явно указать. Цель нужна для правильной компиляции core/alloc под конкретный процессор. Аллокатор linked_list_allocator для блокировки (spinlock) использует spinning_top который нуждается в атомарных инструкциях (compare_exchange, compare_exchange_weak) на AtomicBool. Но цель сборки thumbv6m-none-eabi не поддерживают атомарные инструкции. Цель сборки thumbv7em-none-eabihf поддерживают атомарные инструкции и всё работает. Для симуляции no_std-бинарника, таргет без атомарных инструкций thumbv6m-none-eabi для проверки отсутствия std зависимостей: (нужен свой аллокатор)
Для no_std-бинарника без проверки отсутствия std зависимостей, таргет с атомарными инструкциями thumbv7em-none-eabihf: (используем библиотечный аллокатор linked_list_allocator)
|
|
libcore он предоставляет API-интерфейсы для языковых примитивов, таких как числа с плавающей запятой, строки и срезы, а также API-интерфейсы, которые предоставляют функции процессора, такие как атомарные операции и инструкции SIMD. Однако ему не хватает API-интерфейсов для всего, что связано с распределением памяти в куче и вводом-выводом. |
||
alloc crate spin |
Когда вы используете no_std, вы фактически отключаете поддержку многих возможностей стандартной библиотеки, но вы всё ещё можете использовать функциональность, предоставляемую alloc, если ваша среда поддерживает её. Вы можете использовать no_std без alloc, если вам не нужны динамические структуры данных, и вы хотите минимизировать зависимость от дополнительного кода. Втягивание ящика alloc позволяет многим знакомым друзьям обращаться к ним по их настоящим именам:
Благодаря этим возможностям становится возможным обеспечить no_std совместимость многих библиотечных ящиков, например, если библиотека не использует ввод-вывод или сетевое взаимодействие. Но в alloc нет коллекций HashMap и HashSet специфичных для std, поэтому используйте BTreeMap и BTreeSet Еще одним заметным отсутствием является отсутствие функций синхронизации, таких как std::sync::Mutex, который требуется для многопоточного кода ( пункт 17 ). Эти типы специфичны для std потому что они полагаются на специфичные для ОС примитивы синхронизации, которые не доступны без ОС. Если вам нужно написать код, который является no_std и многопоточным, сторонние контейнеры, такие как spin вероятно, ваш единственный вариант.
|
|
Ошибочное распределение |
К сожалению, стандарт Rust библиотека alloc включает в себя распространенное предположение, что Выделение памяти в куче не может завершиться неудачей, и это не всегда верное предположение и скорее всего, сведется к следующему: panic! и завершение программы при недостатке памяти в куче.
|
|
|
File .cargo/config.toml:
Build:
File main.rs:
|
|
global (heap) allocator |
|
|
Будет управлять версией компилятора
|
||
Портирование библиотеки std в no_std |
Портирование библиотеки std в no_std Если библиотека не поддерживает no_std, ее все равно можно перенести в среду без ОС, особенно для анализаторов форматов файлов и других рабочих нагрузок, не зависящих от ОС. Функциональность более высокого уровня, такая как обработка файлов, многопоточность и асинхронный код, может представлять собой более сложную задачу. В таких случаях такая функциональность может быть скрыта за флажками функций, чтобы по-прежнему обеспечивать базовую функциональность в сборке no_std. Чтобы перенести крейт std в no_std (core+alloc): В файле Cargo.toml добавьте стандартную функцию, затем добавьте эту стандартную функцию к функциям по умолчанию. Добавьте следующие строки в начало lib.rs:
|
|