QEMU-tech.html или технология QEMU


Часть 1. Введение
1.1 Возможности
QEMU - это быстрый эмулятор процессора, использующий портируемый динамический транслятор.
QEMU имеет 2 режима работы:
Полная эмуляция. В этом режиме QEMU эмулирует систему(обычно PC) полностью, включая процессор и периферию. Этот режим может использоваться для запуска другой ОС без перезагрузки или для отладки системного кода.
Эмуляция на уровне пользователя(Только Linux). В этом режиме QEMU может запускать linux-программы, написанные под один процессор на другом. Этот режим может быть использован для запуска Wine(http://winehq.org) или чтобы упростить кросс-компиляцию и кросс-отладку.
Так как QEMU не требует модуля в ядре для работы, его безопасно и легко использовать.
Общие возможности QEMU:
Эмуляция уровня пользователя или всей системы.
Использование динамической трансляции на "родной" код для увеличения скорости.
Работает на x86 и PowerPC хостах. Тестируется на архитектурах ARM, Sparc32,
Alpha и S390.
Поддержка самоизменяющегося кода.
Аккуратная поддержка исключений.
Виртуальный CPU это библиотека (libqemu) которая может использоваться в других проектах(пример использования в qemu/tests/qruncom.c).
Возможности пользовательского режима QEMU:
Преобразователь системных вызовов Linux, включая большинство ioctl.
Эмуляция clone() использует "родную" clone() процессора чтобы использовать диспетчер(планировщик) Linux для нитей(потоков).
Аккуратная обработка сигналов с помощью преобразования сигналов хоста в сигналы объекта эмуляции.
Возможности полной эмуляции:
QEMU также может использовать полный программный MMU для максимальной переносимости или использовать системный вызов mmap() хоста чтобы возпроизвести MMU объекта.
1.2. Эмуляция x86
Возможности эмуляции цели x86:
Виртуальный х86 процессор поддерживает 16-ти и 32-х битную адресацию с сегментацией. Эмулируются LDT/GDT и IDT. Режим VM86 также поддерживается для запуска DOSEMU.
Поддержка страниц(памяти?) размером более 4Кб в пользовательском режиме.
QEMU может эмулировать себя на x86.Программа тестирования процессора для Linux находится в tests/test-i386. Она может быть использована для тестирования других виртуальных машин.
Текущие ограничения QEMU:
Нет поддержки SSE/MMX (еще)
Нет поддержки x86-64
Системные вызовы IPC теряются
Права доступа и ограничения доступа к памяти не протестированы. К счастью, очень мало ОС использую их
На не-х86 хостах, doubleS используется вместо не стандартного 10 байтного long doubleS используемого на x86 для чисел с плавающей точкой для получения максимальной производительности.
1.3 Эмуляция ARM
Полная эмуляция ARM 7.
Поддержка NWFPE FPU включена в эмуляцию Linux.
Возможен запуск большинства программ Linux для ARM.
1.4. Эмуляция PowerPC
Полная эмуляция 32-х битного PowerPC, включая привелегированные инструкции, FPU и MMU.
Возможен запуск большинста программ Linux для PowerPC.
1.5. Эмуляция SPARC
Поддержка SPARC V8, исключая инструкции FPU.
Возможен запуск большинста программ Linux для SPARC.
Часть 2. QEMU изнутри
2.1. QEMU в сравнении с другими эмуляторами
Как bochs [3], QEMU эмулирует процессор х86. Но QEMU намного быстрее чем bochs так как он использует динамическую компиляцию. Bochs полностью привязан к эмуляции х86 РС, а QEMU может эмулировать несколько процессоров.
Как Valgrind [2], QEMU выполняет эмуляцию на уровне пользователя и использует динамическую трансляцию. Valgrind в большей cтепени отладчик памяти, а QEMU не поддерживает это. (QEMU может быть использован для выявления "скачкового" (bound) доступа к памяти как Valgrind, но не может отслеживать неинициализированные данные как Valgrind). Динамический транслятор Valgrind генерирует лучший код, чем QEMU(например он производит register allocation) но он привязан к х86 и не поддерживает исключений и системной эмуляции.
ЕМ86 [4] наиболее близкий к пользовательскому QEMU проект (и QEMU еще использует его код, например загрузчик файлов ELF). ЕМ86 ограничен на alpha-хостах и использует проприетарный и медленный интерпретатор(из FX!32 Digital Win32 code translator [5]).
TWIN [6] - это эмулятор Windows API типа Wine. Он менее аккуратен чем wine, но включает x86-интерпретатор защищенного режима чтобы запускать исполняемые файлы Windows. Такой подход обладает большим потенциалом т.к. большая часть Windows API выполняется nativly (вероятно имеется ввиду прямо на железе - прим. переводчика), но разработка его намного сложнее т.к. все структуры данных и параметры функций обмена между API и x86-кодом должны быть конвертированы.
User Mode Linux [7] был единственным решением до QEMU, чтобы запустить ядро LInux как процесс без патчей ядра хоста. Но User Mode Linux требует много патчей для гостевого ядра, а QEMU использует обычный ядра Linux. Цена этого - QEMU работает медленнее.
Новый Plex86 [8] PC Virtualizer сделан в том же духе что и qemu-fast системный эмулятор. Он требует пропатченого ядра Linux для работы(невозможно запустить то же ядро на обычном PC), но патчей на самом деле немного. Так как это виртуализатор PC (никакой эмуляции кроме некоторых привелегированных интсрукций нет), он может быть потенциально быстрее QEMU. Другая сторона медали - требуется увесистый ( и потенциально небезопасный) патч ядра.
Коммерческие виртуализаторы PC (VMWare [9], VirtualPC [10], TwoOStwo [11]) бычтрее чем QEMU, но требуют специфичных, проприетарных и потенциально небезопасных драйверов для хоста. Более того, они не способны обеспечить точную эмуляцию как может симулятор.

2.2. Переносимая динамическая трансляция
QEMU - динамический транслятор. Когда он встречает кусок кода, он переводит его в набор инструкций хоста. Обычно динамические трансляторы очень увесисты и процессорно-зависимы. QEMU использует несколько ухищрений которые делают его относительно легко портируемым и простым, тем не менее достигая хорошей производительности.
Основополагающая идея состоит в разделении каждой x86 инструкции на несколько простых инструкций. Каждая простая инструкция осуществляется отрезком кода на Си.(см. target-i386/op.c). Затем утилита компиляции(dyngen) берет соответствующий объектный файл (op.o) чтобы породить генератор динамического кода, который объединяет простые инструкции в функции(см. op.h:dyngen_code()). В сущности, процесс подобен [1], но больше работы производится во время компиляции.
Ключевая идея получения оптимальной производительности в том что постоянные параметры могут быть переданы простым операциям. Для этой цели, простые ELF relocations генерируются с помощью GCC для каждого постоянного параметра. Затем, утилита(dyngen) может определить relocations и вызвать соответствующий код на Си, чтобы их решить во время сборки динамического кода.
Поэтому, QEMU не сложнее портировать чем динамический линкер(компоновщик).
2.3. Размещение(allocation) регистров

Так как QEMU использует фиксированные простые инструкции, эффективного размещения регистров быть не может. Ведь т.к. процессоры RISC имеют много регистров, большая часть состояния виртуального пройессора может быть размешена в регистрах без тяжелой процедуры размещения регистров.
2.4. Оптимизации кода условий
Хорошая эмуляция кода условий(регистр EFLAGS на х86) - критическая точка в для получения хорошей производительности. QEMU использует "ленивое" вычисление кода условий: вместо вычисления кода условия после каждой инструкции, он просто хранит один операнд(CC_SRC),результат(CC_DST) и тип операции (CC_OP).
CC_OP почти никогда не задается в генерируемом коде т.к. он известен во время трансляции.
Для повышения производительность генерируемыми простыми инструкциями выполняется ход назад(см. target-i386/translate.c:optimize_flags()). Когда может быть доказано что код условий не требуется для следующей инструкции, он не вычисляется.
2.5. Оптимизации состояния процессора
х86 процессор имеет много внутренних положений, которые изменяют метод оценки им инструкций. Чтобы достигнуть хорошей скорости, на фазе трансляции считается что некоторые значения состояния виртуального процессора не могут изменяться. Например, если сегменты SS,DS,и ES имеют нулевую базу, то транслятор даже не будет генерировать добавление для сегментной базы.
2.6. Кэш трансляций
16-ти мегабайтный кэш содержит недавно использованные трансляции. Для простоты, он полностью очищается после наполнения. Блок трансляции содержит просто один базовый блок (блок x86-инструкций завершенный переходом или состоянием виртуального процесоора, которое транслятор не может считать постоянным).
2.7. Прямая цепь блоков
После выполнения каждого базового блока QEMU использует симулируемый Program Counter (PC) и другие значения состояния процессора(как например базовое значениесегмента CS) чтобы найти следующий блок.
Чтобы ускорить наиболее общие случаи в которых может побывать симулируемый PC,QEMU может пропатчить базовый блок чтобы он сразу переходил к следующему.
Наиболее портируемый код использукт непрямые переходы. Непрямой переход делаетпроще изменение цели перехода. На некоторых архитектурах(таких как х86 и PowerPC) JUMP патчится таким образом что цепь блоков не имеет преимуществ.
2.8. Самоизменяющийся код и аннулирование транслируемого кода
Самоизменяющийся код особое требование в эмуляции x86, т.к. аннулирование кэша инструкций не сигнализируется приложением когда код изменяется.
Когда транслируемый код генерируется для базового блока, соответствующая страница памяти хоста защищается от записи,если она уже не только для чтения(с помощью системного вызова mprotect()). Если же производится запись, Linux выдает сигнал SEGV. QEMU затем аннулирует весь транслированный код на странице и позволяет доступ на запись.
Корректное аннулирование кода производится эффективно при помощи поддержки списка соединений каждого транслируемого блока, содержащегося на данной странице. Обновляются и другие списки чтобы разовать прямую цепь блоков.
Хотя преимущества вызовов mprotect() важны, большинство программ MSDOS могут быть эмулируемы на приемлимой скорости при помощи QEMU и DOSEMU.
Заметьте, QEMU также аннулирует страницы транслируемого кода, когда он выявляет, что память изменена с помощью mmap() или munmap().
Когда используется программный MMU, аннулирование кода наиболее эффективно6 если данная страница кода аннулируется слишком часто из-за записи на нее, тогдастроится битовая карта показывающая весь код страницы. Каждое сохранение на эту страницу проверяет требуется ли аннулирование кода. Аннулирование кода избегается только если данные изменились.
2.9. Поддержка исключений
Когда встречаются исключения(например деление на 0) используется longjump().
Хостовые обработчики SIGSEGV и SIGBUS используется для обработки недопустимого доступа к памяти. Точное состояние процессора может быть получено т.к. все регистры x86 хранится в фиксированных регистрах хоста. Симулируемый program counter основан на ретрансляции соответствующего базого блока и поиске точки исключения хостового program counter'a.

Виртуальный процессор не может обнаружить точный регистр EFLAGS, т.к. в некоторых случаях он не подсчитывается для оптимизации кода. Это не особо важно, т.к. эмулируемый код может быть перезапущен в любом случае.
2.10. Эмуляция MMU
Для системной эмуляции QEMU использует системный вызов mmap() для эмуляции MMU нужного процессора. Это работает пока эмулируемая ОС не использует области памяти зарезервированные для хостовой ОС (например 0xc0000000 на Linux x86).
Чтобы можно было запускать любую ОС QEMU также поддерживает программный MMU. В этом режиме MMU транслирует виртуальный адрес в физический при каждом доступе к памяти. QEMU использует кэш адресов трансляции чтобы ускорить их.
Чтобы избежать уничтожение транслируемого кода каждый раз, когда MMU mappings изменяются, QEMU использует физически индексированный кэш трансляций. Это означает что каждый базовый блок индексируется своим физическим адресом.
Когда изменяются MMU mappings, происходит только cброс базовых блоков(т.к. базовый блок не может более переходить сразу к следующему).
2.11. Прерывания
Чтобы быть быстрее QEMU не проверяет каждый базовый блок если прерывание необработано. Вместо этого, пользователь асинхронно вызывает специфичную функцию чтобы сообщить что прерывание не обработано. Эта функция разрывает текущую цепь исполняющихся базовых блоков. Она гарантирует что выполнение скоро продолжится в главном цикле процессора. Затем главный цикл обрабатывает прерывание.
2.12. Эмуляция уровня пользователя
2.12.1. Трансляция системных вызовов Linux
QEMU включает в себя транслятор системных вызовов для Linux. Это значит чтопараметры системных вызовов конвертируются с исправлением endianness и 32/64 битissues(?). IOCTL конвертируются с помощью системы общих описаний.(см. ioctls.h и thunk.c).
QEMU поддерживает хостовый процессоры имеющий страницы более 4Кб. Он записывает все mappings, которые делает процесс и пытается эмулировать системный вызов mmap() если хостовый mmap() валится из-за "плохой" страницы.
2.12.2. Сигналы Linux
Нормальные сигналы и сигналы реального времени ставятся в очередь со своей информацией (siginfo_t) как это делается в ядре Linux. Затем делается запрос прерывания к виртуальному процессору. Когда он прерывается, один сигнал обрабатывается созданием frame stack в виртуальном процессоре как это делается в ядре Linux. Системный вызов sigreturn() эмулирует возвращение из виртуального обработчика сигналов.
Некоторые сигналы(напр. SIGALRM) приходят прямо с хоста. Другие синтезируются из исключений виртуального процессора, как SIGFPE когда производится деление на 0.(см.main.c:cpu_loop()).
Маска блокированного сигнала обрабатывается хостовым linux-ядром, поэтому большинство сигналов могут быть перенаправлены прямо хостовому ядру. Только системные вызовы sigaction() и sigreturn() нуждаются в полной эмуляции.(см. signal.c).
2.12.3. Системный вызов clone() и потоки
Системный вызов Linux clone() обычно используется для создания потоков. QEMU использует системный вызов clone() хоста так что один реальный поток создается для каждого эмулирующего потока. Один виртуальный процессор создается для каждого потока.
Заметьте, в настоящее время есть несколько проблем с блокировкой в QEMU. Например, кэш трансляций не защищен от повторного входа.
2.12.4. Самовиртуализация
QEMU может эмулировать себя. Хотя это не особо полезно, это важный тест мощи эмулятора. Самовиртуализация не простая задача, т.к. могут быть конфликты адресов. QEMU решает эту проблему тем что он является исполняемым ELF-объектом как и ld-linux.so ELF(интрерпретатор ELF). Поэтому он может быть перемещен во время загрузки.(вероятно имеются ввиду адреса - прим. переводчика).

Обновлено: 12.03.2015