Добавил:
Developer Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

учебник

.pdf
Скачиваний:
36
Добавлен:
25.03.2023
Размер:
2 Мб
Скачать
☆

*/

size = read(fd, resstring, 14); if(size < 0)

{

/* Если прочитать не смогли, вывод об этом сообщения и прекращение работы */

cout<<"Can't read string"<<endl;

}

/* Печать прочитанной строки */ cout<<resstring<< endl;

/* Закрытие входного потока и завершение работы */ close(fd);

}

return 0;

}

Листинг 4-04. Программа, осуществляющая однонаправленную связь через FIFO между процессом-родителем и процессомребенком

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

4.4 Открытие файла. Системный вызов open()

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

81

Системный вызов open

Прототип системного вызова

#include <fcntl.h>

int open(char *path, int flags);

int open(char *path, int flags, int mode);

Описание системного вызова

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

Параметр path является указателем на строку, содержащую полное или относительное имя файла.

Параметр flags может принимать одно из следующих трех значений: O_RDONLY – если над файлом в дальнейшем будут совершаться только операции чтения;

O_WRONLY – если над файлом в дальнейшем будут осуществляться только операции записи;

O_RDWR – если над файлом будут осуществляться и операции чтения, и операции записи.

Каждое из этих значений может быть скомбинировано посредством операции «побитовое или ( | )» с одним или несколькими флагами: O_CREAT – если файла с указанным именем не существует, он должен быть создан;

O_EXCL – применяется совместно с флагом O_CREAT. При совместном их использовании и существовании файла с указанным именем, открытие файла не производится и констатируется ошибочная ситуация;

O_NDELAY – запрещает перевод процесса в состояние «ожидание» при выполнении операции открытия и любых последующих операциях над этим файлом;

O_APPEND – при открытии файла и перед выполнением каждой операции записи (если она, конечно, разрешена) указатель текущей позиции в файле устанавливается на конец файла;

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

82

Кроме того, в некоторых версиях операционной системы UNIX могут применяться дополнительные значения флагов:

O_SYNC – любая операция записи в файл будет блокироваться (т. е. процесс будет переведен в состояние «ожидание») до тех пор, пока записанная информация не будет физически помещена на соответствующий нижележащий уровень hardware;

O_NOCTTY – если имя файла относится к терминальному устройству, оно не становится управляющим терминалом процесса, даже если до этого процесс не имел управляющего терминала.

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

Этот параметр задается как сумма следующих восьмеричных значений:

0400 – разрешено чтение для пользователя, создавшего файл; 0200 – разрешена запись для пользователя, создавшего файл; 0100 – разрешено исполнение для пользователя, создавшего файл;

0040 – разрешено чтение для группы пользователя, создавшего файл; 0020 – разрешена запись для группы пользователя, создавшего файл; 0010 – разрешено исполнение для группы пользователя, создавшего файл; 0004 – разрешено чтение для всех остальных пользователей;

0002 – разрешена запись для всех остальных пользователей;

0001 – разрешено исполнение для всех остальных пользователей.

При создании файла реально устанавливаемые права доступа получаются из стандартной комбинации параметра mode и маски создания файлов текущего процесса umask, а именно – они равны mode & ~umask.

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

83

процесс, осуществивший системный вызов, блокируется до тех пор, пока какой-либо другой процесс не откроет FIFO на чтение. Если флаг O_NDELAY задан, то констатируется возникновение ошибки и возвращается значение -1.

Возвращаемое значение Системный вызов возвращает значение файлового дескриптора для

открытого файла при нормальном завершении и значение -1 при возникновении ошибки.

4.5 Системные вызовы read(), write()

Для совершения потоковых операций чтения информации из файла и ее записи в файл применяются системные вызовы read() и write().

Прототипы системных вызовов

#include <sys/types.h> #include <unistd.h>

size_t read(int fd, void *addr, size_t nbytes);

size_t write(int fd, void *addr, size_t nbytes);

