Программирование для Linux. Профессиональный подход

Митчелл Марк

Оулдем Джеффри

Самьюэл Алекс

Часть III

Приложения

 

 

Приложение А

Вспомогательные инструменты разработки

 

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

 

А.1. Статический анализ программы

Некоторые программные ошибки можно выявить, воспользовавшись средствами статического анализа исходных текстов. Если вызвать компилятор gcc с флагами -Wall и -pedantic, он выдаст предупреждения о рискованных и потенциально ошибочных программных конструкциях. Исправив эти конструкции, вы снизите вероятность появления в программе скрытых ошибок, а также упростите компиляцию программы в других вариантах Linux или даже в других операционных системах.

С помощью различных флагов командной строки можно заставить компилятор gcc выдавать предупреждения о множестве спорных программных конструкций. Большинство проверок включается флагом -Wall. Например, компилятор будет сообщать о комментарии, начинающемся в другом комментарии, о неправильном типе возвращаемого значении в функции main(), о функциях, в которых пропущена инструкция return, и т.д. При наличии флага -pedantic компилятор будет выдавать предупреждения о несоответствии стандарту ANSI. В частности, будет сообщаться о наличии функции asm() и других GNU-расширений языка. В документации к компилятору не рекомендуется использовать этот флаг. Мы же советуем избегать большинства GNU-расширений, так как они имеют тенденцию меняться со временем и плохо поддаются оптимизации.

Попробуем скомпилировать программу "Hello, World", представленную в листинге А.1.

Листинг А.1. ( hello.c ) Простейшая программа

main() {

 printf("Hello, world.\n");

}

Будучи вызванным без флагов, компилятор не выдаст никаких предупреждений, хотя программа не соответствует стандарту ANSI Если же включить флаги -Wall и -pedantic, то обнаружатся три спорные конструкции:

% gcc -Wall -pedantic hello.c

hello.c:2: warning: return type defaults to 'int'

hello.c: In function 'main':

hello.c:3: warning: implicit declaration of function 'printf'

hello.c:4: warning: control reaches end of non-void function

Компилятор сообщает о следующих проблемах:

■ не указан тип возвращаемого значения функции main();

■ функция printf() не объявлена, так как файл не включен в программу;

■ функция main(), которая неявно возвращает значение типа int, не содержит инструкцию return.

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

 

А.2. Поиск ошибок в динамической памяти

 

При написании программы зачастую неизвестно, сколько памяти потребуется ей во время выполнения. Например, строка, читаемая из файла, может иметь любую длину. Работа с динамической памятью осуществляется посредством функций malloc(), free() и их вариантов. Следует придерживаться таких правил:

■ число запросов на выделение памяти (вызовов функции malloc()) должно в точности совпадать с чистом запросов на освобождение памяти (вызовов функции free());

■ операции чтения и записи динамической памяти должны выполняться в рамках выделенной области, не выходя за ее пределы;

■ к выделенной области нельзя обращаться после того, как она была освобождена.

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

■ чтение памяти до того, как она была выделена;

■ запись в память до того, как она была выделена;

■ чтение данных по адресу, предшествующему началу выделенной области;

■ запись данных по адресу, предшествующему началу выделенной области;

■ чтение данных по адресу, стоящее после выделенной области;

■ запись данных по адресу, стоящему после выделенной области;

■ чтение памяти после того, как она была освобождена;

■ запись в память после того, как она была освобождена;

■ неудачная попытка освободить выделенную память;

■ попытка повторно освободить ту же самую область памяти;

■ попытка освободить память, которая не была выделена.

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

В табл. А.1 описаны возможности четырех диагностических средств. К сожалению, ни одно из них не выявляет все возможные ошибки. Кроме того, ни одно средство не позволяет обнаруживать попытки чтения или записи памяти до того, как она была выделена, хотя такая попытка наверняка приведет к нарушению сегментации. Обнаруживаются те ошибки, которые действительно происходят в процессе работы программы. Если передать программе такие входные данные, что выделять память не понадобится, ошибки обращения к памяти не будут найдены. Для максимально тщательной проверки программы рекомендуется передавать ей самые разные входные данные, чтобы протестировать все возможные пути ее выполнения. Желательно также тестировать программу всеми имеющимися средствами.

Таблица А.1. Возможности средств проверки динамической памяти (X — обнаружение, О — обнаружение в некоторых случаях):

Ошибка Проверка функции malloc() Утилита mtrace Библиотека ccmalloc Библиотека Electric Fence
Чтение памяти до того, как она была выделена
Запись в память до того, как она была выделена
Чтение данных по адресу, предшествующему началу выделенной области X
Запись данных по адресу, предшествующему началу выделенной области О О X
Чтение данных по адресу, стоящему после выделенной области X
Запись данных по адресу, стоящему после выделенной области X X
Чтение памяти после того, как она была освобождена X
Запись в память после того, как она была освобождена X
Неудачная попытка освободить выделенную память X X
Попытка повторно освободить ту же самую область памяти X X
Попытка освободить память. которая не была выделена X X
Выделение памяти нулевого размера X X

 

А.2.1. Программа для тестирования динамической памяти

Программа malloc-use, приведенная в листинге А.2, позволяет тестировать операции выделения, освобождения и обращения к памяти. Единственный аргумент командной строки задает максимальное число выделяемых буферов. Например, по команде malloc-use 12 будет создан массив А из двенадцати пустых указателей. Программа принимает пять разных команд.

■ Если ввести a i b , для элемента массива А[ i ] будет выделено b байтов. Индекс i должен быть неотрицательным числом, меньшим, чем аргумент командной строки. Число байтов также должно быть неотрицательным.

■ Если ввести d i , будет удален буфер A[ i ] .

■ Если ввести r i p , из буфера A[ i ] будет прочитан p-й символ (A[ i ][ p ] ). Значение p должно быть целым.

■ Если ввести w i p , в позицию p буфера A[ i ] будет записан символ.

■ Для завершения работы программы введите q.

Прежде чем привести исходный текст программы, опишем, как работать с ней.

 

А.2.2. Проверка функции malloc()

Функции выделения и освобождения памяти, имеющиеся в GNU-библиотеке языка С, способны обнаруживать факт записи в память до начала выделенной области, а также попытку освободить одну и ту же область дважды. Если задать переменную среды MALLOC_CHECK_ равной 2, программа malloc-use аварийно завершит работу в случае выявления такого рода ошибки. Подобное изменение поведения не требует перекомпиляции программы.

Вот что произойдет, если записать символ перед началом массива;

% export MALLOC_CHECK_=2

% ./malloc-use 12

Please enter a command: a 0 10

Please enter a command: w 0 -1

Please enter a command: d 0

Aborted (core dumped)

Команда export включила проверку функции malloc(), а значение 2 заставило программу завершиться сразу после обнаружения ошибки.

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

 

А.2.3. Поиск потерянных блоков памяти с помощью утилиты mtrace

Утилита mtrace позволяет выявить наиболее распространенную ошибку при работе с динамической памятью: несоответствие числа операций выделения и освобождения памяти. Алгоритм применения утилиты таков.

1. Включите в программу файл и разместите в самом начале программы вызов функции mtrace(). Эта функция активизирует трассировку операций выделения и освобождения памяти.

2. Задайте имя файла, в котором будет сохраняться трассировочная информация. Это делается следующим образом:

% export MALLOC_TRACE=memory.log

3. Запустите программу. Все операции выделения и освобождения памяти будут зарегистрированы в журнальном файле.

4. Вызовите утилиту mtrace, которая проверит, совпадает ли число выделенных блоков памяти с числом освобожденных блоков.

% mtrace my_program $MALLOC_TRACE

Сообщения, выдаваемые утилитой mtrace, достаточно понятны. Например, в случае программы malloc-use будет получена такая информация:

- 0000000000 Free 3 was never alloc'd malloc-use.с:39

Memory not freed:

-----------------

Address    Size    Caller

0x08049d48  0xc at malloc-use.с:30

Эти сообщения говорят о том, что в строке 39 файла malloc-use.c делается попытка освободить память, которая никогда не была выделена, а память, выделенная в строке 30, так и не была освобождена.

Функция malloc() заставляет программу фиксировать все операции выделения и освобождения памяти в файле, указанном в переменной среды MALLOC_TRACE. Чтобы данные были записаны в файл, программа должна завершиться нормальным образом. Утилита mtrace анализирует этот файл и находит в нем непарные записи.

 

А.2.4. Библиотека ccmalloc

Библиотека ccmalloc замещает функции malloc() и free() кодом трассировки. Если программа завершается успешно, создается отчет о потерянных блоках памяти и прочих ошибках. Библиотеку ccmalloc написал Армин Бир (Armin Biere).

Код библиотеки требуется загрузить и инсталлировать самостоятельно. Дистрибутив можно найти по адресу http://www.inf.ethz.ch/personal/biere/projects/ccmalloc. Распакуйте дистрибутив и запустите сценарий configure. Далее выполните команды make и make install, скопируйте файл ccmalloc.cfg в каталог, из которого будет запускаться проверяемая программа, и переименуйте копию в .ccmalloc.

К объектным файлам программы необходимо подключить библиотеку ccmalloc и библиотеку функций динамической компоновки, Вот как это делается:

% gcc -g -Wall -pedantic malloc-use.o -о ccmalloc-use -lccmalloc -ldl

Запустите программу, чтобы получить отчет. Например, если попросить программу malloc-use выделить память и забыть ее освободить, будут выданы следующие результаты:

% ./ccmalloc-use 12

