Ядро 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 в качестве обозначения успеха.


Содержание раздела