Ядро Linux в комментариях

         

Do_proc_dointvec


Функция proc_dointvec (строка ) делегирует свою работу этой функции. Функция do_proc_dointvec читает или пишет массив данных типа int, на который указывает член data объекта table. Число переменных типа int, которые должны быть считаны или записаны, передается параметром lenp; обычно этот параметр равен 1, поэтому данная функция, как правило, используется для чтения или записи только единственной переменной типа int. Значения данных типа int заданы параметром buffer. Однако данные типа int не передаются в виде массива данных типа int во внутреннем представлении; вместо этого, они представлены в виде текста ASCII, который записывается пользователем в соответствующий файл /proc.

Начинаются итерации по всем данным типа int для чтения или записи. Переменная left следит за оставшимся числом данных типа int, которые должны быть считаны или записаны вызывающей программой, а переменная vleft следит за числом допустимых элементов, оставшихся в объекте table->data. Цикл завершается, когда любое из этих значений достигает 0 или когда выход из него происходит в середине. Обратите внимание, что весь цикл можно было бы сделать чуть более эффективным, но также более сложным для сопровождения, если вывести из этого цикла оператор if в строке , то есть вместо кода такой структуры:

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, которое не изменяется внутри цикла, можно было бы проверять только один раз, а не на каждой итерации цикла.

Поиск вперед непробельного символа, то есть начала следующего числа на входе.

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


Такой подход выглядит не очень эффективным, поскольку он допускает чтение большего объема, чем это необходимо. Однако, поскольку размер буфера buf равен только 20 (TMPBUFLEN, строка ), в него нельзя считать намного больше, чем необходимо. Здесь идея, вероятно, состоит в том, что дешевле просто считать немного больше, чем проверять каждый байт и следить за тем, нужно ли закончить чтение.

Величина buf выбрана достаточно большой для того, чтобы в ней можно было разместить представление в коде ASCII любого 64-разрядного целого числа, поэтому данная функция может поддерживать не только 32-разрядные, но и 64-разрядные платформы. И действительно, буфер достаточно велик для того, чтобы в нем могло разместиться самое большое 64-разрядное положительное целое число, десятичное представление которого состоит из 19 цифр (завершающий байт NUL будет записан в 20-м байте). Но помните, что существуют целые числа со знаком, поэтому значение –9223372036854775808, наименьшее 64-разрядное целое число со знаком, также представляет собой допустимый ввод. Оно не может быть считано правильно. К счастью, исправление тривиально и очевидно.

Вскоре мы покажем, как будет действовать этот код при получении подобного ввода.

Обработка ведущего знака минус (–), переход к символу за знаком минус и установка флажка, если знак минус был найден.

Проверка того, что текст, который был считан из буфера (возможно, за ведущим знаком минус), по крайней мере, начинается с цифры, чтобы его можно было успешно преобразовать в целое число. Без этой проверки было бы невозможно узнать, вернул ли вызов функции simple_strtoul в строке значение 0 из-за того, что на входе был «0», или из-за того, что она совсем не смогла преобразовать входной текст.

Преобразование текста в целое число и масштабирование результата с использованием параметра conv. Этот этап масштабирования применяется в таких функциях, как proc_dointvec_jiffies (строка ), которая преобразует свой вход из секунд в единицы измерения процессорного времени, просто путем умножения на константу HZ. Однако, как правило, коэффициент масштабирования равен 1, что равносильно отсутствию масштабирования.



Если остается еще текст, который должен быть считан из буфера, и следующий символ, который должен быть считан, отличается от пробела, разделяющего параметры, это значит, что в буфер buf весь параметр не поместился. Такие входные данные являются недопустимыми, поэтому цикл преждевременно прекращается. (Одной из версий развития событий, при которой функция могла бы оказаться в этом состоянии, была бы передача в качестве параметра наименьшего 64-разрядного целого числа со знаком, как было описано выше.) Однако код ошибки не возвращается, поэтому вызывающая программа может ошибочно считать, что все было нормально. Однако это не совсем так: код ошибки будет возвращен в строке , но только если недопустимый параметр был обнаружен при первой итерации цикла; если он будет обнаружен в последующей итерации цикла, ошибка не будет замечена.

Параметр был прочитан успешно. Теперь учитывается ведущий знак минус, если он присутствовал на входе, корректируются другие локальные переменные для перехода к следующему параметру и параметр записывается во вход таблицы под указателем i.

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

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



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



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

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

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

Завершает вывод символом новой строки, если вызывающая программа выполняла чтение. Условие if также выполняет проверку того, что цикл не закончился при его первой итерации и что есть место для записи символа новой строки. Обратите внимание, что буфер вывода не завершается байтом NUL кода ASCII (как можно было ожидать), поскольку этого не требуется: вызывающая программа может определить длину возвращенной строки из нового значения, записанного с помощью lenp.

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

Обновляет текущую позицию файла и значение lenp, а затем возвращает 0 в качестве обозначения успеха.


Do_rw_proc


Функция do_rw_proc реализует содержательную часть функций proc_readsys (строка ) и proc_writesys (строка ), которые используются в коде файловой системы /proc для чтения и записи данных в объектах ctl_table.

Проверка того, что с этим входом под каталогом /proc/sys связана какая-то таблица.

Обратите внимание, что первая проверка в этой строке дублирует вторую проверку в строке , поскольку объект table инициализирован из члена de->data.

Проверка того, что вызывающий процесс имеет, соответственно, право на чтение или запись.

Вызов функции proc_handler данного входа таблицы для фактического выполнения чтения или записи. (Отметим, что в строке было проверено, что член proc_handler отличен от NULL.) Как было упомянуто ранее, член proc_handler обычно имеет значение proc_dostring или proc_dointvec (строки и ), которые описаны в следующих нескольких разделах.