Описание системных вызовов

Системные вызовы read и write предназначены для осуществления потоковых операций ввода (чтения) и вывода (записи) информации над каналами связи, описываемыми файловыми дескрипторами, т.е. для файлов, pipe, FIFO и socket.

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

Параметр addr представляет собой адрес области памяти, начиная с которого будет браться информация для передачи или размещаться принятая информация.

Параметр nbytes для системного вызова write определяет количество байт, которое должно быть передано, начиная с адреса памяти addr.

84

Параметр nbytes для системного вызова read определяет количество байт, которое мы хотим получить из канала связи и разместить в памяти, начиная с адреса addr.

Возвращаемые значения В случае успешного завершения системный вызов возвращает

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

Особенности поведения при работе с файлами При работе с файлами информация записывается в файл или читается

из файла, начиная с места, определяемого указателем текущей позиции в файле. Значение указателя увеличивается на количество реально прочитанных или записанных байт. При чтении информации из файла она не пропадает из него. Если системный вызов read возвращает значение 0, то это означает, что файл прочитан до конца.

4.6 Системный вызов close()

Прототип системного вызова

#include <unistd.h> int close(int fd);

Описание системного вызова

Системный вызов close предназначен для корректного завершения работы с файлами и другими объектами ввода-вывода, которые описываются в операционной системе через файловые дескрипторы: pipe, FIFO, socket.

Параметр fd является дескриптором соответствующего объекта, т. е. значением, которое вернул один из системных вызовов open(), pipe()

или socket().

Возвращаемые значения Системный вызов возвращает значение 0 при нормальном завершении

и значение -1 при возникновении ошибки.

85

4.7 Особенности поведения вызова open() при открытии FIFO

Системные вызовы read() и write() при работе с FIFO имеют те же особенности поведения, что и при работе с pip’ом. Системный вызов open() при открытии FIFO ведет себя несколько иначе, чем при открытии других типов файлов, что связано с возможностью блокирования выполняющих его процессов. Если FIFO открывается только для чтения и флаг O_NDELAY не задан, то процесс, осуществивший системный вызов, блокируется до тех пор, пока какой-либо другой процесс не откроет FIFO на запись. Если флаг O_NDELAY задан, то возвращается значение файлового дескриптора, ассоциированного с FIFO. Если FIFO открывается только для записи и флаг O_NDELAY не задан, то процесс, осуществивший системный вызов, блокируется до тех пор, пока какой-либо другой процесс не откроет FIFO на чтение. Если флаг O_NDELAY задан, то констатируется возникновение ошибки и возвращается значение -1. Задание флага O_NDELAY в параметрах системного вызова open() приводит и к тому, что процессу, открывшему FIFO, запрещается блокировка при выполнении последующих операций чтения из этого потока данных и записи в него.

Для иллюстрации сказанного давайте рассмотрим следующую программу:

Программа 4-05.c для записи информации в файл.

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

#include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <stdio.h> #include <unistd.h> #include <iostream> using namespace std; int main()

{

int fd; size_t size;

86

char string[] = "Hello, world!";

/* Обнуление маски создания файлов текущего процесса для того, чтобы права доступа у создаваемого файла точно соответствовали параметру вызова open() */

(void)umask(0);

/* Открытие файла с именем myfile в текущей директории только для операций вывода. Если файла не существует, то его создание с правами доступа 0666, т. е. read-write для всех категорий пользователей */

if((fd = open("myfile", O_WRONLY | O_CREAT, 0666)) < 0)

{

/* Если файл открыть не удалось, вывод об этом сообщения и прекращение работы */

cout<<”Can't open file"<<endl;

}

/* Запись в файл 14 байт из нашего массива, т.е. всю строку "Hello, world!" вместе с признаком конца строки */

size = write(fd, string, 14); if(size == write(fd, string, 14))

{

cout<<”File written"<<endl;

}

if(size != 14)

{

/* Если записалось меньшее количество байт, то вывод сообщения об ошибке */

cout<<"Can't write all string"<<endl;

}

/* Закрытие файла */ if(close(fd) < 0)

{

cout<<"Can't close file"<<endl;

}

return 0;

}