file-name=a.out does not contain valid symbols

trying to find executable in current directory ...

using symbols from 'ccmalloc-use'

(to speed up this search specify 'file ccmalloc-use'

in the startup file '.ccmalloc')

Please enter a command: a 0 12

Please enter a command: q

.-----------------.

| ccmalloc report |

=====================================================

|  total # of | allocated | deallocated |    garbage |

+-------------+-----------+-------------+------------+

|       bytes |        60 |          48 |         12 |

+-------------+-----------+-------------+------------+

| allocations |         2 |           1 |          1 |

+----------------------------------------------------+

| number of checks: 1                                |

| number of counts: 3                                |

| retrieving function names for addresses ... done.  |

| reading file info from gdb ... done.               |

| sorting by number of not reclaimed bytes ... done. |

| number of call chains: 1                           |

| number of ignored call chains: 0                   |

| number of reported call chains: 1                  |

| number of internal call chains: 1                  |

| number of library call chains: 0                   |

=====================================================

|

*100.0% = 12 Bytes of garbage allocated in 1 allocation

|       |

|       |      0x400389cb in

|       |

|       |      0x08045198 in

|       |                 at malloc-use.с:89

|       |

|       |      0x06048fdc in

|       |                 at malloc-use.c:30

|       |

|       '-----> 0x08049647 in

|                          at src/wrapper.c:284

'------------------------------------------------------

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

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

 

А.2.5. Библиотека Electric Fence

Библиотека Electric Fence, написанная Брюсом Перензом (Bruce Perens), останавливает выполнение программы в той строке, где происходит обращение к памяти за пределами выделенной области. Это единственное средство, позволяющее выявить неправильные операции чтения. Библиотека входит в большинство дистрибутивов Linux, а ее исходные коды можно найти по адресу http://www.perens.com/FreeSoftware.

Как и в случае библиотеки ccmalloc, к объектным файлам программы необходимо подключить код библиотеки Electric Fence:

% gcc -g -Wall -pedantic malloc-use.o -o emalloc-use -lefence

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

% ./emalloc-use 12

Electric Fence 2.0.5 Copyright (C) 1987-1998 Bruce Perens.

Please enter a command a 0 12

Please enter a command r 0 12

Segmentation fault

Контекст неправильной операции можно определить с помощью отладчика.

По умолчанию библиотека Electric Fence выявляет только обращения к памяти после выделенной области. Если необходимо, чтобы она находила только обращения к памяти по адресам, предшествующим началу выделенной области, введите такую команду:

% export EF_PROTECT_BELOW=1

Чтобы библиотека отслеживала доступ к освобожденным областям, задайте переменную EF_PROTECT_FREE равной 1. Дополнительные возможности описаны на man-странице libefence.

С целью выявления ошибок доступа библиотека Electric Fence запрашивает для каждой выделенной области как минимум две страницы памяти. По умолчанию конец области приходится на конец первой страницы. Выход за пределы области, т.е. обращение ко второй странице, вызывает ошибку сегментации. Если переменная EF_PROTECT_BELOW равна 1, начало области выравнивается по началу второй страницы. В связи с тем, что за один вызов функции malloc() выделяется не менее двух страниц памяти, библиотека Electric Fence способна потреблять достаточно много памяти, поэтому ее рекомендуется использовать только при отладке.

 

А.2.6. Выбор средств отладки

Мы рассмотрели четыре разных, несовместимых друг с другом средства диагностирования неправильных случаев использования динамической памяти. Ни одно из средств не гарантирует нахождение всех ошибок, но это лучше, чем полное отсутствие проверок. Чтобы облегчить поиск ошибок, выделите код, в котором происходит работа с динамической памятью. Если программа пишется на C++, создайте класс, обрабатывающий все обращения к динамической памяти. При написании программы на языке С постарайтесь минимизировать число функций, в которых выделяется и освобождается память. Тестируя программу, не забывайте о том, что одновременно должно использоваться только одно средство отладки памяти, так как эти средства несовместимы.

Какое же из четырех средств выбрать? Поскольку чаще всего забывают согласовать число операций выделения и освобождения памяти, на начальных этапах разработки лучше применять утилиту mtrace. Она доступна во всех Linux-системах и хорошо себя зарекомендовала. Пройдя данную фазу тестирования, воспользуйтесь утилитой Electric Fence для нахождения неправильных обращений к памяти. Связка двух этих утилит позволяет найти практически все ошибки, связанные с использованием динамической памяти.

 

А.2.7. Исходный текст программы, работающей с динамической памятью

В листинге А.2 показан исходный текст программы, на примере которой иллюстрируется выделение, освобождение и использование динамической памяти. Описание программы было дано в разделе А.2.1, "Программа для тестирования динамической памяти".

Листинг А.2. ( malloc-use.c ) Пример работы с динамической памятью

/* Использование функций работы с динамической памятью. */

/* Программе передается один аргумент, определяющий

   размер массива. Этот массив состоит из указателей

   на (возможно) выделенные буферы памяти.

   В процессе работы программы ей можно задавать

   следующие команды:

   выделение памяти    -- а <индекс> <размер_буфера>

   освобождение памяти -- d <индекс>

   чтение памяти       -- r <индекс> <смещение>

   запись в память     -- w <индекс> <смещение>

   выход               -- q

   Ответственность за соблюдение правил доступа

   к динамической памяти лежит на пользователе. */

#ifdef MTRACE

#include

#endif /* MTRACE */

#include

#include

#include

/* Выделение памяти указанного размера. */

void allocate(char** array, size_t size) {

 *array = malloc(size);

}

/* Освобождение памяти. */

void deallocate(char** array) {

 free((void*)*array);

}

/* Чтение указанной ячейки памяти. */

void read_from_memory(char* array, int position) {

 volatile char character = array[position];

}

/* Запись в указанную ячейку памяти. */

void write_to_memory(char* array, int position) {

 array[position] = 'a';

}

int main{int argc, char* argv[]) {

 char** array;

 unsigned array_size;

 char command[32];

 unsigned array_index;

 char command_letter;

 int size_or_position;

 int error = 0;

#ifdef MTRACE

 mtrace();

#endif /* MTRACE */

 if (argc != 2) {

  fprintf(stderr, "%s: array-size\n", argv[0]);

   return 1;

 }

 array_size = strtoul(argv[1], 0, 0);

 array = (char**)calloc(array_size, sizeof(char*));

 assert(array != 0);

 /* Выполнение вводимых пользователем команд. */

 while (!error) {

  printf("Please enter a command: ");

  command_letter = getchar();

  assert(command_letter != EOF);

  switch (command_letter) {

  case 'a':

   fgets(command, sizeof(command), stdin);

   if (sscanf(command, "%u %i", &array_index,

    &size_or_position) == 2 &&

    array_index < array_size)

    allocate(&(array[array_index]), size_or_position);

   else

    error = 1;

   break;

  case 'd':

   fgets(command, sizeof(command), stdin);

   if (sscanf(command, "%u", &array_index) == 1 &&

    array_index < array_size)

    deallocate(&(array[array_index]));

   else

    error = 1;

   break;

  case 'r':

   fgets(command, sizeof(command), stdin);

   if (sscanf(command, "%u %i", &array_index,

    &size_or_position) == 2 &&

    array_index < array_size)

    read_from_memory(array[array_index], size_or_position);

   else

    error = 1;

   break;

  case 'w':

   fgets(command, sizeof(command), stdin);

   if (sscanf(command, "%u %i", &array_index,

    &size_or_position) == 2 &&

    array_index < array_size)

    write_to_memory(array[array_index], size_or_position);

   else

    error = 1;

   break;

  case 'q':

   free((void*)array);

   return 0;

  default:

   error = 1;

  }

 }

 free((void*)array);

 return 1;

}

 

A.3. Профилирование

 

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

Для получения профильной информации необходимо следовать такому алгоритму.

1. Скомпилируйте и скомпонуйте программу с опциями профилирования.

2. Запустите программу, чтобы сгенерировать профильные данные.

3. Вызовите утилиту gprof для отображения и анализа профильных данных.

 

А.3.1. Простейший калькулятор

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

Значение унарного числа представляется аналогичным количеством символов. Например, число 1 — это "x", 2 — "xx", 3 — "xxx" и т.д. Вместо символов "x" программа использует связный список, количество элементов которого соответствует значению числа. В файле number.c содержатся функции, позволяющие создавать число 0, добавлять единицу к числу, вычитать единицу из числа, а также складывать, вычитать и умножать числа. Есть функция, которая преобразует строку, содержащую неотрицательное десятичное число, в унарное число. Другая функция преобразует унарное число в значение типа int. Сложение реализуется путем последовательного добавления единицы, вычитание — путем последовательного отнимания единицы, а умножение — путем многократного сложения. Функции even() и odd() возвращают унарный эквивалент единицы тогда и только тогда, когда их единственный операнд является соответственно четным или нечетным числом. В противном случае возвращается унарный эквивалент нуля. Обе функции взаимно рекурсивны. Например, число является четным, если оно равно нулю или если число, на единицу меньшее, является нечетным.

Калькулятор принимает однострочные постфиксные выражения и отображает значение каждого выражения:

% ./calculator

Please enter a postfix expression:

2 3 +

5

Please enter a postfix expression:

2 3 + 4 -

1

Калькулятор, реализованный в файле calculator.c, читает каждое выражение и сохраняет промежуточные результаты в стеке унарных чисел, реализованном в файле stack.c. Унарные числа представляются в стеке в виде связных списков.

 

