<<
>>

Системные вызовы для управления файлами

Многие системные вызовы имеют отношение к файловой системе. В этом разделе мы рассмотрим вызовы, работающие с отдельными файлами, а в следующем разделе обратимся к вызовам, которые оперируют каталогами или файловой системой в целом.
Для создания нового файла служит вызов creât (ответ на вопрос, почему именно creât, а не create, затерялся в тумане времен). Его параметры — имя файла и права доступа, например: fd = creât("abc", 0751);

Эта команда создаст файл с именем abc и установит для него права доступа 0751 (в С числа с ведущим нулем считаются восьмеричными). Младшие 9 бит этого числа показывают, что владелец файла имеет права доступа rwx (7 означает права на чтение, запись и исполнение), члены группы владельца имеют права на чтение и исполнение (5), а прочие пользователи — только на исполнение (1).

Вызов creât не только создает новый файл, но и открывает его для записи независимо от указанного режима. Дальнейшая запись в файл производится через его дескриптор fd, значение которого возвращается вызовом.

Если выполнить вызов creât для существующего файла, то файл усекается до нулевой длины (если, конечно, права доступа позволяют это). Сейчас вызов creât устарел и поддерживается для обратной совместимости, вместо него нужно использовать вызов open.

Чтобы создать специальный файл, нужно вместо creât выполнить вызов mknod. Вот типичный пример:

fd = mknod("/dev/ttyc2", 020744, 0x0402);

Эта команда создает файл с именем /dev/ttyc2 (обычно это имя соответствует второй консоли) и задает для него права доступа 020744 (специальный символьный файл с правами rwxr--r--). Третий параметр составлен из пары байтов, из которых старший задает основное устройство (4), а младший — вторичное устройство (2). Основное устройство может быть любым, а вторичное для файла /dev/ttyс2 должно быть равно 2.

Делать вызов mknod может только суперпользователь, в противном случае возникает ошибка.

Чтобы прочитать или записать файл, его сначала нужно открыть при помощи вызова open. Для этого вызова указывается имя открываемого файла (задается или абсолютный путь файла, или ссылка на рабочий каталог) и код 0_RD0NLY, 0_WR0NLY или 0_RDWR, означающий, что файл открывается для чтения, записи или того и другого. Для создания нового файла служит код 0_CREAT. Возвращаемый дескриптор файла затем можно употребить при чтении или записи. Потом файл закрывается с помощью вызова close, который делает дескриптор файла доступным для последующего вызова creât или open.

Наиболее часто используемыми вызовами, без сомнения, являются read и write. Вызов read мы уже обсуждали, write имеет те же самые параметры.

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

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

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

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

Листинг 1.2. Структура, используемая для получения информации от системных вызовов STAT и FSTAT. В фактическом коде для некоторых типов используются символические имена

