Pull to refresh
1063.78
OTUS
Цифровые навыки от ведущих экспертов

Критический обзор значений атрибутов sysfs

Reading time14 min
Views4.1K
Original author: lwn.net

Одной из многих запоминающихся строк знаменитого произведения Дугласа Адамса "Путеводитель для путешествующих по галактике автостопом" было обвинение, выдвинутое, вероятно, сторонниками "Энциклопедии Галактики", в том, что "Путеводитель автостопом" был "неравномерно отредактирован" и "содержит много отрывков, которые просто показались его редакторам хорошей идеей в то время". С небольшими изменениями, например, заменой "отредактировали" на "рецензировали", это описание кажется очень подходящим для ядра Linux и, несомненно, для многих других программ, открытых или закрытых, свободных или несвободных. Рецензирование в лучшем случае является "неравномерным".

Нетрудно найти жалобы по поводу того, что код ядра Linux недостаточно рецензируется или что нам нужно больше ревьюверов. Создание тегов типа "Reviewed-by" для патчей отчасти было попыткой решить эту проблему, предоставляя больше признания экспертам по рецензированию и, таким образом, поощряя больше людей поучаствовать в этой роли.

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

Найти правильную тему, правильный уровень и правильный форум для рецензирования нелегко (а время на это найти может быть еще труднее). Эта статья не ставит своей целью прямое решение данных вопросов, а скорее представляет образец рецензирования — конкретной темы на конкретном уровне на конкретном форуме, в надежде, что он окажется полезным. Тема, выбранная во многом потому, что в последнее время вашему автору приходится работать с ней, не имея полного понимания, — это "sysfs", виртуальная файловая система, предоставляющая доступ к некоторым внутренним компонентам ядра Linux. И, в частности, файлы атрибутов, которые раскрывают мелкие детали этого доступа.

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

Sysfs и файлы атрибутов

Sysfs имеет интересную историю и ряд целей проектирования, в которых стоит разобраться, но ни одна из них не будет рассмотрена здесь, кроме как в той мере, в какой они отражают конкретно выбранную тему: файлы атрибутов. Ключевой целью дизайна, связанной с файлами атрибутов, является условие — почти мантра — "один файл — одно значение" или иногда "один элемент на файл". Идея заключается в том, что каждый файл атрибутов должен содержать только одно значение. Если требуется много значений, то следует использовать несколько файлов.

Значительная часть истории, связанная с этим условием, — это опыт "procfs" или /proc. /proc —  прекрасная идея, которая, к сожалению, переросла почти в раковую опухоль и стала широко презираемой. Это виртуальная файловая система, которая изначально имела один каталог для каждого запущенного процесса, и этот каталог содержал полезную информацию о запущенном процессе в различных файлах.

Очевидно, что в виртуальную файловую систему можно поместить не только процессы, и, при отсутствии явных причин против этого, в procfs начали добавлять разные вещи. Не имея никакого реального дизайна или структуры, все больше и больше информации впихивалось в procfs, пока она не превратилась в неорганизованный беспорядок. Даже внутри каталогов для каждого процесса procfs выглядит не очень привлекательно. Некоторые файлы (например, limits) содержат таблицы с заголовками столбцов, другие (например, mounts) — таблицы без заголовков, а третьи (например, status) содержат строки с метками, а не столбцы. Есть файлы с единичными значениями (например, wchan), в то время как другие содержат множество разнородных и непоследовательно отформатированных значений (например, mountstats).

На фоне этой дезорганизации и сопутствующих трудностей, которые возникали при добавлении новых полей, чтобы это не нарушало работу приложений, в sysfs была объявлена новая политика — один элемент на файл. На самом деле, в своей превосходной (хотя теперь уже несколько устаревшей) статье о Ядре модели драйвера Грег Кроа-Хартман даже утверждал, что это правило "соблюдается" (см. боковую панель "sysfs").

Было бы несправедливо отчитывать Грега за то, что могло быть случайно брошенной фразой многолетней давности, и я не хочу этого делать. Однако этот комментарий служит хорошей отправной точкой и ориентиром для пересмотра использования файлов атрибутов в sysfs. Можно спросить, действительно ли соблюдается правило, достаточно ли его для того, чтобы избежать прошлых ошибок, и во всех ли случаях оно имеет реальный смысл.