А.3.2. Сбор профильной информации

Первый этап профилирования заключается в настройке исполняемого файла на сбор профильной информации. Для этого при компиляции и компоновке объектных файлов необходимо указывать флаг -pg. Рассмотрим, к примеру, такую последовательность команд:

% gcc -pg -c -o calculator.o calculator.c

% gcc -pg -c -o stack.o stack.c

% gcc -pg -c -o number.o number.c

% gcc -pg calculator.o stack.o number.o -o calculator

Здесь разрешается сбор информации о вызовах функций и времени их выполнения. Чтобы получать сведения о каждой выполняемой строке программы, укажите флаг -g. При наличии флага -a будет подсчитываться количество итераций циклов.

На втором этапе требуется запустить программу. В процессе ее выполнения профильные данные накапливаются в файле gmon.out. Исследуются только те участки программы, которые действительно выполняются. Чтобы профильный файл был записан, программа должна завершиться нормальным образом.

 

А.3.3. Отображение профильных данных

Получив имя исполняемого файла, утилита gprof проверяет файл gmon.out и отображает информацию о том, сколько времени заняло выполнение каждой функции. Давайте проанализируем ход выполнения операции 1787 × 13 - 1918 в нашей программе-калькуляторе, создав простой профиль.

Flat profile:

Each sample counts as 0.01 seconds.

% cumulative    self            self    total

time seconds seconds    calls ms/call ms/call name

26.07   1.76    1.76 20795463    0.00    0.00 decrement_number

24.44   3.41    1.65     1787    0.92    1.72 add

19.85   4.75    1.34 62413059    0.00    0.00 zerop

15.11   5.77    1.02     1792    0.57    2.05 destroy_number

14.37   6.74    0.97 20795463    0.00    0.00 add_one

 0.15   6.75    0.01     1788    0.01    0.01 copy_number

 0.00   6.75    0.00     1792    0.00    0.00 make_zero

 0.00   6.75    0.00       11    0.00    0.00 empty_stack

Вычисление функции decrement_number() и всех вызываемых в ней функций заняло 26,07% общего времени выполнения программы. Эта функция вызывалась 20795463 раза. Каждый вызов выполнялся 0,00 с, т.е. столь малое время, что его не удалось замерить. Функция add() вызывалась 1787 раз, очевидно для вычисления произведения. Каждый проход по функции занимал 0,92 секунды. Функция copy_number() вызывалась почти столько же раз — 1788, но на ее выполнение ушло всего 0.15% общего времени работы программы. Иногда в отчете присутствуют функции mcount() и profil(), используемые профайлером.

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

index % time  self children called name

                                     

[1]     100.0 0.00 6.75              main [1]

              0.00 6.75     2/2       apply_binary_function [2]

              0.00 0.00     1/1792    destroy_number [4]

              0.00 0.00     1/1       number_to_unsigned_int [10]

              0.00 0.00     3/3       string_to_number [12]

              0.00 0.00     3/5       push_stack [16]

              0.00 0.00     1/1       create_stack [16]

              0.00 0.00     1/11      empty_stack [14]

              0.00 0.00     1/5       pop_stack [15]

              0.00 0.00     1/1       clear_stack [17]

--------------------------------------

              0.00 6.75     2/2       main [1]

[2]     100.0 0.00 6.75     2        apply_binary_function [2]

              0.00 6.74     1/1       product [3)

              0.00 0.01     4/1792    destroy_number [4]

              0.00 0.00     1/1       subtract [11]

              0.00 0.00     4/11      empty_stack [14]

              0.00 0.00     4/5       pop_stack [15]

              0.00 0.00     2/5       push_stack [16]

--------------------------------------

              0.00 6.74     1/1       apply_binary_function [2]

[3]      99.6 0.00 6.74     1        product [3]

              1.02 2.65 1767/1792     destroy_number [4]

              1.65 1.43 1767/1767     add [5]

              0.00 0.00 1760/62413059 zerop [7]

              0.00 0.00    1/1792     make_zero [13]

В первой секции сообщается о том, что на выполнение функции main() и всех ее дочерних функций ушло 100% времени (6.75 секунд). Функцию main() вызвал некто : это означает, что профайлер не смог определить, как был осуществлен вызов. В функции main() дважды вызывалась функция apply_binary_function() (всего таких вызовов в программе было тоже два). В третьей секции сообщается о том, что выполнение функции product() и ее дочерних функций заняло 98% времени. Эта функция вызывалась только один раз из функции apply_binary_function().

По схеме вызовов несложно определить время работы той или иной функции. Однако рекурсивные функции требуют особого подхода. Например, функция even() вызывает функцию odd(), а та — снова функцию even(). Самому длинному из таких циклов присваивается номер и выделяется отдельная секция отчета. Следующий фрагмент профильных данных получен в результате проверки того, является ли результат операции 1787 × 13 × 3 четным:

--------------------------------------

              0.00 0.02    1/1        main [1]

[9]       0.1 0.00 0.02    1         apply_unary_function [9]

              0.01 0.00    1/1        even [13]

              0.00 0.00    1/1806     destroy_number [5]

              0.00 0.00    1/13       empty_stack [17]

              0.00 0.00    1/6        pop_stack [16]

              0.00 0.00    1/6        push_stack [19]

--------------------------------------

[10]       0.1 0.01 0.00   1+69993   [10]

               0.00 0.00 34647        even [13]

--------------------------------------

                         34847        even [13]

[11]       0.1 0.01 0.00 34847       odd [11]

               0.00 0.00 34847/186997954 zerop [7]

               0.00 0.00   1/1806     make_zero [16]

                         34846        even [13]

Выражение 1+69693 в секции 10 сообщает о том что цикл 1 выполнялся один раз и в нем насчитывается 69693 обращений к функциям. Первой в цикле вызывалась функция even(), а из нее — функция odd. Обе функции вызывались по 34847 раз.

Утилита gprof располагает рядом полезных опций.

■ При задании опции -s будут суммироваться результаты нескольких запусков программы.

■ С помощью опции -c можно узнать, какие дочерние функции могли быть, но так и не были вызваны

■ При задании опции -l отображается построчная профильная информация.

■ При задании опции -A будет отображен исходный текст программы, сопровождаемый процентными показателями времени выполнения.

 

А.3.4. Как работает утилита gprof

Схема работы утилиты gprof выглядит следующим образом. Когда в ходе выполнения программы происходит вызов функции, счётчик обращений к функции увеличивается на единицу. Утилита периодически прерывает программу, чтобы выяснить, какая функция выполняется в данный момент. На основании этих '"выборок" и определяется время выполнения. В Linux тактовые импульсы генерируются с интервалом 0,01 с, следовательно, это наименьший промежуток между прерываниями. Таким образом, профильные данные о слишком быстро выполняющихся функциях могут оказаться неточными. Во избежание погрешностей рекомендуется запускать программу на длительные периоды времени или суммировать профильные данные по результатам нескольких запусков (это делается с помощью опции -s).

 

А.3.5. Исходные тексты программы-калькулятора

В листинге А.3 показан текст программы, вычисляющей значения постфиксных выражений.

Листинг А.3. ( calculator.c ) Основная часть программы-калькулятора

/* Вычисления в унарном формате. */

/* На вход программы подаются однострочные выражения

   в обратной польской (постфиксной) записи, например:

   602 7 5 - 3 * +

   Вводимые числа должны быть неотрицательными

   десятичными числами. Поддерживаются операторы

   "+", "-" и "*". Унарные операторы "even" и "odd"

   возвращают значение 1 в том случае, когда операнд

   является четным или нечетным соответственно.

   Лексемы разделяются пробелами. Отрицательные числа

   не поддерживаются. */

#include

#include

#include

#include

#include "definitions.h"

/* Эта функция выполняет указанную бинарную операцию над

   операндами, извлекаемыми из стека, помещая результат

   обратно в стек, в случае успеха возвращается

   ненулевое значение. */

int apply_binary_function(number (*function)(number, number),

 Stack* stack) {

 number operand1, operand2;

 if (empty_stack(*stack))

  return 0;

 operand2 = pop_stack(stack);

 if (empty_stack(*stack))

  return 0;

 operand1 = pop_stack(stack);

 push_stack(stack, (*function)(operand1, operand2));

 destroy_number(operand1);

 destroy_number(operand2);

 return 1;

}

/* Эта функция выполняет указанную унарную операцию над

   операндом, извлекаемым из стека, помещая результат

   обратно в стек. В случае успеха возвращается

   ненулевое значение. */

int apply_unary_function(number (*function)(number), Stack* stack) {

 number operand;

 if (empty_stack(*stack))

  return 0;

 operand = pop_stack(stack);

 push_stack(stack, (*function)(operand));

 destroy_number(operand);

 return 1;

}

int main() {

 char command_line[1000];

 char* command_to_parse;

 char* token;

 Stack number_stack = create_stack();

 while (1) {

  printf("Please enter a postfix expression:\n");

  command_to_parse =

   fgets(command_line, sizeof (command_line), stdin);

  if (command_to_parse = NULL)

   return 0;

  token = strtok(command_to_parse, " \t\n");

  command_to_parse = 0;

  while (token != 0) {

   if (isdigit(token[0]))

    push_stack(&number_stack, string_to_number(token));

   else if (((strcmp(token, "+ ") == 0) &&

    !apply_binary_function(&add, &number_stack)) ||

    ((strcmp(token, "-") == 0) &&

    !apply_binary_function(&subtract, &number_stack)) ||

    ((strcmp(token, "*") == 0) &&

    !apply_binary_function(&product, &number_stack)) ||

    ((strcmp(token, "even") == 0) &&

    !apply_unary_function(&even, &number_stack)) ||

    ((strcmp(token, "odd") == 0) &&

    !apply_unary_function(&odd, &number_stack)))

    return 1;

   token = strtok(command_to_parse, " \t\n");

  }

  if (empty_stack(number_stack))

   return 1;

  else {

   number answer = pop_stack(number_stack);

   printf("%u\n", number_to_unsigned_int(answer));

   destroy_number(answer);

   clear_stack(&number_stack);

  }

 }

 return 0;

}

