netlib.narod.ru | < Назад | Оглавление | Далее > |
В эту книгу включен не весь код реализации интерфейса /proc/sys к настраиваемым параметрам ядра; в действительности, не включена основная часть этого кода, поскольку она главным образом относится к файловой системе /proc. К тому же, если вас не интересует, как работает остальная часть /proc, вы сможете легко разобраться в коде программы kernel/sysctl.c, которая работает с файловой системой /proc, для того, чтобы обеспечить доступ к настраиваемым параметрам ядра под каталогом /proc.
30689: Функция register_proc_table регистрирует объект ctl_table под каталогом /proc/sys. Обратите внимание, что эта функция не требует, чтобы таблица, передаваемая в качестве параметра, была узлом корневого уровня (то есть объектом ctl_table без родителя) — эта таблица должна быть таковой, но за соблюдение данного требования отвечает вызывающая программа. Таблица устанавливается непосредственно под каталогом, обозначенным параметром root, который должен соответствовать каталогу /proc/sys или одному из подкаталогов под ним. (При первоначальном вызове root всегда указывает на proc_sys_root, но после рекурсивных вызовов его значение изменяется.)
30696: Начинается итерация по всем элементам массива table; итерация заканчивается, когда член ctl_name текущего элемента становится равным 0, что означает конец массива.
30698: Если поле procname объекта ctl_table имеет значение NULL, этот объект не должен быть видимым под каталогом /proc/sys, даже несмотря на то, что другие элементы того же массива могут быть видимыми. Такие элементы массива пропускаются.
30701: Если данный вход таблицы имеет отличное от NULL значение procname, а это означает, что он должен быть зарегистрирован под каталогом /proc/sys, он должен также иметь отличный от NULL член proc_handler (если это лист дерева или узел, подобный файлу) или член child (если это узел, подобный каталогу). Если в нем отсутствуют оба члена, выводится предупреждающее сообщение и цикл продолжается.
30711: Если вход таблицы имеет отличный от NULL член proc_handler, он отмечается как обычный файл.
30713: Иначе, как можно утверждать на основании строки 30701, он должен иметь отличный от NULL член child, поэтому данный вход будет рассматриваться как каталог. Обратите внимание: ничто не препятствует тому, чтобы оба члена объекта ctl_table, и proc_handler и child, были отличны от NULL, но в таком случае это соглашение должно соблюдаться во всем коде.
30715: Выполняет поиск указанного имени в существующем подкаталоге и оставляет de, указывающим на существующий вход, если он найден, или равным NULL, если он не найден. Трудно понять, почему аналогичная проверка не предусмотрена для файлов; в файловой системе /proc может быть какая-то тонкость, которую автор не смог понять, и ответ, безусловно, лежит здесь.
30723: Если указанный подкаталог не существует или параметр table соответствует файлу, а не каталогу, создается новый файл или каталог путем вызова функции create_proc_entry (не рассматривается).
30728: Если вход таблицы представляет собой лист-узел, функция register_proc_table сообщает коду файловой системы /proc, чтобы в нем использовались файловые операции, которые определены в функции proc_sys_inode_operations (строка 30295). В функции proc_sys_inode_operations определены только две операции — чтение и запись (нет ни поиска, ни отображения памяти, ни всего прочего). Эти операции выполняются с помощью функций proc_readsys и proc_writesys (строки 30802 и 30808), которые рассматриваются далее в этой главе.
30731: К этому моменту уже известно, что значение de не равно NULL; оно либо было уже отлично от NULL, либо было инициализировано в строке 30723.
30733: Если добавляемый вход относится к типу каталога, рекурсивно вызывается функция register_proc_table для добавления также всех дочерних записей данного входа. Это редкий случай использования рекурсии в ядре.
30739: Функция unregister_proc_table удаляет связь между деревом массивов ctl_table и файловой системой /proc. Эти входы размещаются в объекте ctl_table и все входы во всех «подкаталогах» под ними исчезают из /proc/sys.
30743: Как и в строке 30696, здесь начинается итерация по переданному в качестве параметра массиву входов таблицы.
30744: Член de входов таблицы, не связанных ни с одним входом под каталогом /proc/sys, имеет значение NULL; эти входы, безусловно, можно пропустить.
30748: Если с точки зрения файловой системы /proc это — каталог, но вход этой таблицы представляет собой лист-узел (не каталог), эти две структуры противоречивы. Функция unregister_proc_table выводит предупреждающее сообщение и продолжает цикл, не пытаясь удалить этот вход.
30752: Каталоги освобождаются рекурсивно — еще один редкий случай использования рекурсии в ядре.
30756: После возврата из рекурсивного вызова функция unregister_proc_table проверяет, что все подкаталоги и файлы были рекурсивно удалены; если нет, то текущий элемент нельзя безопасно удалить и цикл продолжается.
30762: Вот почему некоторые подкаталоги (и файлы в них) могли не быть удалены: они в настоящее время могли находиться в использовании. Если данный элемент находится в использовании, цикл просто продолжается, поэтому этот элемент не будет удален.
30765: Узел удаляется из файловой системы с помощью функции proc_unregister (не рассматривается в этой книге) и освобождается память, распределенная для слежения за этим узлом.
30771: Функция do_rw_proc реализует содержательную часть функций proc_readsys (строка 30802) и proc_writesys (строка 30808), которые используются в коде файловой системы /proc для чтения и записи данных в объектах ctl_table.
30782: Проверка того, что с этим входом под каталогом /proc/sys связана какая-то таблица.
30785: Обратите внимание, что первая проверка в этой строке дублирует вторую проверку в строке 30782, поскольку объект table инициализирован из члена de->data.
30788: Проверка того, что вызывающий процесс имеет, соответственно, право на чтение или запись.
30795: Вызов функции proc_handler данного входа таблицы для фактического выполнения чтения или записи. (Отметим, что в строке 30785 было проверено, что член proc_handler отличен от NULL.) Как было упомянуто ранее, член proc_handler обычно имеет значение proc_dostring или proc_dointvec (строки 30820 и 30972), которые описаны в следующих нескольких разделах.
30799: Функция do_rw_proc возвращает число фактически считанных или записанных байтов. Обратите внимание, что локальная переменная res совсем не нужна; она может быть заменена параметром count.
30820: Функция proc_dostring представляет собой функцию, которую вызывает код файловой системы /proc для чтения или записи параметра ядра, представляющего собой строку С.
Обратите внимание: флажок write означает, что вызывающая программа пишет в элемент таблицы, но эта процедура в основном сводится к чтению из входного буфера, следовательно, код записи занимает меньше места по сравнению с кодом чтения. Аналогичным образом, если флажок write не установлен, вызывающая программа читает из входа таблицы, что в основном требует записи в буфер, переданный в качестве параметра.
В строке 31085 есть также реализация этой функции в виде заглушки; эта заглушка используется, если файловая система /proc транслируется вне ядра. За описанием этой функции следует описание аналогичных заглушек для большинства других функций, которые будут описаны ниже.
30835: Символы считываются из входного буфера до тех пор, пока не будет найден заключительный символ NUL (0) или символ новой строки кода ASCII, или пока из входного буфера не будет считан максимально допустимый объем данных (который указан параметром lenp). (Чтобы избежать путаницы, напомним, что NULL — константа-указатель языка С, a NUL, с одним L, является обозначением в коде ASCII нулевого символа.)
30842: Если число байтов, считанных из буфера превышает объем, который может быть записан в данный вход таблицы, число байтов уменьшается. Вместо этого, вероятно, было бы эффективнее ограничить максимальную длину входных данных (lenp) перед циклом, поскольку чтение из буфера числа байтов, превышающего значение table->maxlen, в любом случае бессмысленно. В том виде, как есть, цикл может прочитать, скажем, 1024 байта, а затем уменьшить этот объем до 64, поскольку это все, что можно записать во вход таблицы.
30844: Строка считывается из входного буфера, а затем в ее конце записывается символ NUL.
30847: Ядро поддерживает переменную со значением «текущей позиции» для каждого файла, принадлежащего каждому процессу; это член f_pos объекта struct file. Он представляет собой значение, возвращаемое системным вызовом tell и устанавливаемое системным вызовом seek. Здесь текущая позиция в файле увеличивается на число записанных байтов.
30871: Функция proc_doutsstring просто вызывает функцию proc_dostring после приобретения семафора uts_sem (строка 29975). Эта функция используется в нескольких входах объекта kern_table (строка 30341) для установки различных компонентов структуры system_utsname (строка 20094).
30881: Функция proc_dointvec (строка 30972) делегирует свою работу этой функции. Функция do_proc_dointvec читает или пишет массив данных типа int, на который указывает член data объекта table. Число переменных типа int, которые должны быть считаны или записаны, передается параметром lenp; обычно этот параметр равен 1, поэтому данная функция, как правило, используется для чтения или записи только единственной переменной типа int. Значения данных типа int заданы параметром buffer. Однако данные типа int не передаются в виде массива данных типа int во внутреннем представлении; вместо этого, они представлены в виде текста ASCII, который записывается пользователем в соответствующий файл /proc.
30898: Начинаются итерации по всем данным типа int для чтения или записи. Переменная left следит за оставшимся числом данных типа int, которые должны быть считаны или записаны вызывающей программой, а переменная vleft следит за числом допустимых элементов, оставшихся в объекте table->data. Цикл завершается, когда любое из этих значений достигает 0 или когда выход из него происходит в середине. Обратите внимание, что весь цикл можно было бы сделать чуть более эффективным, но также более сложным для сопровождения, если вывести из этого цикла оператор if в строке 30899, то есть вместо кода такой структуры:
for (; left && vleft--; i++, first=0) { if (write) { /* Код записи. */ } else { /* Код чтения. */ } }
применить код со следующей структурой:
if (write) { for (; left && vleft--; i++, first=0) { /* Код записи. */ } } else { for (; left && vleft--; i++, first=0) { /* Код чтения. */ } }
Таким образом, значение переменной write, которое не изменяется внутри цикла, можно было бы проверять только один раз, а не на каждой итерации цикла.
30900: Поиск вперед непробельного символа, то есть начала следующего числа на входе.
30913: Копирует фрагмент данных из пространства пользователя в локальный буфер buf, а затем записывает символ NUL в конце buf Теперь buf содержит весь оставшийся код ASCII со входа, или такую часть этого текста, которая в нем поместилась.
Такой подход выглядит не очень эффективным, поскольку он допускает чтение большего объема, чем это необходимо. Однако, поскольку размер буфера buf равен только 20 (TMPBUFLEN, строка 30885), в него нельзя считать намного больше, чем необходимо. Здесь идея, вероятно, состоит в том, что дешевле просто считать немного больше, чем проверять каждый байт и следить за тем, нужно ли закончить чтение.
Величина buf выбрана достаточно большой для того, чтобы в ней можно было разместить представление в коде ASCII любого 64-разрядного целого числа, поэтому данная функция может поддерживать не только 32-разрядные, но и 64-разрядные платформы. И действительно, буфер достаточно велик для того, чтобы в нем могло разместиться самое большое 64-разрядное положительное целое число, десятичное представление которого состоит из 19 цифр (завершающий байт NUL будет записан в 20-м байте). Но помните, что существуют целые числа со знаком, поэтому значение –9223372036854775808, наименьшее 64-разрядное целое число со знаком, также представляет собой допустимый ввод. Оно не может быть считано правильно. К счастью, исправление тривиально и очевидно.
Вскоре мы покажем, как будет действовать этот код при получении подобного ввода.
30919: Обработка ведущего знака минус (–), переход к символу за знаком минус и установка флажка, если знак минус был найден.
30923: Проверка того, что текст, который был считан из буфера (возможно, за ведущим знаком минус), по крайней мере, начинается с цифры, чтобы его можно было успешно преобразовать в целое число. Без этой проверки было бы невозможно узнать, вернул ли вызов функции simple_strtoul в строке 30925 значение 0 из-за того, что на входе был «0», или из-за того, что она совсем не смогла преобразовать входной текст.
30925: Преобразование текста в целое число и масштабирование результата с использованием параметра conv. Этот этап масштабирования применяется в таких функциях, как proc_dointvec_jiffies (строка 31077), которая преобразует свой вход из секунд в единицы измерения процессорного времени, просто путем умножения на константу HZ. Однако, как правило, коэффициент масштабирования равен 1, что равносильно отсутствию масштабирования.
30927: Если остается еще текст, который должен быть считан из буфера, и следующий символ, который должен быть считан, отличается от пробела, разделяющего параметры, это значит, что в буфер buf весь параметр не поместился. Такие входные данные являются недопустимыми, поэтому цикл преждевременно прекращается. (Одной из версий развития событий, при которой функция могла бы оказаться в этом состоянии, была бы передача в качестве параметра наименьшего 64-разрядного целого числа со знаком, как было описано выше.) Однако код ошибки не возвращается, поэтому вызывающая программа может ошибочно считать, что все было нормально. Однако это не совсем так: код ошибки будет возвращен в строке 31070, но только если недопустимый параметр был обнаружен при первой итерации цикла; если он будет обнаружен в последующей итерации цикла, ошибка не будет замечена.
30929: Параметр был прочитан успешно. Теперь учитывается ведущий знак минус, если он присутствовал на входе, корректируются другие локальные переменные для перехода к следующему параметру и параметр записывается во вход таблицы под указателем i.
30936: Вызывающая программа считывает значения из входа таблицы; это гораздо более простой случай, поскольку не требуется интерпретация текста ASCII. Вывод разграничен символами табуляции, поэтому после каждой итерации цикла, кроме первой, во временный буфер записывается символ табуляции (этот символ также не записывается после последнего параметра, а только между параметрами).
30938: Затем текущее значение целого числа делится на коэффициент conv и передается во временный буфер. Этот код страдает от той же проблемы, которая была описана выше: временный буфер, buf, может быть не достаточно велик для хранения всех целочисленных значений, которые могут быть в него выведены. В этом случае проблема усугубляется тем фактом, что в первой позиции буфера может находиться символ табуляции. При этом, полезный объем буфера buf становится на один символ меньше, что приводит к дальнейшему сужению диапазона входных данных, которые будут обработаны правильно.
Последствия появления слишком большого или слишком малого целого числа могут быть еще более неблагоприятными, чем в случае записи. В том случае код просто отбрасывает часть входных данных, которые он должен был принять, а в этом случае функция sprintf может выполнить запись за концом буфера buf.
Однако оказывается, что этот код, по-видимому, все равно работает правильно. В типичной реализации происходит примерно следующее: за концом буфера buf записываются, самое большее, два лишних байта (один, потому что может быть выполнена запись большего числа, чем ожидалось, и еще один — для символа табуляции). Указатель р обычно находится в стеке непосредственно после buf, поэтому запись с выходом за конец буфера buf перекрывает р. Но поскольку указатель р в дальнейшем больше не используется без предварительной повторной инициализации, нет никакого вреда в том, что его значение будет временно перезаписано.
Это любопытный выход из положения, но простое незначительное увеличение buf было бы гораздо лучшим решением, которое позволило бы обеспечить надежную работу этого кода на законных основаниях, а не по воле случая. Если этот код останется в том виде, как есть, то небольшое, вполне невинное изменение генератора объектного кода gcc может заставить проявиться эту скрытую ошибку.
30939: Копирует текстовое представление текущего значения int в буфер вывода или, по крайней мере, такую часть этого текстового представления, какая в ней поместится, и обновляет локальные переменные для перехода к следующему элементу массива данного входа таблицы.
30949: Завершает вывод символом новой строки, если вызывающая программа выполняла чтение. Условие if также выполняет проверку того, что цикл не закончился при его первой итерации и что есть место для записи символа новой строки. Обратите внимание, что буфер вывода не завершается байтом NUL кода ASCII (как можно было ожидать), поскольку этого не требуется: вызывающая программа может определить длину возвращенной строки из нового значения, записанного с помощью lenp.
30954: Пропуск всех пробельных символов, следующих за последним параметром, который был считан из входного буфера, если вызывающая программа писала значения во вход таблицы.
30967: Обновляет текущую позицию файла и значение lenp, а затем возвращает 0 в качестве обозначения успеха.
30978: Функция proc_dointvec_minmax почти аналогична do_proc_dointvec, за исключением того, что она дополнительно рассматривает члены extra1 и extra2 входа таблицы как массивы ограничений на те значения, которые могут быть записаны во вход таблицы. Значения в extra1 представляют собой минимумы, а значения в extra2 — максимумы. Еще одним отличием является то, что функция proc_dointvec_minmax не имеет параметра conv.
В связи с тем, что эти функции так похожи, в настоящем разделе будут рассмотрены только различия между ними.
31033: Вот самое значительное различие: при записи значения, выходящие за пределы диапазона, определенного величинами min и max (которые берутся в цикле из массивов extra1 и extra2), молча пропускаются. Очевидно, назначение этого кода состоит в обработке min и max наряду с val. После того, как значение будет считано со входа, оно должно быть проверено по отношению к следующему значению min и следующему значению max, а затем либо принято, либо пропущено. Однако это происходит не совсем так. Предположим, что текущее значение из буфера, которое было интерпретировано и записано в переменную val, меньше минимума; предположим также ради уточнения, что это третья итерация цикла, поэтому и min, и max указывают на третьи элементы соответствующих им массивов. Тогда значение val будет проверено по min и будет обнаружено, что оно выходит за пределы диапазона (слишком мало), поэтому цикл будет продолжен. Однако в качестве побочного эффекта этой проверки значение min будет обновлено, а значение max — нет. Теперь min указывает на четвертый элемент соответствующего ему массива, a max все еще указывает на третий элемент своего массива. Эти две величины не согласованы друг с другом и таковыми и останутся, поэтому следующее значение (и в действительности, все последующие значения), считанные из буфера, могут проверяться по неправильному пределу. Ниже описано простейшее исправление:
if (min && val < *min++) { ++max; /* Синхронизация значений max и min. */ continue; } if (max && val > *max++) continue;
Однако, как вы узнаете далее из этой главы, оказывается, что эта ошибка никогда не проявит себя в текущем исходном коде Linux. (Другое дело — будущие выпуски, но это еще не написанный роман.)
netlib.narod.ru | < Назад | Оглавление | Далее > |