netlib.narod.ru | < Назад | Оглавление | Далее > |
Еще одним интерфейсом к настраиваемым параметрам ядра является системный вызов sysctl, наряду со связанными с ним функциями. У автора создалось впечатление, что этот интерфейс попал в немилость. А как же иначе? В большинстве практических ситуаций sysctl (каким бы великолепным вначале он ни казался по сравнению со старым методом настройки ядра путем корректировки исходного кода) просто является более неуклюжим по сравнению с доступом к файлам через /proc. Чтение и запись через sysctl требует применения программы на С (или чего-то подобного), тогда как файловая система /proc легко доступна через команды командного интерпретатора (или, равным образом, через сценарии командного интерпретатора).
С другой стороны, если вы уже работаете в С, то вызов sysctl может оказаться гораздо более удобным по сравнением с открытием файла, чтением из него и/или записью в него, а затем закрытием файла, поэтому sysctl может найти применение в своем месте. Так или иначе, рассмотрим реализацию этого интерфейса.
30471: Функция do_sysctl реализует значительную часть работы sys_sysctl (строка 30504), системного вызова sysctl. Обратите внимание, что sys_sysctl появляется также в строке 31275 и эта версия представляет собой просто функцию-заглушку, которая используется при трансляции системного вызова sysctl вне ядра. Старое значение параметра ядра возвращается через oldval, если параметр oldval не равен NULL, а его новое значение устанавливается из newval, если параметр newval не равен NULL. Параметры oldlenp и newlen указывают, соответственно, сколько байтов должно быть записано в oldval и считано из newval, если соответствующие указатели отличны от NULL; они игнорируются, если указатели равны NULL.
Обратите внимание на асимметрию: функция принимает указатель на старую длину, но не указатель на новую длину. Это связано с тем, что старая длина представляет собой одновременно входной и выходной параметр; его входное значение обозначает максимальное число байтов, которые могут быть возвращены через oldval, а его выходное значение обозначает число байтов, которые фактически были возвращены. В отличие от этого, новая длина — это только входной параметр.
30482: Устанавливает значение old_len из oldlenp, если вызывающая программа хочет получить старое значение параметра ядра.
30490: Начинает проход по циклическому списку деревьев таблицы. (См. описание функции register_sysctl_table далее в этой главе.)
30493: Использует функцию parse_table (строка 30560, рассматривается в следующем разделе) для поиска настраиваемого параметра ядра и для чтения и/или записи его значения.
30495: Если функция parse_table распределила память под какую-либо контекстную информацию, она освобождается. Трудно точно сказать, что подразумевается под этой контекстной информацией. Она не используется ни в каком коде, рассматриваемом в этой книге; в действительности, насколько может судить об этом автор, она в настоящее время не применяется ни в каком коде где-либо в ядре.
30497: Ошибка ENOTDIR просто означает, что указанный параметр ядра не был найден в этом дереве таблиц; он все еще может быть найден в другом дереве таблиц, где еще не выполнялся поиск. В ином случае, переменная error может содержать какой-то другой код ошибки или 0, в случае успеха; так или иначе, она должна быть (и будет) возвращена.
30499: Продвижение вперед итератора цикла с использованием макрокоманды DLIST_NEXT (не включена в эту книгу).
30501: Возвращает ошибку ENOTDIR, которая сообщает о том, что указанный параметр ядра не был найден ни в одной таблице.
30560: Функция parse_table ищет вход в дереве таблиц по аналогии с тем, как происходит поиск в дереве каталогов полностью квалифицированного имени файла. Идея состоит в следующем: поиск происходит вдоль массива данных типа int (массива name) с просмотром каждого элемента типа int в массиве ctl_table. Если будет обнаружено совпадение, выполняется рекурсивный просмотр соответствующей дочерней таблицы (если совпадающий вход представляет собой вход типа каталога); или выполняется чтение и/или запись входа (если это вход типа файла).
30566: К некоторому удивлению, это начало цикла, который перебирает все элементы целочисленного массива name. Было бы более привычно, если бы все, начиная с этой строки и кончая строкой 30597, было заключено в цикл for, который мог бы начинаться примерно так:
for (; nlen; ++name, --nlen, table = table->child)
(Здесь также нужно было бы удалить строки 30567 и 30568 и заменить строки с 30587 по 30590 оператором break.) Возможно, фактически применяемая версия генерирует лучший объектный код.
30570: Начинается цикл по всем входам таблицы в поиске входа с текущим значением name; цикл заканчивается по окончании таблицы (когда значение table->ctl_name становится равным 0) или после того, как будет найден и обработан указанный вход таблицы.
30572: Считывает текущий вход массива name в переменную n, чтобы его можно было сверить со значением ctl_name в текущем входе таблицы. Поскольку значение name не меняется во внутреннем цикле, чтение этой переменной можно было бы вынести из цикла (то есть перенести в строку 30569) для небольшого ускорения.
30574: Проверяет, совпадает ли имя текущего элемента ctl_table с искомым именем или имеет специальное «подстановочное» значение CTL_ANY (строка 17761). Назначение второй части не ясно, поскольку CTL_ANY в настоящее время в коде ядра нигде не используется. Может быть применение этого значения запланировано на будущее; автор не думает, что оно осталось от прошлого, поскольку значение CTL_ANY не использовалось и в ядре 2.0, и весь интерфейс sysctl относится только к проекту разработок, который предшествовал версии 2.0.
30576: Если этот элемент таблицы имеет дочерний элемент, то он является «каталогом».
30577: В соответствии со стандартными правилами поведения Unix, выполняется проверка бита х (выполнимый) каталога для определения того, следует ли разрешить текущему процессу войти в него. Обратите внимание, что это происходит во многом аналогично тому, что принято в файловой системе, хотя этот интерфейс (/proc) не является интерфейсом файловой системы. Это сделано для того, чтобы оба интерфейса к настраиваемым параметрами ядра давали единообразные результаты, поскольку было бы очень удивительно, если бы один и тот же пользователь имел право изменять какой-то параметр ядра через один интерфейс, но не через другой.
30579: Если данный вход таблицы содержит strategy-функцию, может потребоваться отменить это решение, которое позволяет процессу войти в каталог. Выполняется обращение за консультацией к strategy-функции, и если она возвращает ненулевое значение, прерывается весь поиск.
30587: Выполнен вход в каталог. Это фактически приводит к продолжению внешнего цикла и к переходу в нем к следующему компоненту имени.
30592: Этот узел таблицы представляет собой лист-узел, а это значит, что найден параметр ядра. Обратите внимание, что функция не утруждает себя проверкой того, находится ли массив name в его последнем элементе (то есть того, равно ли теперь значение nlen единице), хотя можно утверждать, что иная ситуация представляла бы собой своего рода ошибку. Так или иначе, функция do_sysctl_strategy (строка 30603) получает поручение выполнить чтение и/или запись текущего элемента таблицы.
30598: Массив name не был пуст, но его элементы были исчерпаны до того, как был найден лист-узел. Функция parse_table возвращает ошибку ENOTDIR в качестве сигнала о неудаче при поиске указанного узла. Кстати, обратите внимание на лишнюю точку с запятой в предыдущей строке.
30603: Функция do_sysctl_strategy выполняет чтение и/или запись данных в отдельном объекте ctl_table. Ее замысел состоит в использовании члена strategy данного элемента таблицы, если он присутствует, для выполнения чтения/записи. Если данный элемент таблицы не имеет свою собственную strategy-процедуру, вместо нее используется некоторый универсальный код чтения/записи. Как будет показано ниже, функция работает именно так, как задумано.
30610: Если значение oldval отлично от NULL, вызывающая программа пытается прочитать старое значение, поэтому в переменной ор устанавливается бит r. Аналогичным образом, если отлично от NULL значение newval, устанавливается бит w. Затем в строке 30614 выполняется проверка прав доступа и возвращается ошибка EPERM, если текущий процесс не имеет соответствующих прав.
30617: Если данный вход таблицы имеет свою собственную strategy-процедуру, эта процедура получает шанс выполнить запрос на чтение/запись. Если она возвращает отрицательное значение (ошибку), ошибка передается вызывающей программе. Если она возвращает положительное значение, вызывающей программе передается 0 (успех). Если strategy-процедура возвращает 0, это значит, что она отказалась сама выполнить запрос, и вместо нее будет использоваться правило поведения, принятое по умолчанию. (Вполне можно представить себе strategy-процедуру, которая всегда будет возвращать только 0, но все равно будет иметь право на существование, если ей будет поручена какая-то другая работа, например, сбор статистической информации о том, как часто происходил ее вызов.)
30630: Это начало универсального кода чтения. Обратите внимание, что возвращаемое значение функции get_user (строка 13254) не проверяется. (Аналогичная ошибка возникает в строках 9537 и 31186.)
30632: Проверка того, что будет возвращено не больше данных, чем указано в поле maxlen данного входа таблицы.
30634: Копирует затребованные данные из таблицы с помощью oldval и сохраняет фактически считанный объем данных с помощью oldlenp.
30642: Аналогично oldlenp, проверка того, что в данный вход таблицы не может быть записано больше данных, чем допускает его член maxlen. Обратите внимание, что может оказаться выполненным только частичное обновление члена table->data, если в ходе выполнения функции copy_from_user в строке 30644 будет обнаружена ошибка.
30648: Возвращает 0 в качестве обозначения успеха. Эта точка достигается в любом из следующих трех случаев:
Первый из этих случаев в определенной степени удивителен, а последний удивляет еще больше. В первом случае удивительным является то, что вызов sysctl без запроса ни на чтение, ни на запись в указанный вход таблицы не имеет смысла, поэтому такой вызов можно на законных основаниях рассматривать как ошибку. Тем не менее, он в целом соответствует принципам реализации в ядре других системных вызовов, согласно которым запрос пустой команды не является ошибкой. Например, функция sys_brk (строка 33155), рассматриваемая в главе 8, не сообщает об ошибке, если новое значение brk, которое указано вызывающей программой, совпадает со старым значением.
Третий случай является более удивительным по сравнению с первым, поскольку он действительно может свидетельствовать об ошибке. Вызывающий код может, например, попытаться выполнить запись в параметр, для которого значение maxlen равно 0, и посчитать, что эта попытка удалась, поскольку системный вызов возвратил значение, свидетельствующее об успехе. Однако создается впечатление, что это в действительности не имеет значения, поскольку вход таблицы со значением maxlen равным 0, является бесполезным во всех отношениях; тем не менее, в коде есть вход таблицы со значением maxlen равным 0 — см. строку 30380. В конечном итоге, все это сводится к тому, что в действительности предусмотрено документацией к функции sysctl, но справочное руководство по этому поводу умалчивает. Однако, автор считает, что в этом случае функция do_sysctl_strategy должна возвращать ошибку EPERM.
30651: Вставляет новое дерево объектов ctl_table, для которых указан корневой каталог, в циклически связанный список деревьев.
30655: Распределяет объект struct ctl_table_header для управления информацией о новом дереве.
30659: Вставляет новый заголовок (который отслеживает новое дерево массивов объектов ctl_table) в связанный список заголовков.
30666: Вызывает функцию register_proc_table (строка 30689, описана ранее в этой главе) для регистрации нового дерева таблиц под каталогом /proc/sys. Этот код транслируется вне ядра, если ядро транслируется без поддержки файловой системы /proc.
30668: Вновь распределенный заголовок возвращается вызывающей программе, с тем чтобы вызывающая программа могла в дальнейшем удалить дерево, передав этот заголовок функции unregister_sysctl_table (строка 30672).
30672: Как было указано выше, эта простая функция только удаляет дерево объектов ctl_table из циклического списка таких деревьев ядра. Она также удаляет соответствующие данные из файловой системы /proc, если ядро транслировано с поддержкой /proc. Снова просмотрев строки 30490 и 30500, можно видеть, что объект root_table_header (строка 30256), или лист-узел, соответствующий объекту root_table, используется в качестве начального и конечного узла при проходе по циклическому списку деревьев. Теперь можно видеть, что в функции unregister_sysctl_table ничто не исключает возможности удаления объекта root_table_header из списка заголовков таблиц, просто этого никто не делает.
31121: sysctl_string — это одна из strategy-процедур объекта ctl_table. Напомним, что strategy-процедуры при желании могут быть вызваны из строки 30618 (в функции do_sysctl_strategy) для замещения применяемого по умолчанию кода чтения/записи входа таблицы. (strategy-процедуры могут быть также вызваны из строки 30580, но данная процедура к ним не относится.)
31127: Если таблица не имеет связанных с ней данных или если длина доступной части равна 0, возвращается ошибка ENOTDIR. Это не совместимо с правилами поведения функции do_sysctl_strategy, которая в аналогичном случае возвращает успешный результат.
31138: Текущее значение строки копируется в пространство пользователя и результат оканчивается символом NUL (это значит, что может быть выведено число байтов, которое на единицу больше указанного параметром lenp; возможно, это — ошибка, в зависимости от того, что указано в документации). Поскольку текущее значение уже оканчивается символом NUL, эти четыре строки кода можно легко свести к двум:
if (copy_to_user(oldval, table->data, len + 1)) return -EFAULT;
Допустимость этого изменения частично зависит от трех свойств, которые учитываются в остальной части кода при записи в член table->data:
Поскольку все эти три свойства соблюдаются, значение len всегда будет строго меньше значения table->maxlen в строке 31138, и завершающий символ NUL должен появляться в позиции или перед позицией table->data[len + 1].
31146: По аналогии с предыдущим случаем, новое значение копируется из пространства пользователя и результат оканчивается символом NUL. Однако в этом случае не стоит копировать байт NUL из пространства пользователя, поскольку будет менее эффективным копировать его из пространства пользователя, чем просто присвоить значение NUL соответствующему байту объекта . К тому же, это позволяет записывать символ NUL в конце table->data, даже если его не было на входе. Безусловно, строка, считанная из newval, уже могла оканчиваться символом NUL, и в этом случае присваивание в строке 31154 будет излишним. Это еще один пример того, что иногда быстрее просто выполнить работу, чем проверять, нужно ли ее выполнять.
31156: Возвращает 0 в качестве обозначения успеха. Вместо этого, возвращаемое значение должно быть положительным, чтобы результат интерпретировался в строка 30618 как успех. В ином случае, вызывающий код считает, что функция sysctl_string запрашивает выполнение обработки, применяемой по умолчанию, и снова переходит к выполнению ненужного копирования данных из пространства пользователя.
31163: Функция sysctl_intvec это еще одна strategy-процедура, которая определена в программе kernel/sysctl.c. Если вызывающая программа выполняет запись в этот вход таблицы, эта функция проверяет, чтобы все записываемые данные типа int находились в пределах, указанных минимальным и максимальным значениями. (Кстати, функция sysctl_intvec в этом файле используется только один раз, в строке 30414; хотя она широко применяется в других местах в ядре, в коде, не включенном в эту книгу.)
31170: Если новый объем данных, подлежащий записи, не оканчивается на границе данных с размером int, он является недопустимым, поэтому попытка отвергается.
31173: Если вход таблицы не указывает набор максимальных или минимальных значений, входные значения никогда не могут выйти за пределы диапазона, поэтому для вызывающей программы вполне приемлем универсальный код записи (do_sysctl_strategy, строка 30603). Поэтому в данном случае функция sysctl_intvec возвращает 0.
31184: Начинается цикл, который проверяет, что все значения из входного массива лежат в соответствующем диапазоне.
31186: Этот код не проверяет возвращаемое значение функции get_user, поскольку в этом нет настоятельной необходимости. Если функция sysctl_intvec возвращает 0 (успех), не имея возможности прочесть входные данные из этого места в памяти, данную проблему обнаружит функция do_sysctl_strategy при попытке прочесть весь массив. Иначе, если из этого места в памяти не сможет прочесть функция get_user, в переменной value может оказаться мусор и ее значение может быть некорректно отброшено. В этом случае вызывающая программа получит ошибку EINVAL, а не ошибку EFAULT, которая является менее значимой ошибкой.
31187: Обратите внимание, что здесь не проявляется ошибка, которая нарушала работу аналогичного кода в строке 31033, где параллельная итерация по массивам минимальных и максимальных значений могла нарушить их синхронизацию.
Именно этот код препятствует проявлению ошибки в строке 31033. Когда это происходит, и sysctl_intvec и proc_dointvec_minmax всегда связаны с одними и теми же входами объекта ctl_table. Следовательно, любые значения, выходящие за пределы допустимого диапазона, будут перехвачены strategy-процедурой, sysctl_intvec, перед тем, как появится возможность вызвать процедуру обработки proc_dointvec_minmax.
Поэтому мы знаем, что с учетом текущих определений всех объектов ctl_table в ядре, функция proc_dointvec_minmax никогда не встретит значения, выходящего за пределы диапазона, а это единственный тип значения, который может активизировать эту ошибку.
Однако некоторые вызывающие программы могли бы зарегистрировать объект ctl_table, в котором используется функция proc_dointvec_minmax, но не strategy-процедура, поэтому все равно ошибка в функции proc_dointvec_minmax может когда-нибудь выйти наружу.
31193: Возвращает 0 в качестве обозначения успеха. Это не ошибка, как было в строке 31156, поскольку функция sysctl_intvec не пишет в table->data. Значения, считанные из пространства пользователя, просто попадают во временную переменную и проверяются на соответствие диапазону, а затем отбрасываются; одну единственную запись в table->data будет выполнять функция do_sysctl_strategy.
netlib.narod.ru | < Назад | Оглавление | Далее > |