Зачем выносить Linux-консоль из ядра: разбираемся с kmscon
Нажимаем Ctrl+Alt+F3, получаем чёрный экран с приглашением login: и обычно называем всё это «консолью» или «TTY».
Кто рисует буквы? Кто читает клавиатуру? Где здесь getty, а где terminal emulator? Почему kitty и Alacritty живут в userspace, а системная Linux-консоль исторически устроена иначе?
В статье я разбираю этот стек на работающем kmscon (преимущественно конечно потому, что мне очень интересно было посмотреть, как организован перенос консоли в userspace). Программа здесь нужна как стенд: её можно запустить на одном VT, посмотреть файловые дескрипторы и журнал, и увидеть границу между kernel VT, PTY, эмулятором терминала и DRM.
Что обычно называют консолью
Упрощённо классический путь выглядит так:
/dev/tty3 здесь не bash и не эмулятор. Это виртуальная консоль ядра. agetty печатает /etc/issue, выставляет скорость линии (на VT это формальность) и запускает login. Shell читает и пишет байты. Escape-последовательности вроде ESC[31m кто-то должен интерпретировать: в классической Linux console значительная часть этой работы исторически сидит в ядре. fbcon рисует в framebuffer; на современных GPU этот framebuffer часто даёт fbdev-эмуляция DRM-драйвера, но сама kernel console не ходит в KMS так, как это делает kmscon.
Короткий словарь, без которого дальше легко запутаться:
Что kmscon уносит в userspace
kmscon остаётся системной консолью: на обычном seat0 он привязан к конкретному kernel VT, например tty3, чтобы ядро переключало его вместе с остальными консолями. Эмуляция терминала, шрифты, клавиатура и отрисовка идут уже в userspace; на монитор картинка уходит через DRM/KMS или fbdev.
Это не схема исходников один в один, но она совпадает с устройством объекта terminal в v10.0.3:
struct kmscon_terminal {
struct tsm_screen *console;
struct tsm_vte *vte;
struct kmscon_pty *pty;
struct kmscon_font *font;
/* ... */
};
В одном процессе собраны state machine терминала (libtsm), PTY, шрифт и вывод. Видеоподсистема в этой сборке умеет drm2d, drm3d и fbdev; среди renderer'ов есть software bbulk и OpenGL ES gltex.
Оговорка, без которой легко написать лишнее: kmscon на seat0 не «удаляет kernel VT». Он занимает один VT как слот переключения и рисует уже своим renderer'ом. Соседние консоли могут по-прежнему быть обычным fbcon.
Запуск на tty
Первый запуск лучше делать не на tty1 (как минимум потому что некоторые дистрибутивы любят занимать его для запуска графического сервера) . Unit kmsconvt@.service занимает конкретный VT и конфликтует с getty на том же номере:
Conflicts=getty@%i.service
OnFailure=getty@%i.service
ExecStart=kmscon --vt=%I --no-switchvt
--no-switchvt не перехватывает активную консоль: процесс садится на tty3 и ждёт, пока этот VT станет передним. Conflicts при старте остановит getty@tty3, поэтому перед командой стоит глянуть, что там никого нет.
who
fgconsole
systemctl is-active getty@tty3.service
sudo systemctl start kmsconvt@tty3.service
Сразу после старта в журнале есть VT и шрифт, но ещё нет DRM:
NOTICE: using tty /dev/tty3
NOTICE: font_freetype: Using font Hack Regular
NOTICE: font_freetype: Using font Hack Bold
В /proc/$pid/fd то же самое: открыт /dev/tty3, /dev/dri/card0 нет. kmscon не забирает GPU, пока его VT неактивен.
После chvt 3 появляется строка, которая уже описывает реальный путь отрисовки:
NOTICE: terminal: Display [...] with backend [drm2d] text renderer
[bbulk] font engine [freetype]
Software-путь: DRM без 3D (drm2d), renderer bbulk, глифы через FreeType. chvt 1 вернул графический сеанс на место.
На рабочей машине я бы несколько дней пожил с kmscon только на дополнительном VT и только потом решал, нужен ли он на всех консолях. Это кусок recovery path.
Что видно снаружи процесса
Пока VT активен:
kmscon --vt=tty3 --no-switchvt
`- login -p
agetty в дереве нет. С 10.0.2 kmscon сам разбирает /etc/issue и запускает login.
Дескрипторы процесса kmscon после активации VT:
/dev/tty3
/dev/dri/card0
/dev/ptmx
/dev/input/event* # клавиатура и мышь
У login stdin/stdout/stderr смотрят в /dev/pts/2. Внутри сессии то же устройство:
/dev/pts/2
COLORTERM=truecolor
Shell сидит на PTY slave. /dev/tty3 держит kmscon как слот kernel VT. Байты от shell идут в PTY master, libtsm разбирает escape-последовательности, FreeType рисует глифы, bbulk кладёт их в DRM.
--reset-env по умолчанию включён, поэтому дочернему процессу kmscon отдаёт короткое окружение. COLORTERM=truecolor он выставляет сам.
printf 'English: Hello world\n'
printf 'Русский: Привет, мир\n'
printf 'CJK: 日本語 中文\n'
printf '\e[31mRED\e[0m \e[32mGREEN\e[0m \e[34mBLUE\e[0m\n'
Байты ушли в PTY. Корректная обработка UTF-8 и наличие глифа в выбранном шрифте это разные вещи.
С --hwaccel тот же VT идёт другим путём:
Display [...] with backend [drm3d] text renderer [gltex] font engine [freetype]
Консоль поднялась. Насколько gltex быстрее bbulk, без своего бенчмарка я цифр не ставлю. Man page обещает заметный выигрыш на новом железе.
setfont меняет шрифт kernel console. kmscon рисует сам, поэтому setfont ему безразличен. loadkeys тоже: раскладка идёт через libxkbcommon. Оба отличия прямо описаны в README.
Zoom (Ctrl++ / Ctrl+-) и scrollback (Shift+PageUp) в man заданы как штатные grab'ы: это уже поведение userspace-renderer'а, а не fbcon.
Что происходит по SSH без terminfo для kmscon
По умолчанию kmscon выставляет TERM=kmscon, а проект поставляет собственное описание terminfo.
$TERM едет на удалённую машину вместе с SSH PTY. Описание возможностей нужно уже там.
Если записи нет, приложения это видят сразу:
REMOTE_TERM=kmscon
infocmp: error: no match in terminfo database for terminal type
"kmscon"
tput: unknown terminal "kmscon" # exit 3
Тот же SSH с TERM=xterm-256color:
REMOTE_TERM=xterm-256color
tput colors
256
$TERM не имя бинарника, в котором запущена shell. Это идентификатор набора terminal capabilities. Приложение смотрит его через terminfo. Лечится установкой описания kmscon на удалённой стороне. Подменять тип терминала вручную на каждой SSH-сессии хуже: приложениям сообщают чужой набор возможностей.
То же чинят в разделе troubleshooting ArchWiki.
Кто выдаёт GPU и клавиатуру
Доступ к modesetting через primary DRM node и к сырым input devices нужно координировать: кто-то должен решить, какой сеанс сейчас активен, кому разрешено работать с GPU и клавиатурой и когда эти устройства нужно отдать другому сеансу.
Нужно понимать, какой процесс сейчас на переднем плане seat, когда он должен отпустить карту, и когда может взять её обратно.
kmscon умеет ходить в libseat (systemd-logind, elogind или seatd). Сборка с libseat опциональна, meson default false. Без неё остаётся собственная работа с VT. На практике это выглядит так:
старт с --no-switchvt на неактивном tty3: процесс есть, /dev/dri/card0 ещё не открыт;
chvt 3: у kmscon появляются /dev/dri/card0 и /dev/input/event*;
chvt 1: графический сеанс снова на месте.
Открытый /dev/dri/card0 показывает, что на активном VT kmscon получает доступ к DRM-устройству. Сам по себе этот fd ещё не доказывает статус DRM master. Но переключение обратно на vt1 показывает, что управление display path корректно возвращается графической сессии.
Fallback и где kmscon не нужен
OnFailure=getty@%i.service срабатывает. Если instance kmscon падает, systemd поднимает обычный getty на том же VT:
/sbin/agetty --noreset --noclear --issue-file=... - linux
systemctl enable kmsconvt@ делает autovt@.service alias на этот шаблон: автоматически создаваемые VT пойдут в kmscon. Сервисы, которые явно зависят от getty@.service, не изменятся. На машине, где tty1 это recovery, я бы так не делал с первого дня.
Где от kmscon мало прока:
headless, только SSH;
serial console;
облачная VM без GPU, на которую вы всё равно не смотрите.
Пока его VT активен, kmscon использует DRM/KMS display path и должен координировать доступ к primary DRM device с графической сессией. README предупреждает: второй display server/compositor не сможет просто независимо забрать KMS-управление той же картой. Для передачи карты есть kmscon-launch-gui; связка с современным Wayland/libseat это уже отдельная история.