Функция do_rw_proc возвращает число фактически считанных или записанных байтов. Обратите внимание, что локальная переменная res совсем не нужна; она может быть заменена параметром count.



Do_sysctl


Функция do_sysctl реализует значительную часть работы sys_sysctl (строка ), системного вызова sysctl. Обратите внимание, что sys_sysctl появляется также в строке и эта версия представляет собой просто функцию-заглушку, которая используется при трансляции системного вызова sysctl вне ядра. Старое значение параметра ядра возвращается через oldval, если параметр oldval не равен NULL, а его новое значение устанавливается из newval, если параметр newval не равен NULL. Параметры oldlenp и newlen указывают, соответственно, сколько байтов должно быть записано в oldval и считано из newval, если соответствующие указатели отличны от NULL; они игнорируются, если указатели равны NULL.

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

Устанавливает значение old_len из oldlenp, если вызывающая программа хочет получить старое значение параметра ядра.

Начинает проход по циклическому списку деревьев таблицы. (См. описание функции register_sysctl_table далее в этой главе.)

Использует функцию parse_table (строка , рассматривается в следующем разделе) для поиска настраиваемого параметра ядра и для чтения и/или записи его значения.

Если функция parse_table распределила память под какую-либо контекстную информацию, она освобождается. Трудно точно сказать, что подразумевается под этой контекстной информацией. Она не используется ни в каком коде, рассматриваемом в этой книге; в действительности, насколько может судить об этом автор, она в настоящее время не применяется ни в каком коде где-либо в ядре.

Ошибка ENOTDIR просто означает, что указанный параметр ядра не был найден в этом дереве таблиц; он все еще может быть найден в другом дереве таблиц, где еще не выполнялся поиск. В ином случае, переменная error может содержать какой-то другой код ошибки или 0, в случае успеха; так или иначе, она должна быть (и будет) возвращена.

Продвижение вперед итератора цикла с использованием макрокоманды DLIST_NEXT (не включена в эту книгу).

Возвращает ошибку ENOTDIR, которая сообщает о том, что указанный параметр ядра не был найден ни в одной таблице.



Do_sysctl_strategy


Функция do_sysctl_strategy выполняет чтение и/или запись данных в отдельном объекте ctl_table. Ее замысел состоит в использовании члена strategy данного элемента таблицы, если он присутствует, для выполнения чтения/записи. Если данный элемент таблицы не имеет свою собственную strategy-процедуру, вместо нее используется некоторый универсальный код чтения/записи. Как будет показано ниже, функция работает именно так, как задумано.

Если значение oldval отлично от NULL, вызывающая программа пытается прочитать старое значение, поэтому в переменной ор устанавливается бит r. Аналогичным образом, если отлично от NULL значение newval, устанавливается бит w. Затем в строке выполняется проверка прав доступа и возвращается ошибка EPERM, если текущий процесс не имеет соответствующих прав.

Если данный вход таблицы имеет свою собственную strategy-процедуру, эта процедура получает шанс выполнить запрос на чтение/запись. Если она возвращает отрицательное значение (ошибку), ошибка передается вызывающей программе. Если она возвращает положительное значение, вызывающей программе передается 0 (успех). Если strategy-процедура возвращает 0, это значит, что она отказалась сама выполнить запрос, и вместо нее будет использоваться правило поведения, принятое по умолчанию. (Вполне можно представить себе strategy-процедуру, которая всегда будет возвращать только 0, но все равно будет иметь право на существование, если ей будет поручена какая-то другая работа, например, сбор статистической информации о том, как часто происходил ее вызов.)

Это начало универсального кода чтения. Обратите внимание, что возвращаемое значение функции get_user (строка ) не проверяется. (Аналогичная ошибка возникает в строках и .)

Проверка того, что будет возвращено не больше данных, чем указано в поле maxlen данного входа таблицы.

Копирует затребованные данные из таблицы с помощью oldval и сохраняет фактически считанный объем данных с помощью oldlenp.

Аналогично oldlenp, проверка того, что в данный вход таблицы не может быть записано больше данных, чем допускает его член maxlen. Обратите внимание, что может оказаться выполненным только частичное обновление члена table->data, если в ходе выполнения функции copy_from_user в строке будет обнаружена ошибка.


Возвращает 0 в качестве обозначения успеха. Эта точка достигается в любом из следующих трех случаев:

Вызывающая программа не пыталась ни читать, ни писать в этот вход таблицы.

Вызывающая программа пыталась читать и/или писать в этот вход таблицы и все этапы на этом пути были успешными.

Этот вход таблицы не имеет связанных с ним данных или он фактически предназначен только для чтения, поскольку его значение maxlen равно0.

Первый из этих случаев в определенной степени удивителен, а последний удивляет еще больше. В первом случае удивительным является то, что вызов sysctl без запроса ни на чтение, ни на запись в указанный вход таблицы не имеет смысла, поэтому такой вызов можно на законных основаниях рассматривать как ошибку. Тем не менее, он в целом соответствует принципам реализации в ядре других системных вызовов, согласно которым запрос пустой команды не является ошибкой. Например, функция sys_brk (строка ), рассматриваемая в , не сообщает об ошибке, если новое значение brk, которое указано вызывающей программой, совпадает со старым значением.

Третий случай является более удивительным по сравнению с первым, поскольку он действительно может свидетельствовать об ошибке. Вызывающий код может, например, попытаться выполнить запись в параметр, для которого значение maxlen равно 0, и посчитать, что эта попытка удалась, поскольку системный вызов возвратил значение, свидетельствующее об успехе. Однако создается впечатление, что это в действительности не имеет значения, поскольку вход таблицы со значением maxlen равным 0, является бесполезным во всех отношениях; тем не менее, в коде есть вход таблицы со значением maxlen равным 0 — см. строку . В конечном итоге, все это сводится к тому, что в действительности предусмотрено документацией к функции sysctl, но справочное руководство по этому поводу умалчивает. Однако, автор считает, что в этом случае функция do_sysctl_strategy должна возвращать ошибку EPERM.