Как можно догадаться, ответы будут "нет", "нет" и "нет", но объяснение будет гораздо более познавательным, чем сам ответ.

Выполняется ли оно?

Лучший способ проверить, соблюдается ли правило, — это изучить содержимое sysfs: хранятся ли в файлах простые значения или что-то большее? В качестве весьма грубой оценки степени сложности содержимого файла атрибутов sysfs можно выполнить простую команду:

find /sys -mount -type f | xargs wc -w | grep -v ' total$'

чтобы получить подсчет количества слов в каждом файле атрибутов ("-mount" — важно, если у вас смонтирован /sys/kernel/debug, так как чтение вещей в нем может вызвать проблемы).

Обработка этих результатов на ноутбуке автора (Linux 2.6.32) показывает, что из 9254 файлов 1189 пустые, а 7168 содержат только одно слово. Вполне резонно предположить, что они представляют только одно значение (хотя многие из пустых файлов, вероятно, предназначены только для записи, и этот механизм не дает информации о том, какое значение или значения могут быть записаны). Остается 897 (почти 10%), которые требуют дальнейшего изучения. Они варьируются от двух слов (487 случаев) до 297 слов (один случай).

В то время как файлов почти 900, базовых имен меньше 100. Если отфильтровать некоторые общие шаблоны (например, gpe%X), то количество отдельных атрибутов приблизится к 62, то есть к числу, которое вполне можно исследовать вручную (с небольшой помощью некоторых сценариев). Некоторые из этих многословных файлов атрибутов содержат данные не в формате ASCII и поэтому почти наверняка являются единичными значениями с точки зрения разумной логики. Другие содержат строки, для которых пробел является законным символом, например, "Dell Inc.", "порт i8042 KBD" или "write back". Поэтому они не являются явным отклонением от правила.

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

  • В некоторых случаях, таких как атрибут "queue/scheduler" блочного устройства или атрибут "trigger" светодиодного устройства, перечисляются все возможные опции, при этом активный в данный момент вариант заключен в скобки, таким образом:

noop anticipatory deadline [cfq]
  • Во втором варианте есть два файла, один содержит список возможностей, как в случае "cpufreq/scaling_available_governors", а другой — выбранное в данный момент значение, "cpufreq/scaling_governor".

  • Наконец, и это просто может оказаться особым случаем одного из вышеперечисленных, у нас есть "/sys/power/state", для которого нет текущего значения, поэтому он просто содержит список возможных значений.

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

Однако есть и другие многословные файлы атрибутов, которые не так легко объяснить. /sys/class/bluetooth содержит некоторые атрибуты класса, такие как rfcomm, l2cap и sco. Каждый из них содержит структурированные данные, по одной записи в строке с 3-9 различными единицами информации в каждой из них (в зависимости от конкретного файла), причем первая из этих единиц выглядит как BD-адрес локального bluetooth-интерфейса.

Это выглядит явным нарушением политики "один элемент в файле". Файлы выглядят очень хорошо структурированными и их легко парсить, так что возникает соблазн считать, что они достаточно безопасны. Однако размер файлов атрибутов sysfs ограничен одной страницей - обычно 4 КБ. Если количество записей в этих файлах станет слишком большим (около 70 строк в файле l2cap), то обращение к файлу приведет к повреждению памяти или аварийному завершению работы. Надеюсь, этого никогда не произойдет, но "надежда" обычно не является приемлемой основой для хорошего проектирования. Из беседы с мейнтейнером bluetooth выяснилось, что планируется переместить эти файлы в "debugfs", где они смогут воспользоваться преимуществами реализации "seq_file", которая также широко используется в /proc и позволяет создавать файлы произвольного размера .

