Архитектура Аудит Военная наука Иностранные языки Медицина Металлургия Метрология
Образование Политология Производство Психология Стандартизация Технологии


Манипуляция таблицей дескрипторов



 

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

Для закрытия дескриптора используется уже рассматривавшийся вызов close().

Кроме этого, очень важны еще два системных вызова, создающие синонимы существующих дескрипторов:

int dup (int fd);

int dup2(int fd, int new_fd);

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

Вызов dup2() отличается тем, что новый дескриптор создается под заданным номером (параметр new_fd ). Если этот номер был связан с открытым дескриптором, тот дескриптор закрывается.

 

Рассмотрим пример. Допустим, у нас возникла потребность в программе на С смоделировать функционирование команды Shell:

 

> Ls –L –a –R/> filelist

 

(попросту говоря, сгенерировать файл filelist, содержащий список всех файлов в системе). Это можно сделать с помощью следующего фрагмента программы:

 

main() {

int pid, status;

pid = fork( );

if(pid = = -1) { /*…обработка ошибки …*/ }

if(pid = = 0) { /*…дочерний процесс …*/

int fd = open(”filelist”, O_REAT|O_WRONLY|O_TRUNK, 0666);

if(fd = = -1) exit(1);

dup2(fd, 1);

close(fd);

execlp(“ls”, “ls”, “-1”, “-a”, “-R”, “/”, NULL);

perror(“ls”);

exit(1);

}

/*родительский процесс */

wait(& status);

if(! WIFEXITED(status) || WEXITSTATUS(status)! =0) {

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

}

}

Задание 1.

Организация конвейера и перенаправления ввода-вывода.

Написать программу, моделирующую команду SHELL : (здесь pri - имена процессов, argi - аргументы процессов, f.dat - файл входных данных, f.res - файл результатов; в каждом из процессов pri использован стандартный ввод-вывод). Аргументы, необходимые этой программе, задаются в командной строке (массив argv[] ).

a) pr1½ pr2½ pr3

b) pr1½ pr2 > f.res

c) pr1 < f.dat½ pr2½ pr3> f.res

d) pr1½ pr2 > > f.res

e) pr1; pr2½ pr3 > f.res

f) pr1½ pr2½ …½ prn

g) pr1 < f.dat½ pr2 arg2

h) pr1 arg1 < f.dat; pr2| pr3 > f.rez

 

 

Именованный канал FIFO

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

Для организации потокового взаимодействия любых процессов в операционной системе UNIX применяется средство связи, получившее название FIFO (от FirstInput First Output ) или именованный pipe. FIFO во всем подобен pip'y, за одним исключением: данные о расположении FIFO в адресном пространстве ядра и его состоянии процессы могут получать не через родственные связи, а через файловую систему. Для этого при создании именованного pip'a на диске заводится файл специального типа, обращаясь к которому процессы могут получить интересующую их информацию. Для создания FIFO используется системный вызов mknod() или существующая в некоторых версиях UNIX функция mkfifo().

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

После открытия именованный pipe ведет себя точно так же, как и неименованный. Для дальнейшей работы с ним применяются системные вызовы read(), write() и close(). Время существования FIFO в адресном пространстве ядра операционной системы, как и в случае с pip'oм, не может превышать время жизни последнего из использовавших его процессов. Когда все процессы, работающие с FIFO, закрывают все файловые дескрипторы, ассоциированные с ним, система освобождает ресурсы, выделенные под FIFO. Вся непрочитанная информация теряется. В то же время файл-метка остается на диске и может использоваться для новой реальной организации FIFO в дальнейшем.

 

Системный вызов mknod для создания FIFO

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

#include < sys/stat.h>

#include < unistd.h>

int mknod(char *path, int mode, int dev);

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

 

 

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

Параметр dev является несущественным в нашей ситуации, и мы будем всегда задавать его равным 0.

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

Параметр mode устанавливает атрибуты прав доступа различных категорий пользователей к FIFO. Этот параметр задается как результат побитовой операции «или» значения S_IFIFO, указывающего, что системный вызов должен создать FIFO, и некоторой суммы следующих восьмеричных значений:

0400 - разрешено чтение для пользователя, создавшего FIFO;

0200 - разрешена запись для пользователя, создавшего FIFO;

0040 - разрешено чтение для группы пользователя, создавшего FIFO;

0020 - разрешена запись для группы пользователя, создавшего FIFO;

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

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

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

 

 

Возвращаемые значения

 

 