Настраиваемые параметры ядра


В соответствии с традицией, которая впервые возникла в версии BSD4.4 системы Unix, в Linux предусмотрен системный вызов sysctl для динамической проверки и перенастройки некоторых средств системы без редактирования и перетрансляции исходного кода ядра и перезагрузки системы. Это значительный шаг вперед по сравнению с ранними версиями Unix, в которых настройка системы часто представляла собой нудную и утомительную работу. В Linux системные средства, которые могут быть проверены и перенастроены таким образом, сгруппированы по нескольким категориям: общие параметры ядра, параметры виртуальной памяти, сетевые параметры и так далее.

Те же средства доступны также через другой интерфейс — через файловую систему /proc. (Поскольку это на самом деле окно в систему, а не контейнер для настоящих файлов, /proc часто называют «псевдофайловой системой», но это — неуклюжий термин, и здесь эта разница не имеет никакого значения.) Категории настраиваемых параметров ядра соответствуют подкаталогам каталога /proc/sys, и каждый отдельный настраиваемый параметр ядра представлен файлом в одном из этих подкаталогов. Подкаталоги могут, в свою очередь, включать другие подкаталоги, которые содержат еще больше файлов, представляющих настраиваемые параметры ядра и, возможно, еще больше подкаталогов и так далее, хотя на практике уровень вложенности никогда не бывает слишком глубоким.

Каталог /proc/sys позволяет обойти обычный интерфейс sysctl, поскольку значение настраиваемого параметра ядра можно узнать, просто прочитав его из соответствующего файла, а его значение можно установить, просто выполнив запись в этот файл. К этим файлам применяются обычные права файловой системы Unix, поэтому их не может читать или писать первый попавшийся пользователь. Большинство этих файлов доступны для чтения любому пользователю, но для записи доступны только пользователю root, хотя есть и исключения; например, файлы в каталоге /proc/sys/vm (параметры виртуальной памяти) может читать и писать только пользователь root. Если бы не было каталога /proc/sys, проверка и настройка системы потребовала бы написания программы для вызова sysctl с необходимыми параметрами; это не сложная работа, но далеко не так удобна, как использование /proc/sys.



Parse_table


Функция parse_table ищет вход в дереве таблиц по аналогии с тем, как происходит поиск в дереве каталогов полностью квалифицированного имени файла. Идея состоит в следующем: поиск происходит вдоль массива данных типа int (массива name) с просмотром каждого элемента типа int в массиве ctl_table. Если будет обнаружено совпадение, выполняется рекурсивный просмотр соответствующей дочерней таблицы (если совпадающий вход представляет собой вход типа каталога); или выполняется чтение и/или запись входа (если это вход типа файла).

К некоторому удивлению, это начало цикла, который перебирает все элементы целочисленного массива name. Было бы более привычно, если бы все, начиная с этой строки и кончая строкой , было заключено в цикл for, который мог бы начинаться примерно так:

for (; nlen; ++name, --nlen, table = table->child)

(Здесь также нужно было бы удалить строки и и заменить строки с по оператором break.) Возможно, фактически применяемая версия генерирует лучший объектный код.

Начинается цикл по всем входам таблицы в поиске входа с текущим значением name; цикл заканчивается по окончании таблицы (когда значение table->ctl_name становится равным 0) или после того, как будет найден и обработан указанный вход таблицы.

Считывает текущий вход массива name в переменную n, чтобы его можно было сверить со значением ctl_name в текущем входе таблицы. Поскольку значение name не меняется во внутреннем цикле, чтение этой переменной можно было бы вынести из цикла (то есть перенести в строку ) для небольшого ускорения.

Проверяет, совпадает ли имя текущего элемента ctl_table с искомым именем или имеет специальное «подстановочное» значение CTL_ANY (строка ). Назначение второй части не ясно, поскольку CTL_ANY в настоящее время в коде ядра нигде не используется. Может быть применение этого значения запланировано на будущее; автор не думает, что оно осталось от прошлого, поскольку значение CTL_ANY не использовалось и в ядре 2.0, и весь интерфейс sysctl относится только к проекту разработок, который предшествовал версии 2.0.


Если этот элемент таблицы имеет дочерний элемент, то он является «каталогом».

В соответствии со стандартными правилами поведения Unix, выполняется проверка бита х (выполнимый) каталога для определения того, следует ли разрешить текущему процессу войти в него. Обратите внимание, что это происходит во многом аналогично тому, что принято в файловой системе, хотя этот интерфейс (/proc) не является интерфейсом файловой системы. Это сделано для того, чтобы оба интерфейса к настраиваемым параметрами ядра давали единообразные результаты, поскольку было бы очень удивительно, если бы один и тот же пользователь имел право изменять какой-то параметр ядра через один интерфейс, но не через другой.

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

Выполнен вход в каталог. Это фактически приводит к продолжению внешнего цикла и к переходу в нем к следующему компоненту имени.

Этот узел таблицы представляет собой лист-узел, а это значит, что найден параметр ядра. Обратите внимание, что функция не утруждает себя проверкой того, находится ли массив name в его последнем элементе (то есть того, равно ли теперь значение nlen единице), хотя можно утверждать, что иная ситуация представляла бы собой своего рода ошибку. Так или иначе, функция do_sysctl_strategy (строка ) получает поручение выполнить чтение и/или запись текущего элемента таблицы.

Массив name не был пуст, но его элементы были исчерпаны до того, как был найден лист-узел. Функция parse_table возвращает ошибку ENOTDIR в качестве сигнала о неудаче при поиске указанного узла. Кстати, обратите внимание на лишнюю точку с запятой в предыдущей строке.


