Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Учеб.пос.СП.doc
Скачиваний:
28
Добавлен:
31.03.2015
Размер:
1.33 Mб
Скачать
    1. Использование системных вызовов

Опишем принципы корректного использования системных вызовов на языке С. Вызов read может быть выполнен так: rst=read(fd, buf, numbyte);

Это небольшая функция, которая выполняет некоторый специальный код, передающий управление от пользовательского процесса ядру и затем возвращающий результаты. Реальная работа выполняется внутри, вот почему это системный вызов, а не просто библиотечная функция вроде qsort или strlen. Библиотечные функции выполняют всю свою работу в пользовательском режиме. Системный вызов приводит к двум переключениям контекста (из пользовательского режима в режим ядра и обратно) и поэтому выполняется гораздо дольше, чем простой вызов функции в адресном пространстве процесса. Таким образом без необходимости системные вызовы использовать не следует.

Каждый системный вызов определен в заголовочном файле. Вызов не выполнится, если не включены нужные ему заголовочные файлы. Например, вызову read нужен заголовочный файл unistd: #include <unistd.h>. Как правило заголовочный файл включает определения многих вызовов (в файле unistd.h определены около 80 вызовов), так что типичная программа содержит несколько директив #include. Если включите в программу ненужный заголовочный файл, ничего страшного не случится.

Рассмотрим некоторые общие принципы вызова библиотечных функций из языка С, относящиеся также и к системным вызовам:

  1. Включите необходимые заголовочные файлы.

  2. Узнайте, как функция сообщает об ошибках, и проверяйте все ли в порядке. Если не собираетесь выполнять проверку, то задокументируйте свое решение, приведя возвращаемое функцией значение к типу void: (void)close(fd);

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

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

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

    1. Краткие описания функций и обработка ошибок

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

atexit - регистрирует функцию, вызываемую при завершении работы процесса

#include <stdlib.h>

int atexit(

void (*fcn) (void) /* вызываемая функция */);

/* Возвращает 0 в случае успеха и ненулевое значение при ошибке */.

Используя atexit, вы объявляете функцию, вызываемую при завершении процесса:

static void fcn(void)

{

/* действия, которые следует выполнить при завершении процесса */

}

Затем нужно провести регистрацию ее: if (atexit(fcn) !=0) {

/* обработка ошибки */

}.

После этого при завершении процесса будет вызвана ваша функция. Обратите внимание, что в операторе if выполняется проверка на ненулевое значение, так как именно это сказано в кратком описании – не 1, не больше нуля, не -1, не false или NULL, а именно ненулевое значение. В данном случае, это единственный надежный способ проверки успешности выполнения функции.

Большинство системных вызовов возвращают значения. Например, если все нормально, вызов read возвращает число прочитанных байтов. Если системный вызов хочет сообщить об ошибке, он обычно возвращает значение, которое, как правило, равняется -1. Таким образом, пример с вызовом read следовало бы переписать так:

if ((rst = read(fd, buf, numbyte)) == -1) {

fprintf(stderr, “Read falled! \n”);

exit(EXIT_FAILURE);

}

Обратите внимание, что exit также является системным вызовом, но он не может возвратить ошибку, потому что он не возвращает управление. Символ EXIT_FAILURE определен в стандартном С.

Системный вызов, сообщающий об ошибках, может завершиться неудачей по многим причинам. В 80% случаев код, определяющий причину ошибки, записывается в целочисленный символ errno. Для использования errno нужно включить заголовочный файл errno.h. Можно использовать errno как целое число, но это не обязательно целочисленная переменная. Если работаете с несколькими потоками, при обращении к errno, скорее всего, будет выполнен вызов функции, потому что одна глобальная переменная не отвечает требованиям такой среды. Поэтому, не объявляйте errno самостоятельно – включайте вместо этого соответствующий заголовочный файл:

if ((rst = read(fd, buf, numbyte)) == -1) {

fprintf(stderr, “Read falled! errno = %d\n, errno)”;

exit(EXIT_FAILURE);

}

Например, при некорректном дескрипторе файла было бы выведено следующее: Read falled! errno = 9. При выполнении системного вызова проверяйте значение errno только после того, как вы убедились, что произошла ошибка. Это правило относится почти ко всем системным вызовам.

Сообщение errno = 9 неинформативно и нестандартизировано, поэтому лучше использовать функцию perror стандартного С:

if ((rst = read(fd, buf, numbyte)) == -1) {

perror( “Read falled!);

exit(EXIT_FAILURE);

}

Теперь выведенное сообщение было бы таким: Read falled!: Bad file number.