Есть особый момент в жизни каждого 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 и без подсказок прямо на экране. Но именно в нём ядро говорит открытым текстом о том, что реально происходит с железом, драйверами и подсистемами. Умение читать этот поток, фильтровать нужное и сопоставлять временны́е метки с действиями системы отличает диагностику, которая занимает минуты, от той, что растягивается на часы предположений и перезагрузок.