Поддержка /proc/sys


В эту книгу включен не весь код реализации интерфейса /proc/sys к настраиваемым параметрам ядра; в действительности, не включена основная часть этого кода, поскольку она главным образом относится к файловой системе /proc. К тому же, если вас не интересует, как работает остальная часть /proc, вы сможете легко разобраться в коде программы kernel/sysctl.c, которая работает с файловой системой /proc, для того, чтобы обеспечить доступ к настраиваемым параметрам ядра под каталогом /proc.



Proc_dointvec_minmax


Функция proc_dointvec_minmax почти аналогична do_proc_dointvec, за исключением того, что она дополнительно рассматривает члены extra1 и extra2 входа таблицы как массивы ограничений на те значения, которые могут быть записаны во вход таблицы. Значения в extra1 представляют собой минимумы, а значения в extra2 — максимумы. Еще одним отличием является то, что функция proc_dointvec_minmax не имеет параметра conv.

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

Вот самое значительное различие: при записи значения, выходящие за пределы диапазона, определенного величинами 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. (Другое дело — будущие выпуски, но это еще не написанный роман.)



Proc_dostring


Функция proc_dostring представляет собой функцию, которую вызывает код файловой системы /proc для чтения или записи параметра ядра, представляющего собой строку С.

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

В строке есть также реализация этой функции в виде заглушки; эта заглушка используется, если файловая система /proc транслируется вне ядра. За описанием этой функции следует описание аналогичных заглушек для большинства других функций, которые будут описаны ниже.

Символы считываются из входного буфера до тех пор, пока не будет найден заключительный символ NUL (0) или символ новой строки кода ASCII, или пока из входного буфера не будет считан максимально допустимый объем данных (который указан параметром lenp). (Чтобы избежать путаницы, напомним, что NULL— константа-указатель языка С, a NUL, с одним L, является обозначением в коде ASCII нулевого символа.)

Если число байтов, считанных из буфера превышает объем, который может быть записан в данный вход таблицы, число байтов уменьшается. Вместо этого, вероятно, было бы эффективнее ограничить максимальную длину входных данных (lenp) перед циклом, поскольку чтение из буфера числа байтов, превышающего значение table->maxlen, в любом случае бессмысленно. В том виде, как есть, цикл может прочитать, скажем, 1024 байта, а затем уменьшить этот объем до 64, поскольку это все, что можно записать во вход таблицы.

Строка считывается из входного буфера, а затем в ее конце записывается символ NUL.

Ядро поддерживает переменную со значением «текущей позиции» для каждого файла, принадлежащего каждому процессу; это член f_pos объекта struct file. Он представляет собой значение, возвращаемое системным вызовом tell и устанавливаемое системным вызовом seek. Здесь текущая позиция в файле увеличивается на число записанных байтов.



Proc_doutsstring


Функция proc_doutsstring просто вызывает функцию proc_dostring после приобретения семафора uts_sem (строка ). Эта функция используется в нескольких входах объекта kern_table (строка ) для установки различных компонентов структуры system_utsname (строка ).



Register_proc_table


Функция register_proc_table регистрирует объект ctl_table под каталогом /proc/sys. Обратите внимание, что эта функция не требует, чтобы таблица, передаваемая в качестве параметра, была узлом корневого уровня (то есть объектом ctl_table без родителя)— эта таблица должна быть таковой, но за соблюдение данного требования отвечает вызывающая программа. Таблица устанавливается непосредственно под каталогом, обозначенным параметром root, который должен соответствовать каталогу /proc/sys или одному из подкаталогов под ним. (При первоначальном вызове root всегда указывает на proc_sys_root, но после рекурсивных вызовов его значение изменяется.)

Начинается итерация по всем элементам массива table; итерация заканчивается, когда член ctl_name текущего элемента становится равным 0, что означает конец массива.

Если поле procname объекта ctl_table имеет значение NULL, этот объект не должен быть видимым под каталогом /proc/sys, даже несмотря на то, что другие элементы того же массива могут быть видимыми. Такие элементы массива пропускаются.

Если данный вход таблицы имеет отличное от NULL значение procname, а это означает, что он должен быть зарегистрирован под каталогом /proc/sys, он должен также иметь отличный от NULL член proc_handler (если это лист дерева или узел, подобный файлу) или член child (если это узел, подобный каталогу). Если в нем отсутствуют оба члена, выводится предупреждающее сообщение и цикл продолжается.

Если вход таблицы имеет отличный от NULL член proc_handler, он отмечается как обычный файл.

Иначе, как можно утверждать на основании строки , он должен иметь отличный от NULL член child, поэтому данный вход будет рассматриваться как каталог. Обратите внимание: ничто не препятствует тому, чтобы оба члена объекта ctl_table, и proc_handler и child, были отличны от NULL, но в таком случае это соглашение должно соблюдаться во всем коде.

Выполняет поиск указанного имени в существующем подкаталоге и оставляет de, указывающим на существующий вход, если он найден, или равным NULL, если он не найден. Трудно понять, почему аналогичная проверка не предусмотрена для файлов; в файловой системе /proc может быть какая-то тонкость, которую автор не смог понять, и ответ, безусловно, лежит здесь.


Если указанный подкаталог не существует или параметр table соответствует файлу, а не каталогу, создается новый файл или каталог путем вызова функции create_proc_entry (не рассматривается).

Если вход таблицы представляет собой лист-узел, функция register_proc_table сообщает коду файловой системы /proc, чтобы в нем использовались файловые операции, которые определены в функции proc_sys_inode_operations (строка ). В функции proc_sys_inode_operations определены только две операции — чтение и запись (нет ни поиска, ни отображения памяти, ни всего прочего). Эти операции выполняются с помощью функций proc_readsys и proc_writesys (строки и ), которые рассматриваются далее в этой главе.

