Управление ресурсами в ОС UNIX

       

Методические указания к лабораторной работе


Обычно в ОС UNIX доступны несколько интерпретаторов. Наиболее распространены Bourne-shell (или просто - shell), C-shell, Korn-shell. В идейном плане все эти интерпретаторы близки и в дальнейшем речь будет идти о стандартном Shell (/bin/sh).

Работая на командном языке, пользователь может вводить переменные, присваивать им значения, выполнять простые команды, строить составные команды, управлять потоком выполнения команд, объединять последовательность команд в процедуры (командные файлы). На уровне командного языка доступны такие свойства системы как соединение процессов через программный канал, направление стандартного ввода/вывода в конкретные файлы, синхронное и асинхронное выполнение команд.

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

Коротко перечислим средства группирования команд и перенаправления ввода/вывода:

  • cmd1 arg ...; cmd2 arg ...; ... cmdN arg ... - последовательное выполнение команд;
  • cmd1 arg ...& cmd2 arg ...& ... cmdN arg ... - асинхронное выполнение команд;
  • cmd1 arg ... && cmd2 arg ... - зависимость последующей команды от предыдущей таким образом, что последующая команда выполняется, если предыдущая выдала нулевое значение;
  • cmd1 arg ... cmd2 arg ... - зависимость последующей команды от предыдущей таким образом, что последующая команда выполняется, если предыдущая выдала ненулевое значение;
  • cmd > file - стандартный вывод направлен в файл file;
  • cmd >> file - стандартный вывод направлен в конец файла file;
  • cmd < file - стандартный ввод выполняется из файла file;
  • cmd1 | cmd2 - конвейер команд, в котором стандартный вывод команды cmd1 направлен на стандартный вход команды cmd2.

  • Интерфейс между пользовательской программой и внешним устройством (или между двумя пользовательскими программами) в ОС UNIX осуществляется в рамках единой структуры данных, называемой файлом ОС UNIX.

    Всякий файл ОС UNIX в соответствие с его типом может быть отнесен к одной из следующих четырех групп: обычные файлы, каталоги, специальные файлы, каналы.

    Обычный файл представляет собой совокупность блоков диска, входящих в состав файловой системы ОС UNIX. В указанных блоках может быть произвольная информация.



    Каталоги представляют собой файлы особого типа, отличающиеся от обычных прежде всего тем, что осуществить запись в них может только ядро ОС UNIX, в то время как доступ по чтению может получить любой пользовательский процесс, имеющий соответствующие полномочия. Каждый элемент каталога состоит из двух полей: поля имени файла и поля, содержащего указатель на описатель файла, где хранится вся информация о файле: дата создания, размер, код защиты, имя владельца и т.д. В любом каталоге содержится, по крайней мере, два элемента, содержащие в поле имени файла имена "." и "..". Элемент каталога, содержащий в поле имени файла контекст ".", в поле ссылки содержит ссылку на описатель файла, описывающий этот каталог. Элемент каталога, содержащий в поле имени файла контекст "..", в поле ссылки содержит ссылку на описатель файла, в котором хранится информация о родительском каталоге текущего каталога.

    Специальные файлы - это некоторые файлы, каждому из которых ставится в соответствие свое внешнее устройство, поддерживаемое ОС UNIX и имеющее специальную структуру. Его нельзя использовать для хранения данных, как обычный файл или каталог. В то же время над специальным файлом можно производить те же операции, что и над обычным файлом: открывать, вводить и/или выводить информацию и т.д. Результат применения любой из этих операций зависит от того, какому конкретному устройству соответствует обрабатываемый специальный файл, однако в любом случае будет осуществлена соответствующая операция ввода-вывода на внешнее устройство, которому соответствует выбранный специальный файл.

    Четвертый вид файлов - каналы, будет рассмотрен отдельно в последующих лабораторных работах.

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

    В представленной ниже таблице 1 приведены системные функции ОС UNIX для работы с файловой системой.

    Таблица 1




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

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

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

    С таблицей описателей файлов тесно связана другая структура данных, называемая таблицей файлов. Каждый элемент таблицы файлов содержит информацию о режиме открытия файла, специфицированным при открытии файла, а также информацию о положении указателя чтения-записи. При каждом открытии файла в таблице файлов появляется новый элемент.

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




    Для порождения нового процесса (процесс-потомок) используется системный вызов fork(). Формат вызова:




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

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

  • Создание, завершение процесса, получение информации о процессе, - fork(), exit(), getpid(), getppid();
  • Синхронизация процессов - signal(), kill(), sleep(), alarm(), wait(), pause();
  • Создание информационного канала и работа с ним - pipe(), read(), write().



  • При выполнении операции перенаправления ввода-вывода важным моментом является наследование пользовательских дескрипторов, осуществляемое с помощью системных вызовов dup() и fcntl().

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

    Системный вызов fcntl(), имеющий формат




    Механизм IPC (Inter-Process Communication Facilities) включает:

  • средства, обеспечивающие возможность синхронизации процессов при доступе к совместно используемым ресурсам (семафоры - semaphores);
  • средства, обеспечивающие возможность посылки процессом сообщений другому произвольному процессу (очереди сообщений - message queries);
  • средства, обеспечивающие возможность наличия общей для процессов памяти (сегменты разделяемой памяти - shared memory segments).
  • Наиболее общим понятием IPC является ключ, хранимый в общесистемной таблице и обозначающий объект межпроцессного взаимодействия, доступный нескольким процессам. Обозначаемый ключом объект может быть очередью сообщений, набором семафоров или сегментом разделяемой памяти. Ключ имеет тип key_t, состав которого зависит от реализации и определяется в файле <sys/types.h&gt. Ключ используется для создания объекта межпроцессного взаимодействия или получения доступа к существующему объекту. Обе операции выполняются посредством операции get. Результатом операции get является его целочисленный идентификатор, который может использоваться в других функциях межпроцессного взаимодействия.

    I. Семафоры.

    Для работы с семафорами поддерживаются три системных вызова:

  • semget() для создания и получения доступа к набору семафоров;
  • semop() для манипулирования значениями семафоров (это тот системный вызов, который позволяет процессам синхронизоваться на основе использования семафоров
  • semctl() для выполнения разнообразных управляющих операций над набором семафоров
  • Прототипы перечисленных системных вызовов описаны в файлах

    #include <sys/ipc.h>

    #include <sys/sem.h>

    Системный вызов semget() имеет следующий синтаксис:




    Существует две модели взаимодействия между процессами в сети: модель соединений с протоколом TCP (Transmission Control Protocol), и модель дейтаграмм с протоколом UDP (User Datagram Protocol). В данной лабораторной работе используется первая из названных моделей.

    Ниже приводятся основные шаги и необходимые системные вызовы для выполнения основных этапов при работе с сокетами в режиме TCP-соединения.

    1. Адресация и создание сокета

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

  • обобщенный сокет (generic socket), определяется в файле <sys/socket.h>:
  • struct sockaddr {

    u_char sa_family; /* Семейство адресов (домен) */

    char sa_data[]; }; /* Адрес сокета */

  • Сокеты для связи через сеть, определяется в файле <netinet/in.h>:
  • struct sockaddr_in {

    u_char sin_len; /* Длина поля sockaddr_in (для FreeBSD) */

    u_char sin_family; /* Семейство адресов (домен) */

    u_short sin_port; /* Номер порта */

    struct in_addr sin_addr; /* IP-адрес */

    char sin_zero[8]; }; /* Поле выравнивания */

    где struct in_addr {

    n_int32_t s_addr}.

    Создается сокет при помощи системного вызова socket().

    #include <sys/socket.h>

    int socket (int domain, int type, int protocol);

  • Параметр domain - домен связи, в котором будет использоваться сокет (значение AF_INET - для домена Internet (соединение через сеть), AF_UNIX - домен, если процессы находятся на одном и том же компьютере);
  • Параметр type определяет тип создаваемого сокета (значение SOCK_STREAM - для режима соединений, SOCK_DGRAM - для режима дейтаграмм);
  • Параметр protocol определяет используемый протокол (в случае protocol = 0 по умолчанию для сокета типа SOCK_STREAM будет использовать протокол TCP, а сокета типа SOCK_DGRAM - протокол UDP).
  • При программировании TCP-соединения должны быть созданы сокеты (системный вызов socket()) и в программе сервера и в программе клиента, при этом в обеих программах сокеты связываются с адресом машины, на которую будет установлена программа сервера. Но, если в программе сервера для определения IP-адреса в структуре сокета может быть использована переменная INADDR_ANY, то в программе клиента для занесения в структуру сокета IP-адреса машины сервера необходимо использовать системный вызов inet_addr().




    Shell- переменные могут хранить строки текста. Правила формирования их имен аналогичны правилам задания имен переменных в обычных языках программирования. При необходимости присвоить Shell-переменной значение, содержащее пробелы и другие специальные знаки, оно заключается в кавычки. При использовании Shell-переменной в выражении ее имени должен предшествовать знак $. В последовательности символов те из них, которые составляют имя, должны быть выделены в { } или " ". Кроме того интерпретатор Shell автоматически присваивает значения пяти своим переменным:

  • $? - значение, возвращаемое последней выполняемой командой;


  • $$ - идентификационный номер процесса Shell;


  • $! - идентификационный номер фонового процесса, запускаемого интерпретатором Shell последним;


  • $# - число аргументов, переданных в Shell;


  • $- - флаги, переданные в Shell.


  • Для отмены специальных символов ($,|,пробел и т.д.) в Shell-программах существуют следующие правила:

  • если символу предшествует обратная косая черта, то его специальный символ отменяется;


  • отменяется специальный смысл всех символов, вошедших в последовательность, заключенную в апострофы.


  • При вызове Shell-программ им могут передаваться параметры. Соответствующие аргументы в Shell-программах идентифицируются $1, $2, $3 и т.д. Кроме того, переменная $0 соответствует имени выполняемой Shell-программы, а переменная $# - числу аргументов в команде.

    Shell-интерпретатор дает возможность выполнять подстановку результатов выполнения команд в Shell-программах. Если команда заключена в одиночные обратные кавычки, то интерпретатор Shell выполняет эту команду и подставляет вместо нее полученный результат.

    Наиболее важные команды для составления Shell-программ:

  • команда echo выводит в выходной поток значения своих аргументов;


  • команда expr выполняет арифметические действия над своими аргументами;


  • команда eval обеспечивает дополнительный уровень подстановки своих аргументов, а затем их выполнение;


  • команда test с соответствующими ключами проверяет необходимое условие;


  • команда sleep служит для реализации задержки.


  • Программные конструкции Shell-программ:



    Тpетий набоp данных называется таблицей откpытых файлов пpоцесса. Каждому пpоцессу в ОС UNIX сpазу после поpождения ставится в соответствие таблица откpытых файлов пpоцесса. Если, в свою очеpедь, указанный пpоцесс поpождает новый пpоцесс, напpимеp, с помощью системного вызова fork(), то пpоцессу-потомку ставится в соответствие таблица откpытых файлов пpоцесса, котоpая в пеpвый момент функциониpования пpоцесса-потомка пpедставляет собой копию таблицы откpытых файлов пpоцесса-пpедка.

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

    Лабоpатоpная pабота пpедполагает написание пpогpаммы, показыващей действия системы упpавления вводом-выводом пpи выполнении некотоpых действий с файлами. Пpогpамма должна демонстpиpовать динамику фоpмиpования таблиц и их изменений в пpоцессе указанных в ваpианте задания событий.

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

    Полученную информацию из структуры stat, дополненную именем файла и следует в лабораторных работах трактовать в качестве таблицы описателей файлов.

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

    Структура элемента таблицы файлов в программах лабораторной работы (упрощенный вариант) должен иметь вид:



    Сетевые вызовы inet_addr() и inet_ntoa() выполняют преобразования IP- адреса из формата текстовой строки "x.y.z.t" в структуру типа in_addr и обратно.

    #include <arpa/inet.h>

    in_addr_t inet_addr (const char *ip_address);

    char * inet_ntoa(const struct in_addr in);

    Для того чтобы процесс мог ссылаться на адрес своего компьютера, файле <netinet/in.h> определена переменная INADDR_ANY, содержащая локальный адрес компьютера в формате in_addr_t.



    2. Связывание



    Системный вызов bind() связывает сетевой адрес компьютера с идентификатором сокета.

    #include<sys/types.h>

    #include<sys/socket.h>

    int bind (int sockfd, const struct sockaddr *address, size_t add_len);

  • sockfd - дескриптор файла сокета, созданным с помощью вызова socket(),


  • address - указателем на обобщенную структуру адреса сокета, к которой преобразуется структура sockaddr_in, в случае передачи данных через сеть.


  • size_t add_len - размер указанной структуры адреса сокета.


  • В случае успешного завершения вызова bind() он возвращает значение 0. В случае ошибки, например, если сокет для этого адреса уже существует, вызов bind() возвращает значение -1. Переменная errno будет иметь при этом значение EADDRINUSE.

    Oперация связывания выполняется только в программе сервера.

    3. Включение приема TCP-соединений

    После выполнения связывания с адресом и перед тем, как какой-либо клиент сможет подключиться к созданному сокету, сервер должен включить прием соединений посредством системного вызова listen().

    #include<sys/socket.h>

    int listen (int sockfd, int queue_size);

  • sockfd - дескриптор файла сокета, созданным с помощью вызова socket(),


  • queue_size - число запросов на соединение с сервером, которые могут стоять в очереди.


  • Данная операция выполняется только в программе сервера.



    4. Прием запроса на установку TCP-соединения



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



    #include<sys/types.h>

    #include<sys/socket.h>

    int accept (int sockfd, struct sockaddr *address, size_t *add_len);

  • sockfd - дескриптор сокета, для которого ведется прием соединений;


  • address - указатель на обобщенную структуру адреса сокета с информацией о клиенте; так как связь использует соединение адрес клиента знать не обязательно и допустимо задавать параметр address значением NULL;


  • add_len - размер структуры адреса, заданной параметром address, если значение address не равно NULL.


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

    Данная операция выполняется только в программе сервера.

    5. Подключение клиента

    Для выполнения запроса на подключение к серверному процессу клиент использует системный вызов connect().

    #include<sys/types.h>

    #include<sys/socket.h>

    int connect (int csockfd, const struct sockaddr *address, size_t add_len);

  • сsockfd - дескриптор файла сокета клиента, созданным с помощью вызова socket();


  • address - указателем на обобщенную структуру адреса сокета, к которой преобразуется структура sockaddr_in, в случае передачи данных через сеть;


  • size_t add_len - размер указанной структуры адреса сокета.


  • В случае успешного завершения вызова connect() он возвращает значение 0. В случае ошибки, системный вызов connect() возвращает значение -1, а переменная errno идентифицирует ошибку.

    Данная операция выполняется только в программе клиента.

    6. Пересылка данных

    Для сокетов типа SOCK_STREAM дескрипторы сокетов, полученные сервером посредством вызова accept() и клиентом с помощью вызова socked(), могут использоваться для чтения или записи. Для этого могут использоваться обычные вызовы read() и write(), либо специальные системные вызовы send() и recv(), позволяющие задавать дополнительные параметры пересылки данных по сети. Синхронизация данных при работе с сокетом аналогична передаче данных через программный канал.



    #include<sys/types.h>

    #include<sys/socket.h>

    ssize_t recv (int sockfd, void *buffer, size_t length, int flags);

    ssize_t send (int sockfd, const void *buffer, size_t length, int flags);

  • socfd - дескриптор сокета, через который читаются или записываются данные;


  • buffer - буфер, в который они помещаются или откуда отсылаются через сокет;


  • length - размер буфера;


  • flags - поле дополнительных опций при получении или передаче данных.


  • В случае успешного чтения/записи системные вызовы send() и recv() возвращают число прочитанных/отосланных байт, или -1 в случае ошибки; в случае разорванной связи (клиент разорвал TCP-соединение) вызов recv() (или read()) возвращают нулевое значение; если процесс пытается записать данные через разорванное TCP-соединение посредством write() или send(), то он получает сигнал SIGPIPE, который можно обработать, если предусмотрена обработка данного сигнала.

    В случае flags = 0 вызовы send() и recv() полностью аналогичны системным вызовам read() и write().

    Возможные комбинациями констант параметра flags системнного вызова send():

  • MSG_PEEK Процесс может просматривать данные, не "получая" их;


  • MSG_OOB Обычные данные пропускаются. Процесс принимает только срочные данные, например, сигнал прерывания;


  • MSG_WAITALL Возврат из вызова recv произойдет только после получения всех данных.


  • Возможные комбинациями констант параметра flags системного вызова recv():

  • MSG_OOB Передать срочные (out of band) данные;


  • MSG_DONTROUTE При передаче сообщения игнорируются условия маршрутизации протокола более низкого уровня. Обычно это означает, что сообщение посылается по прямому, а не по самому быстрому маршруту (самый быстрый маршрут не обязательно прямой и может зависеть от текущего распределения нагрузки сети).

    Данные операции выполняются и в программе сервера и в программе клиента.

    7. Закрытие TCP-соединения

    Закрываются сокеты так же, как и обычные дескрипторы файлового ввода/вывода, - при помощи системного вызова close(). Для сокета SOCK_STREAM ядро гарантирует, что все записанные в сокет данные будут переданы принимающему процессу.

    Данные операции выполняются и в программе сервера и в программе клиента.


    Содержание раздела