Функции, приведенные в листинге А.4 выполняют операции над унарными числами, представленными в виде связных списков.

Листинг А.4. ( number.c ) Арифметика унарных чисел

/* Операции над унарными числами */

#include

#include

#include

#include "definitions.h"

/* Создание числа, равного нулю. */

number make_zero() {

 return 0;

}

/* Эта функция возвращает ненулевое значение,

   если аргумент равен нулю. */

int zerop(number n) {

 return n == 0;

}

/* Уменьшение числа на единицу. */

number decrement_number(number n) {

 number answer;

 assert(!zerop(n));

 answer = n->one_less_;

 free(n);

 return answer;

}

/* Добавление единицы к числу. */

number add_one(number n) {

 number answer = malloc(sizeof(struct LinkedListNumber));

 answer->one_less_ = n;

 return answer;

}

/* Удаление числа. */

void destroy_number(number n) {

 while (!zerop(n))

 n = decrement_number(n);

}

/* Копирование числа. Эта функция необходима для того,

   чтобы при временных вычислениях не искажались

   исходные операнды. */

number copy_number(number n) {

 number answer = make_zero();

 while (!zerop(n)) {

  answer = add_one(answer);

  n = n->one_less_;

 }

 return answer;

}

/* Сложение двух чисел. */

number add(number n1, number n2) {

 number answer = copy_number(n2);

 number addend = n1;

 while(!zerop(addend)) {

  answer = add_one(answer);

  addend = addend->one_less_;

 }

 return answer;

}

/* Вычитание одного числа из другого. */

number subtract(number n1, number n2) {

 number answer = copy_number(n1);

 number subtrahend = n2;

 while(!zerop(subtrahend)) {

  assert(!zerop(answer));

  answer = decrement_number(answer);

  subtrahend = subtrahend->one_less_;

 }

 return answer;

}

/* Умножение двух чисел. */

number product(number n1, number n2) {

 number answer = make_zero();

 number multiplicand = n1;

 while (!zerop(multiplicand)) {

  number answer2 = add(answer, n2);

  destroy_number(answer);

  answer = answer2;

  multiplicand = multiplicand >one_less_;

 }

 return answer;

}

/* Эта функция возвращает ненулевое значение, если

   ее аргумент является четным числом. */

number even(number n) {

 if (zerop(n))

  return add_one(make_zero());

 else

  return odd(n->one_less_);

}

/* Эта функция возвращает ненулевое значение, если

   ее аргумент является нечетным числом. */

number odd (number n) {

 if (zerop(n))

  return make_zero();

 else

  return even(n->one_less_);

}

/* Приведение строки, содержащей десятичное целое,

   к типу "number". */

number string_to_number(char* char_number) {

 number answer = make_zero();

 int num = strtoul(char_number, (char **)0, 0);

 while (num != 0) {

  answer = add_one(answer);

  --num;

 }

 return answer;

}

/* Приведение значения типа "number"

   к типу "unsigned int". */

unsigned number_to_unsigned_int (number n) {

 unsigned answer = 0;

 while (!zerop(n)) {

  n = n->one_less_;

  ++answer;

 }

 return answer;

}

Функции, приведенные в листинге A.5, реализуют стек унарных чисел, представленных в виде связных списков.

Листинг А.5. ( stack.c ) Стек унарных чисел

/* Реализация стека значений типа "number". */

#include

#include

#include "definitions.h"

/* Создание пустого стека. */

Stack create_stack() {

 return 0;

}

/* Эта функция возвращает ненулевое значение,

   если стек пуст. */

int empty_stack(Stack stack) {

 return stack == 0;

}

/* Удаление числа, находящегося на вершине стека.

   Если стек пуст, программа аварийно завершается. */

number pop_stack(Stack* stack) {

 number answer;

 Stack rest_of_stack;

 assert(!empty_stack(*stack));

 answer = (*stack)->element_;

 rest_of_stack = (*stack)->next_;

 free(*stack);

 *stack = rest_of_stack;

 return answer;

}

/* Добавление числа в начало стека. */

void push_stack(Stack* stack, number n) {

 Stack new_stack =

  malloc(sizeof(struct StackElement));

 new_stack->element_ = n;

 new_stack->next_ = *stack;

 *stack = new_stack;

}

/* Очистка стека. */

void clear_stack(Stack* stack) {

 while(!empty_stack(*stack)) {

  number top = pop_stack (stack);

  destroy_number(top);

 }

}

В листинге А.6 показаны объявления типов данных и функций работы со стеком и унарными числами.

Листинг А.6. ( definitions.h ) Файл заголовков для файлов number.c и stack.c

#ifndef DEFINITIONS_H

#define DEFINITIONS_H 1

/* Представление числа в виде связного списка. */

struct LinkedListNumber {

 struct LinkedListNumber* one_less_;

};

typedef struct LinkedListNumber* number;

/* Реализация стека чисел, представленных в виде

   связных списков. Значение 0 соответствует

   пустому стеку. */

struct StackElement {

 number element_;

 struct StackElement* next_;

};

typedef struct StackElement* Stack;

/* Операции над стеком. */

Stack create_stack();

int empty_stack(Stack stack);

number pop_stack Stack* stack);

void push_stack(Stack* stack, number n);

void clear_stack(Stack* stack);

/* Операции над числами */

number make_zero();

void destroy_number(number n);

number add(number n1, number n2);

number subtract(number n1, number n2);

number product(number n1, number n2);

number even(number n);

number odd(number n);

number string_to_number(char* char_number);

unsigned number_to_unsigned_int(number n);

#endif /* DEFINITIONS_H */

 

Приложение Б

Низкоуровневый ввод-вывод

 

Программисты, пишущие Linux-программы на языке С. имеют в своем распоряжении два набора функций ввода-вывода. Один из них включен в стандартную библиотеку языка С: printf(), fopen() и т.д. Мы предполагаем, что читатели уже знакомы с языком С и знают, как использовать эти функции ввода-вывода, поэтому не будем их подробно описывать.

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

 

Б.1. Чтение и запись данных

 

Первая функция ввода-вывода, с которой сталкиваются те, кто начинают изучать язык С, называется printf(). Она форматирует текстовую строку и записывает ее в стандартный выходной поток. Обобщенная ее версия fprintf() записывает текст в заданный поток. Поток данных представляется в программе указателем типа FILE*. Чтобы получить этот указатель, необходимо открыть файл с помощью функции fopen(). По завершении работы с файлом его необходимо закрыть с помощью функции fclose(). Помимо функции fprintf() существуют также функции fputc(), fputs() и fwrite(), записывающие данные в поток. Функции fscanf(), fgetc(), fgets() и fread() читают данные из потока.

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

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

 

Б.1.1. Открытие файла

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

Если второй аргумент равен O_RDONLY, файл открывается только для чтения. При попытке записи в такой файл будет выдана ошибка. Точно так же флаг O_WRONLY объявляет файл доступным только для записи. В случае флага O_RDWR файл открывается и для чтения. и для записи. Не всякий файл можно открыть в любом из трех режимов. Например, существующие права доступа к файлу могут не позволить конкретному процессу открывать файл для чтения или записи. Файл, находящийся в устройстве, запись в которое невозможна (скажем, компакт-диск), тем более нельзя открыть для записи.

Существуют и другие флаги, определяющие режим открытия файла. Все они могут объединяться с помощью операции побитового ИЛИ. Перечислим наиболее распространенные флаги.

■ O_TRUNC — приводит к очистке существующего файла. Данные, записываемые в файл, замещают предыдущее содержимое файла.

■ O_APPEND — приводит к открытию файла в режиме добавления. Данные, записываемые в файл, добавляются в его конец.

■ O_CREAT — означает создание нового файла. Если указанное имя соответствует несуществующему файлу, он будет создан при условии, что заданный каталог существует и процесс имеет разрешение создавать в нем файлы. Если файл уже существует, он будет открыт. При наличии дополнительного флага O_EXCL функция open() откажется открывать существующий файл.

Когда в функции open() задан флаг O_CREAT, должен присутствовать третий аргумент, определяющий права доступа к создаваемому файлу. О режиме доступа к файлу и битах режима рассказывалось в разделе 10.3, "Права доступа к файлам".

Программа, представленная в листинге Б.1, создает файл, имя которого задано в командной строке. Функции open() передается флаг O_EXCL, поэтому в случае указания существующего файла возникнет ошибка. Владельцу и группе нового файла предоставляются права чтения и записи, остальным пользователям — только право чтения (если для пользователя, которому принадлежит программа, установлено значение umask, права доступа к файлу могут оказаться более жесткими).

Значения umask

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

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

Например, функция

umask(S_IRWXO | S_IWGPF);

и команда

% umask 027

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

Листинг Б.1. ( createfile.c ) Создание файла

#include

#include

#include

#include

#include