К этому моменту уже известно, что значение de не равно NULL; оно либо было уже отлично от NULL, либо было инициализировано в строке .

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


Register_sysctl_table


Вставляет новое дерево объектов ctl_table, для которых указан корневой каталог, в циклически связанный список деревьев.

Распределяет объект struct ctl_table_header для управления информацией о новом дереве.

Вставляет новый заголовок (который отслеживает новое дерево массивов объектов ctl_table) в связанный список заголовков.

Вызывает функцию register_proc_table (строка , описана ранее в этой главе) для регистрации нового дерева таблиц под каталогом /proc/sys. Этот код транслируется вне ядра, если ядро транслируется без поддержки файловой системы /proc.

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



Системный вызов sysctl


Еще одним интерфейсом к настраиваемым параметрам ядра является системный вызов sysctl, наряду со связанными с ним функциями. У автора создалось впечатление, что этот интерфейс попал в немилость. А как же иначе? В большинстве практических ситуаций sysctl (каким бы великолепным вначале он ни казался по сравнению со старым методом настройки ядра путем корректировки исходного кода) просто является более неуклюжим по сравнению с доступом к файлам через /proc. Чтение и запись через sysctl требует применения программы на С (или чего-то подобного), тогда как файловая система /proc легко доступна через команды командного интерпретатора (или, равным образом, через сценарии командного интерпретатора).

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



Struct ctl_table


Это центральная структура данных, используемая в коде, который описан в этой главе. Объекты struct ctl_table обычно объединяются в массивы и каждый такой массив соответствует входам в отдельном каталоге, находящемся где-то под каталогом /proc/sys. (По мнению автора, этот тип данных лучше было бы назвать struct ctl_table_entry.) Объект root_table (строка ) и массивы, которые следуют за ним, создают дерево массивов, в которых указатели child объекта struct ctl_table используются для соединения узлов дерева (указатели child рассматриваются в приведенном ниже списке). Отметим, что все эти массивы являются массивами объекта ctl_table, который просто служит объявлением typedef для объекта struct ctl_table; это установлено в строке .

Связь в виде дерева массивов показана на рис. 11.1.


Рис. 11.1. Часть дерева объектов struct ctl_table

На этом рисунке показана небольшая часть дерева, состоящего из объекта root_table и таблиц, на которые он указывает.

Объект struct ctl_table имеет следующие члены:

ctl_name. Целое число, которое однозначно обозначает вход таблицы (тем не менее, оно однозначно обозначает вход только внутри массива, в котором он находится); эти числа в разных массивах могут повторяться. Любой элемент массива уже имеет такой уникальный номер (его индекс в массиве), но это число не может применяться для данной цели, поскольку разработчики ядра обязаны обеспечить совместимость с последующими выпусками ядра на уровне двоичного кода. Настраиваемый параметр ядра, связанный со входом массива, в одной версии ядра, может исчезнуть в будущей версии ядра, поэтому, если бы параметры обозначались их индексами массива, то повторное использование позиции устаревшего элемента массива могло бы нарушить работу программ, которые не были перетранслированы под новое ядро. Со временем, массивы стали бы забиты неиспользуемыми входами, которые просто занимали бы место ради обратной совместимости. Вместо этого, в данном подходе просто «неэкономно» используются целые числа, в которых никогда нет недостатка. (С другой стороны, поиск становится медленнее, поскольку при таком методе нельзя просто использовать индекс массива.)


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

procname. Короткое, предназначенное для восприятия человеком имя файла для соответствующего входа под каталогом /proc/sys.

data. Указатель на данные, связанные с этим входом таблицы. Он обычно указывает на данные типа int или char (и, безусловно, указатель на char — это строка).

maxlen. Максимальное число байтов, которые могут быть считаны или записаны в член data. Например, если data указывает на один элемент типа int то maxlen должен быть равен sizeof(int).

mode. Биты прав доступа к файлу в стиле Unix для файла (или каталога) /proc, соответствующего этому входу. Для описания этого члена нужно сделать краткий экскурс в мир файловых систем. Как и в других реализациях Unix, в Linux используется три набора по три бита в каждом для регистрации прав доступа к файлу (в листинге, который выдает команда ls -l, они обозначаются в виде трех групп, состоящих из символов r, w и х); см. рис. 11.2. Они занимают нижние 9 битов члена mode. В файловой системе оставшиеся биты параметра mode файла предназначены для других целей, например, для слежения за тем, является ли файл обычным файлом (если да, то устанавливается бит 16), каталогом (бит 15), выполнимой программой, для которой установлены права setuid или setgid (биты 12 и 11), и так далее. Однако для материала настоящей главы эти прочие биты не имеют значения.



Рис. 11.2. Биты режима доступа к файлу

В результате, в сочетании с членом mode можно часто видеть восьмеричные константы 004, 002 и 001, которые предназначены, соответственно, для проверки битов чтения (r), записи (w) и выполнения (х), возможно, после сдвига члена mode для получения требуемого набора из трех битов. В конечном итоге, этот сдвиг и проверка выполняются в функции test_perm, строка .



Обратите внимание, что если вход таблицы имеет значение maxlen, равное 0, то он фактически не подлежит ни чтению, ни записи, независимо от значения его члена mode.

child. Указатель на дочернюю таблицу, если это — вход типа каталога. В этом случае с самим входом не связаны никакие данные, поэтому член data будет иметь значение NULL, a maxlen будет равен 0.

proc_handler. Указатель на функцию, которая фактически читает или пишет член data; он используется при чтении или записи данных с применением файловой системы /proc. Таким образом, член data может указывать на данные любого типа, а корректную работу с ними обеспечивает функция proc_handler. В качестве proc_handler обычно применяются функции proc_dostring (строка ) или proc_dointvec (строка ); эти и другие часто используемые функции рассматриваются далее в настоящей главе. (Безусловно, может применяться любая функция с правильным прототипом.) Для входов типа каталога член proc_handler имеет значение NULL.

