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

Кольцевой буфер ядра - это не файл на диске и не сервис с daemon-процессом. Это участок оперативной памяти фиксированного размера, в который ядро непрерывно пишет диагностические сообщения через единственный механизм - функцию printk(). Когда буфер заполняется, новые сообщения вытесняют самые старые. Отсюда и название: буфер замкнут в кольцо, и данные в нём никогда не накапливаются бесконечно. Это архитектурное решение, принятое осознанно: ядро не должно тратить ресурсы на хранение диагностики, и никакой объём сообщений не способен исчерпать память системы.

Утилита dmesg - просто читатель этого буфера. Она существует с самых ранних версий Linux и за десятилетия обросла богатым набором опций фильтрации и мониторинга. Но буфер живёт в ядре независимо от того, запущен ли dmesg.

Как устроен кольцевой буфер внутри ядра

Размер буфера определяется на этапе компиляции ядра параметром CONFIG_LOG_BUF_SHIFT. Итоговый размер вычисляется как 2^CONFIG_LOG_BUF_SHIFT байт. При значении 17 это даёт 128 КБ, при значении 18 - 256 КБ. Диапазон допустимых значений - от 12 (4 КБ) до 21 (2 МБ). На системах с несколькими CPU ядро автоматически увеличивает буфер при загрузке, поскольку многоядерные системы генерируют значительно больше диагностических событий в период инициализации.

Каждая запись в буфере - это не просто строка текста. Начиная с ядра 3.5, формат записи включает временну́ю метку в наносекундах от момента загрузки, числовой уровень важности, идентификатор подсистемы (facility) и текст. Такая структура позволяет фильтровать и сортировать сообщения при выводе, не теряя контекст о том, когда и откуда пришло каждое сообщение.

До ядра 3.5.0 единственным способом читать буфер был системный вызов syslog() через /proc/kmsg. У этого подхода был неприятный побочный эффект: чтение было деструктивным, прочитанные записи не возвращались повторно, и два одновременно запущенных dmesg делили буфер между собой. Начиная с версии 3.5, основным интерфейсом стал /dev/kmsg, лишённый этого ограничения: одни и те же записи читают многократно разными процессами независимо.

# Параметр конфигурации текущего ядра
cat /boot/config-$(uname -r) | grep CONFIG_LOG_BUF_SHIFT

# Размер буфера косвенно через journald
journalctl -k --disk-usage 2>/dev/null

# Сырые данные буфера в нативном формате
dd if=/dev/kmsg iflag=nonblock 2>/dev/null | head -5

Уровни важности сообщений и фильтрация вывода

Каждое сообщение printk() помечается одним из восьми уровней важности. Это не условная классификация и не просто удобство для администратора - уровень напрямую определяет, будет ли сообщение выведено на консоль прямо во время работы ядра или только записано в буфер. Уровни пронумерованы от 0 до 7, где 0 - наивысшая критичность:

  • KERN_EMERG (0) - система неработоспособна
  • KERN_ALERT (1) - требуется немедленное действие
  • KERN_CRIT (2) - критические условия
  • KERN_ERR (3) - ошибки
  • KERN_WARNING (4) - предупреждения
  • KERN_NOTICE (5) - значимые, но нормальные события
  • KERN_INFO (6) - информационные сообщения
  • KERN_DEBUG (7) - отладочные сообщения

Параметр console_loglevel в /proc/sys/kernel/printk определяет порог вывода на консоль: сообщения с уровнем строго ниже этого значения выводятся немедленно. Файл содержит четыре числа: текущий уровень, уровень по умолчанию, минимально допустимый и уровень при загрузке. На production-серверах значение нередко снижают до 4 или 3, чтобы консоль не засорялась информационными сообщениями.

# Текущие настройки loglevel
cat /proc/sys/kernel/printk

# Вывести только ошибки и критические события
dmesg --level=err,crit,alert,emerg

# Только предупреждения и выше
dmesg --level=warn+

# Только сообщения ядра, без userspace событий
dmesg --facility=kern

# Декодировать числовые коды в читаемые префиксы уровней
dmesg -x

Временны́е метки и навигация по буферу

Временны́е метки в кольцевом буфере - источник распространённой путаницы для тех, кто сталкивается с ними впервые. По умолчанию ядро хранит монотонное время в секундах с момента загрузки. Это время не привязано к реальным часам, не учитывает смену часового пояса и, что важнее, не обновляется корректно после выхода системы из режима сна (suspend/resume). Если ноутбук засыпает и просыпается, отметки времени в dmesg после пробуждения могут выглядеть так, словно пауза между событиями составила доли секунды, хотя на самом деле прошло несколько часов.

Флаг -T конвертирует монотонные метки в человекочитаемый формат, но именно по причине suspend/resume эта конвертация не всегда точна. Для серверов без режима сна это не проблема - там -T работает надёжно и превращает безликие [12345.678] в понятные даты и время.

# Читаемые временные метки
dmesg -T

# ISO 8601 формат - удобен для grep и скриптов
dmesg --time-format=iso