int main(int argc, char* argv[]) {

 /* Путевое имя нового файла */

 char* path = argv[1];

 /* Права доступа к файлу. */

 mode_t mode =

  S_IRUSR | S_IWUSR| S_IRGRP | S_IWGRP | S_IROTH;

 /* Создание файла. */

 int fd = open(path, O_WRONLY | O_EXCL | O_CREAT, mode);

 if (fd == -1) {

  /* Произошла ошибка. Выводим сообщение и завершаем работу. */

  perror("open");

  return 1;

 }

 return 0;

}

Результаты работы программы будут такими:

% ./create-file testfile

% ls -l testfile

-rw-rw-r-- 1 samuel users 0 Feb 1 22:47 testfile

% ./create-file testfile

open: File exists

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

 

Б.1.2. Закрытие файла

По окончании работы с файлом его следует закрыть с помощью функции close(). В ряде случаев, например в программе, показанной в листинге Б.1, нет необходимости вызывать данную функцию явно, так как ОС Linux автоматически закрывает все открытые файлы по завершении программы. Естественно, после того как файл был закрыт, обращаться к нему нельзя.

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

Linux ограничивает число файлов, которые могут быть открыты процессом в определенный момент времени. Дескрипторы открытых файлов занимают ресурсы ядра, поэтому желательно вовремя закрывать файлы, чтобы дескрипторы удалялись из системных таблиц. Обычно процессам назначается лимит в 1024 дескриптора. Изменить это значение позволяет системный вызов setrlimit() (см. раздел 8.5, "Функции getrlimit() и setrlimit(): лимиты ресурсов").

 

Б.1.3. Запись данных

Для записи данных в файл предназначена функция write(). Она принимает дескриптор файла, указатель на буфер данных и число записываемых байтов. Файл должен быть открыт для записи. Функция write() работает не только с текстовыми данными, но и с произвольными байтами.

В листинге Б.2 показана программа, которая записывает в указанный файл значение текущего времени. Если файл не существует, он создается. Для получения и форматирования значения времени программа использует функции time(), localtime() и asctime().

Листинг Б.2. ( timestamp.c ) Запись в файл метки времени

#include

#include

#include

#include

#include

#include

#include

/* Эта строка возвращает строку, содержащую значение

   текущих даты и времени. */

char* get_timestamp() {

 time_t now = time(NULL);

 return asctime(localtime(&now));

}

int main(int argc, char* argv[]) {

 /* Файл, в который записывается метка времени. */

 char* filename = argv[1];

 /* Получение метки времени. */

 char* timestamp = get_timestamp();

 /* Открытие файла для записи. Если файл существует, он

    открывается в режиме добавления; в противном случае

    файл создается. */

 int fd =

  open(filename. O_WRONLY | O_CREAT | O_APPEND, 0666);

 /* Вычисление длины строки с меткой времени. */

 size_t length = strlen(timestamp);

 /* Запись метки времени в файл. */

 write(fd, timestamp, length);

 /* Конец работы. */

 close(fd);

 return 0;

}

Вот как работает программа:

% ./timestamp tsfile

% cat tsfile

The Feb 1 23:25:20 2001

% ./timestamp tsfile

% cat tsfile

Thu Feb 1 23:25:20 2001

Thu Feb 1 23:25:47 2001

Обратите внимание на то, что при первом вызове программы timestamp файл был создан, а при втором вызове — дополнен.

Функция write() возвращает число записанных байтов или -1, если произошла ошибка. Для некоторых типов файлов чисто фактически записанных байтов может оказаться меньше требуемого. Программа должна выявлять подобные случаи и вызывать функцию write() повторно, чтобы передать оставшуюся часть данных. Этот прием продемонстрирован в листинге Б.3. Но иногда даже таких методов недостаточно. Например, если показанная функция будет записывать данные в сокет, в нее придется добавить код проверки того, не произошел ли в ходе операции записи разрыв соединения.

Листинг Б.3. ( write-all.c ) Запись буфера

/* Запись указанного числа байтов (COUNT) из буфера BUFFER

   в файл FD. В случае ошибки возвращается -1,

   иначе -- число записанных байтов. */

ssize_t write_all(int fd, const void* buffer, size_t count) {

 size_t left_to_write = count;

 while (left_to_write > 0) {

  size_t written = write(fd, buffer, count);

  if (written == -1)

   /* Произошла ошибка, завершаем работу. */

   return -1;

  else

   /* подсчитываем число оставшихся байтов. */

   left_to_write -= written;

 }

 /* Нельзя записать больше, чем COUNT байтов! */

 assert(left_to_write == 0);

 /* Число записанных байтов равно COUNT. */

 return count;

}

 

Б.1.4. Чтение данных

Функция, осуществляющая чтение данных из файла, называется read(). Подобно функции write(), она принимает дескриптор файла, указатель на буфер и счетчик числа извлекаемых байтов. Функция возвращает число прочитанных байтов или -1 в случае ошибки. Иногда читается меньше байтов, чем требовалось, если, например, в файле содержится недостаточно байтов.

Чтение текстовых файлов DOS/Windows

В Linux-программах нередко приходится читать файлы, созданные в DOS или Windows. Важно понимать разницу между тем, как структурируются текстовые файлы в Linux и в DOS/Windows.

В Linux каждая строка текстового файла оканчивается символом новой строки. Он представляется символьной константой '\n' , ASCII-код которой равен 10. В Windows строки разделяются двухсимвольной комбинацией символ возврата каретки (константа '\r' , ASCII-код 13), за которым идет символ новой строки.

Некоторые текстовые редакторы Linux при отображении текстовых файлов Windows ставят в конце каждой строки обозначение ^M — символ возврата каретки. В Emacs такие файлы отображаются правильно, но в строке режима появляется запись (DOS) . Многие Windows-редакторы, например Notepad (Блокнот), показывают содержимое текстовых файлов Linux в виде одной длинной строки, так как предполагают наличие в конце строки символа возврата каретки.

Если программа читает текстовые файлы, сгенерированные Windows-программами, желательно менять последовательность '\r\n' одним символом новой строки. Точно так же при записи текстовых файлов, которые будут читаться Windows-программами, нужно менять одиночные символы новой строки комбинациями '\r\n' .

В листинге Б.4 демонстрируется применение функции read(). Программа отображает шестнадцатиричный дамп файла, заданного в командной строке. В каждой строке показано смещение от начала файла, а затем — следующие 16 байтов.

Листинг Б.4. ( hexdump.c ) Отображение шестнадцатеричного дампа файла

#include

#include

#include

#include

#include

int main(int argc, char* argv[]) {

 unsigned char buffer[16];

 size_t offset = 0;

 size_t bytes_read;

 int i;

 /* Открытие файла для чтения. */

 int fd = open(argv[1], O_RDONLY);

 /* Чтение данных из файла по одному блоку за раз. Чтение

    продолжается до тех пор, пока размер очередной порции байтов

    не окажется меньше размера буфера. Это свидетельствует

    о достижении конца буфера. */

 do {

  /* чтение следующей строки байтов. */

  bytes_read = read(fd, buffer, sizeof(buffer));

  /* Отображение смещения, а затем самих байтов. */

  printf("0x%06x : ", offset);

  for (i = 0; i < bytes_read; ++i)

   printf("%02x ", buffer[i]);

  printf("\n");

  /* Вычисление позиции в файле. */

  offset += bytes_read;

 }

 while (bytes_read == sizeof(buffer));

 /* Конец работы. */

 close(fd);

 return 0;

}

Ниже показаны результаты работы программы. Она выводит дамп самой себя.

% ./hexdump hexdump

0x000000 : 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00

0x000010 : 02 00 03 00 01 00 00 00 c0 B3 04 0B 34 00 00 00

0x000020 : e8 23 00 00 00 00 00 00 34 00 20 00 06 00 28 00

0x000030 : 1d 00 1a 00 06 00 00 00 34 00 00 00 34 80 04 08

...

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

 

Б.1.5. Перемещение по файлу

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

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

■ Если третий аргумент равен SEEK_SET, функция lseek() интерпретирует второй аргумент как смещение (в байтах) от начала файла.

■ Если третий аргумент равен SEEK_CUR, функция lseek() интерпретирует второй аргумент как смещение (положительное или отрицательное) от текущей позиции.

■ Если третий аргумент равен SEEK_END, функция lseek() интерпретирует второй аргумент как смещение (в байтах) от конца файла.

Функция lseek() возвращает смещение новой позиции от начала файла. Тип этого значения — off_t. В случае ошибки возвращается -1. Функция неприменима к файлам некоторых типов, например к сокетам.

Если требуется узнать текущую позицию файла, задайте смещение 0:

off_t position = lseek(free_descriptor, 0, SEEK_CUR);

ОС Linux позволяет перемещать указатель текущей позиции за пределы файла. Обычно, если текущая позиция находится за концом файла и выполняется операция записи, операционная система автоматически увеличивает файл, чтобы вместить в него новые данные. "Промежуток" между старым признаком конца файла и указателем текущей позиции не записывается на диск. Linux лишь помечает его длину. Если впоследствии попытаться прочесть файл, окажется, что данный промежуток заполнен нулевыми байтами.

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

Листинг Б.5. ( lseek-huge.c ) Создание огромных файлов с помощью функции lseek()

#include

#include

#include

#include

#include

int main (int argc, char* argv[]) {

 int zero = 0;

 const int megabyte = 1024 * 1024;

 char* filename = argv[1];

 size_t length = (size_t)atoi(argv[2]) * megabyte;

 /* Создание нового файла. */

 int fd = open(filename, O_WRONLY | O_CREAT | O_EXCL, 0666);

 /* Перемещение в точку, где должен быть записан последний байт

    файла. */

 lseek(fd, length - 1, SEEK_SET);

 /* Запись нулевого байта. */

 write(fd, &zero, 1);

 /* Конец работы. */

 close(fd);

 return 0;

}