strategy. Указатель на другую функцию, которая фактически выполняет чтение или запись в члене data; именно эта функция используется при чтении или записи с помощью системного вызова sysctl. Обычно это — функция sysctl_string (строка ), но может также применяться функция sysctl_intvec (строка ); обе эти функции рассматриваются далее в этой главе. По определенным причинам большинство настраиваемых параметров ядра подлежат изменению через интерфейс /proc, а не через системный вызов sysctl, поэтому этот указатель чаще имеет значение NULL, а не иное значение.

de. Указатель на объект struct proc_dir_entry, используемый в коде файловой системы /proc для отслеживания файла или каталога в этой файловой системе. Если он отличен от NULL, то где-то под каталогом /proc зарегистрирован соответствующий объект struct ctl_table.

extra1 и extra2. Указатели на любые дополнительные данные, необходимые при обработке этого элемента таблицы. Они в настоящее время используются только для указания минимальных и максимальных значений некоторых целочисленных параметров.


Sysctl_intvec


Функция sysctl_intvec это еще одна strategy-процедура, которая определена в программе kernel/sysctl.c. Если вызывающая программа выполняет запись в этот вход таблицы, эта функция проверяет, чтобы все записываемые данные типа int находились в пределах, указанных минимальным и максимальным значениями. (Кстати, функция sysctl_intvec в этом файле используется только один раз, в строке ; хотя она широко применяется в других местах в ядре, в коде, не включенном в эту книгу.)

Если новый объем данных, подлежащий записи, не оканчивается на границе данных с размером int, он является недопустимым, поэтому попытка отвергается.

Если вход таблицы не указывает набор максимальных или минимальных значений, входные значения никогда не могут выйти за пределы диапазона, поэтому для вызывающей программы вполне приемлем универсальный код записи (do_sysctl_strategy, строка ). Поэтому в данном случае функция sysctl_intvec возвращает 0.

Начинается цикл, который проверяет, что все значения из входного массива лежат в соответствующем диапазоне.

Этот код не проверяет возвращаемое значение функции get_user, поскольку в этом нет настоятельной необходимости. Если функция sysctl_intvec возвращает 0 (успех), не имея возможности прочесть входные данные из этого места в памяти, данную проблему обнаружит функция do_sysctl_strategy при попытке прочесть весь массив. Иначе, если из этого места в памяти не сможет прочесть функция get_user, в переменной value может оказаться мусор и ее значение может быть некорректно отброшено. В этом случае вызывающая программа получит ошибку EINVAL, а не ошибку EFAULT, которая является менее значимой ошибкой.

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

Именно этот код препятствует проявлению ошибки в строке . Когда это происходит, и 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 может когда-нибудь выйти наружу.

Возвращает 0 в качестве обозначения успеха. Это не ошибка, как было в строке , поскольку функция sysctl_intvec не пишет в table->data. Значения, считанные из пространства пользователя, просто попадают во временную переменную и проверяются на соответствие диапазону, а затем отбрасываются; одну единственную запись в table->data будет выполнять функция do_sysctl_strategy.



Sysctl_string


sysctl_string— это одна из strategy-процедур объекта ctl_table. Напомним, что strategy-процедуры при желании могут быть вызваны из строки (в функции do_sysctl_strategy) для замещения применяемого по умолчанию кода чтения/записи входа таблицы. (strategy-процедуры могут быть также вызваны из строки , но данная процедура к ним не относится.)

Если таблица не имеет связанных с ней данных или если длина доступной части равна 0, возвращается ошибка ENOTDIR. Это не совместимо с правилами поведения функции do_sysctl_strategy, которая в аналогичном случае возвращает успешный результат.

Текущее значение строки копируется в пространство пользователя и результат оканчивается символом NUL (это значит, что может быть выведено число байтов, которое на единицу больше указанного параметром lenp; возможно, это — ошибка, в зависимости от того, что указано в документации). Поскольку текущее значение уже оканчивается символом NUL, эти четыре строки кода можно легко свести к двум:

if (copy_to_user(oldval, table->data, len + 1)) return -EFAULT;

Допустимость этого изменения частично зависит от трех свойств, которые учитываются в остальной части кода при записи в член table->data:

В остальной части кода не происходит копирование в table->data больше символов, чем указано в table->maxlen. (При этом, также становится не нужной проверка в строке . Даже если бы эта проверка была необходима, в ней нужно было проверять только соответствие оператору сравнения >, в не >=).

Затем в конце table->data записывается символ NUL с перекрытием в случае необходимости последнего скопированного байта с тем, чтобы общая длина, включая символ NUL, не превышала величины table->maxlen.

Величина table->maxlen никогда не изменяется.

Поскольку все эти три свойства соблюдаются, значение len всегда будет строго меньше значения table->maxlen в строке , и завершающий символ NUL должен появляться в позиции или перед позицией table->data[len + 1].

По аналогии с предыдущим случаем, новое значение копируется из пространства пользователя и результат оканчивается символом NUL. Однако в этом случае не стоит копировать байт NUL из пространства пользователя, поскольку будет менее эффективным копировать его из пространства пользователя, чем просто присвоить значение NUL соответствующему байту объекта . К тому же, это позволяет записывать символ NUL в конце table->data, даже если его не было на входе. Безусловно, строка, считанная из newval, уже могла оканчиваться символом NUL, и в этом случае присваивание в строке будет излишним. Это еще один пример того, что иногда быстрее просто выполнить работу, чем проверять, нужно ли ее выполнять.

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



Unregister_proc_table


Функция unregister_proc_table удаляет связь между деревом массивов ctl_table и файловой системой /proc. Эти входы размещаются в объекте ctl_table и все входы во всех «подкаталогах» под ними исчезают из /proc/sys.

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