# Дельта между событиями - полезна при анализе последовательности
dmesg -e

# Сообщения за последний час
dmesg --since "1 hour ago"

# Сообщения между двумя точками времени
dmesg --since "2 hours ago" --until "1 hour ago"

# Мониторинг новых сообщений в реальном времени
dmesg --follow

Режим --follow превращает dmesg в живой монитор событий ядра. Подключить USB-устройство, загрузить модуль, воспроизвести сетевую ошибку - и сразу видеть реакцию ядра без перезапуска команды. Это принципиально ускоряет цикл отладки оборудования и драйверов.

Контроль вывода на консоль и управление буфером

В некоторых сценариях поток сообщений ядра на консоли мешает работе. При отладке сетевого стека с включённым подробным логированием консоль буквально захлёбывается сообщениями, перекрывая командную строку. При автоматизированных тестах ядра сотни сообщений в секунду делают вывод нечитаемым. Управление уровнем вывода на консоль решает эту проблему без потери данных: все сообщения по-прежнему пишутся в кольцевой буфер, изменяется только то, что появляется на экране.

# Отключить вывод всех сообщений кроме паники ядра
dmesg -n 1

# Включить вывод предупреждений и выше на консоль
dmesg -n 4

# Полностью заглушить консольный вывод
dmesg --console-off

# Восстановить консольный вывод
dmesg --console-on

Очистка буфера - операция, которую стоит выполнять перед воспроизведением конкретной проблемы, чтобы не искать нужные сообщения среди многих часов накопленной истории:

# Вывести содержимое и очистить буфер одновременно
dmesg -c

# Только очистить без вывода
dmesg -C

# Сохранить в файл перед очисткой
dmesg > /var/log/dmesg-$(date +%Y%m%d-%H%M%S).log && dmesg -C

Очистка требует прав root. На системах с systemd-journald стоит помнить, что journald имеет собственную копию ядерных сообщений и очистка буфера dmesg не затрагивает journal.

Запись в буфер и прямой доступ через /dev/kmsg

Неожиданно полезная возможность: любой процесс с правами root может добавить собственное сообщение прямо в кольцевой буфер ядра. Это превращает буфер в общую временну́ю шкалу, где события ядра и пользовательских программ идут вперемешку с едиными временны́ми метками. При отладке сложных сценариев такой подход позволяет точно привязать действие userspace-приложения к реакции ядра.

# Добавить своё сообщение в кольцевой буфер ядра
echo "myapp: начало тестового сценария" > /dev/kmsg

# Прочитать сырые данные в нативном формате ядра
dd if=/dev/kmsg iflag=nonblock 2>/dev/null | head -10

Сырой формат записей в /dev/kmsg выглядит иначе, чем вывод dmesg. Каждая строка начинается с числового приоритета, затем следует монотонная временна́я метка в микросекундах, порядковый номер и текст. Именно этот формат dmesg преобразует в привычный вид. При написании собственных инструментов парсинга знание сырого формата необходимо.

Диагностика реальных проблем через кольцевой буфер

Кольцевой буфер - первое место, куда нужно смотреть при проблемах с оборудованием, нестабильной работе системы и необъяснимых сбоях. Несколько паттернов поиска, которые покрывают большинство реальных сценариев.

Аппаратные ошибки памяти и шин PCI появляются в буфере раньше, чем где-либо ещё. MCE (Machine Check Exception) - это аппаратный механизм процессора для сигнализации об ошибках, и ядро немедленно записывает детали в буфер:

# Ошибки памяти и MCE
dmesg | grep -i "mce\|machine check\|memory error\|edac"

# Проблемы с дисками и контроллерами
dmesg | grep -iE "ata[0-9]|nvme|error|failed|reset|timeout"

# OOM killer - убийства процессов из-за нехватки памяти
dmesg | grep -i "oom\|out of memory\|killed process"

# Сетевые ошибки и смена состояния интерфейсов
dmesg | grep -E "eth[0-9]|ens|Link is (Up|Down)|NETDEV"

# Проблемы USB - отключения и ошибки перечисления устройств
dmesg | grep -i "usb\|xhci\|ehci" | grep -i "error\|fail\|disconnect"

Для хранения ядерных сообщений между перезагрузками и анализа прошлых инцидентов используется journald с опцией Storage=persistent в /etc/systemd/journald.conf. После её включения доступ к сообщениям ядра из прошлых загрузок открывается через journalctl:

# Ядерные сообщения предыдущей загрузки
journalctl -k -b -1

# Сравнить ядерные сообщения двух последних загрузок
diff <(journalctl -k -b -1 --no-pager) <(journalctl -k -b -2 --no-pager)

Кольцевой буфер ядра - инструмент без интерфейса, без GUI и без подсказок прямо на экране. Но именно в нём ядро говорит открытым текстом о том, что реально происходит с железом, драйверами и подсистемами. Умение читать этот поток, фильтровать нужное и сопоставлять временны́е метки с действиями системы отличает диагностику, которая занимает минуты, от той, что растягивается на часы предположений и перезагрузок.