Порядок выполнения работы. 1.Изучить теоретическую часть лабораторной работы

1.Изучить теоретическую часть лабораторной работы

2.Написать программу, создающую дочерний процесс. Родительский процесс создаёт семафор (сем1) и общий файл. Дочерний процесс записывает в файл по одной строке всего 100 строк вида: номер_строкиpid_процессатекущее_время (мсек). Родительский процесс читает из файла строки и выводит их на экран в следующем виде: pid строка_прочитанная_из_файла. Семафор сем1 используется процессами для разрешения, кому из процессов получить доступ к файлу.

Варианты индивидуальных заданий

1. Найти эффективный размер кластера на диске, подсчитав в % отношении общий объем занимаемый файлами в заданном каталоге и ниже к реальному размеру этих файлов. Для каждого подкаталога запускается отдельный процесс подсчета. Количество процессов в любой момент времени должно быть равно N. Данные передаются с использованием общего файла. Использовать семафор для доступа к общему файлу.

2. Написать программу копирования больших ( > 2 Гб ) файлов несколькими процессами одновременно. Пользователь задает N- количество одновременно работающих процессов и имена входного и выходного файлов. Для копирования входной файл разбивается на N частей и каждая часть копируется в отдельном процессе. Для решения задачи взаимного исключения при одновремнной записи в выходной файл использовать семафор. Количество процессов в любой момент времени должно быть равно N.

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

4. Тоже что и в п.1 не вместо процессов использовать потоки.

5. Тоже что и в п.2 не вместо процессов использовать потоки.

6. Тоже что и в п.3 не вместо процессов использовать потоки

Лабораторная работа №7 ИСПОЛЬЗОВАНИЕ КАНАЛОВ В ОС LINUX

Цель работы - изучение механизма взаимодействия процессов с использованием каналов.

Теоретическая часть

Каналы являются одной из самых сильных и характерных особенностей ОС Linux, доступных даже с уровня командного интерпретатора. Каналы. Программный канал – это файл особого типа (FIFO: «первым вошел – первым вышел»). Процессы могут записывать и считывать данные из канала как из обычного файла. Если канал заполнен, процесс записи в канал останавливается до тех пор, пока не появится свободное место, чтобы снова заполнить его данными. С другой стороны, если канал пуст, то читающий процесс останавливается до тех пор, пока пишущий процесс не запишет данные в этот канал. В отличие от обычного файла здесь нет возможности позиционирования по файлу с использованием указателя.

В ОС Linux различают два вида программных каналов:

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

int mkfifo(const char *filename, mode_t mode);

· Неименованный программный канал. Неименованным программным каналом могут пользоваться только создавший его процесс и его потомки. Для создания используется вызов:

int pipe(int fd[2]);

Переменная fd является массивом из двух целых чисел, который будет содержать дескрипторы файлов, обозначающие канал. После успешного вызова fd [0] будет открыт для чтения из канала, а fd [1] – для записи в канал. В случае неудачи вызов pipe вернет значение -1. Это может произойти, если в момент вызова произойдет превышение максимально возможного числа дескрипторов файлов, которые могут быть одновременно открыты процессами пользователя (в этом случае переменная errno будет содержать значение EMFILE), или если произойдет переполнение таблицы открытых файлов в ядре (в этом случае переменная errno будет содержать значение ENFILE).

После создания канала с ним можно работать просто при помощи вызовов read и write. Следующий пример демонстрирует это: он создает канал, записывает в него три сообщения, а затем считывает их из канала:

#include <unistd.h>

#include <stdio.h>

char *msg1 = “hello, world #1”;

Main ()

{

char inbuf [15];

int p [2], j;

if (pipe (p) == -1) {

perror (“Ошибка вызова pipe”);

exit (1);

}

write (p[1], msg1, 15); /*Запись в канал*/

read (p[0], inbuf, MSGSIZE); /*Чтение из канала*/

printf (“%s\n”, inbuf);

exit (0);

}

Размеры блоков при записи в канал и чтении из него необязательно должны быть одинаковыми, хотя в нашем примере это и было так. Можно, например, писать в канал блоками по 512 байт, а затем считывать из него по 1 символу, так же как и в случае обычного файла. Тем не менее, использование блоков фиксированного размера дает определенные преимущества.

