Системное программирование Линукс. Linux. Системное программирование. Вступление
Скачать 0.65 Mb.
|
58 int fd; fd = open ("/home/teach/pearl", O_WRONLY | O_TRUNC); if (fd == –1) /* ошибка */ Владельцы новых файлов Определить, какой пользователь является владельцем файла, довольно просто: uid владельца файла является действительным uid процесса, создавшего файл. Определить владеющую группу уже сложнее. Как правило, принято устанав ливать значение группы файла в значение действительного uid процесса, созда вшего файл. Такой подход практикуется в System V; вообще такая модель и такой образ действия очень распространены в Linux и считаются стандартными. Тем не менее операционная система BSD вносит здесь лишнее усложнение и определяет собственный вариант поведения: группа файлов получает gid роди тельского каталога. Такое поведение можно обеспечить в Linux с помощью одного из параметров времени монтирования 1 . Именно такое поведение будет срабатывать в Linux по умолчанию, если для родительского каталога данного файла задан бит смены индикатора группы ( setgid ). Хотя в большинстве систем Linux предпочита ется поведение System V (при котором новые файлы получают gid родительского каталога), возможность поведения в стиле BSD (при котором новые файлы при обретают gid родительского каталога) подразумевает следующее: код, занятый работой с владеющей группой нового файла, должен самостоятельно задать эту группу посредством системного вызова fchown() (подробнее об этом — в гл. 8). К счастью, вам нечасто придется заботиться о том, к какой группе принадлежит файл. Права доступа новых файлов Обе описанные выше формы системного вызова open() допустимы. Аргумент mode игнорируется, если файл не создается. Этот аргумент требуется, если задан флаг O_CREAT . Если вы забудете указать аргумент mode при использовании O_CREAT , то резуль тат будет неопределенным и зачастую неприятным, поэтому лучше не забывайте. При создании файла аргумент mode задает права доступа к этому новому файлу. Режим доступа не проверяется при данном конкретном открытии файла, поэтому вы можете выполнить операции, противоречащие присвоенным правам доступа, например открыть для записи файл, для которого указаны права доступа только для чтения. Аргумент mode является знакомой UNIXпоследовательностью битов, регла ментирующей доступ. К примеру, он может представлять собой восьмеричное значение 0644 (владелец может читать файл и записывать в него информацию, все остальные — только читать). С технической точки зрения POSIX указывает, что точные значения зависят от конкретной реализации. Соответственно, различные 1 Есть два параметра времени монтирования — bsdgroups или sysvgroups. Глава 2. Файловый ввод-вывод 59 UNIXподобные системы могут компоновать биты доступа по собственному усмотрению. Однако во всех системах UNIX биты доступа реализованы одинаково, поэтому пусть с технической точки зрения биты 0644 или 0700 и не являются пе реносимыми, они будут иметь одинаковый эффект в любой системе, с которой вы теоретически можете столкнуться. Тем не менее, чтобы компенсировать непереносимость позиций битов в режиме доступа, POSIX предусматривает следующий набор констант, которые можно указывать в виде двоичного «ИЛИ» и добавлять к аргументу mode : S_IRWXU — владелец имеет право на чтение, запись и исполнение файла; S_IRUSR — владелец имеет право на чтение; S_IWUSR — владелец имеет право на запись; S_IXUSR — владелец имеет право на исполнение; S_IRWXG — владеющая группа имеет право на чтение, запись и исполнение файла; S_IRGRP — владеющая группа имеет право на чтение; S_IWGRP — владеющая группа имеет право на запись; S_IXGRP — владеющая группа имеет право на исполнение; S_IRWXO — любой пользователь имеет право на чтение, запись и исполнение файла; S_IROTH — любой пользователь имеет право на чтение; S_IWOTH — любой пользователь имеет право на запись; S_IXOTH — любой пользователь имеет право на исполнение. Конкретные биты доступа, попадающие на диск, определяются с помощью двоич ного «И», объединяющего аргумент mode с пользовательской маской создания файла (umask). Такая маска — это специфичный для процесса атрибут, обычно задаваемый интерактивной оболочкой входа. Однако маску можно изменять с помощью вызова umask() , позволяющего пользователю модифицировать права доступа, действующие для новых файлов и каталогов. Биты пользовательской маски файла отключаются в аргументе mode , сообщаемом вызову open() . Таким образом, обычная пользовательская маска 022 будет преобразовывать значение 0666 , сообщенное mode , в 0644 . Вы, как сис темный программист, обычно не учитываете воздействия пользовательских масок, когда задаете права доступа. Смысл подобной маски в том, чтобы сам пользователь мог ограничить права доступа, присваиваемые программами новым файлам. Рассмотрим пример. Следующий код открывает для записи файл, указанный в file . Если файл не существует, то при действующей пользовательской маске 022 он создается с правами доступа 0644 (несмотря на то что в аргументе mode указано значение 0664 ). Если он существует, то этот файл усекается до нулевой длины: int fd; fd = open (file, O_WRONLY | O_CREAT | O_TRUNC, S_IWUSR | S_IRUSR | S_IWGRP | S_IRGRP | S_IROTH); if (fd == –1) /* ошибка */ Открытие файлов 60 Если мы готовы частично пожертвовать переносимостью (как минимум теоре тически) в обмен на удобочитаемость, то можем написать следующий код, функ ционально идентичный предыдущему: int fd; fd = open (file, O_WRONLY | O_CREAT | O_TRUNC, 0664); if (fd == –1) /* ошибка */ Функция creat() Комбинация O_WRONLY | O_CREAT | O_TRUNC настолько распространена, что существует специальный системный вызов, обеспечивающий именно такое поведение: #include #include #include int creat (const char *name, mode_t mode); ПРИМЕЧАНИЕ Да, в названии этой функции не хватает буквы «e». Кен Томпсон (Ken Thompson), автор UNIX, как- то раз пошутил, что пропуск этой буквы был самым большим промахом, допущенным при создании данной операционной системы. Следующий типичный вызов creat() : int fd; fd = creat (filename, 0644); if (fd == –1) /* ошибка */ идентичен такому: int fd; fd = open (filename, O_WRONLY | O_CREAT | O_TRUNC, 0644); if (fd == –1) /* ошибка */ В большинстве архитектур Linux 1 creat() является системным вызовом, хотя его можно легко реализовать и в пользовательском пространстве: int creat (const char *name, int mode) { return open (name, O_WRONLY | O_CREAT | O_TRUNC, mode); } 1 Не забывайте, что системные вызовы определяются в зависимости от архитектуры. Та ким образом, в архитектуре x8664 есть системный вызов creat(), а в Alpha — нет. Функ цию creat() можно, разумеется, использовать в любой архитектуре, но она может быть реализована как библиотечная функция, а не как самостоятельный системный вызов. Глава 2. Файловый ввод-вывод 61 Такое дублирование исторически сохранилось с тех пор, когда вызов open() имел только два аргумента. В настоящее время creat() остается системным вызовом для обеспечения обратной совместимости. Новые архитектуры могут реализовывать creat() как библиотечный вызов. Он активирует open() , как показано выше. Возвращаемые значения и коды ошибок При успешном вызове как open() , так и creat() возвращаемым значением является дескриптор файла. При ошибке оба этих вызова возвращают –1 и устанавливают errno в нужное значение ошибки (выше (см. гл. 1) подробно обсуждается errno и перечисляются возможные значения ошибок). Обрабатывать ошибку при откры тии файла несложно, поскольку перед открытием обычно выполняется совсем мало шагов, которые необходимо отменить (либо вообще не совершается никаких). Типичный ответ — это предложение пользователю выбрать новое имя файла или просто завершение программы. Считывание с помощью read() Теперь, когда вы знаете, как открывать файл, давайте научимся его читать, а в сле дующем разделе поговорим о записи. Самый простой и распространенный механизм чтения связан с использованием системного вызова read() , определенного в POSIX.1: #include ssize_t read (int fd, void *buf, size_t len); Каждый вызов считывает не более len байт в памяти, на которые содержится указание в buf . Считывание происходит с текущим значением смещения, в файле, указанном в fd . При успешном вызове возвращается количество байтов, записанных в buf . При ошибке вызов возвращает -1 и устанавливает errno . Файловая позиция продвигается в зависимости от того, сколько байтов было считано с fd . Если объект, указанный в fd , не имеет возможности позиционирования (например, это файл символьного устройства), то считывание всегда начинается с «текущей» позиции. Принцип использования прост. В данном примере информация считывается из файлового дескриптора fd в word . Количество байтов, которые будут считаны, рав но размеру типа unsigned long , который (как минимум в Linux) имеет размер 4 байт на 32битных системах и 8 байт на 64битных системах. При возврате nr содержит количество считанных байтов или –1 при ошибке: unsigned long word; ssize_t nr; /* считываем пару байт в 'word' из 'fd' */ nr = read (fd, &word, sizeof (unsigned long)); if (nr == –1) /* ошибка */ Считывание с помощью read() 62 В данной упрощенной реализации есть две проблемы. Вопервых, вызов может вернуться, считав не все байты из len ; вовторых, он может допустить ошибки, требующие исправления, но не проверяемые и не обрабатываемые в коде. К сожа лению, код, подобный показанному выше, встречается очень часто. Давайте по смотрим, как его можно улучшить. Возвращаемые значения Системный вызов read() может вернуть положительное ненулевое значение, мень шее чем len . Это может произойти по нескольким причинам: доступно меньше байтов, чем указано в len , системный вызов прерван сигналом, конвейер оказался поврежден (если fd ссылается на конвейер) и т. д. Еще одна возможная проблема при использовании read() — получение возвра щаемого значения 0 . Возвращая 0 , системный вызов read() указывает конец файла (endoffile, EOF). Разумеется, в данном случае никакие байты считаны не будут. EOF не считается ошибкой (соответственно, не дает возвращаемого значения –1 ). Эта ситуация попросту означает, что файловая позиция превысила последнее до пустимое значение смещения в этом файле и читать больше нечего. Однако если сделан вызов на считывание len байт, а необходимое количество байтов для счи тывания отсутствует, то вызов блокируется (переходит в спящий режим), пока нужные байты не будут доступны. При этом предполагается, что файл был открыт в неблокирующем режиме. Обратите внимание: эта ситуация отличается от воз врата EOF, то есть существует разница между «данные отсутствуют» и «достигнут конец данных». В случае с EOF достигнут конец файла. При блокировании считы вающая функция будет дожидаться дополнительных данных — такие ситуации возможны при считывании с сокета или файла устройства. Некоторые ошибки исправимы. Например, если вызов read() прерван сигна лом еще до того, как было считано какоелибо количество байтов, то возвращает ся –1 ( 0 можно было бы спутать с EOF) и errno присваивается значение EINTR В таком случае вы можете и должны повторить считывание. На самом деле последствия вызова read() могут быть разными. Вызов возвращает значение, равное len . Все len считанных байтов сохраняются в buf . Именно это нам и требовалось. Вызов возвращает значение, меньшее чем len , но большее чем нуль. Считанные байты сохраняются в buf . Это может случиться потому, что во время выполнения считывания этот процесс был прерван сигналом. Ошибка возникает в середине процесса, становится доступно значение, большее 0 , но меньшее len . Конец файла был достигнут ранее, чем было прочитано заданное количество байтов. При повторном вызове (в котором соответствующим образом обновлены зна чения len и buf ) оставшиеся байты будут считаны в оставшуюся часть буфера либо укажут на причину проблемы. Вызов возвращает 0 . Это означает конец файла. Считывать больше нечего. Вызов блокируется, поскольку в текущий момент данные недоступны. Этого не происходит в неблокирующем режиме. Глава 2. Файловый ввод-вывод 63 Вызов возвращает –1 , а errno присваивается EINTR . Это означает, что сигнал был получен прежде, чем были считаны какиелибо байты. Вызов будет повторен. Вызов возвращает –1 , а errno присваивается EAGAIN . Это означает, что вызов блокировался потому, что в настоящий момент нет доступных данных, и запрос следует повторить позже. Это происходит только в неблокирующем режиме. Вызов возвращает –1 , а errno присваивается иное значение, нежели EINTR или EAGAIN . Это означает более серьезную ошибку. Простое повторение вызова в дан ном случае, скорее всего, не поможет. Считывание всех байтов Все описанные возможности подразумевают, что приведенный выше упрощенный вариант использования read() не подходит, если вы желаете обработать все ошиб ки и действительно прочитать все байты до достижения len (или по крайней мере до достижения конца файла). Для такого решения требуется применить цикл и не сколько условных операторов: ssize_t ret; while (len != 0 && (ret = read (fd, buf, len)) != 0) { if (ret == –1) { if (errno == EINTR) continue; perror ("read"); break; } len -= ret; buf += ret; } В этом фрагменте кода обрабатываются все пять условий. Цикл считывает len байт с актуальной файловой позиции, равной значению fd , и записывает их в buf Разумеется, значение buf должно быть как минимум равно значению len . Чтение продолжается, пока не будут получены все len байт или до достижения конца фай ла. Если прочитано ненулевое количество байтов, которое, однако, меньше len , то значение len уменьшается на количество прочитанных байтов, buf увеличивается на то же количество и вызов повторяется. Если вызов возвращает –1 и значение errno , равное EINTR , то вызов повторяется без обновления параметров. Если вызов возвращает –1 с любым другим значением errno , вызывается perror() . Он выводит описание возникшей проблемы в стандартную ошибку, и выполнение цикла пре кращается. Случаи частичного считывания не только допустимы, но и вполне обычны. Изза программистов, не озаботившихся правильной проверкой и обработкой неполных операций считывания, возникают бесчисленные ошибки. Старайтесь не пополнять их список! Считывание с помощью read() 64 Неблокирующее считывание Иногда программист не собирается блокировать вызов read() при отсутствии до ступных данных. Вместо этого он предпочитает немедленный возврат вызова, ука зывающий, что данных действительно нет. Такой прием называется неблокирующим вводом-выводом. Он позволяет приложениям выполнять вводвывод, потенциаль но применимый сразу ко многим файлам, вообще без блокирования, поэтому не достающие данные могут быть взяты из другого файла. Следовательно, будет целесообразно проверять еще одно значение errno — EAGAIN Как было сказано выше, если определенный дескриптор файла был открыт в не блокирующем режиме (вызову open() сообщен флаг O_NONBLOCK ) и данных для счи тывания не оказалось, то вызов read() возвратит –1 и вместо блокирования устано вит значение errno в EAGAIN . При выполнении неблокирующего считывания нужно выполнять проверку на наличие EAGAIN , иначе вы рискуете перепутать серьезную ошибку с тривиальным отсутствием данных. Например, вы можете использовать примерно следующий код: сhar buf[BUFSIZ]; ssize_t nr; start: nr = read (fd, buf, BUFSIZ); if (nr == –1) { if (errno == EINTR) goto start; /* вот незадача */ if (errno == EAGAIN) /* повторить вызов позже */ else /* ошибка */ } ПРИМЕЧАНИЕ Если бы мы попытались обработать случай с EAGAIN так же, как и с EAGAIN (с применением goto start), это практически не имело бы смысла. Мы могли бы и не применять неблокирующий ввод- вывод. Весь смысл использования неблокирующего ввода-вывода заключается в том, чтобы пере- хватить EAGAIN и выполнить другую полезную работу. Другие значения ошибок Другие коды относятся к ошибкам, допускаемым при программировании или (как EIO) к низкоуровневым проблемам. Возможные значения errno после неуспешно го вызова read() таковы: EBADF — указанный дескриптор файла недействителен или не открыт для чте ния; EFAULT — указатель, предоставленный buf , не относится к адресному простран ству вызывающего процесса; Глава 2. Файловый ввод-вывод 65 EINVAL — дескриптор файла отображается на объект, не допускающий считы вания; EIO — возникла ошибка низкоуровневого вводавывода. Ограничения размера для read() Типы size_t и ssize_t types предписываются POSIX. Тип size_t используется для хранения значений, применяемых для измерения размера в байтах. Тип ssize_t — это вариант size_t , имеющий знак (отрицательные значения ssize_t используются для дополнительной характеристики ошибок). В 32битных системах базовыми типами C для этих сущностей являются соответственно unsigned int и int . Эти два типа часто используются вместе, поэтому потенциально более узкий диапазон ssize_t лимитирует и размер size_t Максимальное значение size_t равно SIZE_MAX , максимальное значение ssize_t составляет SSIZE_MAX . Если значение len превышает SSIZE_MAX , то результаты вызо ва read() не определены. В большинстве систем Linux значение SSIZE_MAX соответ ствует LONG_MAX , которое, в свою очередь, равно 2 147 483 647 на 32битной машине. Это относительно большое значение для однократного считывания, но о нем не обходимо помнить. Если вы использовали предыдущий считывающий цикл как обобщенный суперсчитыватель, то, возможно, решите сделать нечто подобное: if (len > SSIZE_MAX) len = SSIZE_MAX; При вызове read() с длиной, равной нулю, вызов немедленно вернется с возвра щаемым значением 0 |