Член de входов таблицы, не связанных ни с одним входом под каталогом /proc/sys, имеет значение NULL; эти входы, безусловно, можно пропустить.

Если с точки зрения файловой системы /proc это — каталог, но вход этой таблицы представляет собой лист-узел (не каталог), эти две структуры противоречивы. Функция unregister_proc_table выводит предупреждающее сообщение и продолжает цикл, не пытаясь удалить этот вход.

Каталоги освобождаются рекурсивно — еще один редкий случай использования рекурсии в ядре.

После возврата из рекурсивного вызова функция unregister_proc_table проверяет, что все подкаталоги и файлы были рекурсивно удалены; если нет, то текущий элемент нельзя безопасно удалить и цикл продолжается.

Вот почему некоторые подкаталоги (и файлы в них) могли не быть удалены: они в настоящее время могли находиться в использовании. Если данный элемент находится в использовании, цикл просто продолжается, поэтому этот элемент не будет удален.

Узел удаляется из файловой системы с помощью функции proc_unregister (не рассматривается в этой книге) и освобождается память, распределенная для слежения за этим узлом.



Unregister_sysctl_table


Как было указано выше, эта простая функция только удаляет дерево объектов ctl_table из циклического списка таких деревьев ядра. Она также удаляет соответствующие данные из файловой системы /proc, если ядро транслировано с поддержкой /proc. Снова просмотрев строки и , можно видеть, что объект root_table_header (строка ), или лист-узел, соответствующий объекту root_table, используется в качестве начального и конечного узла при проходе по циклическому списку деревьев. Теперь можно видеть, что в функции unregister_sysctl_table ничто не исключает возможности удаления объекта root_table_header из списка заголовков таблиц, просто этого никто не делает.



Аппаратные средства:


Просмотр кода не накладывает каких-либо особых требований на процессор или оперативную память. (Однако, все же следует иметь устройство чтения CD-ROM, поскольку одними невооруженными глазами здесь не обойтись.)

Код может компилироваться практически на любой системе, на которой может запускаться GNU/Linux, Компиляцию рекомендуется выполнять в системе с процессором Intel Pentium 133MHz (или эквивалентным клоном) и установленной оперативной памятью не менее 16 Мб. Разумеется, годятся и платформы, подобные рабочим станциям Alpha или SPARC, однако большой выигрыш в быстродействии вряд ли получится.

Код можно просматривать непосредственно из CD-ROM, поэтому дополнительное пространство на жестком диске не понадобится. Однако, для компиляции ядра исходный код должен быть скопирован на жесткий диск, что потребует приблизительно 130 Мб пространства на каждый дистрибутив версии 2.x. В результате компиляции это число может вырасти до 150 Мб и более в зависимости от конкретной конфигурации ядра.



Больше процессов


В Linux2.4 практически устранены статические ограничения на количество одновременно выполняющихся процессов. Остался лишь один жестко закодированный предел, определяющий максимальное количество PID (вспомните, что PID могут совместно использоваться). Отмененные ограничения позволяют использовать Linux для запуска сложных серверных приложений, в том числе приложений Web-серверов, которые зачастую требуют очень большого числа одновременно выполняющихся процессов.

Как утверждалось в , актуальный ранее максимум в 4090 задач был обоснован максимальным количеством сегментов состояний задачи (TSS) и локальных таблиц дескрипторов (LDT), которые могли поддерживаться глобальной таблицей дескрипторов (GDT): Linux 2.2 хранил в GDT по одной TSS и LDT на процесс, а общее количество элементов в GDT составляло ровно 8192. Linux 2.4 преодолевает это ограничение за счет хранения TSS и LDT самих по себе, а не в GDT. Сейчас GDT содержит только элементы TSS и LDT для каждого процессора, при этом Linux записывает в эти элементы информацию, необходимую для задач, выполняющихся в текущий момент времени в процессорах.

Массив task (строка ) из кода исчез, однако пока еще остался массив init_tasks, отслеживающий простаивающие процессы для каждого ЦП.



До свидания, Java!


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



How to Apply These Terms to Your New Programs


If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms.

To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the «copyright» line and a pointer to where the full notice is found.

<one line to give the program's name and a brief idea of what it does.> Copyright (C) 19yy <name of author>

This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

Also add information on how to contact you by electronic and paper mail.

If the program is interactive, make it output a short notice like this when it starts in an interactive mode:

Gnomovision version 69, Copyright (C) 19yy name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type 'show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details.

The hypothetical commands 'show w' and 'show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than 'show w' and 'show c'; they could even be mouse-clicks or menu items — whatever suits your program.

You should also get your employer (if you work as a programmer) or your school, if any, to sign a «copyright disclaimer» for the program, if necessary. Here is a sample; alter the names:

Yoyodyne, Inc., hereby disclaims all copyright interest in the program 'Gnomovision' (which makes passes at compilers) written by James Hacker.

<signature of Ty Coon>, 1 April 1989 Ty Coon, President of Vice

This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Library General Public License instead of this License.



No Warranty


BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM «AS IS» WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.

IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.



Preamble


The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software— to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) You can apply it to your programs, too.

When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things.

To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it.

For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights.

We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software.

Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations.

Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all.

The precise terms and conditions for copying, distribution and modification follow.



я писал книгу, посвященную коду


Пока я писал книгу, посвященную коду ядра Linux, разработчики ни на минуту не останавливали свои действия. (Знали бы вы, как я их упрашивал...) Поскольку ядро развивается огромными темпами, по идее, новая устойчивая версия 2.4.0 должна появиться одновременно с этой книгой. Несмотря на невозможность рассмотреть все изменения, все же я попытаюсь в этом приложении очертить наиболее существенные из них. Большинство из этих изменений уже воплощены в версии 2.3.12 ядра, которая находится на сопровождающем книгу CD-ROM.

