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

Запускаем Linux на Python

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

На чем только уже не запускали Doom. Мы же будем запускать Linux. Да не где-нибудь, а на Python. Да-да, прямо внутри него, где в качестве среды выполнения будет выступать интерпретатор Python. Ну как... Не будем пытаться переписать ядро и другие части Linux на этот язык, а попробуем написать (точнее портировать) виртуальную машину на Python и уже в ней запускать ОС.

Начнем с позитивного, а именно с плюсов такого решения.

– Можно будет запустить Linux вообще везде, где есть интерпретатор Python.

– Можно использовать как бенчмарк конкретного интерпретатора.

– Веселимся, пока все это пишем и отлаживаем. Пожалуй, это самый главный плюс.

Минусы: будет работать оооочень не быстро (ну логично же).

Немного технических подробностей. Внезапно поработаем с нейросетями, посмотрим, что получится и насколько быстро будет работать.

Статья не претендует на какие-то сильно новые знания, и все это было сделано Just for Fun и ради эксперимента.

Приступаем!

На старт

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

Итак, эмулятор RISC-V, который умеет запускать образ Linux, у нас имеется. Первый — это простенькая виртуальная машина с поддержкой 32х-битного RISC-V процессора без MMU и прочих аппаратных «излишеств». Второй — это собранный для этой архитектуры такой же простенький Linux без поддержки MMU. Осталось дело за малым: портировать первый с Си на Python.

Можно, конечно, руками переписать на красивый код, но как будто это слишком рутинная операция в наше прогрессивное время. Попробуем вооружиться нейросетями, и пусть они впахивают, а не мы. Для перевода кода из одного языка программирования в другой существует, например, такой онлайн конвертер (не реклама, если что, одно из первых, что попалось в Google). Скармливаем наш код этому AI-конвертеру иии... он выплевывает наружу что-то вполне корректное. С кучей оговорок, конечно же, но то, что этот код похож на оригинал, уже радует. Не совсем красивый, каким мог бы быть. Например, рабочее, но сомнительное решение:

	elif (ir >> 12) & 0x7 == 1:
    	if rs1 != rs2:
        	pc = immm4
	elif (ir >> 12) & 0x7 == 4:
    	if rs1 < rs2:
        	pc = immm4
	elif (ir >> 12) & 0x7 == 5:
    	if rs1 >= rs2:
        	pc = immm4
	elif (ir >> 12) & 0x7 == 6:
	...

Ну да ладно, сами виноваты, что не пишем код сами. Главное, чтобы было корректно. А оптимизацию и рефакторинг оставим на потом.

Итак, у нас имеется переведенный с Си на Python код. Уже неплохо.

Внимание

С первой попытки ничего, конечно же, не заработало. «Переводчик» удалил почему-то части кода, которыми не знает, как пользоваться (а скармливал я ему по одному файлу). Быстренько допиливаем и запускаем.

Разбираться, где еще нейросеть недосмотрела, и вникать в тонкости работы архитектуры не хочется, поэтому пытаемся отлавливать ошибки по ходу работы. А по ходу дела начинают происходить все более изощренные баги, особенно в работе нашего виртуального RISC-V.

Сделаем такой трюк. Считаем, что вся загрузка ядра — это очень детерминированный процесс. Поэтому, сколько бы раз мы ни запускали ОС, мы должны получать на каждом шаге загрузки всегда одно и то же состояние системы. Состояние нашего эмулятора будет обусловлено всего двумя факторами:

– внутренними регистрами виртуального процессора;

– памятью виртуального процессора.

Вроде больше ничего. Плюс, конечно же, состояние периферии, но ее не так много и на начальном этапе загрузки она не используется.

Поскольку у нас есть оригинальная виртуальная машина на Си, которая точно так же работает, давайте логировать состояние системы там и одновременно в нашем эмуляторе на Python. Затем сравним эти логи и посмотрим, на каком шаге пошли отличия. Если такие отличия есть, нужно уже отлаживать конкретно этот шаг эмулятора.

Единственное уточнение: нет необходимости в сохранении всей памяти виртуального процессора на каждом шаге, так как считаем, что доступ к нашей памяти имеет только виртуальный процессор, а он может делать там изменения, только если происходят специальные команды записи в память, поэтому логируем только их.

Быстро дописываем код для логирования и там и там, ну и далее по циклу: запускаем-логируем-смотрим-дорабатываем и опять запускаем-логируем-смотрим-дорабатываем, пока все не запустится. Благо, в RISC-V команд процессора не так много и, соответственно, ошибок тоже не так много можно допустить.

Самые частые ошибки, которые сделал AI-переводчик следующие:

– Ошибки переполнения. Так как Int в Python условно безграничный, он может хранить бо'льшие значения, чем RISC-V (помним, что последний у нас 32х-битный). Поэтому нужно искусственно расставлять ограничения на длину числа после операций с регистрами.

– Отступы не всегда корректны в функциях, а в Python-e это важно. Просто молча правим.

– Некорректное поведение «переводчика». Например, это: 

uint32_t * dtb = (uint32_t*)(ram_image + dtb_ptr);
if( dtb[0x13c/4] == 0x00c0ff03 )

он преобразует в это:

  dtb = struct.unpack('<I', ram_image[dtb_ptr:dtb_ptr+4])[0]
  if dtb == 0x00c0ff03:

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

Марш

Через некоторое время отладки и запусков видим:

Ура, оно живое! На удивление, наш Linux достаточно быстро запускается, что не может не радовать.

Надо бы протестировать, как быстро такое решение работает в более конкретных цифрах. В образе Linux заботливо оставлен Coremark для тестирования производительности. Запустим его пару раз и сравним скорость работы Linux в виртуальной машине на Python и на Си. А еще, ради интереса, протестируем пару нестандартных Python интерпретаторов/компиляторов.

Итого, сравнительная табличка производительности для coremark:

Компилятор/интерпретатор

Результат в попугаях в Iterations/Sec

Visual C++ 14.0

798.258345

CPython 3.13

2.132803

CPython 3.12

2.418575

PyPy 3.10

4.362685

CPython 3.12+Nutika

3.552819

Не густо. Python не быстрый. Этого стоило ожидать. Улучшать и оптимизировать код нашей Python-виртуальной машины можно и, соответственно, улучшить результат, но примем результат, какой он есть.

Финиш

Эта статья получилась из разряда «что получится, если…». AI-переводчик сэкономил немало времени на разработку этого всего, но и добавил другой работы по исправлению. Но теперь всё позади, можем смело запускать Linux на Python. Пусть и с ограничениями по скорости, да и вообще, у нас виртуальная машина и Linux без поддержки MMU, но можем же.

В самом лучшем случае (при помощи JIT компилятора PyPy) такое решение почти в 183 раза хуже по производительности нативной виртуальной машины. И промолчим, что вообще-то можно нативно скомпилировать и запустить Linux на реальном железе и будет еще быстрее и разница будет еще существеннее. Но зато, наше решение полностью портативное и может запускаться в любой (или почти любой) Python-среде исполнения. А еще получилось крайне компактно — всего порядка 900 строк рабочего кода. Все исходники и инструкция по запуску, конечно же, есть на github.

Теги:
Хабы:
Всего голосов 11: ↑11 и ↓0+12
Комментарии4

Публикации

Работа

Data Scientist
41 вакансия

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