company_banner

Совместный запуск Linux и baremetal OS

    Недавно я выложил в сеть под BSD лицензией небольшой проект на 8 килострочек C кода. Официально это коллекция бенчмарков для моих клиентов — вендоров промавтоматики. Код очень специфический, и, на первый взгляд, малоприменим за пределами узкой области PLC и motion control. Но есть небольшая изюминка, на которой я не очень акцентировал внимание в статье на IDZ. В поставку бенчмарков включена baremetal среда для их исполнения. В этом посте я опишу, что это такое, и как ее можно использовать.

    Что такое baremetal OS? Это небольшой код, инициализирующий ядро/ядра, и передающий управление пользовательской программе, которую по какой-то причине требуется запустить без нормальной ОС и даже без ОСРВ. Естественно, в код этой программы приходится статически линковать библиотеки и драйвера для всех необходимых ей устройств… Этой весной на Хабре уже был цикл статей, посвященный созданию baremetal OS. Поэтому я не буду повторяться и описывать процедуры загрузки. Однако для полного понимания этого поста желательно перечитать две ключевых статьи той серии. В этой описано, как собрать, загрузить и запустить произвольную программу вместо ОС сразу из GRUB. А здесь — как запустить свой код сразу на нескольких ядрах. Еще могу порекомендовать англоязычные первоисточники — osdev и самые знаменитые примеры — tutorial'ы от Bran и от James Molloy.

    Во второй статье подробно описано, как запустить код инициализации нового ядра. Для этого надо послать на него Init IPI(Inter-Processor-Interrupt) и потом еще два Startup IPI. Отправление IPI инициируется простой записью нескольких байт по определенному физическому адресу. А что будет, если написать небольшой Linux user mode bootloader — программу, которая копирует бинарник по определенному адресу, и посылает другому ядру Init IPI, 2xSIPI, чтобы он загрузился в этот бинарник?

    Думаете, будет kernel panic? Ведь на этом ядре Linux уже выполняет процессы пользователя, idle процесс или прерывание. На самом деле сразу все не упадет, но система начнет работать нестабильно и вскоре зависнет. А что будет, если загрузить Linux только с одним ядром, или заоффлайнить остальные ядра на работающей системе? (Например, вот так:
    $ sudo echo 0 > /sys/devices/system/cpu/cpu1/online
    И повторить для каждого ядра, кроме 0.

    В этом случае конкуренции с планировщиком задач можно не опасаться. Но всю доступную физическую память Linux mm при загрузке уже нарезал на bucket'ы и справедливо считает своей. К счастью, и здесь есть воркэраунд. Даже два — можно написать модуль ядра, который вежливо попросит немного непрерывной физической памяти, поменять linker скрипт, чтобы положить .text и .data области бинарника в эту память. А можно сделать еще проще — попросить Linux при загрузке зарезервировать немного физической памяти по определенному адресу. Например, при загрузке ядра с опцией memmap=0x80M$0x18000000 ОС выделит 128 мегабайт непрерывной физической памяти, начиная с адреса 0x18000000. К сожалению, ядра Linux начиная с 3.1 иногда падают в самом начале загрузки с этим параметром. Сейчас как раз пытаюсь найти причину.

    Voilà, теперь мы можем держать Linux на нескольких ядрах, и произвольные baremetal программы на всех остальных. Но как это может пригодиться? Пусть у нас уже есть задача запустить какой-то код без ОС. Например, мне нужны были бенчмарки, работающие на разных x86 платформах и измеряющие разброс между средним временем выполнения кода, и наихудшим временем. x86 — вообще отнюдь не образец детерминированности, а если добавить еще и операционную систему… Даже если это компактная и шустрая ОСРВ, все равно не сразу очевидно, откуда берется latency и jitter. К тому же у каждого моего клиента своя ОСРВ, некоторые ненамного сложнее baremetal. Или если есть код с микроконтроллера, чувствительный к задержкам, который и так работает без ОС, и надо его просто портануть-перекинуть на одно из ядер x86 процессора. Если просто включить его kernel thread'ом на ядре, а все остальное на нем запретить, его все равно будут прерывать. С RT-Linux патчем все получается лучше, но он добавляет latency и иногда не совместим с другими полезными патчами.

    А чем Linux и baremetal вместе лучше, чем чистый baremetal образ, загружаемый бутлоадером (GRUB)? А с Linux удобнее! Сравните перезагрузку всей железки или просто пару shell команд в командной строке Linux хоста. Аналогично с отладкой — с Linux'а можно прекрасно читать-писать память baremetal OS, диагностировать железки в процессе работы. (Конечно, если осторожно!) Есть и некоторые минусы у этого подхода. Для запуска baremetal OS с Linux надо знать ACPI ID ядра, которому посылается IPI. Если вы думаете, что у первого ядра номер 0, у второго — 1, и т.д., то вы — оптимист. Я видел очень разные варианты :). Придется либо делать энумерацию, это описано в одном из постов, на которые я сослался, либо подбирать варианты. (И не забываем [выключать] Hyperthreading!). Другая возможная засада — все же недетерминизм. Если отключить в Linux все ядра, кроме первого, и на нем ничего серьезного не запускать, то измерить влияние Linux на производительность ядер, исполняющих baremetal код невозможно. Но 100% гарантии все же нет — при желании или случайно можно через общие ресурсы (кэш, контроллер памяти) сильно влиять на производительность baremetal кода. Например, попробуйте попереключать VGA console (Ctrl-Alt-F1, Ctrl-Alt-F2), измеряя производительность baremetal OS.

    Если кому-то интересно, как оно все работает, или есть идеи, как это можно практически использовать (лицензия BSD позволяет) — скачивайте исходники с IDZ или github (там свежее). Сама baremetal OS лежит в tools/src/baremetal. В первом каталоге — phymem лежит тул для чтения и записи физической памяти. А во втором — smallos — собственно рантайм. Там все заточено на прогон бенчмарков, так что придется применить напильник, чтобы их отрезать — просто выкинуть из smallos/Makefile ссылки на bench/*.o, и заменить в smallos/apps/mwaitTester.c код вызова бенчмарков своим кодом. Linux usermode бутлоадер лежит в smallos/linux_boot. Удачи!
    Intel 110,53
    Компания
    Поделиться публикацией
    Похожие публикации
    Комментарии 13
    • 0
      Спасибо за статью! Тема очень интересная. Я какое-то время назад разбирался с программированием гибридных ЦП/ЦСП OMAP3 от TI. Там тоже загрузчику Linux указывают зарезервированные под DSP/BIOS операционку области памяти.
      Не увидел в Вашей статье описание межпроцессорного взаимодействия. Как передавать сообщения туда-обратно? Можете про это поподробнее рассказать?
      • +1
        Шареная память, судя по всему.
        • 0
          Либо посылать IPI, либо через общую память. Кстати в поставке как раз есть тест bench/503.slots, в котором поднимаются 3 независимых baremetal thread'a и измеряется скорость обмена сообщениями через lockless queue на общей памяти.
        • 0
          Я вот утверждаю, что запросто можно на 64-битном многоядерном процессоре в 64-битной ОС запускать программы в режиме virtual 8086. Ну очевидно же, вот у нас например четыре ядра, три из них — в long mode, а одно — в 32bit protected mode, в котором выполняется v86-код.

          Короче, вполне реально, а то, что MS выпилили из 64-битной винды поддержку совместимости с 16-битными приложениями — это может быть вопрос экономической целесообразности или их лени, но никак не невозможности реализации.
          • 0
            Технически конечно возможно.

            Фичи от Microsoft комментировать не могу.
          • 0
            Я не могу сказать про x86, т.к. сама идея ОСРВ под x86 мне не очень понятна… Но под ARM давно работает вот такая связка: паравиртуализованный Линукс, как процесс ОСРВ Iguana L4.

            Iguana L4 идет как ядро ОС, pistachios — userspace для процессов L4. Wombat идет как набор патчей линукса для запуска ядра. Если я ничего не попутал, потому как очень давно работал с этим проектом.

            Пользователь линукс со своей стороны разницы не видит, только имеет дополнительный набор драйверов для общения с L4.

            К сожалению, со смертью автора проект заглох. Как минимум, опенсорсная его часть. Именно на форке iguana OS построены смартфоны от quallcomm.

            Другое увы, что iguana плохо документирована и также плохо портируется — достаточно большая часть кода написана на ассемблере.
            • 0
              Да, на ARM я тоже видел такую связку, только с threadX.

              Если реализации новых ARM будут догонять x86 по производительности, им придется идти на те же самые компромиссы, которые усложняют реализацию ОСРВ на x86.
              • 0
                > Если реализации новых ARM будут догонять x86 по производительности, им придется идти на те же самые компромиссы, которые усложняют реализацию ОСРВ на x86.

                Тут мы рискуем вступить на тропу холиваров и разругаться :)
                • 0
                  Холивар — тоже может быть хорошим делом, если при этом стороны узнают что-то новое. Я неплохо знаю, как устроены x86 платформы, но совсем не глубоко знаю ARM. Поэтому мне было бы интересно понять, как можно решать проблемы с плохой совместимостью SMT, power management, deep OOO pipilenes, shared cache(фич, которые хорошо помогают производительности) и предсказуемость времени выполнения кода, необходимого ОСРВ.
                  • 0
                    Что касается межядерного взаимодействия, я туда особо не лез — больше на микроконтроллерах специализируюсь. В теории — с ARMv7 есть аппаратная поддержка мьютексов, есть инструкции вроде VFE — остановить ядром выполнение задач, пока не будет сигнала от другого ядра. Про кеш статья недавно на хабре пробегала.

                    Основная эффективность в системе команд. Это очень хорошо продуманная архитектура с очень емким ассемблером. RISC — в идеале один такт на команду (если, конечно, отбросить mem latency и кеш). Это прямая адресация памяти без всяких страниц, сегментов и смещений. Никаких портов ввода/вывода — только зарезервированные адреса памяти. Для RTOS можно использовать MPU — это быстро и защищенно. Для не реалтаймовых ОС — MMU. С помощью него же можно делать виртуализацию любой степени вложенности. Аппаратная поддержка USER/SYSTEM стеков. Реализация вложенных прерываний с минимальным сохранением контекста.

                    Что касается power management — это возможность подключения/отключения питания на любой блок периферии отдельно. Возможность выключение тактования ядра процессора с, практически, полным отключением питания, программирование событий на просыпание, гибкое наращивание мощности на каждую шину и блок периферии отдельно, естественно, программно.

                    • +1
                      >если, конечно, отбросить mem latency и кеш
                      Так и в x86 mem latency и cache — причина 90% jitter. Я согласен, что оставшиеся 10% — тоже важно, и у ARM этих проблем нет, но считать циклы инструкций на АРМ конечно гораздо удобнее, но учитывая память и кэш — примерно так же бессмысленно, как и на x86.

                      Порта ввода-вывода на x86 — легаси.
            • 0
              Очень важная деталь: если мы говорим про идеальную изолированную от помех от чужого кода среду, то следует учитывать существование SMM, который без ведома вашей программы может отъедать произвольное количество ресурсов во благо USB-мышки и ANB.
              • 0
                Совершенно верно. Когда я делаю тест в своей лабе, я знаю точно, откуда могут прийти SMI и еще на всякий случай мониторю SMI counter. Надо бы кстати добавить в код бенчмарка, спасибо, что напомнили!

              Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

              Самое читаемое