При успешном создании FIFO системный вызов возвращает значение 0, при неуспешном - отрицательное значение.

 

 

Функция mkfifo

Прототип функции:

#include < sys/stat.h>

#include < unistd.h>

int mkfifo(char *path, int mode);

Описание функции

Функция mkfifo предназначена для создания FIFO в операционной системе.

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

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

0400 - разрешено чтение для пользователя, создавшего FIFO;

0040 - разрешено чтение для группы пользователя, создавшего FIFO;

0020 - разрешена запись для группы пользователя, создавшего FIFO;

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

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

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

Возвращаемые значения

При успешном создании FIFO функция возвращает значение 0, при неуспешном - отрицательное значение.

Важно понимать, что файл типа FIFO не служит для размещения на диске информации, которая записывается в именованный pipe. Эта информация располагается внутри адресного пространства операционной системы, а файл является только меткой, создающей предпосылки для ее размещения.

 

Не пытайтесь просмотреть содержимое этого файла с помощью Midnight Commander (mc)!!! Это приведет к его глубокому зависанию!

 

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

 

 

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

 

 

Пример программы с FIFO в родственных процессах

 

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

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

#include < sys/types.h>

#include < sys/stat.h>

#include < fcntl.h>

#include < unistd.h>

#include < stdio.h>

int main(){

int fd, result; size_t size; char resstring[14];

char name[]=" aaa.fifo";

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

(void)umask(0);

/* Попытаемся создать FIFO с именем aaa.fifo в текущей директории *./

if(mknod(name, S_IFIFO I 0666, 0) < 0){

/* Если создать FIFO не удалось, печатаем об этом сообщение и прекращаем работу */

printf(" Can\'t create FIFO\n" );

exit(-1);

}

/* Порождаем новый процесс */

if((result = fork()) < 0){

/* Если создать процесс не удалось, сообщаем об этом и завершаем работу */ printf(" Can\'t fork child\n" ); exit(-1); } else if (result > 0) {

/* Мы находимся в родительском процессе, который будет передавать информацию процессу-ребенку. В этом процессе открываем FIFO на запись.*/

if< (fd = open(name, O_WRONLY)) < 0){

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

printf(" Can\'t open FIFO for writing\n" );

exit(-l);

}

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

size = write(fd, " Hello, world! ", 14);

if(size! = 14){

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

printf(" Can\'t write all string to FIFO\n" )

exit(-1);

}

/* Закрываем входной поток данных и на этом родитель прекращает работу */

close(fd);

printf(" Parent exit\n" );

} else {

/* Мы находимся в порожденном процессе, который будет получать информацию от процесса-родителя. Открываем FIFO на чтение.*/

if((fd = open(name, O_RDONLY)) < 0){

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

printf(" Can\'t open FIFO for reading\n" );

exit(-1);

}

/* Пробуем прочитать из FIFO 14 байт в массив, т.е. всю записанную строку */

size = read(fd, resstring, 14);

if(size < 0){

/* Если прочитать не смогли, сообщаем об ошибке и завершаем работу */

printf(" Can\'t read string\n" );

exit (-1);

}

/* Печатаем прочитанную строку */

printf(" %s\n", resstring);

/* Закрываем входной поток и завершаем работу */

close(fd);

}

return 0;

}

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

Задание 2.

Написать, откомпилировать и запустить на выполнение программу с FIFO в неродственных процессах. Модель " клиент-сервер" должна работать следующим образом: ’’Процесс-сервер запускается на выполнение первым. Создает именованный канал FIFO , открывает его на чтение в неблокирующем режиме и входит в цикл, пытаясь прочесть что-либо. Затем запускается процесс-клиент, подключается к каналу с известным ему именем и записывает в него свой pid . Сервер выходит из цикла, прочитав идентификатор клиента, и печатает его.”

 

 

Контрольные вопросы

1.Системный вызов для организации неименованного канала в OC UNIX.

2.В каких задачах используют организацию неименованного канала, а когда следует использовать именованный канал?

3.Какого размера файл именованного канала?

4.Как организовать канал FIFO?

5.Программная организация перенаправления ввода-вывода.

 

 


 

Доцент

 

Филоненко Ирина Николаевна

 

СИСТЕМНОЕ программирование

 

 

Лабораторный практикум


Поделиться:



Популярное:

Последнее изменение этой страницы: 2016-05-03; Просмотров: 785; Нарушение авторского права страницы


lektsia.com 2007 - 2024 год. Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав! (0.079 с.)
Главная | Случайная страница | Обратная связь