Некоторые другие примеры включают "/sys/devices/system/node/node0/meminfo", который, кажется, является версией для каждого узла "/proc/meminfo" и явно имеет несколько значений, а также атрибуты "options" в /sys/devices/pnp*/*, содержащие, похоже, именно такое специальное форматирование множества значений разных типов, которое многие считают неприемлемым в /proc. Файлы "resources" pnp аналогично являются многозначными, хотя и в меньшей степени.

В качестве последнего примера несоблюдения правил: каталог PCI-устройств для беспроводной сети (Intel 3945) в этом ноутбуке содержит файл под названием "statistics", который содержит шестнадцатеричный дамп 240 байт данных, с расшифровкой ASCII в конце каждой строки, типа:

02 00 03 00 d9 05 00 00 28 03 00 00 45 02 00 00  ........(...E...
0d 00 00 00 00 00 00 00 00 00 00 00 d6 00 00 00  ................
b1 02 00 00 00 00 00 00 00 00 00 00 00 00 00 00  ................
00 00 00 00 00 00 00 00 67 00 00 00 00 00 00 00  ........g.......

Это определенно, не то, о чем должен был сообщать sysfs. Во всяком случае, похоже, что речь идет о двоичном атрибуте, а не файле ASCII с двойной кодировкой.

Итак, ответом на наш вводный вопрос будет “нет”, так как правило “один элемент на файл” никоим образом не применяется. Конечно, подавляющее большинство файлов атрибутов содержат только один элемент, и это хорошо. Но есть и такие, которые содержат несколько значений, причем разными способами. И это число, скорее всего, будет только расти, поскольку люди либо копируют данные неудачные примеры, либо находят новые случаи использования, которые не соответствуют существующим шаблонам, и изобретают новые подходы, которые не учитывают целостную картину.

Достаточно ли этого правила?

Следующий вопрос, который мы зададим, — достаточно ли изложенного правила для атрибутов sysfs, чтобы избежать все более неорганизованных и специальных sysfs, которые следуют по печальному пути procfs. Мы уже видели, по крайней мере, один случай, когда это не так. У нас нет стандартизированного способа представления перечислимого типа в атрибуте sysfs, и потому имеются как минимум две реализации этого, как уже упоминалось. Есть также еще одна реализация (раскрытая в атрибуте "md/level" устройств md/raid), где видно только текущее значение, а различные опции не видны. Наличие стандарта здесь было бы хорошо для согласованности и поощрения оптимальной функциональности. Но такого стандарта у нас нет.

Аналогичная проблема возникает и с простыми числовыми значениями, которые представляют измеримые элементы, такие как размер хранилища или время. Было бы хорошо, если бы они передавались с использованием стандартных единиц, возможно, байтов и секунд. Но мы видим, что это не так. Объем памяти иногда указывается в байтах (/sys/devices/system/memory/block_size_bytes), иногда в секторах (/sys/class/block/*/size), а иногда в килобайтах (block/*/queue/read_ahead_kb).

Как показывают эти конкретные примеры, одним из способов избежать двусмысленности является включение названия единиц измерения (здесь байты или кб) как части имени атрибута, что известно как Венгерская нотация. Однако это далеко не всегда применяется единообразно, и приведенные выше примеры являются скорее исключением, чем правилом.

Показатели измерения продолжительности сталкиваются с той же проблемой. Многие временные интервалы, о которых необходимо знать ядру, существенно меньше одной секунды. Однако вместо того, чтобы использовать проверенную десятичную точку для обозначения единиц измерения, некоторые файлы атрибутов выводят значения в миллисекундах (unload_heads в устройствах libata), другие в микросекундах (cpuide/state*/time), а иногда даже в секундах (/sys/class/firmware/timeout). В качестве дополнительной путаницы есть ещё и такие (.../bridge/hello_time), которые используют единицы измерения, варьирующиеся в зависимости от архитектуры, от сантисекунд до мибисекунд (если это правильное название для 1-1024-й части секунды). Пожалуй, удачно, что для времени не существует метрической/имперской дифференциации единиц, иначе мы, вероятно, обнаружили бы и то, и другое.

И еще есть истинностные значения: Вкл, вкл, 1, Выкл, выкл, 0.

Таким образом, похоже, что ответ на наш второй вопрос тоже "нет", хотя в этом случае трудно быть уверенным, поскольку нет четко сформулированной цели, по которой можно было бы все это оценивать. Если же нашей целью является высокая степень единообразия при представлении значений в атрибутах, то мы явно не достигаем ее.

Всегда ли это требование имеет смысл?

