Марк Митчелл - Программирование для Linux. Профессиональный подход
Инструкция bsrl вычисляет местоположение старшего значащего бита в первом операнде и записывает результат (номер позиции начиная с нуля) во второй операнд. Например, следующая команда анализирует переменную number и помещает результат в переменную position:
asm("bsrl %1, %0" : "=r" (position) : "r" (number)};
Ей соответствует такой фрагмент на языке С:
long i;
for (i = (number >> 1), position = 0; i != 0; ++position)
i >>= 1;
Чтобы сравнить скорость выполнения двух фрагментов, мы поместили их в цикл, где перебирается большое количество чисел. В листинге 9.1 приведена реализация на языке С. Программа перебирает значения от единицы до числа, указанного в командной строке. Для каждого значения переменной number вычисляется позиция старшего значащего бита. В листинге 9.2 показано, как сделать то же самое с помощью ассемблерной вставки. Обратите внимание на то, что в обоих случаях результат вычислений заносится в переменную result, объявленную со спецификатором volatile. Это необходимо для подавления оптимизации со стороны компилятора, который удалит весь блок вычислений, если их результаты не используются или не заносятся в память.
Листинг 9.1. (bit-pos-loop.c) Нахождение позиции старшего значащего бита в цикле#include <stdio.h>
#include <stdlib.h>
int main(int argc, char* argv[]) {
long max = atoi(argv[1]);
long number;
long i;
unsigned position;
volatile unsigned result;
/* Повторяем вычисления для большого количества чисел. */
for (number = 1; number <= max; ++number) {
/* Сдвигаем число вправо, пока результат не станет
равным нулю.
Запоминаем количество операций сдвига. */
for (i = (number >> 1), position = 0; i != 0; ++position)
i >>= 1;
/* Позиция старшего значащего бита — это общее число
операций сдвига, кроме первой. */
result = position;
}
return 0;
}
Листинг 9.2. (bit-pos-asm.c) Нахождение позиции старшего значащего бита с помощью инструкции bsrl#include <stdio.h>
#include <stdlib.h>
int main(int argc, char* argv[]) {
long max = atoi(argv[1]);
long number;
unsigned position;
volatile unsigned result;
/* Повторяем вычисления для большого количества чисел. */
for (number = 1; number <= max; ++number) {
/* Вычисляем позицию старшего значащего бита с помощью
ассемблерной инструкции bsrl. */
asm("bsrl %1, %0" : "=r" (position) : "r" (number));
result = position;
}
return 0;
}
Скомпилируем обе версии программы в режиме полной оптимизации:
% cc -O2 -о bit-pos-loop bit-pos-loop.c
% cc -O2 -о bit-pos-asm bit-pos-asm.c
Теперь запустим их с помощью команды time, которая замеряет время выполнения. В командной строке каждой программы указано большое значение, чтобы программа выполнялась хотя бы несколько секунд.
% time ./bit-pos-loop 250000000
19.51user 0.00system 0:20.40elapsed 95%CPU (0avgtext+0avgdata
0maxresident)k0inputs+0outputs (73major+11minor)pagefaults 0swaps
% time ./bit-pos-asm 250000000
3.19user 0.00system 0:03.32elapsed 95%CPU (0avgtext+0avgdata
0maxresident)k0inputs+0outputs (73major+11minor)pagefaults 0swaps
Приведенные результаты могут немного меняться в зависимости от загруженности системы, но хорошо видно, что ассемблерная версия выполняется гораздо быстрее.
9.5. Вопросы оптимизации
Даже при наличии в программе ассемблерных вставок модуль оптимизации компилятора пытается переупорядочить и переписать код программы, чтобы минимизировать время ее выполнения. Когда оптимизатор обнаруживает, что выходные данные функции asm() не используются, он удаляет ее, если только ему не встречается ключевое слово volatile. Любой вызов функции asm() может быть перемещен самым непредсказуемым образом. Единственный способ гарантировать конкретный порядок ассемблерных инструкций — включить все нужные инструкции в одну функцию asm().
Применение функции asm() ограничивает эффективность оптимизации, поскольку компилятор не понимает семантику используемых в ней ассемблерных выражений. Помните об этом!
9.6. Вопросы сопровождения и переносимости
Если вы решили включить в программу архитектурно-зависимые ассемблерные вставки. поместите их в отдельные макросы или функции, что облегчит сопровождение программы. Когда все макросы находятся в одном файле и задокументированы, программу легче будет перенести в другую систему, так как придется переписать один-единственный файл. Например, большинство вызовов asm() в исходных текстах Linux сгруппировано в файлах /usr/src/linux/include/asm и /usr/src/linux/include/asm-i386.
Глава 10
Безопасность
Одним из основных достоинств Linux является поддержка одновременной работы нескольких пользователей, в том числе по сети. Но у всякой медали есть обратная сторона. В данном случае — это угрозы безопасности, возникающие, когда система подключена к Internet. При благоприятном стечении обстоятельств хакер сможет войти в систему и прочитать, модифицировать или удалить хранящиеся в ней файлы. Кроме того, сами пользователи системы могут получать несанкционированный доступ к чужим файлам.
В ядре Linux есть множество средств, позволяющих предотвратить подобные события. Но защищать следует и обычные приложения. Предположим, к примеру, что разрабатывается бухгалтерская программа. Любой пользователь может зарегистрировать в программе отчет о расходах, но далеко не каждый имеет право утвердить этот отчет. Пользователям разрешается просматривать информацию о своих зарплатах, но, естественно, не о зарплатах своих коллег. Менеджерам может быть разрешено получать данные о зарплатах служащих их отделов, но не служащих других отделов.
Все эти меры предосторожности требуют особого внимания. Очень легко допустить ошибку, из-за которой пользователи смогут делать то, что им не разрешено. Конечно, лучше всего воспользоваться помощью экспертов по системам безопасности. Но базовыми знаниями должен владеть любой разработчик программного обеспечения.
10.1. Пользователи и группы
В Linux каждому пользователю назначается уникальный номер, называемый его идентификатором (UID, user identifier). При регистрации в системе, естественно, вводится имя пользователя, а не идентификатор. Система преобразовывает введенное имя в соответствующий идентификатор и дальше работает только с ним.
С одним идентификатором может быть связано несколько пользовательских имен. С точки зрения системы это не имеет никакого значения и не представляет для нее проблемы: она учитывает только идентификатор. Пользовательские имена совершенно равноправны, если им соответствует одинаковый идентификатор.
Доступ к файлу или другому ресурсу контролируется путем закрепления за ним конкретного идентификатора пользователя. Только пользователь с этим идентификатором имеет привилегированный доступ к ресурсу. Например, можно создать файл, который будет открыт для чтения лишь его владельцу, либо создать каталог, в котором только владелец сможет создавать новые файлы. Это самые простые способы защиты данных.
Иногда требуется делить ресурсы с несколькими пользователями. К примеру, менеджер может создать файл, предназначенный для чтения другими менеджерами, но не рядовыми служащими. ОС Linux не позволяет закреплять за файлом несколько пользовательских идентификаторов, поэтому нельзя задать список лиц, имеющих доступ к данному конкретному файлу.
Но выход все же есть — это создание группы. Ей также назначается уникальный номер, называемый идентификатором группы (GID, group identifier). В каждую группу входит один или несколько идентификаторов пользователей. Один и тот же пользователь может быть членом множества групп, но членами групп не могут быть другие группы. У групп, как и у пользователей, есть имена, но они не играют практически никакой роли, так как система работает с идентификаторами групп.
Например, можно создать группу managers и включить в нее идентификаторы всех менеджеров компании. Тогда любой файл, принадлежащий этой группе, будет доступен только менеджерам и никому другому. Всякому системному ресурсу соответствует только одна группа.
Команда id позволяет узнать идентификатор текущего пользователя и группы, в которые он входит:
% id
uid=501(mitchell) gid=501(mitchell) groups=501(mitchell), 503(csl)
В первой части выходных данных указано, что идентификатор пользователя равен 501. В скобках приведено соответствующее этому идентификатору имя пользователя. Как следует из результатов работы команды, пользователь mitchell входит в две группы: с номером 501 (mitchell) и с номером 503 (csl). Читатели, возможно, удивлены тем, что группа 501 появляется дважды: в поле gid и в поле groups. Объяснение этому факту будет дано позже.