Встроенные команды ядра: смотреть, как система работает
В прошлой части я показал консоль ядра с точки зрения её полномочий. Она работает в ring 0 и получает прямой доступ к самым глубоким внутренним механизмам системы. Теперь пришло время разобраться, как именно она использует эти возможности.
Главную ценность представляют вовсе не файловые команды. Намного важнее инструменты, которые открывают окно в работающую систему. С их помощью я наблюдаю за планировщиком, читаю журнал ядра, проверяю активный драйвер дисплея и исследую множество других внутренних деталей. Именно такие команды помогли мне обнаружить большую часть ошибок, описанных в этой книге.
Почему этим командам место в ядре
Многие системные утилиты способны работать где угодно. Диагностические команды лишены такой свободы. Им требуется непосредственный доступ к структурам ядра, поскольку именно там находятся нужные данные.
Возьмём taskstats. Команда читает очередь планировщика со всеми потоками, их классами, виртуальным временем выполнения и текущими состояниями. Все эти сведения хранит само ядро. Код команды находится рядом, поэтому чтение проходит напрямую. Не нужны системные вызовы, промежуточные копии или преобразование форматов.
Тот же принцип действует и для остальных диагностических средств. Журнал ядра живёт в кольцевом буфере. Сведения о графическом драйвере поступают непосредственно из слоя аппаратной абстракции. Сетевая статистика хранится в глобальных структурах ядра.
Программа из ring 3 ничего подобного увидеть не сможет. Доступ к внутренним структурам для неё закрыт. Ради каждого набора данных пришлось бы вводить отдельный системный вызов, копировать информацию в пользовательское пространство и только затем выполнять обработку. По этой причине диагностические инструменты остаются внутри ядра, тогда как большинство остальных программ постепенно переезжает в ring 3. Тот, кто хочет наблюдать работу системы в реальном времени, должен находиться рядом с её механизмами. Здесь близость важнее изоляции. Цена известна заранее - никакой страховочной сетки нет.
Что показывают команды
Теперь посмотрим на наиболее полезных команд. Они позволяют изучать живую систему без остановки её работы.
dmesg, который также доступен под именем log, выводит журнал ядра. Каждый вызов printf попадает в кольцевой буфер объёмом 64 килобайта, а команда печатает его содержимое. В журнале сохраняется вся история загрузки: обнаружение процессора, запуск драйверов, получение адреса через DHCP и многое другое. Очистку буфера выполняет kclear.
Команда taskstats выводит список потоков планировщика вместе с их TID, именами, классами, приоритетами и виртуальным временем выполнения. Когда требуется понять, кто расходует процессорное время или страдает от голодания, именно сюда я смотрю в первую очередь. Затем картину дополняет cpuload. Она показывает число готовых к запуску потоков, количество спящих задач и частоту переключений контекста. На многопроцессорных системах такой вывод быстро обнаруживает перекос распределения нагрузки.
Команда vgainfo сообщает, какой драйвер дисплея работает сейчас. Вместе с названием она показывает разрешение, глубину цвета и состояние аппаратного ускорения. Один взгляд позволяет понять, использует ли система SVGA2 с FIFO, перешла ли на VESA или вообще вернулась к обычному текстовому режиму VGA.
Команда netstat показывает сетевую подсистему уже со стороны ядра. Она показывает работу NetworkManager, количество принятых и отброшенных пакетов, а также состояние моста к сетевому стеку пользовательского пространства.
Кроме диагностических средств консоль содержит привычные файловые команды: ls, cat, cd, mkdir и rm. Их работу я уже описал в разделе про оболочки. Есть и небольшие вспомогательные утилиты вроде lt, которая меняет раскладку клавиатуры. Однако именно диагностические инструменты образуют сердце консоли ядра.
Ловушка при выводе сообщений
Во время работы над этими командамиобнаружился неприятный момент. Ошибка кажется безобидной, однако её последствия способны остановить всю систему.
Во время чтения очереди планировщика необходимо удерживать spinlock. Без него другой процессор изменит список прямо во время обхода. Проблемы начинаются после вызова printf внутри такого цикла. Вывод может упереться в заполненную очередь консоли и начать ждать свободное место. Всё это время spinlock продолжает оставаться захваченным. Остальные процессоры теряют доступ к планировщику, после чего работа системы прекращается.
Правильный подход выглядит намного проще. Сначала данные копируют в отдельный буфер. Затем освобождают заблокированную структуру. Только после этого начинается вывод.
spinlock_acquire(&rq.lock);
// только копировать, это идёт за микросекунды:
for (каждый поток) snap[n++] = (Row){ tid, name, class, ... };
spinlock_release(&rq.lock);
// теперь блокировка свободна, медленная печать безопасна:
for (i = 0; i < n; i++) printf("%s\n", snap[i]);
Блокировка удерживает систему лишь во время короткого копирования. Медленная печать проходит уже после освобождения критической секции. Когда-то именно нарушение этого правила вызвало крайне упрямое зависание. С тех пор в ядре действует жёсткое требование: никаких printf, kfree и других потенциально блокирующих операций под spinlock. Причина проста. Внутри ядра ошибка влияет не на отдельный процесс, а сразу на всю систему.
Честное место
В таблице команд можно заметить несколько закомментированных строк. Они относятся к отладочным инструментам для памяти, PCI и списка устройств. Такое состояние появилось не случайно. В дальнейшем эти средства покинут ядро и превратятся в обычные программы ring 3.
Такое решение соответствует общей стратегии проекта. Максимально возможное количество компонентов должно работать вне привилегированного режима. В ядре останутся лишь те инструменты, которым действительно необходим прямой доступ к механизмам ring 0.
Ещё одно ограничение связано с очередью консоли. Через неё проходит весь вывод, а её объём конечен. Большой файл, открытый через cat, или длинный журнал dmesg способны заполнить буфер, после чего старые строки исчезнут. Чтобы уменьшить вероятность такой ситуации, команды отправляют данные крупными порциями вместо построчной печати. Такой подход заметно сокращает количество операций записи. Полностью проблему он не устраняет. При действительно огромном объёме вывода предел буфера всё равно проявит себя.
Что дальше
На этом рассказ о встроенных инструментах ядра подходит к концу. Однако в повседневной работе главную роль играют уже не они. Пользователь почти всегда запускает программы ring 3: файловый менеджер, панель настроек, сетевые утилиты, терминалы и множество других приложений. Именно программы ring 3- весь набор инструментов пользовательского пространства станут темой следующей части.
Было бы интересно увидеть ваши комментарии и улучшить статьи.
◀ Предыдущая статья · Содержание · Следующая статья ▶
*Система не стоит на месте, поэтому в дальнейшем тексты могут не совпадать с реальным положением