Таким образом, основополагающий принцип "один элемент на файл" не выполняется единообразно, и его недостаточно, чтобы избежать ненужных несоответствий. Но если бы его применяли единообразно, дал бы он действительно то, что мы хотим, или же он слишком упрощен и расплывчат, чтобы быть полезным в качестве строгого правила?

Хорошим местом для начала изучения этого вопроса является атрибут "capabilities/key" устройств "ввода". Содержимое этого файла представляет собой битовую карту со списком событий нажатия клавиш, которые может генерировать устройство ввода. Битовая карта представлена в шестнадцатеричном формате с пробелом через каждые 64 бита. Очевидно, что это одно значение — битовая карта — но это также и массив битов. Или, может быть, массив "long". Является ли это несколькими значениями в одном атрибуте?

Хотя это тривиальный пример, который, несомненно, все принимают за одно значение, несмотря на то, что оно имеет длину много бит, нетрудно найти примеры, которые не так однозначны. Каждое блочное устройство имеет атрибут под названием "inflight", который содержит два числа: количество запросов на чтение, которые находятся в процессе выполнения (были отправлены, но еще не выполнены), и количество запросов на запись, которые находятся в процессе выполнения. Это единый массив, как в битовой карте, или два отдельных значения? Реализация "inflight" в виде двух отдельных атрибутов, тем самым четко следуя правилу, не потребует больших затрат, но, возможно, и пользы от этого тоже будет немного.

Атрибут "cpufreq/stats/time_in_state" идет на шаг дальше. Он содержит пары, по одной на строку, частот процессора (приятно, что в Гц) и общее время, проведенное на этой частоте (к сожалению, в микросекундах). Это скорее словарь, чем массив. Поразмыслив, можно сказать, что это действительно то же самое, что и предыдущие два примера. И для "key", и для "inflight" ключ — это перечислимый тип, который просто отображается на последовательность целых чисел, начинающуюся с нуля. Таким образом, в каждом случае мы видим словарь. В последнем случае ключи являются явными, а не неявными.

