В статье разбирается принцип работы xvc0 как с точки зрения domU, так и с точки зрения dom0, а так же описывается, что с этой консолью делают дальше.
Сразу предупреждаю, топик интересен только тем, кто с Xen’ом плотно работает.
Консоль с точки зрения Xen’a
Консоль и XenStore — два устройства в виртуальной машине, которые не анонсируются через XenStore, а пишутся в святую святых — start info page домена. Штатным механизмом для всех остальных устройств является их анонс в XenStore. Понятно, что анонсировать XenStore в XenStore несколько неудобно, так что это устройство «должно быть с самого начала».
Консоль можно было бы поместить в список «обычных» устройств. Но его вынесли в особый класс и сделали «равным XenStore» ради удобства отладки и диагностики. Чем раньше ядро сможет писать на консоль (и мы сможем это читать), тем больше вероятность, что информации на консоли будет достаточно для определения причины поломки.
На самом-самом низком уровне консоль использует кольцевой буфер, причём размером в одну страницу памяти (4кб для i386/x86_64). Этот кольцевой буфер поделен в соотношении 1024/2048, первая половинка используется для ввода (буфер для «нажатых» кнопок), вторая для вывода (символы и esc-коды, которые выводятся на экран).
Что такое кольцевой буфер? Это структура данных, в которой адресное пространство замкнуто (после 4095 идёт 0, 1, 2…, 4095, 0, 1… и так до бесконечности). В буфере существуют два маркера — начало очереди и конец. Очевидно, что новые данные пишутся в конец, а читаются с начала. Или наоборот (это вопрос терминов). Проверяется только одно условие — конец не достиг начала. Или начало не достигло конца.
Оставлю алгоритмическую часть в стороне. Куда более важным является то, что страница памяти, которая используется для кольцевого буфера, доступна одновременно и dom0 и domU (в общем случае она доступна двум доменам, но в нашем конкретном случае один из доменов — dom0).
Этот момент трудно понять (и его объяснение сильно выходит за рамки описания «консоли»), но в кратце это выглядит так: одна и так же физическая страница памяти (грубо говоря, биты на планке памяти) имеют два адреса. Один — в dom0, второй в domU. Xen при обращении машины к памяти транслирует виртуальный адрес в физический. И делает это так, чтобы оба домена могли писать в одну и ту же физическую страницу. При этом адрес страницы у каждого свой.
«У каждого свой», кстати, не просто причуда. Если адрес страницы памяти для domU можно зафиксировать, то сделать это для dom0 не получится — вокруг куча domU, и понятно, что для консоли каждой из машин используется своя страница памяти.
Итак, у нас есть механизм передачи данных от domU к dom0 и обратно (этот механизм на самом деле используется практически всюду, но в нашем конкретном случае он используется в том числе и для работы консоли).
Второй механизм взаимодействия — это event channel, некий аналог прерываний в PV-режиме. На самом деле, эти два механизма, плюс hypercall — это всё, что есть в Xen’е для гостевых машин, так что консоль по своей сложности мало отличается от сетевого драйвера, например.
Откуда domU узнает о своей консоли?
Консоль с точки зрения domU
Когда виртуальная машина стартует, специальная программа в dom0 создаёт её домен. Эта программа называется domain_builder. Помимо загрузки кода ядра, она ещё заполняет start info page, информацию, по которой домен сможет найти всё остальное. Адрес этой страницы передаётся (в случае x86) в регистре ESI. На других архитектурах, понятно, методы другие.
Очень важным является то, что эта информация (о положении консоли и номере канала событий) не является константной — домен могут в любой момент мигрировать, что, фактически, означает, что у домена внезапно меняется содержимое start_info_page.
В этом месте должно было быть примерно 50-60кб объяснения, как именно работают ring buffer’ы и привязка к event channel’у, но с позволения читателя, эту часть я пропущу, так как рассказ идёт именно про консоль, а не про то, как работает Xen. Более подробный разбор вплоть до исходного текста каждого участка есть в замечательной книге «The Definitive Guide to the Xen Hypervisor».
Что же происходит с драйвером консоли?
Как у большинства устройств зена, драйвер делится на две половинки: backend и frontend.
Frontend работает в domU, и именно на него мы посмотрим. Этот драйвер, обнаружив соответствующие данные в start_info_page создаёт символьное устройство (если быть точным, его создаёт udev). Имя этого устройства — источник для приличного количества головной боли начинающих администраторов Xen, т.к. за время существования гипервизора оно менялось несколько раз, и руководства могут вполне себе нести ахинею в этом вопросе.
Имя может быть /dev/xvc0, /dev/hvc0, /dev/ttyS0. Первое — «xen virtual console», второе — «h? virtual console» — общее название для виртуальных консолей виртуализаторов в линуксе (как h расшифровывается я не скажу), ttyS0 — наследие ранней эпохи Xen’а, когда виртуальная консоль маскировалась под последовательный порт.
Итак, устройство создано. Что же ещё? Ядро при загрузке «знает» куда нужно писать сообщения времени загрузки — на виртуальную консоль.
Загрузка заканчивается… И начинающий администратор паникует, ибо «оно зависло». Запускается крон — и всё, виртуальная машина перестаёт реагировать на кнопки и не спрашивает логина.
Чтобы объяснить, что происходит (и что должно происходить) давайте подумаем, что происходит на обычной консоли по завершению загрузки. Процесс init читает inittab и запускает указанные в нём процессы (getty и им подобные). getty настраивает консоль (или последовательный порт) и запускает /bin/login, который и выводит долгожданное «login:». Если getty не запустился, то это означает, что login писать некому. «оно зависло».
Решение простое:0:12345:respawn:/sbin/getty xvc0 9600
в /etc/inittab.
Заметим, что имя xvc0 должно соответствовать тому, что создаёт драйвер. А драйвер создаёт то, что ему передали в виде extra через аргументы командной строки ядра (console=xvc0). Если не передали, то используется «по-умолчанию», и угадать его довольно сложно с бухты-барахты.
Итак, прописываем, загружаем, видим заветное login, пытаемся залогиниться рутом, и… нам говорят, что имя неправильное.
Дело в том, что login читает файл /etc/securetty для определения, с каких устройств можно логиниться. В некоторых дистрибутивах xvc0 не прописан, и нам нужно его прописать руками. Прописываем, ура. Логинимся.
Пардон, сбился со стиля, я пишу не руководство «как увидеть консоль», а описание того, что происходит.
Итак, что происходит в правильно настроенном domU?
- Драйвер читает данные из start_info_page
- Драйвер создаёт символьное устройство
- init запускает getty для этого устройства (это может быть и не init, кстати, запустить getty с нужными параметрами можно хоть из rc.local, хоть по ssh).
- getty запускает login
- login запускает -bash, который становится ответственным за control terminal для всех остальных процессов (грубо говоря, обрабатывает Ctrl-C, Ctrl-Z и т.д. подобающим образом).
- Баш дальше живёт обычной жизнью, запускает программы. Программы пишут в обычные для них stdout, читают из stdin и т.д., всё это попадает драйверу, от драйвера в блочный буфер, откуда попадает в dom0 к …
xenconsoled
(консоль с точки зрения dom0)
Тут мы приходим к второй, куда более интересной части консоли xen’а.
В dom0 слушает специальный демон xenconsoled (кстати, авторства IBM, а не XenSource). Наверное, можно написать ему альтернативу, но я не видел пока таких смельчаков.
Xenconsoled занимается всей ерундистикой, связанной с получением содержимого ring buffer’а и отсылки событий в event channel.
Для того, чтобы давать администратору домена доступ к консоли, xenconsoled использует механизм unix98, позволяющий создать псевдотерминал. Точнее, создаётся ДВА устройства, связанные с друг другом невидимым loopback’ом. Одно из них даже не появляется в виде устройства, а остаётся абстракцией с файловым хэндлером, а второе появляется в /dev/pty/. Фактически, всё, что будет записано в /dev/pty/X выйдет из этого хэндлера при чтении. Верно и обратное — что запишут в этот хэндлер, выйдет из /dev/pty/X. Я думаю, вполне понятно, что делает xenconsoled с данными из хэндлера. Пишет их в ring buffer консоли. И читает оттуда, записывая в этот же хэндлер.
При появлении нового домена xenconsoled пишет в xenstore для него путь к /dev/pty/X (например, /dev/pty/0, /dev/pty/1). Как только домен исчезает (ребут, выключение, миграция), псевдотерминал исчезает.
Что же делать с этим /dev/pty? Ну, собственно, то же, что и с любым com-портом, на обратном конце которого находится компьютер, маршрутизатор и т.д. Подключаться терминалкой. Пижоны могут использовать putty или другой графический терминал, остальные люди могут обойтись minicom’ом. Для зена написана специальная терминалка xenconsole (/usr/(local)/lib/xen/bin/xenconsole), которая позволяет отключаться по комбинации Ctrl-].
Именно её, кстати, запускает xm console (xl console тоже).
Магия раскрыта? Да. Дальше начинается ‘value added services’. Если мы предоставляем услуги виртуализации и нам хочется дать людям возможность посмотреть на консоль их виртуальной машины без выдачи им прав администратора на dom0…
Дальше всё очень просто. Альтернативой xenconsole является vncterm (специальная программа, которая «берёт» символьное устройство и делает из него VNC-подключение), позволяя смотреть на консоль через VNC. VNCterm цепляется на псевдотерминал, получает оттуда байты, рисует их (console_codes) и, если к VNC подключен пользователь, шлёт ему видеопоток. От него же он получает кнопки для засылки их в псевдотерминал (и далее по цепочке).
У такого метода доступа к консоли для клиентов есть несколько минусов: во-первых, VNC обычно не шифрован. Во-вторых, он требует отдельного пароля для подключения. В третьих, соединение рвётся при миграции или перезагрузке компьютера (т.к. домен новый, его «слушает» совсем другой VNCterm).
Графическая консоль?
Для графической консоли в Xen’е используется совсем другой механизм — framebuffer. Пискельная область, в которую может писать виртуальная машина, и откуда эти данные читает драйвер консоли. Данные консоли (разрешение и глубина цвета) анонсируются через xenstore. В отличие от изящной и компактной текстовой консоли, framebuffer очень большой. Например, в разрешении 1920х1050 он занимает аж 8 мегабайт. Плюс, из всей акселерации доступен только скроллинг изображения вверх.
Собственно, в Xen фреймбуффер был добавлен специально для HVM-режима (сильно за пределами этой статьи), а сам HVM-режим во многом был вызван существованием Microsoft Windows, которая даже в Core Server всё ещё не научилась грузиться в текстовую консоль. Впрочем, HVM успешно используется и для linux’ов, но возможности и производительность HVM уступают PV-режиму.
Ещё одной головной болью в framebuffer’е является мышь. Очевидно, что без мышки виндовый гуй не мил, так что приходится отдельно создавать ring buffer для клавиатуры, для мыши, и для обновлений framebuffer’а. При этом, в отличие от сравнительно спокойной клавиатуры, мышь требует куда большей скорости работы (как в смысле передачи сообщений о движении мыши, так и в смысле задержки при отрисовке реакции).
Наружу смотреть на framebuffer можно уже только одним методом — VNC, так как весь пласт текстовых терминалок оказывается не при делах. Это, кстати, объясняет, почему в Xen Cloud Platform режимом по-умолчанию для всех виртуальных машин является VNC — он получается одинаковый и для HVM, и для PV-режимов.
Третий путь
Просто для полноты картины я должен упомянуть и его — в domU можно «пробрасывать» физические устройства. Например, видеокарту. В этом случае домен оказывается один на один с обычной видеокартой и может делать с ней что хочет. Включая opengl и прочие directx’ы. Однако, видеокарта доступна только одному домену (две видеокарты — два домена и т.д.), плюс, про миграцию следует забыть, так как вероятность, что драйверу после саспенда удастся правильно инициализировать видеокарту несколько спорна. В реальной жизни (в серверной виртуализации) такой метод не используется.
Будущее текстовой консоли
В настоящий момент в Xen4 активно доделывают поддержку нескольких консолей. В отличие от «отладочной», главной консоли, они будут анонсироваться через XenStor, а по принципам своей работы будут не сильно отличаться от существующей «единственной».