Автор выражает особую благодарность Джо Праневичу (Joe Pranevich) за его статью «Wonderful World of Linux 2.4» («Удивительный мир Linux 2.4»), которую можно найти по адресу . Его статья оказала неоценимую помощь при подготовке этого приложения.


Приложение B. GNU General Public License


Version 2, June 1991

Copyright (C) 1989, 1991 Free Software Foundation, Inc.

59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed.



Программное обеспечение:


Для просмотра кода подходит любая операционная система, поддерживающая устройства чтения CD-ROM. Разумеется, упомянутому требованию соответствует GNU/Linux. Однако, компилировать код проще всего под управлением именно GNU/Linux с установленным gcc 2.7.2.

Просмотр и навигация по коду окажутся намного удобнее в редакторах, поддерживающик концепцию тегов, например, Emacs или vi. (Следует отметить, что CD-ROM не содержит ни одного текстового редактора.)



Разряды возможности ELF


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



Сопровождающий CD-ROM


На сопровождающем книгу CD-ROM находится программное обеспечение, которое окажет существенную помощь во время изучения книги:

linux-0.01. Оригинальный дистрибутив ядра Linux, датируемый 1991г. Представляет интерес с исторической точки зрения.

linux-2.2.5. Полный дистрибутив ядра Linux, код которого рассматривается в книге.

linux-2.2.10. Наиболее свежая версия устойчивого ядра по состоянию на осень 1999 г.

linux-2.3.12. Наиболее свежая версия ядра, находящегося в стадии разработки, по состоянию на осень 1999 г.

Теговые файлы для версий ядра 2.x, которые во многих редакторах упрощают поиск информации в коде.

lckc_code. Большой файл, включающий код, приведенный в первой части книги. Номера строк сохранены.

lckc-find-line.el. Короткий файл с кодом Emacs Lisp, который обеспечивает эффективную навигацию по файлу lckc_code.

Все перечисленное выше программное обеспечение подчиняется лицензии GNU General Public License, которая разрешает вам бесплатно использовать, копировать и модифицировать содержимое любых файлов.



Существенное усовершенствование поддержки SMP-машин


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

Linux 2.0 имеет только одну глобальную блокировку ядра, поэтому в один и тот же момент времени ядро может выполнять только один процессор.

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



Terms And Conditions For Copying, Distribution And Modification


This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The «Program», below, refers to any such program or work, and a «work based on the Program» means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term «modification».) Each licensee is addressed as «you».

Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does.

You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program.

You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee.

You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions:

You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change.


You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License.

If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.)

These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it.

Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program.

In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License.

You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following:





Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or,

Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or,

Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.)

The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable.

If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code.

You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance.



You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it.

Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License.

If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program.

If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances.

It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice.



This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License.

If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License.

The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns.

Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and «any later version», you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation.

If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally.


Уменьшение случаев бегства


Как говорилось в , функция __wake_up (строка ) заставляет «проснуться» все процессы в очереди ожидания. Однако, представьте себе Web-сервер наподобие Apache, который пытается уменьшить время ответа, вызывая fork для ряда процессов, чтобы те отслеживали соединения на одном и том же порте (как альтернатива ожиданию прихода запроса на соединение и только затем— вызова fork для процесса). Все эти процессы попадают в очередь ожидания поступления запросов на соединения, и как только запросы поступают, процессы пробуждаются. Запрос может обслужить только один из них, поэтому все остальные процессы вновь погружаются в спячку.

Подобного рода «бегство» отымает лишние циклы ЦП — было бы лучше пробуждать только один процесс, который, собственно, и займется обслуживанием запроса. В этой связи и был введен новый разряд состояния задачи TASK_EXCLUSIVE; за одно обращение к __wake_up может пробуждаться максимум один процесс с установленным разрядом TASK_EXCLUSIVE. TASK_EXCLUSIVE — это не новое состояние задачи, а всего лишь дополнительный разряд в существующем состоянии задачи. Так сделано для удобства его использования.

Теперь __wake_up проверяет, установлен ли разряд TASK_EXCLUSIVE для пробуждаемого процесса, а затем прекращает пробуждать процессы после активизации данного (выходит из цикла по break). В настоящее время разряд TASK_EXCLUSIVE задействуется только в очередях ожидания, связанных с сетевой обработкой, и здесь он имеет хороший шанс прижиться. Для большинства других очередей все процессы должны получать мгновенные снимки в момент ожидания доступности ресурсов, что позволит избежать зависания. Однако, серверы, ожидающие на одном и том же порте, обычно находятся в том же положении, что и Apache: все ожидающие задачи идентичны, и все сводится к тому, что какая-то одна из задач будет обслуживать поступающий запрос, даже если это такой запрос, который требует обслуживания одной и той же задачей.



Ускорение планировщика


И без того высокооптимальная функция schedule (строка ) была оптимизирована вновь. Большинство изменений связано с реструктуризацией кода, следующего после строки (system_call), т.е. ускорение достигалось за счет прямолинейной организации кода для общих случаев и разбрасывания тел операторов if по всей функции. Например, строки и , которые запускали нижние половины в случае их существования, сейчас приобрели следующий вид:

if (bh_mask & bh_active) goto handle_bh; handle_bh_back: /* ... */ handle_bh: do_bottom_half(); goto handle_bh_back;

Следовательно, если нижние половины должны запускаться, управление передается на новую метку handle_bh, которая обеспечивает выполнение нижних половин и возврат назад. При старом подходе, в нормальном случае, когда нет нижних половин для запуска, имел место переход; это связано с тем, что сгенерированный код содержал после вызова handle_bottom_halves оператор перехода. Как можно заметить, в новой версии в нормальном случае (без нижних половин) все следует прямолинейно, без переходов.