На рис. 3 показано, как канал соединяет два процесса. Здесь видно, что и в родительском, и в дочернем процессах открыто по два дескриптора файла, позволяя выполнять запись в канал и чтение из него. Поэтому любой из процессов может выполнять запись в файл с дескриптором p[1] и чтение из файла с дескриптором p[0]. Это создает определенную проблему – каналы предназначены для использования в качестве однонаправленного средства связи. Если оба процесса будут одновременно выполнять чтение из канала и запись в него, то это приведет к путанице.

Порядок выполнения работы. 1.Изучить теоретическую часть лабораторной работы - student2.ru

Рис. 3. Работы с каналами.

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

В конечном итоге получится однонаправленный поток данных от дочернего процесса к родительскому. Эта упрощенная ситуация показана на рис. 5.3.

Порядок выполнения работы. 1.Изучить теоретическую часть лабораторной работы - student2.ru

Рис. 4. Третий пример работы с каналами

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

Возможна ситуация, когда родительский процесс выступает в качестве серверного процесса и может иметь произвольное число связанных с ним клиентских (дочерних) процессов, как показано на рис. 5.

Порядок выполнения работы. 1.Изучить теоретическую часть лабораторной работы - student2.ru

Рис. 5. Клиент/сервер с использованием каналов

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

Это можно сделать при помощи системного вызова select (существует также аналогичный вызов poll). Системный вызов select используется не только для каналов, но и для обычных файлов, терминальных устройств, именованных каналов и сокетов. Системный вызов select показывает, какие дескрипторы файлов из заданных наборов готовы для чтения, записи или ожидают обработки ошибок. Иногда серверный процесс не должен совсем прекращать работу, даже если не происходит никаких событий, поэтому в вызове select также можно задать предельное время ожидания. Описание данного вызова:

#include <sys/time.h>

int select (int nfds, fd_set *readfds, fd_set *writefds, fd_set *errorfds, struct timeval *timeout);

Первый параметр nfds задает число дескрипторов файлов, которые могут представлять интерес для сервера. Программист может определять это значение самостоятельно или воспользоваться постоянной FD_SETSIZE, которая определена в файле <sys/time.h>. Значение постоянной равно максимальному числу дескрипторов файлов, которые могут быть использованы вызовом select.

Второй, третий и четвертый параметры вызова являются указателями на битовые маски, в которых каждый бит соответствует дескриптору файла. Если бит включен, то это обозначает интерес к соответствующему дескриптору файла. Набор readfds определяет дескрипторы, для которых сервер ожидает возможности чтения; набор writefds – дескрипторы, для которых сервер ожидает возможности выполнить запись; набор errorfds – дескрипторы, для которых сервер ожидает появление ошибки или исключительной ситуации. Так как работа с битами довольно неприятна и приводит к немобильности программ, существуют абстрактный тип данных fd_set, а также макросы или функции для работы с объектами этого типа:

#include <sys/time.h>

/*Инициализация битовой маски, на которую указывает fdset*/

void FD_ZERO (fd_set *fdset);

/*Установка бита fd в маске, на которую указывает fdset*/

void FD_SET (int fd, fd_set *fdset);

/*Установлен ли бит fd в маске, на которую указывает fdset?*/

int FD_ISSET (int fd, fd_set *fdset);

/*Сбросить бит fd в маске, на которую указывает fdset*/

void FD_GLR (int fd, fd_set *fdset);

Следующий пример демонстрирует, как отслеживать состояние двух открытых дескрипторов файлов:

#include <sys/time.h>

#include <sys/types.h>

#include <fcntl.h>

...

int fd1, fd2;

fd_set readset;

fd1 = open (“file1”, O_RDONLY);

fd2 = open (“file2”, O_RDONLY);

FD_ZERO (& readset);

FD_SET (fd1, &readset);

FD_SET (fd2, &readset);

switch (select (5, &readset, NULL, NULL, NULL))

{ /*Обработка ввода*/ }

Пятый параметр вызова select является указателем на следующую структуру timeval:

#include <sys/time.h>

struct timeval {

long tv_sec; /*Секунды*/

long tv_usec; /*и микросекунды*/

};

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

Возвращаемое вызовом select значение равно -1 в случае ошибки, нулю – после истечения временного интервала или целому числу, равному числу «интересующих» программу дескрипторов файлов.

Наши рекомендации