Если мы сравним этот последний пример с каталогом "statistics" в любом устройстве "net" (net/*/statistics), то увидим, что вполне возможно поместить индивидуальную статистику в отдельные файлы. Если бы эти 23 различных значения были помещены в один файл, по одному на строку с метками, вряд ли кто-то согласился бы с тем, что в этом файле находится только один элемент.

Поэтому вопрос заключается в том, где мы проводим черту? В каждом из этих 4 случаев (capabilities/key, inflight, time_in_state, statistics) у нас есть "словарное" отображение перечислимого типа на скалярное значение. В первом случае скалярное значение — это истинностное значение, представленное одним битом, в остальных случаях скалярное значение — это целое число. Размер словаря варьируется от 2-23 до нескольких сотен для "capabilities/key". Рационально ли проводить границу, основываясь на размере словаря или на размере значения? Или это следует оставить на усмотрение разработчика — путь, который обычно приводит к губительным для единообразия результатам.

Следствием этих исследований, похоже, является то, что мы должны разрешить хранение структурированных данных в атрибутах, поскольку нет четкой границы между структурированными и неструктурированными данными. "Один элемент на файл" — это отличная эвристика, которая в большинстве случаев хорошо нас ориентирует, но, как мы видели, есть много случаев, когда разработчики обнаруживают, что она не подходит, поэтому отступают от правил с удручающей непоследовательностью.

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

Итак, чтобы ответить на наш третий вопрос, "нет", нужно отметить, что правило "один элемент на файл" не всегда имеет смысл, потому что не всегда ясно, что считать "одним элементом", а наличие таких мест неопределенности — это дыры для хаоса, который может закрасться в наше ядро.

Можем ли мы сделать лучше?

Рецензия, которая находит проблемы, даже не предлагая их устранения, — действительно плоха. Выше был указан ряд проблем, а теперь мы, как минимум, обсудим решения.

Проблема существующих атрибутов, которые неуместно сложны или непоследовательны в своем форматировании, не предполагает быстрого решения. Невозможно просто изменить формат. В лучшем случае можно предоставить новые способы доступа к той же информации, а затем отказаться от старых атрибутов. Часто утверждается, что как только что-то попадает в интерфейс пространства ядра-пользователя (который включает в себя все sysfs), оно не может быть изменено. Однако существование CONFIG_SYSFS_DEPRECATED_V2 опровергает это утверждение. Политика, которая разрешает и поддерживает обесценивание и удаление атрибутов sysfs на постоянной основе, может причинять некоторую боль, но в долгосрочной перспективе будет полезна для ядра, особенно если мы ожидаем, что наши внуки продолжат разработку Linux.

Проблема, связанная с явной необходимостью структурированных данных в атрибутах sysfs, вероятно, лучше всего решается путем их предоставления, а не игнорирования или опровержения. Создать формат для представления произвольно структурированных данных несложно. Гораздо более сложной задачей является достижение согласия. XML был с энтузиазмом предложен и решительно отвергнут. Разработчикам ядра (которые уже знают C) может больше понравиться что-то похожее на инициализацию структур в C.

Ваш автор в настоящее время размышляет над тем, как лучше передать список "известных плохих блоков" на устройствах RAID (Redundant Arrays of Inexpensive Disks) между ядром и пользовательским пространством. sysfs — очевидное место для управления данными, но иметь один файл на блок было бы глупо, а единственный файл со списком всех плохих блоков достигнет максимума в одну страницу примерно при 300-400 записях, что намного меньше, чем нам хочется поддерживать. Поддержка структурированных атрибутов sysfs очень помогла бы здесь.

Последняя проблема — как обеспечить соблюдение любых правил, которые мы придумаем. Даже об очень простом правиле, которое легко и часто повторяется и о котором многие слышали, недостаточно лишь просто быть осведомленным; важно заставить людей следовать этому правилу. В этом мы только что убедились.

При использовании файлов атрибутов sysfs каждому разработчику предоставляется произвольная текстовая строка, которая затем включается для него в файл sysfs. Эта невероятная гибкость является большим соблазном для разнообразия, нежели для единообразия. Несмотря на то, что убрать эту реализацию невозможно, было бы полезно значительно упростить создание атрибутов sysfs, определенных и хорошо поддерживаемых типов. Например, длительность, температура, переключение, перечисление, размер хранилища, яркость, словарь и т.д. У нас уже есть шаблон для этого: параметры модуля гораздо легче определить, если они конкретного типа — как видно при изучении include/linux/moduleparam.h. Имплементация moduleparam больше сосредоточена на базовых типах, таких как int, short, long и т.д. Для sysfs нас больше интересуют типы более высокого уровня, однако концепция та же.

Если бы большая часть sysfs была переведена на использование интерфейса, обеспечивающего стандартизированный внешний вид, стало бы довольно легко найти нестандартные атрибуты и затем либо оспорить их, либо улучшить стандартный интерфейс для их поддержки.

В заключение

Следует сказать, что взгляд в прошлое дает гораздо более четкое представление, чем прогнозы на будущее. Легко увидеть все эти проблемы в ретроспективе, но гораздо сложнее быть готовым защититься от них с самого начала. В то время как у sysfs, вполне возможно, мог быть лучший дизайн, он, безусловно, мог быть и худшим. Создание несовершенных решений и последующая необходимость их исправления — это признанная часть подхода непрерывной разработки, используемого нами в ядре Linux.

Для полностью внутренних подсистем мы можем и регулярно исправляем разные моменты, не заботясь о поддержке наследия. Для внешних интерфейсов исправление не так просто. Нам нужно либо бесконечно носить с собой неприглядный багаж, либо работать над удалением того, что не работает, и поощрять создание только того, что будет работать. Разве плохо мечтать о том, что наш внук сможет работать с единообразным и последовательным /sys и, возможно, даже с /proc, содержащим только процессы?


Материал подготовлен в рамках курса Administrator Linux. Professional.

Всех желающих приглашаем на открытый урок «Работа с Git, основы и уверенное использование». На уроке вы детальнее погрузитесь в описание внутренней архитектуры git: файлы, деревья, коммиты и теги.
>> РЕГИСТРАЦИЯ

Если вам интересно развитие в этой сфере с нуля до pro, рекомендуем ознакомиться с учебной программой специализации.

Tags:
Hubs:
Total votes 13: ↑12 and ↓1+11
Comments0

Articles

Information

Website
otus.ru
Registered
Founded
Employees
101–200 employees
Location
Россия
Representative
OTUS