Основы операционных систем
Изучение теорий операционных систем. Исследование принципов построения и особенностей проектирования современных ОС. Сущность виртуальной памяти и алгоритма синхронизации. Рассмотрение операционной и файловой системы, система управления вводом-выводом.
Рубрика | Программирование, компьютеры и кибернетика |
Вид | книга |
Язык | русский |
Дата добавления | 12.01.2010 |
Размер файла | 2,6 M |
Отправить свою хорошую работу в базу знаний просто. Используйте форму, расположенную ниже
Студенты, аспиранты, молодые ученые, использующие базу знаний в своей учебе и работе, будут вам очень благодарны.
Наиболее простым способом для передачи информации с помощью потоковой модели между различными процессами или даже внутри одного процесса в операционной системе UNIX является pipe (канал, труба, конвейер).
Важное отличие pip'a от файла заключается в том, что прочитанная информация немедленно удаляется из него и не может быть прочитана повторно.
Pipe можно представить себе в виде трубы ограниченной емкости, расположенной внутри адресного пространства операционной системы, доступ к входному и выходному отверстию которой осуществляется с помощью системных вызовов. В действительности pipe представляет собой область памяти, недоступную пользовательским процессам напрямую, зачастую организованную в виде кольцевого буфера (хотя существуют и другие виды организации). По буферу при операциях чтения и записи перемещаются два указателя, соответствующие входному и выходному потокам. При этом выходной указатель никогда не может перегнать входной и наоборот. Для создания нового экземпляра такого кольцевого буфера внутри операционной системы используется системный вызов pipe ( ) .
Системный вызов pipe Прототип системного вызова
¦include <unistd.h> int pipe(int *fd);
Описание системного вызова
Системный вызов pipe предназначен для создания pip'a внутри операционной системы.
Параметр f d является указателем на массив из двух целых переменных. При нормальном завершении вызова в первый элемент массива - f d [ 0 ] - будет занесен файловый дескриптор, соответствующий выходному потоку данных pip'a и позволяющий выполнять только операцию чтения, а во второй элемент массива - fd[l] - будет занесен файловый дескриптор, соответствующий входному потоку данных и позволяющий выполнять только операцию записи.
Возвращаемые значения
Системный вызов возвращает значение 0 при нормальном завершении и значение -1 при возникновении ошибок.
В процессе работы системный вызов организует выделение области памяти под буфер и указатели и заносит информацию, соответствующую входному и выходному потокам данных, в два элемента таблицы открытых файлов, связывая тем самым с каждым pip'oM два файловых дескриптора. Для одного из них разрешена только операция чтения из pip'a, а для другого -- только операция записи в pipe. Для выполнения этих операций мы можем использовать те же самые системные вызовы read () и write (), что и при работе с файлами. Естественно, по окончании использования входного или/и выходного потока данных, нужно закрыть соответствующий поток с помощью системного вызова close () для освобождения системных ресурсов. Необходимо отметить, что, когда все процессы, использующие pipe, закрывают все ассоциированные с ним файловые дескрипторы, операционная система ликвидирует pipe. Таким образом, время существования pip'a в системе не может превышать время жизни процессов, работающих с ним.
Прогон программы для pipe в одном процессе
Достаточно яркой иллюстрацией действий по созданию pip'a, записи в него данных, чтению из него и освобождению выделенных ресурсов может служить программа, организующая работу с pip'oM в рамках одного процесса, приведенная ниже.
/* Программа 05-2.с, иллюстрирующая работу с pip'ом
в рамках одного процесса */
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
int main(){
int fd[2];
size_t size;
char string[] = "Hello, world!"; char resstring[14]; /* Попытаемся создать pipe */ if(pipe(fd) < 0){
/* Если создать pipe не удалось, печатаем об
этом сообщение и прекращаем работу */
printf("Can\1t create pipe\n");
exit(-1);
}
/* Пробуем записать в pipe 14 байт из нашего массива, т.е. всю строку "Hello, world!" вместе с признаком конца строки */ size = write(fd[l], string, 14); if(size != 14 ) {
/* Если записалось меньшее количество байт,
сообщаем об ошибке */
printf("Can\'t write all string\n");
exit(-1);
}
/* Пробуем прочитать из pip'a 14 байт в другой массив, т.е. всю записанную строку */ size = read(fd[0], resstring, 14); if(size < 0){
/* Если прочитать не смогли, сообщаем об ошибке */
printf("Can\'t read string\n");
exit(-1) ;
}
/* Печатаем прочитанную строку */
print f("%s\n",resstring);
/* Закрываем входной поток*/
if(close(fd[0]) < 0){
printf("Can\'t close input stream\n");
/* Закрываем выходной поток*/ if (close(fd[l]) < 0){
princf("СапЛ 'с close output streamXn");
}
return 0;
}
Наберите программу, откомпилируйте ее и запустите на исполнение.
Организация связи через pipe между процессом-родителем и процессом-потомком. Наследование файловых дескрипторов при вызовах fork() и ехес()
Понятно, что если бы все достоинство pip'oB сводилось к замене функции копирования из памяти в память внутри одного процесса на пересылку информации через операционную систему, то овчинка не стоила бы выделки. Однако таблица открытых файлов наследуется процессом-ребенком при порождении нового процесса системным вызовом fork () и входит в состав неизменяемой части системного контекста процесса при системном вызове exec () (за исключением тех потоков данных, для файловых дескрипторов которых был специальными средствами выставлен признак, побуждающий операционную систему закрыть их при выполнении exec (), однако их рассмотрение выходит за рамки нашего курса). Это обстоятельство позволяет организовать передачу информации через pipe между родственными процессами, имеющими общего прародителя, создавшего pipe.
Прогон программы для организации однонаправленной связи между родственными процессами через pipe
Давайте рассмотрим программу, осуществляющую однонаправленную связь между процессом-родителем и процессом-ребенком:
/* Программа 05-3.с, осуществляющая однонаправленную связь через pipe между процессом-родителем и процессом-ребенком */ #include <sys/types.h> #include <unistd.h> #include <stdio.h>
int main{){
int fd[2], result;
size_t size;
char resstring[14];
/* Попытаемся создать pipe */
if(pipe(fd) < 0){
/* Если создать pipe не удалось, печатаем об
этом сообщение и прекращаем работу */
printf{"Can\'t create pipe\n");
exit(-1);
}
/* Порождаем новый процесс */ result = fork(); if(result){
/* Если создать процесс не удалось, сообщаем об этом и завершаем работу */ printf("Can\1t fork child\n"); exit(-1); } else if (result > 0) {
/* Мы находимся в родительском процессе, который будет передавать информацию процессу-ребенку. В этом процессе выходной поток данных нам не понадобится, поэтому закрываем его.*/ close(fd[0]);
/* Пробуем записать в pipe 14 байт, т.е. всю строку "Hello, world!" вместе с признаком конца строки */
size = write(fd[l], "Hello, world!", 14);
if(size != 14) {
/* Если записалось меньшее количество байт, сообщаем об ошибке и завершаем работу */ printf("Can\'t write all string\n"); exit(-1);
}
/* Закрываем входной поток данных, на этом родитель прекращает работу */ close(fd[l]); printf("Parent exit\n"); } else {
/* Мы находимся в порожденном процессе, который будет получать информацию от процесса-родителя. Он унаследовал от родителя таблицу открытых файлов
и, зная файловые дескрипторы, соответствующие pip'у, может его использовать. В этом процессе входной поток данных нам не понадобится, поэтому закрываем его.*/ close(fd[l]) ;
/* Пробуем прочитать из pip'а 14 байт в массив,
т.е. всю записанную строку */
size = read(fd[0], resstring, 14);
iffsize < 0){
/* Если прочитать не смогли, сообщаем об ошибке и завершаем работу */
printf("Can\'t read string\n"); exi t(-1);
}
/* Печатаем прочитанную строку */ printf("%s\n",resstring);
/* Закрываем входной поток и завершаем работу */ close(fd[0] ) ;
}
return 0;
}
Наберите программу, откомпилируйте ее и запустите на исполнение.
Задача повышенной сложности: модифицируйте этот пример для связи между собой двух родственных процессов, исполняющих разные программы.
Написание, компиляция и запуск программы для организации двунаправленной связи между родственными процессами через pipe
Pipe служит для организации однонаправленной или симплексной связи. Если бы в предыдущем примере мы попытались организовать через pipe двустороннюю связь, когда процесс-родитель пишет информацию в pipe, предполагая, что ее получит процесс-ребенок, а затем читает информацию из pipe, предполагая, что ее записал порожденный процесс, то могла бы возникнуть ситуация, в которой процесс-родитель прочитал бы собственную информацию, а процесс-ребенок не получил бы ничего. Для использования одного pipe в двух направлениях необходимы специальные средства синхронизации процессов, о которых речь идет в лекциях «Алгоритмы синхронизации» (лекция 5) и «Механизмы синхронизации» (лекция 6). Более простой способ организации двунаправленной связи между родственными процессами заключается в использовании двух pipe. Модифицируйте программу из предыдущего примера (раздел «Прогон программы для организации однонаправленной связи между родственными процессами через pipe») для организации такой двусторонней связи, откомпилируйте ее и запустите на исполнение.
Необходимо отметить, что в некоторых UNIX-подобных системах (например, в Solaris 2) реализованы полностью дуплексные pip'bi [Стивене, 2002]. В таких системах для обоих файловых дескрипторов, ассоциированных с pipe, разрешены и операция чтения, и операция записи. Однако такое поведение не характерно для pip'oe и не является переносимым.
Особенности поведения вызовов read() и writeQ для pip'a
Системные вызовы read() и write () имеют определенные особенности поведения при работе с pipe, связанные с его ограниченным размером, задержками в передаче данных и возможностью блокирования обменивающихся информацией процессов. Организация запрета блокирования этих вызовов для pipe выходит за рамки нашего курса.
Будьте внимательны при написании программ, обменивающихся большими объемами информации через pipe. Помните, что за один раз из pipe может прочитаться меньше информации, чем вы запрашивали, и за один раз в pipe может записаться меньше информации, чем вам хотелось бы. Проверяйте значения, возвращаемые вызовами! Одна из особенностей поведения блокирующегося системного вызова read() связана с попыткой чтения из пустого pipe. Если есть процессы, у которых этот pipe открыт для записи, то системный вызов блокируется и ждет появления информации. Если таких процессов нет, он вернет значение 0 без блокировки процесса. Эта особенность приводит к необходимости закрытия файлового дескриптора, ассоциированного с входным концом pipe, в процессе, который будет использовать pipe для чтения (close (fd [ 1 ]) в процессе-ребенке в программе из раздела «Прогон программы для организации однонаправленной связи между родственными процессами через pipe*). Аналогичной особенностью поведения при отсутствии процессов, у которых pipe открыт для чтения, обладает и системный вызов write (), с чем связана необходимость закрытия файлового дескриптора, ассоциированного с выходным концом pipe, в процессе, который будет использовать pipe для записи (close ( f d [ 0 ]) в процессе-родителе в той же программе).
Системные вызовы read и write (продолжение) Особенности поведения при работе с pipe, FIFO и socket Системный вызов read |
||
Ситуация |
Поведение |
|
Попытка прочитать меньше байт, чем есть в наличии в канале связи. |
Читает требуемое количество байт и возвращает значение, соответствующее прочитанному количеству. Прочитанная информация удаляется из канала связи. |
|
В канале связи находится меньше байт, чем затребовано, но не нулевое количество. |
Читает все, что есть в канале связи, и возвращает значение, соответствующее прочитанному количеству. Прочитанная информация удаляется из канала связи. |
|
Попытка читать из канала связи, в котором нет информации. Блокировка вызова разрешена. |
Вызов блокируется до тех пор, пока не появится информация в канале связи и пока существует процесс, который может передать в него информацию. Если информация появилась, то процесс разблокируется, и поведение вызова определяется двумя предыдущими строками таблицы. Если в канал некому передать данные (нет ни одного процесса, у которого этот канал связи открыт для записи), то вызов возвращает значение 0. Если канал связи полностью закрывается для записи во время блокировки читающего процесса, то процесс разблокируется, и системный вызов возвращает значение 0. |
|
Попытка читать из канала связи, в котором нет информации. Блокировка вызова не разрешена. |
Если есть процессы, у которых канал связи открыт для записи, системный вызов возвращает значение -1 и устанавливает переменную еггпо в значение КОЖ Если таких процессов нет, системный вызов возвращает значение 0. |
|
Системный вызов write |
||
Ситуация |
Поведение |
|
Попытка записать в канал связи меньше байт, чем осталось до его заполнения. |
Требуемое количество байт помещается в канал связи, возвращается записанное количество байт. |
|
Попытка записать в канал связи больше байт, чем осталось до его заполнения. Блокировка вызова разрешена. |
Вызов блокируется до тех пор, пока все данные не будут помещены в канал связи. Если размер буфера канала связи меньше, чем передаваемое количество информации, то вызов будет ждать, пока часть информации не будет считана из канала связи. Возвращается записанное количество байт. |
|
Попытка записать в канал связи больше байт, чем осталось до его заполнения, но меньше, чем размер буфера канала связи. Блокировка вызова запрещена. |
Системный вызов возвращает значение -1 и устанавливает переменную егто в значение ЕАСАШ. |
|
В канале связи есть место. Попытка записать в канал связи больше байт, чем осталось до его заполнения, и больше, чем размер буфера канала связи. Блокировка вызова запрещена. |
Записывается столько байт, сколько осталось до заполнения канала. Системный вызов возвращает количество записанных байт. |
|
Попытка записи в канал связи, в котором нет места. Блокировка вызова не разрешена. |
Системный вызов возвращает значение -1 и устанавливает переменную еггпо в значение ЕАСАШ. |
|
Попытка записи в канал связи, из которого некому больше читать, или полное закрытие канала на чтение во время блокировки системного вызова. |
Если вызов был заблокирован, то он разблокируется. Процесс получает сигнал SJ.GPJ.PE. Если этот сигнал обрабатывается пользователем, то системный вызов вернет значение -1 и установит переменную еггпо в значение ЕР1РЕ. |
|
Необходимо отметить дополнительную особенность системного вызова write при работе с рір'ами и FIFO. Запись информации, размер которой не превышает размер буфера, должна осуществляться атомарно - одним подряд лежащим куском. Этим объясняется ряд блокировок и ошибок в предыдущем перечне. |
Задача повышенной сложности: определите размер pipe для вашей операционной системы.
Понятие FIFO. Использование системного вызова mknod() для создания FIFO. Функция mkfifo()
Как мы выяснили, доступ к информации о расположении pip'a в операционной системе и его состоянии может быть осуществлен только через таблицу открытых файлов процесса, создавшего pipe, и через унаследованные от него таблицы открытых файлов процессов-потомков. Поэтому изложенный выше механизм обмена информацией через pipe справедлив лишь для родственных процессов, имеющих общего прародителя, инициировавшего системный вызов pipe (), или для таких процессов и самого прародителя и не может использоваться для потокового общения с другими процессами. В операционной системе UNIX существует возможность использования pip'a для взаимодействия других процессов, но ее реализация достаточно сложна и лежит далеко за пределами наших занятий.
Для организации потокового взаимодействия любых процессов в операционной системе UNIX применяется средство связи, получившее название FIFO (от First Input First Output) или именованный pipe. FIFO во всем подобен pip'y, за одним исключением: данные о расположении FIFO в адресном пространстве ядра и его состоянии процессы могут получать не через родственные связи, а через файловую систему. Для этого при создании именованного pip'a на диске заводится файл специального типа, обращаясь к которому процессы могут получить интересующую их информацию. Для создания FIFO используется системный вызов mknod () или существующая в некоторых версиях UNIX функция mkfifo(). Следует отметить, что при их работе не происходит действительного выделения области адресного пространства операционной системы под именованный pipe, а только заводится файл-метка, существование которой позволяет осуществить реальную организацию FIFO в памяти при его открытии с помощью уже известного нам ситемного вызова open (). После открытия именованный pipe ведет себя точно так же, как и неименованный. Для дальнейшей работы с ним применяются системные вызовы read(), write() и closeO. Время существования FIFO в адресном пространстве ядра операционной системы, как и в случае с pip'oM, не может превышать время жизни последнего из использовавших его процессов. Когда все процессы, работающие с FIFO, закрывают все файловые дескрипторы, ассоциированные с ним, система освобождает ресурсы, выделенные под FIFO. Вся непрочитанная информация теряется. В то же время файл-метка остается на диске и может использоваться для новой реальной организации FIFO в дальнейшем.
Использование системного вызова mknod для создания FIFO Прототип системного вызова
#include <sys/stat.h> tinclude <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(cnar *path, int mode);
Описание функции
Функция mkfifo предназначена для создания FIFO в операционной системе.
Параметр path является указателем на строку, содержащую полное или относительное имя файла, который будет являться меткой FIFO на диске. Для успешного создания FIFO файла с таким именем перед вызовом функции не должно существовать.
Параметр mode устанавливает атрибуты прав доступа различных категорий пользователей к FIFO. Этот параметр задается как некоторая сумма адедующих восьмеричных значений:
0400 - разрешено чтение для пользователя, создавшего FIFO;
0200 - разрешена запись для пользователя, создавшего FIFO:
0040 - разрешено чтение для группы пользователя, создавшего RFO;
0020 - разрешена запись для группы пользователя, создавшего RFO;
0004 - разрешено чтение для всех остальных пользователей;
0002 - разрешена запись для всех остальных пользователей.
При создании RFO реально устанавливаемые права доступа получаются из стандартной комбинации параметра mode и маски создания файлов текущего процесса umask, а именно -они равны (0777 & mode) Ь ~umask.
Возвращаемые значения
При успешном создании RFO функция возвращает значение 0, при неуспешном - отрицательное значение.
Важно понимать, что файл типа FIFO не служит для размещения на диске информации, которая записывается в именованный pipe. Эта информация располагается внутри адресного пространства операционной системы, а файл является только меткой, создающей предпосылки для ее размещения.
Не пытайтесь просмотреть содержимое этого файла с помощью Midnight Commander (mc)!!! Это приведет к его глубокому зависанию!
Особенности поведения вызова ореп() при открытии FIFO
Системные вызовы read () и write () при работе с FIFO имеют те же особенности поведения, что и при работе с pip'oM. Системный вызов open () при открытии FIFO также ведет себя несколько иначе, чем при открытии других типов файлов, что связано с возможностью блокирования выполняющих его процессов. Если FIFO открывается только для чтения, и флаг o_ndelay не задан, то процесс, осуществивший системный вызов, блокируется до тех пор, пока какой-либо другой процесс не откроет FIFO на запись. Если флаг o_ndelay задан, то возвращается значение файлового дескриптора, ассоциированного с FIFO. Если FIFO открывается только для записи, и флаг 0_ndelay не задан, то процесс, осуществивший системный вызов, блокируется до тех пор, пока какой-либо другой процесс не откроет FIFO на чтение. Если флаг o_ndelay задан, то констатируется возникновение ошибки и возвращается значение -1. Задание флага o_ndelay в параметрах системного вызова open () приводит и к тому, что процессу, открывшему FIFO, запрещается блокировка при выполнении последующих операций чтения из этого потока данных и записи в него.
Прогон программы с FIFO в родственных процессах
Для иллюстрации взаимодействия процессов через FIFO рассмотрим такую программу:
/* Программа 05-4.с, осуществляющая однонаправленную
связь через 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.fifо";
/* Обнуляем маску создания файлов текущего процесса для того, чтобы права доступа у создаваемого 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, 0_WRONLY)) < 0){
/* Если открыть FIFO не удалось, печатаем об этом сообщение и прекращаем работу */ printf("Can\'t open FIFO for writing\n");
exit(-1);
}
/* Пробуем записать в 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, 0_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 stringXn");
exit(-1);
}
/* Печатаем прочитанную строку */ printf("%s\n",resstring);
/* Закрываем входной поток и завершаем работу */ close(fd);
}
return 0;
Наберите программу, откомпилируйте ее и запустите на исполнение. В этой программе информацией между собой обмениваются процесс-родитель и процесс-ребенок. Обратим внимание, что повторный запуск этой программы приведет к ошибке при попытке создания FIFO, так как файл с заданным именем уже существует. Здесь нужно либо удалять его перед каждым прогоном программы с диска вручную, либо после первого запуска модифицировать исходный текст, исключив из него все, связанное с системным вызовом mknod (). С системным вызовом, предназначенным для удаления файла при работе процесса, мы познакомимся позже (на семинарах 11 -- 12) при изучении файловых систем.
Написание, компиляция и запуск программы с FIFO в неродственных процессах
Для закрепления полученных знаний напишите на базе предыдущего примера две программы, одна из которых пишет информацию в FIFO, а вторая -- читает из него, так чтобы между ними не было ярко выраженных родственных связей (т. е. чтобы ни одна из них не была потомком другой).
Неработающий пример для связи процессов на различных компьютерах
Если у вас есть возможность, найдите два компьютера, имеющие разделяемую файловую систему (например, смонтированную с помощью NFS), и запустите на них программы из предыдущего раздела так, чтобы каждая программа работала на своем компьютере, a FIFO создавалось на разделяемой файловой системе. Хотя оба процесса видят один и тот же файл с типом FIFO, взаимодействия между ними не происходит, так как они функционируют в физически разных адресных пространствах и пытаются открыть FIFO внутри различных операционных систем.
Семинары 6-7. Средства System V IPC. Организация работы с разделяемой памятью в UNIX. Понятие нитей исполнения (thread)
Преимущества и недостатки потокового обмена данными. Понятие System V IPC. Пространство имен. Адресация в System V IPC. Функция f tok (). Дескрипторы System V IPC. Разделяемая память в UNIX. Системные вызовы shmget (), shmat (), shmdt (). Команды ipc и ipcrm. Использование системного вызова shmctl() для освобождения ресурса. Разделяемая память и системные вызовы fork (), exec () и функция exit (). Понятие о нити исполнения (thread) в UNIX. Идентификатор нити исполнения. Функция pthread_self (). Создание и завершение thread'a. Функции pthread_create (), pthread_exit (), pthread_join(). Необходимость синхронизации процессов и нитей исполнения, использующих общую память.
Ключевые слова: System V IPC, пространство имен средств связи, ключ System V IPC, дескриптор (идентификатор) System V IPC, разделяемая память (shared memory), функция f tok, системные вызовы
shmget, shmat, shmdt, shmctl, флаги IPC_CREAT и IPC_EXCL, ключ IPC_PRIVATE, команды ipcrm и ipcs, нить исполнения (thread), идентификатор нити исполнения (TID), библиотека pthread, функции pthread_self, pthread_create, pthread_exit, pthread_join.
Преимущества и недостатки потокового обмена данными
На предыдущем семинаре мы познакомились с механизмами, обеспечивающими потоковую передачу данных между процессами в операционной системе UNIX, а именно с pip'aMH и FIFO. Потоковые механизмы достаточно просты в реализации и удобны для использования, но имеют ряд существенных недостатков:
* Операции чтения и записи не анализируют содержимое передаваемых данных. Процесс, прочитавший 20 байт из потока, не может сказать, были ли они записаны одним процессом или несколькими, записывались ли они за один раз или было, например, выполнено 4 операции записи по 5 байт. Данные в потоке никак не интерпретируются системой. Если требуется какая-либо интерпретация данных, то передающий и принимающий процессы должны заранее согласовать свои действия и уметь осуществлять ее самостоятельно.
Для передачи информации от одного процесса к другому требуется, как минимум, две операции копирования данных: первый раз -- из адресного пространства передающего процесса в системный буфер, второй раз -- из системного буфера в адресное пространство принимающего процесса.
Процессы, обменивающиеся информацией, должны одновременно существовать в вычислительной системе. Нельзя записать информацию в поток с помощью одного процесса, завершить его, а затем, через некоторое время, запустить другой процесс и прочитать записанную информацию.
Понятие о System V IPC
Указанные выше недостатки потоков данных привели к разработке других механизмов передачи информации между процессами. Часть этих механизмов, впервые появившихся в UNIX System V и впоследствии перекочевавших оттуда практически во все современные версии операционной системы UNIX, получила общее название System V IPC (IPC -- сокращение от InterProcess Communications). В группу System V IPC входят: очереди сообщений, разделяемая память и семафоры. Эти средства организации взаимодействия процессов связаны не только общностью происхождения, но и обладают схожим интерфейсом для выполнения подобных операций, например, для выделения и освобождения соответствующего ресурса в системе. Мы будем рассматривать их в порядке от менее семантически нагруженных с точки зрения операционной системы к более семантически нагруженным. Иными словами, чем позже мы начнем заниматься каким-либо механизмом из System V IPC, тем больше действий по интерпретации передаваемой информации придется выполнять операционной системе при использовании этого механизма. Часть этого семинара мы посвятим изучению разделяемой памяти. Семафоры будут рассматриваться на семинаре 8, а очереди сообщений -- на семинаре 9.
Пространство имен. Адресация в System V IPC. Функция ftok()
Все средства связи из System V IPC, как и уже рассмотренные нами pipe и FIFO, являются средствами связи с непрямой адресацией. Как мы установили на предыдущем семинаре, для организации взаимодействия неродственных процессов с помощью средства связи с непрямой адресацией необходимо, чтобы это средство связи имело имя. Отсутствие имен у pip'oB позволяет процессам получать информацию о расположении pip'a в системе и его состоянии только через родственные связи. Наличие ассоциированного имени у FIFO -- имени специализированного файла в файловой системе -- позволяет неродственным процессам получать эту информацию через интерфейс файловой системы.
Множество всех возможных имен для объектов какого-либо вида принято называть пространством имен соответствующего вида объектов. Для FIFO, пространством имен является множество всех допустимых имен файлов в файловой системе. Для всех объектов из System V IPC таким пространством имен является множество значений некоторого целочисленного типа данных -- key_t - ключа. Причем программисту не позволено напрямую присваивать значение ключа, это значение задается опосредованно: через комбинацию имени какого-либо файла, уже существующего в файловой системе, и небольшого целого числа -- например, номера экземпляра средства связи.
Такой хитрый способ получения значения ключа связан с двумя соображениями:
Если разрешить программистам самим присваивать значение ключа для идентификации средств связи, то не исключено, что два программиста случайно воспользуются одним и тем же значением, не подозревая об этом. Тогда их процессы будут несанкционированно взаимодействовать через одно и то же средство коммуникации, что может привести к нестандартному поведению этих процессов. Поэтому основным компонентом значения ключа является преобразованное в числовое значение полное имя некоторого файла, доступ к которому на чтение разрешен процессу. Каждый программист имеет возможность использовать для этой цели свой специфический файл, например исполняемый файл, связанный с одним из взаимодействующих процессов. Следует отметить, что преобразование из текстового имени файла в число основывается на расположении указанного файла на жестком диске или ином физическом носителе. Поэтому для образования ключа следует применять файлы, не меняющие своего положения в течение времени организации взаимодействия процессов.
Второй компонент значения ключа используется для того, чтобы позволить программисту связать с одним и тем же именем файла более одного экземпляра каждого средства связи. В качестве такого компонента можно задавать порядковый номер соответствующего экземпляра.
Получение значения ключа из двух компонентов осуществляется функцией ftok().
Функция для генерации ключа System V IPC
Прототип функции
iinclude <sys/types.h>
¦include <sys/ipc.h>
key_t ftok(char *pathname, char proj);
Описание функции
Функция f tok служит для преобразования имени существующего файла и небольшого целого числа, например, порядкового номера экземпляра средств связи, в ключ System V IPC.
Параметр pathname должен являться указателем на имя существующего файла, доступного для процесса, вызывающего функцию.
Параметр proj - это небольшое целое число, характеризующее экземпляр средства связи.
В случае невозможности генерации ключа функция возвращает отрицательное значение, в противном случае она возвращает значение сгенерированного ключа. Тип данных key_t обычно представляет собой 32-битовое целое.
Еще раз подчеркнем три важных момента, связанных с использованием имени файла для получения ключа. Во-первых, необходимо указывать имя файла, который уже существует в файловой системе и для которого процесс имеет право доступа на чтение (не путайте с заданием имени файла при создании FIFO, где указывалось имя для вновь создаваемого специального файла). Во-вторых, указанный файл должен сохранять свое положение на диске до тех пор, пока все процессы, участвующие во взаимодействии, не получат ключ System V IPC. В-третьих, задание имени файла, как одного из компонентов для получения ключа, ни в коем случае не означает, что информация, передаваемая с помощью ассоциированного средства связи, будет располагаться в этом файле. Информация будет храниться внутри адресного пространства операционной системы, а заданное имя файла лишь позволяет различным процессам сгенерировать идентичные ключи.
Дескрипторы System V IPC
Мы говорили (см. семинар 5, раздел «Файловый дескриптор» ), что информацию о потоках ввода-вывода, с которыми имеет дело текущий процесс, в частности о pip'ax и FIFO, операционная система хранит в таблице открытых файлов процесса. Системные вызовы, осуществляющие операции над потоком, используют в качестве параметра индекс элемента таблицы открытых файлов, соответствующий потоку, -- файловый дескриптор. Использование файловых дескрипторов для идентификации потоков внутри процесса позволяет применять к ним уже существующий интерфейс для работы с файлами, но в то же время приводит к автоматическому закрытию потоков при завершении процесса. Этим, в частности, объясняется один из перечисленных выше недостатков потоковой передачи информации.
При реализации компонентов System V IPC была принята другая концепция. Ядро операционной системы хранит информацию обо всех средствах System V IPC, используемых в системе, вне контекста пользовательских процессов. При создании нового средства связи или получении доступа к уже существующему процесс получает неотрицательное целое число -- дескриптор (идентификатор) этого средства связи, который однозначно идентифицирует его во всей вычислительной системе. Этот дескриптор должен передаваться в качестве параметра всем системным вызовам, осуществляющим дальнейшие операции над соответствующим средством System V I PC.
Подобная концепция позволяет устранить один из самых существенных недостатков, присущих потоковым средствам связи -- требование одновременного существования взаимодействующих процессов, но в то же время требует повышенной осторожности для того, чтобы процесс, получающий информацию, не принял взамен новых старые данные, случайно оставленные в механизме коммуникации.
Разделяемая память в UNIX. Системные вызовы shmget(), shmat(), shmdt()
С точки зрения операционной системы, наименее семантически нагруженным средством System V IPC является разделяемая память (shared memory). Мы уже упоминали об этой категории средств связи на лекции. Для текущего семинара нам достаточно знать, что операционная система может позволить нескольким процессам совместно использовать некоторую область адресного пространства. Внутренние механизмы, позволяющие реализовать такое использование, будут подробно рассмотрены на лекции, посвященной сегментной, страничной и сегментно-страничной организации памяти.
Все средства связи System V IPC требуют предварительных инициализирующих действий (создания) для организации взаимодействия процессов.
Для создания области разделяемой памяти с определенным ключом или доступа по ключу к уже существующей области применяется системный вызов shmget (). Существует два варианта его использования для создания новой области разделяемой памяти:
Стандартный способ. В качестве значения ключа системному вызову поставляется значение, сформированное функцией ftok() для некоторого имени файла и номера экземпляра области разделяемой памяти. В качестве флагов поставляется комбинация прав доступа к создаваемому сегменту и флага ipc_CREAT. Если сегмент для данного ключа еще не существует, то система будет пытаться создать его с указанными правами доступа. Если же вдруг он уже существовал, то мы просто получим его дескриптор. Возможно добавление к этой комбинации флагов флага ipc_excl. Этот флаг гарантирует нормальное завершение системного вызова только в том случае, если сегмент действительно был создан (т. е. ранее он не существовал), если же сегмент существовал, то системный вызов завершится с ошибкой, и значение системной переменной errno, описанной в файле errno. h, будет установлено в eexist.
Нестандартный способ. В качестве значения ключа указывается специальное значение ipc_PRIVATE. Использование значения ipc_PRIVATE всегда приводит к попытке создания нового сегмента разделяемой памяти с заданными правами доступа и с ключом, который не совпадает со значением ключа ни одного из уже существующих сегментов и который не может быть получен с помощью функции ftok() ни при одной комбинации ее параметров. Наличие флагов ipc_CREAT и IPC_EXCL в этом случае игнорируется.
Системный вызов shmgetO Прототип системного вызова
¦include <sys/types.h> ¦include <sys/ipc.h> ¦include <sys/shm.h>
int shmget(key_t key, int size, int shmflg) ; Описание системного вызова
Системный вызов shmget предназначен для выполнения операции доступа к сегменту разделяемой памяти и, в случае его успешного завершения, возвращает дескриптор System V IPC для этого сегмента (целое неотрицательное число, однозначно характеризующее сегмент внутри вычислительной системы и использующееся в дальнейшем для других операций с ним).
Параметр key является ключом System V IPC для сегмента, т. е. фактически его именем из пространства имен System V IPC. В качестве значения этого параметра может использоваться значение ключа, полученное с помощью функции ftok(), или специальное значение IPC_PRIVATE. Использование значения IPC_PRIVATE всегда приводит к попытке создания нового сегмента разделяемой памяти с ключом, который не совпадает со значением ключа ни одного из уже существующих сегментов и который не может быть получен с помощью функции f tok (у ни при одной комбинации ее параметров.
Параметр size определяет размер создаваемого или уже существующего сегмента в байтах. Если сегмент с указанным ключом уже существует, но его размер не совпадает с указанным в параметре size, констатируется возникновение ошибки.
Параметр shmflg - флаги - играет роль только при создании нового сегмента разделяемой памяти и определяет права различных пользователей при доступе к сегменту, а также необходимость создания нового сегмента и поведение системного вызова при попытке создания. Он является некоторой коабинаш^ (с гюмош^ допределенных значений и восмівричньк прав доступа:
IPC_CREAT - если сегмента для указанного ключа не существует, он должен быть создан;
IPC_EXCL - применяется совместно с флагом IPC_CREAT. При совместном их использовании и существовании сегмента с указанным ключом, доступ к сегменту не производится и кс+ктатируется ошибочная ситуация, при этом переменная ermo, описанная в файле <ermo. h>, примет значение EEXIST;
0400 - разрешено чтение для пользователя, создавшего сегмент;
0200 - разрешена запись для пользователя, созывавшего сегмент;
0040 - разрешено чтение для группы гюльзователя, оседавшего сегмент;
0020 - разрешена запись для группы пользователя, создавшего сегмент;
0004 - разрешено чтение для всех остальных пользователей;
0002 - разрешена запись для всех остальных пользователей.
Возвращаемое значение
Системный вызов возвращает значение дяскриотора System VIK для сегмента р мой памяти при нормальном завершении и значение -1 при возникновении ошибки.
Доступ к созданной области разделяемой памяти в дальнейшем обеспечивается ее дескриптором, который вернет системный вызов shmget (). Доступ к уже существующей области также может осуществляться двумя способами:
Если мы знаем ее ключ, то, используя вызов shmget (), можем получить ее дескриптор. В этом случае нельзя указывать в качестве составной части флагов флаг IPC_EXCL, а значение ключа, естественно, не может быть IPC_PRIVATE. Права доступа игнорируются, а размер области должен совпадать с размером, указанным при ее создании.
Либо мы можем воспользоваться тем, что дескриптор System V IPC действителен в рамках всей операционной системы, и передать его значение от процесса, создавшего разделяемую память, текущему процессу. Отметим, что при создании разделяемой памяти с помощью значения IPC_PRIVATE -- это единственно возможный способ.
После получения дескриптора необходимо включить область разделяемой памяти в адресное пространство текущего процесса. Это осуществляется с помощью системного вызова shmat (). При нормальном завершении он вернет адрес разделяемой памяти в адресном пространстве текущего процесса. Дальнейший доступ к этой памяти осуществляется с помощью обычных средств языка программирования.
Системный вызов shmat<) Прототип системного вызова
¦include <sys/types.h> ¦include <sys/ipc.h> ¦include <sys/shm.h>
char *shmat(int shmid, char *shmaddr( int shmflg) ; Описание системного вызова
Системный вызов shmat предназначен для включения области разделяемой памяти в адресное поостранстю текущего
темного вызова, а ограничивается рамками текущего курса. Для полного описания ос^мщайтесь (Ш Manual.
Параметр shmid является дескриптором System V IPC для сегмента разделяемой памяти, т. е. значением, которое вернул системный вызов shmget () при создании сегмента или при его поиске по ключу.
В качестве параметра shmaddr в рамках нашего курса мы всегда будем передавать значение NULL, позволяя операционной системе самой разместить разделяемую память в адресном пространстве нашего процесса.
Параметр shmflg в нашем курсе может принимать только два значения: 0 - для осуществления операций чтения и записи над сегментом и SHMJffiONLY - если мы хотим только читать из него. При этом процесс должен иметь соответствующие права доступа к сегменту.
Воавращаомое знаменно
Системный вызов возвращает адрес сегмента разделяемой памяти в адресном пространстве процесса при нормальном завершении и значение -1 при возникновении ошибки.
После окончания использования разделяемой памяти процесс может уменьшить размер своего адресного пространства, исключив из него эту область с помощью системного вызова Бпт6± (). Отметим, что в качестве параметра системный вызов бптпс^ () требует адрес начала области разделяемой памяти в адресном пространстве процесса, т. е. значение, которое вернул системный вызов бгш^ (), поэтому данное значение следует сохранять на протяжении всего времени использования разделяемой памяти.
Системный вызов shmdt()
Прототип системного вызова
Sinclude <sys/types.h> Sinclude <sys/ipc.h> ¦include <sys/shm.h> int shmdtfchar *shneddr);
Описание системного вызова
Системный вызов эптй; предназначен для исключения области разделяемой памяти из адресного пространства текущего процесса.
Параметр зптаббг является адресом сегмента разделяемой памяти, т. е. значением, которое вернул системный вызов вптаЬ ().
Возвращаемое значение
Системный вызов возвращает значение 0 при нормальном завершении и значение -1 при возникновении ошибки.
Прогон программ с использованием разделяемой памяти
Для иллюстрации использования разделяемой памяти давайте рассмотрим две взаимодействующие программы:
/* Программа 1 (06-1а.с) для иллюстрации работы с разделяемой памятью */
/* Мы организуем разделяемую память для массива из трех целых чисел. Первый элемент массива является счетчиком числа запусков программы 1, т. е. данной программы, второй элемент массива - счетчиком числа запусков программы 2, третий элемент массива -счетчиком числа запусков обеих программ */ #include <sys/types.h> #include <sys/ipc.h> #include <sys/shm.h> #include <stdio.h> #include <errno.h> int main() {
int *array; /* Указатель на разделяемую память */
int shmid; /* IPC дескриптор для области
разделяемой памяти */ int new = 1; /* Флаг необходимости инициализации
элементов массива */ char pathname[] = "06-la.c"; /* Имя файла,
используемое для генерации ключа. Файл с таким
именем должен существовать в текущей директории */ key_t key; /* IPC ключ */
/*Генерируем IPC ключ из имени файла 06-1а.с в
текущей директории и номера экземпляра области
разделяемой памяти 0 */ if((key = ftok(pathname, 0) ) < 0){
printf("Can\'t generate key\n");
exit(-1);
}
/* Пытаемся эксклюзивно создать разделяемую память
для сгенерированного ключа, т.е. если для этого ключа она уже существует, системный вызов вернет отрицательное значение. Размер памяти определяем как размер массива из трех целых переменных, права доступа 0666 - чтение и запись разрешены для всех */ if((shmid = shmget(key, 3 * sizeof(int) , 066 6iIPC_CREAT|IPC_EXCL)) < 0){
Подобные документы
Основные понятия об операционных системах. Виды современных операционных систем. История развития операционных систем семейства Windows. Характеристики операционных систем семейства Windows. Новые функциональные возможности операционной системы Windows 7.
курсовая работа [60,1 K], добавлен 18.02.2012Понятие виртуальной памяти, ее реализация. Особенности страничной организации по требованию. Этапы обработки ситуации отсутствия страницы в памяти. Стратегии (алгоритмы) замещения страниц. Особенности некоторых операционных систем: Windows NT и Solaris.
презентация [2,2 M], добавлен 24.01.2014Изучение особенностей операционной системы, набора программ, контролирующих работу прикладных программ и системных приложений. Описания архитектуры и программного обеспечения современных операционных систем. Достоинства языка программирования Ассемблер.
презентация [1,3 M], добавлен 22.04.2014Общая характеристика преимуществ взаимодействующих процессов: модульность, ускорение вычислений. Знакомство с основами современных операционных систем. Анализ особенностей использования общего почтового ящика, рассмотрение способов создания и удаления.
презентация [1,6 M], добавлен 24.01.2014Характеристика сущности, назначения, функций операционных систем. Отличительные черты их эволюции. Особенности алгоритмов управления ресурсами. Современные концепции и технологии проектирования операционных систем, требования, предъявляемые к ОС XXI века.
курсовая работа [36,4 K], добавлен 08.01.2011Операционная система - программа, которая загружается при включении компьютера. Способы реализации интерфейса и классификация операционных систем. Организация файловой системы, типы файлов и их наименования. Понятие каталога, атрибуты файловой системы.
реферат [16,6 K], добавлен 25.02.2011Важность операционной системы для мобильных устройств. Популярность операционных систем. Доля LINUX на рынке операционных систем. История OS Symbian, BlackBerry OS, Palm OS. Отличия смартфона от обычного мобильного телефона. Учет ограничений по памяти.
презентация [477,3 K], добавлен 01.12.2015Основные понятия операционных систем. Современное оборудование компьютера. Преимущества и недостатки операционной системы Linux. Функциональные возможности операционной системы Knoppix. Сравнительная характеристика операционных систем Linux и Knoppix.
реферат [1,5 M], добавлен 17.12.2014Основные понятия операционных систем. Синхронизация и критические области. Сигналы и взаимодействие между процессами. Управление памятью. Драйверы устройств. Особенности современных операционных систем. Центральный процессор, микросхемы часов и таймеров.
учебное пособие [1,2 M], добавлен 24.01.2014Использование операционных систем Microsoft Windows. Разработка операционной системы Windows 1.0. Возможности и характеристика последующих версий. Выпуск пользовательских операционных систем компании, доработки и нововведения, версии Windows XP и Vista.
реферат [23,3 K], добавлен 10.01.2012