Давайте теперь создадим файл размером 1 Гбайт. Обратите внимание на объем свободного места на диске до и после выполнения программы.

% df -h .

Filesystem Size Used Avail Use% Mounted on

/dev/hda5  2.9G 2.1G  655M  76% /

% ./lseek-huge bigfile 1024 % ls -l bigfile

-rw-r----- 1 samuel samuel 1073741824 Feb 5 16:29 bigfile

% df -h .

Filesystem Size Used Avail Use% Mounted on

/dev/hda5  2.9G 2.1G  655M  76% /

Как видите, файл практически не занимает место на диске, несмотря на свой огромный размер. Но если открыть его и попытаться прочитать данные, окажется, что в нем находится 1 Гбайт нулей. Давайте, к примеру, проверим это с помощью программы hexdump:

% ./hexdump bigfile / head -10

0x000000 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

0x000010 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

0x000020 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

0x000030 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

0x000040 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

0x000050 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00

...

Чтобы не наблюдать, как по экрану проносятся 230 нулей, нажмите .

"Волшебные промежутки" в файлах являются особенностью файловых систем типа ext2, обычно создаваемых на жестких дисках Linux. Если попытаться с помощью программы lseek-huge создать файл в файловой системе типа fat или vfat, то он займет весь указанный объем диска.

ОС Linux не позволяет функции lseek() ставить указатель текущей позиции перед началом файла.

 

Б.2. Функция stat()

Функция read() позволяет прочесть только содержимое файла. Но как насчет остальной информации? Например, команда ls -l сообщает такие сведения о файлах в текущем каталоге, как размер, время последнего обновления, права доступа, владелец и пр. Аналогичную информацию об отдельном файле можно получить с помощью функции stat(). Ей необходимо передать путевое имя файла и указатель на структуру типа stat. В случае успешного завершения функция возвращает 0 и заполняет поля структуры данными о файле, иначе возвращается -1.

Перечислим наиболее полезные поля структуры stat.

■ В поле st_mode содержится код доступа к файлу. О правах доступа к файлам рассказывалось в разделе 10.3. "Права доступа к файлам". В старшем бите поля закодирован тип файла. Об этом пойдет речь ниже.

■ В полях st_uid и st_gid содержатся идентификаторы соответственно пользователя и группы, которым принадлежит файл. Назначение идентификатора описывалось в разделе 10.1, "Пользователи и группы".

■ В поле st_size хранится размер файла в байтах.

■ В поле st_atime записано время последнего обращения к файлу (для чтения или записи).

■ В поле st_mtime записано время последней модификации файла.

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

■ S_ISBLK( код доступа ) — блочное устройство:

■ S_ISCHR( код доступа ) — символьное устройство;

■ S_ISDIR( код доступа ) — каталог;

■ S_ISFIFO( код доступа ) — FIFO-файл (именованный канал):

■ S_ISLNK( код доступа ) — символическая ссылка.

■ S_ISREG( код доступа ) — обычный файл;

■ S_ISSOCK( код доступа ) — сокет.

В поле st_dev содержатся старший и младший номера аппаратного устройства, в котором расположен файл (о номерах устройств рассказывалось в главе 6, "Устройства"). Старший номер находится в старшем байте поля, а младший — в младшем. В поле st_infо содержится номер индексного дескриптора файла, определяющий местоположение файла в файловой системе.

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

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

В листинге Б.6 показана функция которая создает буфер достаточного размера и загружает в него содержимое указанного файла. Размер файла определяется с помощью функции fstat(). Она же позволяет проверить, соответствует ли заданное имя обычному файлу.

Листинг Б.6. ( read-file.c ) Загрузка файла в буфер

#include

#include

#include

#include

#include

/* Загрузка содержимого файла FILENAME в память.

   Размер буфера записывается в аргумент LENGTH.

   Создаваемый буфер должен удаляться в вызывающей функции.

   Если аргумент FILENAME не соответствует обычному файлу,

   возвращается NULL. */

char* read_file(const char* filename, size_t* length) {

 int fd;

 struct stat file_info;

 char* buffer;

 /* Открытие файла. */

 fd = open(filename, O_RDONLY);

 /* Получение информации о файле. */

 fstat(fd, &file_info);

 *length = file_info.st_size;

 /* Проверка того, что это обычный файл. */

 if (!S_ISREG(file_info.st_mode)) {

  /* Этот тип файла не поддерживается. */

  close(fd);

  return NULL;

 }

 /* выделение буфера достаточного размера. */

 buffer = (char*)malloc(*length);

 /* Загрузка файла в буфер. */

 read(fd, buffer, *length);

 /* Конец работы. */

 close(fd);

 return buffer;

}

 

Б.3. Векторные чтение и запись

Аргументами функции write() являются указатель на буфер и длина буфера. Эта функция записывает в файл непрерывный блок данных, хранящихся в памяти. Но программам часто требуется записывать группы блоков, хранящихся по разным адресам. Если использовать функцию write(), придется либо предварительно объединять блоки в памяти, что неэффективно, либо многократно вызывать функцию. Последнее тоже не всегда приемлемо. Например, при записи в сокет два вызова функции write() приведут к отправке в сеть двух пакетов, тогда как те же самые данные можно перестать в одном пакете.

Функция writev() позволяет записать в файл несколько несвязанных буферов одновременно. Это называется векторной записью. Сложность применения функции writev() заключается в создании структуры, задающей начало и конец каждого буфера. Эта структура представляет собой массив элементов типа struct iovec. Каждый элемент описывает одну область памяти. В поле iov_base указывается адрес начала области, а в поле iov_len — ее длина. Если число буферов известно заранее, можно просто объявить массив типа struct iovec. В противном случае придется выделять массив динамически.

Функции writev() передается дескриптор записываемого файла, массив структур iovec и размер массива. Функция возвращает общее число записанных байтов.

Программа, показанная в листинге Б.7, записывает свои аргументы командной строки в файл с помощью одной-единственной функции writev(). Первый аргумент — это имя файла, в котором сохраняются все последующие аргументы, каждый в отдельной строке. Число элементов в массиве структур iovec в два раза превышает число аргументов командной строки, так как после каждого аргумента записывается символ новой строки. Поскольку количество аргументов неизвестно заранее, массив создается с помощью функции malloc().

Листинг Б.7. ( write-args.c ) Запись списка аргументов в файл с помощью функции writev()

#include

#include

#include

#include

#include

#include

int main(int argc, char* argv[]) {

 int fd;

 struct iovec* vec;

 struct iovec* vec_next;

 int i;

 /* Символ новой строки хранится в обычной переменной

    типа char. */

 char newline = '\n';

 /* Первый аргумент командной строки -- это имя выходного

    файла. */

 char* filename = argv[1];

 /* Пропускаем первые два элемента списка аргументов.

    Элемент номер 0 -- это имя самой программы,

    а элемент номер 1 -- это имя выходного файла */

 argc -= 2;

 argv += 2;

 /* Выделяем массив элементов типа iovec каждому аргументу

    командной строки соответствует два элемента массива:

    один -- для самого аргумента,

    а другой -- для символа новой строки. */

 vec =

  (struct iovec*)malloc(2 * argc * sizeof(struct iovec));

 /* Просмотр списка аргументов и создание массива. */

 vec_next = vec;

 for (i = 0; i < argc; ++i) {

  /* первый элемент -- это текст аргумента */

  vec_next->iov_base = argv[i];

  vec_next->iov_len = strlen(argv[i]);

  ++vec_next;

  /* Второй элемент -- это символ новой строки, допускается,

     чтобы несколько элементов массива указывали на одну и

     ту же область памяти. */

  vec_next->iov_base = &newline;

  vec_next->iov_len = 1;

  ++vec_next;

 }

 /* Запись аргументов в файл. */

 fd = open(filename, O_WRONLY | O_CREAT);

 writev(fd, vec, 2 * argc);

 close(fd);

 free(vec);

 return 0;

}

Вот пример работы программы:

% ./write-args outputfile "first arg" "second arg" "third arg"

% cat outputfile

first arg

second arg

third arg

В Linux имеется также функция readv(), которая загружает содержимое файла в несколько несвязанных областей памяти. Как и в функции writev(), массив структур типа iovec определяет начало и размер каждой области.

 

Б.4. Взаимосвязь с библиотечными функциями ввода-вывода

Выше уже говорилось о том. что функции ввода-вывода стандартной библиотеки языка С реализованы на основе низкоуровневых функций. Иногда удобнее работать с одними, иногда — с другими.

Если файл был открыт с помощью функции fopen(), то узнать его дескриптор позволяет функция fileno(). Она принимает аргумент типа FILE* и возвращает соответствующий ему дескриптор. Например, можно открыть файл с помощью библиотечной функции fopen(), но осуществить в него запись посредством функции writev():

FILE* stream = fopen(filename, "w");

int file_descriptor = fileno(stream);

writev(file_descriptor, vector, vector_length);

Учтите, что переменные stream и file_descriptor соответствуют одному и тому же открытому файлу. Если выполнить следующую функцию, дескриптор file_descriptor станет недействительным:

fclose(stream);

Аналогичным образом следующая функция делает недействительным файловый указатель stream:

close(file_descriptor);

