Добавлен: 29.10.2018
Просмотров: 47992
Скачиваний: 190
1.6. Системные вызовы
81
execve(command, parameters, 0); /* выполнение command */
}
}
Далее в этой книге предполагается, что для TRUE определено значение 1.
В наиболее общем случае команда execve имеет три параметра: имя выполняемого фай-
ла, указатель на массив аргументов и указатель на массив переменных окружения. Эти
параметры мы рассмотрим в дальнейшем. Различные библиотечные подпрограммы,
включая execl, execv, execle и execve, предусматривают возможность пропуска параме-
тров или указания их различными способами. Далее в этой книге мы воспользуемся
именем exec для представления системного вызова, который инициируется всеми
этими подпрограммами.
Рассмотрим следующую команду:
cp file1 file2
Она используется для копирования одного файла в другой —
file1
в
file2
. После созда-
ния оболочкой дочернего процесса последний находит и выполняет файл
cp
и передает
ему имена исходного и целевого файлов.
Основная программа
cp
(и большинство других основных программ на языке С) со-
держит объявление
main(argc, argv, envp)
где argc — количество элементов командной строки, включая имя программы. Для
предыдущего примера argc равен 3.
Второй параметр — argv — представляет собой указатель на массив. Элемент i этого
массива является указателем на i-й строковый элемент командной строки. В нашем
примере argv[0] будет указывать на строку cp, argv[1] — на строку file1, а argv[2] — на
строку file2.
Третий параметр функции main — envp — является указателем на массив переменных
окружения, то есть на массив строк вида имя = значение, используемый для передачи
программе такой информации, как тип терминала и имя домашнего каталога программ.
Существуют библиотечные процедуры, которые программа может вызвать для получе-
ния переменных окружения. Они часто используются для настройки пользовательских
предпочтений при выполнении определенных задач (например, для настройки прин-
тера, используемого по умолчанию). В листинге 1.1 окружение дочернему процессу не
передается, поэтому третий параметр execve имеет нулевое значение.
Если вызов exec кажется сложным, не стоит отчаиваться — это (с точки зрения семанти-
ки) наиболее сложный из всех имеющихся в POSIX системных вызовов. Все остальные
выглядят намного проще. В качестве более простого примера можно рассмотреть exit,
который используется процессами, когда они заканчивают выполнение. У него всего
один параметр — статус выхода (0–255), который возвращается родительской про-
грамме через statloc в системном вызове waitpid.
В UNIX память каждого процесса делится на три сегмента: текстовый сегмент (то есть
код программы), сегмент данных (переменные) и сегмент стека. Как показано на
рис. 1.18, сегмент данных растет вверх, а стек растет вниз. Между ними существует
часть неиспользованного адресного пространства. Стек заполняет пустое пространство
автоматически по мере надобности. Расширение сегмента данных за счет пустого про-
82
Глава 1. Введение
Рис. 1.18. Память процессов состоит из трех сегментов:
текста, данных и стека
странства выполняется явным образом. Для этого предназначен системный вызов brk,
указывающий новый адрес окончания сегмента данных. Однако этот вызов стандар-
том POSIX не определен, так как программистам для динамического распределения
памяти рекомендуется пользоваться библиотечной процедурой malloc. При этом низ-
коуровневая реализация malloc не рассматривалась в качестве объекта, подходящего
для стандартизации, поскольку мало кто из программистов использует ее в непо-
средственном виде и весьма сомнительно, чтобы кто-нибудь даже заметил отсутствие
в POSIX системного вызова brk.
1.6.2. Системные вызовы для управления файлами
Многие системные вызовы имеют отношение к файловой системе. В этом разделе будут
рассмотрены вызовы, работающие с отдельными файлами, а в следующем разделе — те
вызовы, которые оперируют каталогами или файловой системой в целом.
Чтобы прочитать данные из файла или записать их в файл, сначала его необходимо
открыть. Для данного вызова необходимо указать имя открываемого файла (с указа-
нием абсолютного пути либо пути относительно рабочего каталога) и код O_RDONLY,
O_WRONLY или O_RDWR, означающий, что файл открывается для чтения, записи или
для чтения и записи. Для создания нового файла используется параметр O_CREAT.
Возвращаемый дескриптор файла впоследствии может быть использован для чтения
или записи. После этого файл может быть закрыт с помощью системного вызова close,
который делает дескриптор файла доступным для повторного использования при по-
следующем системном вызове open.
Наиболее часто используемыми вызовами, несомненно, являются read и write. Вызов
read мы уже рассмотрели, вызов write имеет те же параметры.
Несмотря на то что большинство программ читают и записывают файлы последова-
тельно, некоторым прикладным программам необходима возможность произвольного
доступа к любой части файла. С каждым файлом связан указатель на текущую пози-
цию. При последовательном чтении (записи) он обычно указывает на байт, который
должен быть считан (записан) следующим. Системный вызов lseek может изменить
значение указателя, при этом последующие вызовы read или write начнут свою работу
с нового произвольно указанного места в файле.
Вызов lseek имеет три параметра: первый — это дескриптор файла, второй — позиция
в файле, третий — указание, относительно чего задана позиция — начала файла, теку-
1.6. Системные вызовы
83
щей позиции или конца файла. Вызов lseek возвращает абсолютную позицию в файле
(в байтах) после изменения указателя.
Для каждого файла UNIX хранит следующие данные: код режима файла (обычный
файл, специальный файл, каталог и т. д., а также права доступа к файлу), размер, вре-
мя последнего изменения и другую информацию. Программы могут запрашивать эту
информацию посредством системного вызова stat. Его первый параметр определяет
файл, информацию о котором необходимо получить, второй является указателем на
структуру, в которую она должна быть помещена. Для открытого файла то же самое
делает системный вызов fstat.
1.6.3. Системные вызовы для управления каталогами
В этом разделе мы рассмотрим некоторые системные вызовы, относящиеся скорее
к каталогам или к файловой системе в целом, чем к отдельным файлам, как в предыду-
щем разделе. Первые два вызова — mkdir и rmdir — соответственно создают и удаляют
пустые каталоги. Следующий вызов — link. Он позволяет одному и тому же файлу
появляться под двумя или более именами, зачастую в разных каталогах. Этот вызов
обычно используется, когда несколько программистов, работающих в одной команде,
должны совместно использовать один и тот же файл. Тогда этот файл может появиться
у каждого из них в собственном каталоге, возможно, под разными именами. Совместное
использование файла отличается от предоставления каждому члену команды личной
копии; наличие общего доступа к файлу означает, что изменения, которые вносит
любой из представителей, тут же становятся видимыми другим, поскольку они ис-
пользуют один и тот же файл. А при создании копии файла последующие изменения
одной копии не влияют на другие.
Чтобы увидеть, как работает вызов link, рассмотрим ситуацию, показанную на
рис. 1.19, a. У каждого из двух пользователей с именами ast и jim есть собственные
каталоги, в которых имеется ряд файлов. Если ast выполнит программу, содержащую
системный вызов
link("/usr/jim/memo", "/usr/ast/note");
то файл
memo
в каталоге
jim
теперь будет входить в каталог
ast
под именем
note
. После
этого
/usr/jim/memo
и
/usr/ast/note
будут ссылаться на один и тот же файл. Попутно
следует заметить, что место, где хранятся каталоги пользователей —
/usr
,
/user
,
/home
или какое-нибудь другое, определяет местный системный администратор.
Возможно, станет понятнее, что именно делает link, если разобраться в том, как он
работает. Каждый файл в UNIX имеет свой уникальный номер — идентификатор, или
i-номер. Этот i-номер является единственным для каждого файла индексом в таблице
i-узлов
(i-nodes). Каждый i-узел (i-node или inode) хранит информацию о том, кто
является владельцем файла, где расположены его блоки на диске и т. д. Каталог — это
просто файл, содержащий набор пар (i-номер, ASCII-имя). В первой версии UNIX
каждый элемент каталога занимал 16 байт: 2 байта для i-номера и 14 байт для имени.
Сейчас для поддержки длинных имен файлов требуется более сложная структура, но
концептуально каталог по-прежнему является набором пар (i-номер, ASCII-имя). На
рис. 1.19 у файла
имеется i-номер 16 и т. д. Системный вызов link просто создает
новый элемент каталога, возможно, с новым именем, используя i-номер существующего
файла. На рис. 1.19, б один и тот же i-номер (70) имеется у двух элементов, которые
84
Глава 1. Введение
таким образом ссылаются на один и тот же файл. Если позже с помощью системного
вызова unlink одна из этих записей будет удалена, то вторая останется нетронутой. Если
будут удалены обе записи, UNIX увидит, что записей для файла не существует (поле
в i-узле отслеживает количество указывающих на данный файл элементов в каталогах),
и удалит файл с диска.
Рис. 1.19. Два каталога: а — перед созданием ссылки на файл /usr/jim/memo в каталоге ast;
б — после создания ссылки
Как уже упоминалось, системный вызов mount позволяет объединять в одну две фай-
ловые системы. Обычная ситуация такова: на разделе (или подразделе) жесткого диска
находится корневая файловая система, содержащая двоичные (исполняемые) версии
общих команд и другие интенсивно используемые файлы, а пользовательские файлы
находятся на другом разделе (или подразделе). Затем пользователь может вставить
USB-диск с файлами для чтения.
При помощи системного вызова mount файловая система USB-диска может быть под-
ключена к корневой файловой системе (рис. 1.20). В языке C типичный оператор, вы-
полняющий подключение («монтирование») файловой системы, выглядит так:
mount("/dev/sdb0", "/mnt", 0);
где первый параметр — это имя блочного специального файла для USB-устройства 0,
второй параметр — место в дереве, куда будет происходить подключение, а третий па-
раметр сообщает, будет ли файловая система подключена для использования в режиме
чтения и записи или только чтения.
Рис. 1.20. Файловая система: a — до вызова mount; б —после вызова mount
После выполнения системного вызова mount для доступа к файлу на устройстве 0
можно использовать путь к нему из корневого или рабочего каталога, не обращая вни-
мания на то, на каком устройстве он находится. Практически к любому месту дерева
может быть подключено второе, третье и четвертое устройство. Вызов mount позволяет
включать сменные носители в единую интегрированную файловую структуру, не об-
ращая внимания на то, на каком устройстве находятся файлы. Хотя в этом примере
1.6. Системные вызовы
85
фигурирует компакт-диск, жесткие диски или их части (часто называемые раздела-
ми
— partition) также могут быть подключены этим способом. Аналогично могут быть
подключены и внешние жесткие диски или флеш-накопители. Когда подключение
файловой системы больше не требуется, она может быть отключена с помощью си-
стемного вызова umount.
1.6.4. Разные системные вызовы
Помимо описанных ранее существуют и другие разновидности системных вызовов.
Здесь будут рассмотрены только четыре из них. Системный вызов chdir изменяет те-
кущий рабочий каталог. После вызова
chdir("/usr/ast/test");
при открытии файла
xyz
будет открыт файл
/usr/ast/test/xyz
. Использование понятия
рабочего каталога избавляет от необходимости постоянно набирать длинные абсолют-
ные пути файлов.
В UNIX каждый файл имеет код режима, хранящийся в i-узле и используемый для его
защиты. Для управления правами доступа к файлу этот код включает биты чтения-
запи си-выполнения (read-write-execute) для владельца, для группы и для других поль-
зователей. Системный вызов chmod позволяет изменять биты прав доступа к файлу.
Например, чтобы сделать файл доступным для чтения для всех, а для владельца — до-
ступным и для чтения и для записи, необходимо выполнить следующий системный
вызов:
chmod("file", 0644);
Системный вызов kill позволяет пользователям и пользовательским процессам по-
сылать сигналы. Если процесс готов принять определенный сигнал, то при его посту-
плении запускается обработчик сигнала. Если процесс не готов к обработке сигнала,
то его поступление уничтожает процесс (что соответствует имени системного вызова:
kill — убивать, уничтожать).
Стандартом POSIX определен ряд процедур для работы со временем. Например, time
просто возвращает текущее время в секундах, где 0 соответствует полуночи (началу,
а не концу дня) 1 января 1970 года. На компьютерах, использующих 32-разрядные
слова, максимальное значение, которое может быть возвращено процедурой time, рав-
но 2
32
– 1 с (предполагается, что используется беззнаковое целое число — unsigned
integer). Это значение соответствует периоду немногим более 136 лет. Таким образом,
в 2106 году 32-разрядные системы UNIX «сойдут с ума», что сравнимо со знаменитой
проблемой 2000 года (Y2K). Если сейчас вы работаете на 32-разрядной UNIX-системе,
то ближе к 2106 году получите совет поменять ее на 64-битную.
1.6.5. Windows Win32 API
До сих пор мы ориентировались в основном на UNIX. Теперь настало время взглянуть
и на Windows. Операционные системы Windows и UNIX фундаментально отличаются
друг от друга в соответствующих моделях программирования. Программы UNIX со-
стоят из кода, который выполняет те или иные действия, при необходимости обращаясь
к системе с системными вызовами для получения конкретных услуг. В отличие от этого
программой Windows управляют, как правило, события. Основная программа ждет,