Листинг 4-05. Программа, иллюстрирующая использование системных вызовов open(), write() и close() для записи информации в файл

87

4.8 Особенности поведения вызовов read() и write() для pip'а

Системные вызовы read() и write() имеют определенные особенности поведения при работе с pip’ом, связанные с его ограниченным размером, задержками в передаче данных и возможностью блокирования обменивающихся информацией процессов.

При написании программ, обменивающихся большими объемами информации через pipe, необходимо проверять значения, возвращаемые вызовами. За один раз из pip’а может прочитаться меньше информации, чем вы запрашивали, и за один раз в pipe может записаться меньше информации, чем вам хотелось бы.

Одна из особенностей поведения блокирующегося системного вызова read() связана с попыткой чтения из пустого pip'а. Если есть процессы, у которых этот pipe открыт для записи, то системный вызов блокируется и ждет появления информации. Если таких процессов нет, он вернет значение 0 без блокировки процесса. Эта особенность приводит к необходимости закрытия файлового дескриптора, ассоциированного с входным концом pip'a, в процессе, который будет использовать pipe для чтения (close (fd[1]) в процессе-ребенке в программе из раздела «Организация связи через pipe между процессом-родителем и процессом потомком»). Аналогичной особенностью поведения при отсутствии процессов, у которых pipe открыт для чтения, обладает и системный вызов write(), с чем связана необходимость закрытия файлового дескриптора, ассоциированного с выходным концом pip'a, в процессе, который будет использовать pipe для записи (close (fd[0] ) в процессе-родителе в той же программе).

Программа 4-06.c. Пример работы с именованным каналом между процессами одного приложения.

Процесс-родитель создает именованный канал и порождает дочерний процесс, который посылает свой идентификатор (pid) в канал. Родительский процесс считывает данные из канала и выводит их на экран.

#include <stdio.h> #include <unistd.h>

88

#include <fcntl.h> #include <sys/wait.h> #include <iostream>

using namespace std;

int main()

{

char s[15] ; //для передачи и получения данных int fd[2] ; //для получения дескрипторов канала

if (mkfifo("mypipe",S_IFIFO|0666)<0) //если не удалось создать канал

{

cout << "\Can’t create channel"; return 0;

}

//получить дескриптор для чтения

//если не указать флаг O_NONBLOCK, процесс заблокирует сам себя fd[0] = open("mypipe",O_RDONLY|O_NONBLOCK);

//получить дескриптор для записи fd[1] = open("mypipe",O_WRONLY); if (fork()==0)

{//программный код для дочернего процесса

int r = sprintf(s,"MyPid=%d",getpid()) ; //записать свой pid в s, r–длина строки

write(fd[1],&s,r); //послать данные s в канал через дескриптор для записи

return 1;

}

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

read(fd[0],&s,15); //прочитать данные из канала через дескриптор для чтения

cout << “Parent read”<< s<<endl; //вывести полученную строку на экран

89

close(fd[0]); //закрыть дескрипторы канала close(fd[1]); //закрыть дескрипторы канала unlink(“mypipe”); //удалить канал

return 1;

}

Листинг 4-06. Программа работы с именованным каналом

Программа 4-07.c. Пример работы с именованным каналом между двумя приложениями: читатель и писатель.

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

Читатель:

#include <stdio.h> #include <stdlib.h> #include <errno.h> #include <unistd.h> #include <sys/types.h> #include <sys/wait.h> #include <cstdlib> #include <iostream> #include <fcntl.h> #include <sys/stat.h> int main()

{

char s[15] ; int fd;

mkfifo("mypipe",S_IFIFO|0666); fd=open("mypipe",O_RDONLY); read(fd,&s,15); fprintf(stdout,"READ:'%s'\n",s); close(fd);

90

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