Как стать автором
Обновить

Как я модель устройства в QEMU на Rust писал. Часть 1. Разбор интеграции Rust в QEMU

Уровень сложностиСредний
Время на прочтение4 мин
Количество просмотров982

Не так давно, с версии 9.2, в QEMU появилась возможность создания моделей устройств на языке Rust. Пока в режиме эксперимента.

Я люблю этот язык и не смог пройти мимо и не попробовать реализовать модель на нем. Целью преследовал обучение и удовлетворение любопытства. Я решил поделиться проделанными шагами и результатами. Возможно, это будет для кого-то полезным.

Повествование я решил разбить на несколько частей:

  1. Разбор интеграция Rust в QEMU (В данной части представлена только он).

  2. Добавление поддержки класса устройств PCI, написанных на Rust. На момент написания таковая реализация от разработчиков QEMU отсутствует, и пришлось добавить собственную.

  3. Реализация устройства PCI EDU.

За основу я взял модель устройства PCI EDU, предназначенную для обучения студентов написанию драйверов для этого класса устройств. "Отлично! Классный кандидат для переписывания на Rust", - подумал я и приступил к делу. Использовался QEMU v10.0.0 как самая свежая и содержащая наиболее солидный набор оберток над C API самого QEMU.

Прежде чем перейти непосредственно к переписыванию, я захотел разобраться в этапах сборки и линковки "растовой" части QEMU. Активируется интеграция простым добавлением параметра --enable-rust на этапе конфигурирования. Вот мои команды для настройки и сборки:

../configure --target-list=x86_64-softmmu --enable-kvm --enable-rust
make -j`nproc`

Дальше система сборки Meson все сделает, и на выходе получится исполняемый файл qemu-system-x86_64 (да, Meson имеет поддержку нескольких языков программирования, среди которых имеется Rust, что для меня было несколько неожиданным; то есть Meson выступает в качестве замены общепринятой в экосистеме Rust системе сборки Cargo).
Хорошо! Скомпилировал, включив Rust, - иду дальше. Перешел к анализу скрипта сборки meson.build.
Первое, что бросилось в глаза, - это конфигурирование создания биндингов к коду на C:

if have_rust
  # ...
  bindings_rs = rust.bindgen(
    input: 'rust/wrapper.h',
    dependencies: common_ss.all_dependencies(),
    output: 'bindings.inc.rs',
    include_directories: include_directories('.', 'include'),
    bindgen_version: ['>=0.60.0'],
    args: bindgen_args,
    )
  subdir('rust')
endif

На входе берется файл rust/wrapper.h. Тут же подключается поддиректория rust, которая и содержит всё, связанное с Rust в QEMU. В ней имеется: модели устройств hpet и pl011 и обвязки вокруг C API в виде крейтов qemu_api и qemu_api_macros. Взглянем на скрипт сборки hpet в rust/hw/timer/hpet/meson.build

_libhpet_rs = static_library(  'hpet',  files('src/lib.rs'),  rust_abi: 'rust',  dependencies: [qemu_api, qemu_api_macros],
)
rust_devices_ss.add(when: 'CONFIG_X_HPET_RUST', if_true: [declare_dependency(  link_whole: [_libhpet_rs],  dependencies: [qemu_api_macros],  variables: {'crate': 'hpet'},
)])

Скрипт описывает сборку крейта в статическую библиотеку с Rust ABI и добавляет ее как зависимость в rust_devices_ss, добавляя в variables пару ключ-значение: 'crate': 'hpet'(это важный момент, на который стоит обратить внимание). Пока остается неясным: как "растовые" библиотеки объединяется с основной частью QEMU? Смотрим meson.build в корне проекта дальше и находим:

if have_rust and target_type == 'system'
    target_rust = rust_devices_ss.apply(config_target, strict: false)
    crates = []
    foreach dep : target_rust.dependencies()
      crates += dep.get_variable('crate')
    endforeach
    if crates.length() > 0
      rlib_rs = custom_target('rust_' + target.underscorify() + '.rs',
                              output: 'rust_' + target.underscorify() + '.rs',
                              command: [rust_root_crate, crates],
                              capture: true,
                              build_by_default: true,
                              build_always_stale: true)
      rlib = static_library('rust_' + target.underscorify(),
                            rlib_rs,
                            dependencies: target_rust.dependencies(),
                            rust_abi: 'c')
    arch_deps += declare_dependency(link_whole: [rlib])
  endif
endif

Тут происходит несколько интересных вещей.

  1. Используется значение по ключу crate из списка variables, про который я чуть выше писал.

  2. Определяется custom_target, вызывающий rust_root_crate и передающий ему crates, содержащий список имен зависимостей: hpet, pl011 и т.д. На выходе получается файл 'rust_' + target.underscorify() + '.rs', содержащий, как следует из названия, какой-то исходный код на Rust.

  3. Определяется сборка статической библиотеки с C ABI из полученного на этапе 2 файла с названием "'rust_' + target.underscorify()" и зависимостями из rust_device_ss. Дальше надо понять, что делает rust_root_crate. Это оказалось несложно, поиски сразу привели меня к скрипту scripts/rust/rust_root_crate.sh:

for crate in $*; do
    echo "extern crate $crate;"
done

Выходит, он генерирует файл с кодом на Rust, содержащий только extern'ы переданных ему крейтов. Из этого и собирается статическая библиотека с C ABI, описанная выше.

❯ cat rust_x86_64_softmmu.rs
extern crate hpet;

❯ ls librust_x86_64_softmmu.a
librust_x86_64_softmmu.a

Догадки оказались верны, в директории build, в которой выполняется сборка, имеется и этот файл, и библиотека, полученная на его основе.

На этом шаге заканчивается процесс интеграции кода на Rust в QEMU - после создания библиотеки. Дальше выполняется линковка.

Так я разобрался, как происходит "скрещивание" кода на Rust с основной кодовой базой QEMU, какие имеются этапы. Вооружившись этими знаниями, я пошел дальше - к реализации враппера над классом устройств PCI.

Теги:
Хабы:
+5
Комментарии0

Публикации

Работа

Программист С
35 вакансий
DevOps инженер
29 вакансий
Rust разработчик
10 вакансий

Ближайшие события