Чтобы получить файловый указатель, соответствующий дескриптору, воспользуйтесь функцией fdopen(). Ее аргументами является дескриптор и строка, определяющая режим создания файлового потока. Синтаксис строки аналогичен синтаксису второго аргумента функции fopen(), а задаваемый режим должен быть совместим с режимом открытия файла. Например, файлу, открытому для чтения, соответствует режим r, а файлу, открытому для записи, — режим w. Как и в случае функции fileno(), файловый указатель и дескриптор ссылаются на один и тот же файл, поэтому закрытие одного сделает недействительным другой.

 

Б.5. Другие низкоуровневые операции

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

■ Функция getcwd() возвращает имя текущего каталога. Она принимает два аргумента — указатель на буфер и длину буфера — и копирует имя каталога в буфер.

■ Функция chdir() делает текущим заданный каталог.

■ Функция mkdir() создает новый каталог. Ее первым аргументом является путевое имя каталога. Второй аргумент задает права доступа к каталогу. Интерпретация этого аргумента такая же, как и третьего аргумента функции open(). На итоговый код доступа влияет значение umask процесса.

■ Функция rmdir() удаляет указанный каталог.

■ Функция unlink() удаляет файл. Ее аргументом является путевое имя файла. С помощью этой функции можно удалять и другие объекты файловой системы, например именованные каналы и файлы устройств.

В действительности функция unlink() не обязательно удаляет содержимое файла. Как подсказывает ее имя, она удаляет из каталога ссылку на файл. Файл не будет больше фигурировать в списке содержимого каталога, но если какой-то процесс владеет открытым дескриптором этого файла, то содержимое файла не удаляется с диска. Это произойдет только тогда, когда не останется открытых дескрипторов файла. Так что если один процесс откроет файл для чтения или записи, а второй процесс в это время удалит ссылку на файл и создаст новый файл с таким же именем, первый процесс продолжит работать со старым содержимым файла. Чтобы получить доступ к новому содержимому первому процессу придется закрыть и повторно открыть файл.

■ Функция rename() переименовывает или перемещает файл. Двумя ее аргументами являются старое и новое путевые имена. Если путевые имена ссылаются на разные каталоги, функция перемещает файл (при условии, что он остается в той же файловой системе). С помощью функции rename() можно перемещать также каталоги и другие объекты файловой системы.

 

Б.6. Чтение содержимого каталога

В Linux имеются функции, предназначенные для чтения содержимого каталога. И хотя они не относятся к низкоуровневым функциям, мы все же решили их описать, так как они широко применяются в программах.

При чтении содержимого каталога необходимо придерживаться такой последовательности действий.

1. Вызовите функцию opendir(), передав ей путевое имя требуемого каталога. Эта функция возвращает дескриптор типа DIR*, который можно использовать для доступа к содержимому каталога. В случае ошибки возвращается NULL.

2. Последовательно вызывайте функцию readdir(), передавая ей дескриптор, полученный от функции opendir(). Всякий раз функция readdir() будет возвращать указатель на структуру типа dirent, содержащую информацию о следующем элементе каталога. По достижении конца каталога будет получено значение NULL. У структуры dirent есть поле d_name, где содержится имя элемента каталога.

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

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

В листинге Б.8 показана программа отображающая список содержимого каталога. Имя каталога задается в командной строке. Если этого не сделать, будет проанализирован текущий каталог. Для каждого элемента каталога отображается его тип и путевое имя. Функция get_file_type() определяет тип объекта файловой системы с помощью функции lstat().

Листинг Б.8. ( listdir.c ) Вывод содержимого каталога

#include

#include

#include

#include

#include

#include

#include

/* Эта функция возвращает строку с описанием типа объекта

   файловой системы, заданного в аргументе PATH. */

const char* get_file_type(const char* path) {

 struct stat st;

 lstat(path, &st);

 if (S_ISLNK(st.st_mode))

  return "symbolic link";

 else if (S_ISDIR(st.st_mode))

  return "directory";

 else if (S_ISCHR(st.st_mode))

  return "character device";

 else if (S_ISBLK(st.st_mode))

  return "block device";

 else if (S_ISFIFO(st.st_mode))

  return "fifo";

 else if (S_ISSOCK(st.st_mode))

  return "socket";

 else if (S_ISREG(st.st_mode))

  return "regular file";

 else

  /* Нераспознанный тип. */

  assert(0);

}

int main(int argc, char* argv[]) {

 char* dir_path;

 DIR* dir;

 struct dirent* entry;

 char entry_path[PATH_MAX + 1];

 size_t path_len;

 if (argc >= 2)

  /* Если каталог указан в командной строке, анализируем его. */

  dir_path = argv[1];

 else

  /* В противном случае анализируем текущий каталог. */

  dir_path = ".";

 /* Копируем имя каталога в переменную entry_path. */

 strncpy(entry_path, dir_path, sizeof(entry_path));

 path_len = strlen(dir_path);

 /* Если имя каталога не заканчивается косой чертой,

    добавляем ее. */

 if (entry_path[path_len - 1] != '/') {

  entry_path[path_len] = '/';

  entry_path[path_len + 1] = '\0';

  ++path_len;

 }

 /* Начинаем обрабатывать список содержимого каталога. */

 dir = opendir(dir_path);

 /* просматриваем все элементы каталога. */

 while ((entry = readdir(dir)) != NULL) {

  const char* type;

  /* Формируем полное путевое имя элемента каталога. */

  strncpy(entry_path + path_len, entry->d_name,

  sizeof(entry_path) — path_len);

  /* Определяем тип элемента. */

  type = get_file_type(entry_path);

  /* Отображаем собранную информацию. */

  printf("%-18s: %s\n", type, entry_path);

 }

 /* Конец работы. */

 closedir(dir);

 return 0;

}

Приведем несколько строк листинга полученного в каталоге /dev (в разных системах могут быть выданы разные результаты)

% ./listdir /dev

directory        : /dev/.

directory        : /dev/..

socket           : /dev/log

character device : /dev/null

regular file     : /dev/MAKEDEV

fifo             : /dev/initctl

character device : /dev/agpgart

...

Для проверки этих данных можно воспользоваться командой ls. Флаг -U отменяет сортировку списка, а флаг -a заставляет включить в список записи текущего (.) и родительского (..) каталогов.

% ls -lua /dev total 124

drwxr-xr-x  7 root root   36864 Feb  1 15:14 .

drwxr-xr-x 22 root root    4096 Oct 11 16:39 ..

srw-rw-rw-  1 root root       0 Dec 18 01:31 log

crw-rw-rw-  1 root root  1,   3 May  5  1998 null

-rwxr-xr-x  1 root root   26689 Mar  2  2000 MAKEDEV

prw-------  1 root root       0 Dec 11 18:37 initctl

crw-rw-r--  1 root root 10, 175 Feb  3  2000 agpgart

Первый символ каждой строки определяет тип элемента каталога.

 

Приложение В

Таблица сигналов

В табл. В.1 перечислены сигналы, которые чаще всего приходится обрабатывать в Linux-программах. Некоторые сигналы имеют разные интерпретации в зависимости от того, где они были получены.

Указанные имена сигналов определены в виде макроконстант препроцессора. Чтобы иметь возможность сослаться на них в программе необходимо подключить файл . Реальное определение сигналов дано в файле /usr/sys/signum.h, который подключается к файлу .

Для получения полного списка сигналов, поддерживаемых в Linux, необходимо выполнить такую команду:

% man 7 signal

Таблица В.1. Сигналы ОС Linux

Название Описание
SIGHUP Linux посылает этот сигнал, когда происходит отключение от терминала. Многие программы применяют этот сигнал в совершенно иных целях: он служит указанием программе повторно прочитать свой файл конфигурации
SIGINT Linux посылает процессу этот сигнал, когда пользователь пытается завершить процесс нажатием клавиш <Ctrl+C>
SIGILL Процесс получает этот сигнал при попытке выполнить недопустимую инструкцию. Это может означать повреждение стека программы
SIGABRT Этот сигнал посылается функцией abort()
SIGFPE По течение этого сигнала означает, что процесс выполнил недопустимую операцию с плавающей запятой. В зависимости от конфигурации центрального процессора результатом операции может стать специальное нечисловое значение, например inf (бесконечность) или NaN (не число), а не сигнал SIGFPE
SIGKILL Этот сигнал приводит к немедленному завершению процесса и не может быть перехвачен
SIGUSR1 Этот сигнал зарезервирован для прикладного использования
SIGUSR2 Этот сигнал зарезервирован для прикладного использования
SIGSEGV Этот сигнал означает, что программа выполнила недопустимое обращение к памяти. Возможно, указанный адрес находится за пределами адресного пространства процесса или процессу запрещен доступ к этому участку памяти
SIGPIPE Этот сигнал означает, что программа обратилась к разрушенному потоку данных, например к сокету, который был закрыт на противоположной стороне
SIGALRM Доставка этого сигнала планируется функциями alarm() и setitimer() (см. раздел 8.13 "Функция setitimer(): задание интервальных таймеров")
SIGTERM Этот сигнал является запросом на завершение процесса и посылается командой kill по умолчанию
SIGCHLD Linux посылает процессу этот сигнал при завершении одного из дочерних процессов (см. раздел 3.4.4, "Асинхронное удаление дочерних процессов")
SIGXCPU Linux посылает процессу этот сигнал в случае превышения разрешенного времени доступа к центральному процессору (см. раздел 8.5, "Функции getrlimit() и setrlimit(): лимиты ресурсов")
SIGVTALRM Доставка этого сигнала планируется функцией setitimer() (см. раздел 8.13, "Функция setitimer() : задание интервальных таймеров")

 