struct stat {

short st_dev; /* устройство, которому принадлежит i-узел */

unsigned short st_ino; /* номер inode */

unsigned short st_mode; /* режим доступа */

short st_nlink; /* число ссылок на файл */

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

fd = dup(1);

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

n = dup(fd);

В результате самый меньший из дескрипторов файлов, а именно 1, станет соответствовать тому же файлу, что и fd. Наконец, f d можно закрыть, в результате мы вернемся к той же ситуации, с которой начали.

У системного вызова dup есть второй вариант, с помощью которого можно связать неиспользованный дескриптор с уже открытым файлом. Он записывается так:

dup2(fd, fd2);

Здесь f d — это дескриптор открытого файла, a f d2 — не связанный ни с каким файлом дескриптор, который после выполнения вызова будет ссылаться на тот же файл.

Таким образом, если fd ссылается на стандартный ввод (дескриптор равен 0), а дескриптор f d2 равен 4, то после выполнения вызова и 0 и 4 будут соответствовать стандартному вводу.

Как уже отмечалось, для обеспечения взаимодействия между процессами в MINIX 3 можно использовать каналы. Например:

cat filel file2 I sort

Когда пользователь дает оболочке эту команду, то оболочка создает канал и соединяет стандартный вывод первого процесса с входом канала, а стандартный ввод второго процесса с выходом канала. Чтобы создать канал, применяется системный вызов pipe, возвращающий два дескриптора файлов: один для чтения из канала, другой для записи в него: pipe(&f d[0]);

Здесь f d — массив двух целых чисел, f d [ О ] — дескриптор для чтения, a f d [ 1 ] — дескриптор для записи. Как правило, после этого делается вызов fork, родительский процесс закрывает дескриптор для чтения, а дочерний процесс — дескриптор для записи (или наоборот), чтобы один процесс мог писать в канал, а другой — читать из него.

В листинге 1.3 приведен каркас процедуры, создающей два процесса таким образом, что выход первого из них передается через канал во второй (более реалистичный пример обеспечивал бы проверку ошибок и обработку аргументов). Сначала создается поток, затем делается вызов fork, и родительский процесс становится первым процессом в канале, а дочерний процесс — вторым. Так как запускаемые файлы, processl и process2, ничего не знают о том, что они соединяются каналом, для работы программы необходимо, чтобы стандартный вывод первого процесса был соединен со стандартным вводом второго процесса каналом. Сначала родительский процесс закрывает дескриптор для чтения из канала. Затем он закрывает стандартный вывод и делает вызов dup, после которого стандартным выводом становится вход канала. Важно понимать, что вызов dup всегда возвращает наименьший допустимый дескриптор, в данном случае — 1. Наконец, исходный дескриптор для записи в канал закрывается.

Листинг 1.3. Каркас процедуры, создающей конвейер из двух процессов

#define STD_INPUT 0 /* Дескриптор файла для стандартного ввода */

#define STD_OUTPUT 1 /* Дескриптор файла для стандартного вывода*/

pipeline(processl, process2) /* Указатели на имена программ */ char *processl, *process2;

{

int fd[2];

pipe(&fd[0]); /* создать конвейер */

if(fork() !=0) {

/* Эти выражения исполняются родительским процессом */ close(fd[0]); /* Процесс 1 не нуждается в чтении с конвейера */

close(STD_OUTPUT); /* Подготовка нового стандартного вывода */

dup(fd[l]); /* Сделать стандартным выводом устройство fd[1] */

close(fd[1]); /* Этот дескриптор файла больше не нужен */

execl(processl,processl,0);

} else {

/* Эти выражения исполняются процессом-наследником */ close(fd[1]); /* Процесс 2 не нуждается в записи в конвейер */

close(STD_INPUT); /* Подготовка нового стандартного ввода */

dup(fd[0]); /* Сделать стандартным вводом устройство fd[0] */

close(fd[0]); /* Этот дескриптор файла больше не нужен */

execl(process2,process2,0);

}

}

После вызова exec стартует процесс, у которого дескрипторы 0 и 2 остались без изменений, а дескриптор 1 соответствует записи в канал.

Код дочернего процесса аналогичен. Значение параметра функции execl повторяется потому, что первым ее параметром должно быть имя программы, а вторым — значение первого аргумента запускаемой программы, которое для большинства программ должно совпадать с именем.

Следующий системный вызов, ioctl, потенциально применим ко всем специальным файлам. Например, он используется драйверами блочных устройств, таких как SCSI-драйверы для работы с ленточными накопителями и CD-ROM. Тем не менее он в основном применяется к символьным специальным файлам, особенно к терминалам. В стандарте POSIX определено несколько функций, которые транслируются библиотекой в вызовы ioctl. С помощью функций tcgetattг и t с set at t г можно изменить характеристики терминала, такие как коррекция ошибок, режим и т. д. Эти функции используют вызов ioctl.

Режим с обработкой (cooked mode) — это нормальный режим работы терминала, в котором возможно удаление символов, клавиши Ctrl+S и Ctrl+Q соответственно останавливают и запускают вывод информации на терминал, клавиши Ctrl+D задают конец файла, а клавиши Ctrl+C генерируют сигнал прерывания. Нажатие клавиш Ctrl +\ в этом режиме генерирует сигнал, по которому процесс принудительно прерывается, и выводится дамп памяти.

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

Режим с прерыванием (ebreak mode) — промежуточный между двумя предыдущими. В этом режиме при редактировании не работают клавиши стирания и удаления, а также комбинация Ctrl+D, но клавиши Ctrl+S, Ctrl+Q, Ctrl+C и Ctrl+\ работают. Как и в режиме без обработки, символы передаются программам сразу, не дожидаясь окончания ввода строки (так как редактирование строк не работает, не обязательно дожидаться окончания ввода, потому что пользователь не сможет передумать и удалить введенные символы, как в режиме с обработкой).

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

У вызова ioctl есть три параметра. Например, вызов функции tesetattr, устанавливающей параметры терминала, выглядит так:

ioctl(fd, TCSETS, ktermios);

Первый параметр задает файл, второй — выполняемую операцию, а третий — адрес POSIX-структуры, содержащей флаги и массив управляющих символов.

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

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

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

Наконец, для управления файлами служит вызов fcntl, в чем-то подобный вызову ioctl (и оба этих вызова — так называемые грязные хаки). У этого вызова есть несколько параметров, самые важные из которых служат для управления захватом файла. Вызовом fcntl можно захватывать и освобождать отдельные части файлов, а также определять, захвачен ли нужный участок. Этот вызов никак не определяет семантику захвата файла. Программы должны сами решать, что делать.

1.4.4.

<< | >>
Источник: Э. ТАНЕНБАУМ, А. ВУДХАЛЛ. ОПЕРАЦИОННЫЕ СИСТЕМЫ Разработка и реализация 3-е издание. 2007

Еще по теме Системные вызовы для управления файлами:

  1. I. 1. СИСТЕМНЫЙ ПОДХОД КАК ИНСТРУМЕНТ ДЛЯ ПОСТРОЕНИЯ СИСТЕМНЫХ ОПИСАНИЙ
  2. Психологическая системность в управлении.
  3. Психологическая системность в управлении.
  4. I. СИСТЕМНЫЕ ОПИСАНИЯ - ГЛАВНЫЙ РЕЗУЛЬТАТ СИСТЕМНОГО ПОДХОДА В ПСИХОЛОГИИ
  5. § 45 Меры к охранению открывшегося наследства. – Опись. – Вызов наследников. – Утверждение в правах наследства. – Срок на явку наследников и вступление явившихся. – Особые правила для торгового сословия.
  6. Психология личности целостна, системна (принцип целостности, системности).
  7. ВЫЗОВ ВРАЧА
  8. СПТ откликается на вызов обстоятельств
  9. Быстрый вызов желаемого
  10. Бросьте себе вызов
  11. Самоубийство как вызов обществу
  12. Статья 1040. Обращение взыскания на имущество, переданное в управление, по требованию кредитора установщика управления
  13. § 14 Условное соглашение. – Предложение и вызов. – Договор посредством публичного торга или состязания. – Одностороннее обещание.
  14. § 3. Право управления предприятием как особый вид абсолютных прав. Право полного и ограниченного управления (п. 1774-1776)
  15. УПРАВЛЕНИЕ ВРЕМЕНЕМ VERSUS УПРАВЛЕНИЕ СОБОЙ
  16. § 46 Принятие наследства. – Значение вызова кредиторов и некоторых публикаций. – Отзыв о принятии и действия, служащие признаком принятия.
  17. ПРИНЦИП СИСТЕМНОСТИ
  18. § 2.4. Системный подход
  19. принцип системности