Приложение Г

Internet-ресурсы

 

В этом приложении перечислен ряд Web-узлов, где можно найти информацию о программировании Linux-систем.

 

Г.1. Общая информация

■ http://www.advancedlinuxprogramming.com. Это Web-узел данной книги. Здесь можно загрузить текст книги в электронном виде вместе с исходными текстами программ, найти ссылки на другие ресурсы и получить дополнительную информацию о программировании в Linux.

■ http://www.linuxdoc.org. Это Web-узел проекта Linux Documentation Project. Здесь находится хранилище всевозможной документации, а также FAQ-архивов.

 

Г.2. Информация о программном обеспечении GNU/Linux

■ http://www.gnu.org. Это Web-узел проекта GNU Project. Здесь можно загрузить всевозможные бесплатно распространяемые программы. Среди них и GNU-библиотека языка С, содержащая многие из описанных в данной книге функций. Здесь же приведена информация о том. как внести свой вклад в развитие системы GNU/Linux, написав программный код и документацию либо использовав бесплатное программное обеспечение.

■ http://www.kernel.org. Это основной Web-узел для распространения исходных кодов ядра Linux и лучшее место для поиска ответов на самые сложные вопросы о том, как работает Linux. В разделе "Documentation" приведена информация о структуре ядра системы.

■ http://www.linuxhq.com. Здесь также распространяются исходные коды ядра Linux наряду с "заплатами" и прочей информацией.

■ http://gcc.gnu.org. Это Web-узел коллекции GNU-компидяторов (GCC). В нее входят компиляторы языков С, C++, Objective C, Java, Chill и Fortran.

■ http://www.gnome.org и http://www.kde.org. Это Web-узлы двух наиболее популярных графических оболочек Linux: Gnome и KDE. Они понадобятся тем, кому необходимо разрабатывать приложения с пользовательским интерфейсом.

 

Г.3. Другие ресурсы

■ http://developer.intel.com. Здесь содержится информация о процессорах Intel, включая архитектуру x86 (IA32). Отметим очень полезные справочники встроенных ассемблерных инструкций.

■ http://www.amd.com. Здесь представлена аналогичная информация о процессорах AMD.

■ http://freshmeat.net. Здесь находится список программ с открытыми кодами, в основном для платформы GNU/Linux. Это одно из лучших мест, где можно оперативно узнавать о программных новинках для Linux, начиная от базовых системных компонентов и заканчивая специализированными приложениями.

■ http://www.linuxsecurity.com. Здесь содержится информация о программах, обеспечивающих безопасность Linux-систем. Этот Web-узел будет интересен пользователи, системным администраторам и разработчикам.

 

Приложение Д

Лицензия на публикацию программ с открытыми кодами, версия 1.0

 

I. Требования к модифицированной и немодифицированной версиям

Материалы, для которых действует лицензия на публикацию программ с открытыми кодами (далее "Лицензия"), могут публиковаться и распространяться как целиком, так и по частям, в любой среде, физической или электронной, при условии соблюдения требований Лицензии и включения самой Лицензии либо ссылки на нее (с любыми дополнительными ограничениями, выбранными автором/авторами и/или издателем) в публикацию.

Правильный способ включения ссылки таков:

© <имя автора или правообладателя: <год>. Представленные материалы могут распространяться только на условиях Лицензии на публикацию программ с открытыми кодами, версии X.Y или более поздней (самая последняя версия в настоящей момент доступна по адресу http://www.opencontent.org/openpub/ ).

Сразу за ссылкой могут быть указаны дополнительные ограничения, выбранные автором/авторами или издателем документа (см раздел VI. "Предусмотренные ограничения").

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

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

 

II. Авторские права

Право на каждую копию материала, распространяемого на условиях Лицензии, принадлежит автору/авторам либо лицу, которое указано как таковое.

 

III. Область действия Лицензии

Следующие условия применимы ко всем материалам, распространяемым на условиях Лицензии, если в документе явно не указано обратное.

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

■ Частичное нарушение лицензии. Если какая-либо часть Лицензии оказывается неприменимой в той или иной правовой сфере, оставшаяся часть Лицензии остается в силе.

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

 

IV. Требования к модифицированным материалам

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

1. Модифицированная версия должна быть помечена как таковая.

2. Должно быть указано лицо, вносящее модификации, а также дата модификации.

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

4. Должно быть указано местоположение исходной, немодифицированной версии документа.

5. Имя (имена) исходного автора (авторов) нельзя использовать для указания авторства полученного документа без разрешения исходного автора (авторов).

 

V. Рекомендации

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

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

2. Все существенные изменения (включая удаления) должны быть либо явно помечены в документе, либо иным образом описаны в приложении к документу

3. Наконец, хоть это и не обязательно, считается хорошим тоном предоставление бесплатного экземпляра печатной копии или компакт-диска с опубликованным материалом его автору (авторам).

 

VI. Предусмотренные ограничения

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

А. Можно запретить распространение существенно модифицированных версий без явного разрешения автора (авторов). "Существенная модификация" определяется как изменение семантического содержания документа и не подразумевает изменение формата или типографские изменения.

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

Б. Можно запретить публикацию данного материала или его производных целиком или по частям в стандартном (печатном) виде а коммерческих целях без получения соответствующего разрешения у владельца авторских прав.

Чтобы добиться этого, необходимо включить фразу "Распространение данного материала или его производных целиком или по частям в стандартном (печатном) виде запрещено без получения соответствующего разрешения v владельца авторских прав" в копию Лицензии или ссылку на нее

 

Дополнение, касающееся политики публикации

(Не является частью Лицензии.)

Материалы, распространяемые на условиях Лицензии, доступны в исходном формате на Web-узле http://works.opencontent.org.

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

Если у вас есть вопросы по Лицензии, обращайтесь к Дэвиду Уайли (David Wiley) или в список рассылки Open Publication Authors (opal@oреncontent.org) no электронной почте.

Чтобы подписаться на список рассылки Open Publication Authors, пошлите электронное сообщение по адресу [email protected] со словом "subscribe" в теле сообщения.

Чтобы опубликовать сообщение в списке рассылки Open Publication Authors, пошлите электронное сообщение по адресу [email protected] или просто ответьте на предыдущее сообщение.

Чтобы отменить подписку на список рассылки Open Publication Authors, пошлите электронное сообщение по адресу [email protected] со словом "unsubscribe" в теле сообщения.

 

Приложение Е

Общая лицензия GNU

[42]

 

Версия 2, июнь 1991 года

Copyright © 1989, 1991 Free Software Foundation Inc.

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

Разрешается копирование и распространение копии этого документа, но запрещается внесение каких бы то ни было изменений в его текст.

 

Преамбула

Для большинства программных продуктов лицензии разрабатываются с целью запрещения их свободного распространения и внесения в них изменений. Данная общая лицензия GNU (GNU General Public License), наоборот, призвана гарантировать всем пользователям свободное обладание и изменение свободно распространяемых программных продуктов. Эта лицензия применяется к большинству программных продуктов организации FSF и любым другим программам, авторы которых берут на себя обязательство придерживаться ее. (Существует также ряд программных продуктов FSF, которые подчиняются правилам "библиотечной" лицензии GNU (GNU Library General Public License).) Вы можете применять ее к своим собственным программам.

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

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

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

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

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

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

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

 

Условия копирования, распространения и модификации программных продуктов

0. Данная лицензия применяется к любой программе или другому продукту, который содержит замечание, внесенное владельцем авторских прав, где указано, что данный продукт может распространяться только на условиях общей лицензии GNU. Термин "Программа" относится к любой такой программе или продукту, а фраза "продукт, основанный на Программе" означает либо программу, либо любой производный продукт, для которого соблюдается авторское право, т.е. продукт, содержащий Программу или ее часть, в неизменном виде либо с модификациями и/или в переводе на другие языки (в дальнейшем возможность перевода подразумевается в термине "модификация"). Ко всем владельцам лицензий используется обращение во втором лице: вы, вам, ваш и т.д.

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

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

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

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

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

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

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

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

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

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

3) Вы можете копировать или распространять Программу (или продукт, основанный на Программе, при соблюдении требований п. 2) в объектном коде или исполняемой форме при соблюдении условий пп. 1 и 2, следуя одному из нижеперечисленных требований.

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

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

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

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

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

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

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

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

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

Предполагается, что в этом разделе внесена ясность в то, что считается следствием остальной части лицензии.

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

9. Организация FSF время от времени может публиковать модифицированную и/или новую версию общей лицензии GNU. Новые версии будут содержать ту же идею, что и настоящая версия, но могут отличаться в деталях, связанных с новыми проблемами или концепциями.

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

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

Гарантия отсутствует

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

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

 

Конец условий

 

Как применить эти требования к новым программным продуктам

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

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

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

Copyright © год создания, фамилия автора

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

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

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

Gnomovision version 69, Copyright © год создания, фамилия автора

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.

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

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

Yoyodyne. Inc., hereby disclaims all copyright interest in the program 'Gnomovision' (which makes passes at compilers) written by James Hacker.
Личная подпись, дата

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

Вопросы, касающиеся FSF и GNU, направляйте по адресу [email protected].

Комментарии к данному тексту посылайте по адресу [email protected].

Сообщение об авторских правах приведено выше

Free Software Foundation Inc. 59 Temple Place - Suite 530 Boston, MA 02111-1307, USA

Обновлено: 31 июня 2000 г.