Литературный редактор Н.К. Костыгина
СИСТЕМЫ РЕАЛЬНОГО ВРЕМЕНИ
Методические указания по выполнению лабораторных работ для студентов, обучающихся по специальности 230102
МОСКВА 2011
Составитель Н.В. Зорина, Л.Б. Зорин
Редактор В.М. Панченко
Методические указания содержат задания по лабораторным работам, примеры программ на языке C, а также справочный материал системным вызовам в стандарте POSIX. Методические указания предназначены для студентов, обучающихся по направлению «Автоматизированные системы обработки информации и управления», изучающих курс «Системы реального времени».
Рецензенты: О.В. Соболев
© МИРЭА, 2011
Литературный редактор Н.К. Костыгина
Изд. лицензия №020456 от 04.03.97
Подписано в печать 00.00.0000. Формат 60х84 1/16
Бумага офсетная. Печать офсетная.
Усл. печ.л. 1,86. Усл.кр.-отт. 7,44. Уч.-изд.л. 2,0.
Тираж 100 экз. Заказ 00. Бесплатно
Московский государственный институт радиотехники,
электроники и автоматики (технический университет)
117454, Москва, просп. Вернадского, 78
Лабораторная работа №1
Тема: Взаимодействие со средой выполнения.
Системные вызовы и библиотечные процедуры: общие
сведения. Управление файлами
Цель работы:
Лабораторный практикум выполняется в среде OS SINIX, доступ к которой осуществляется с рабочего места, функционирующего на базе терминала. При выполнении входа через login user для вас запускается своя копия shell и предоставляется виртуальный компьютер. В ходе выполнения первой лабораторной работы Вам предстоит овладеть некоторыми инструментальными средствами, которые будут Вами использоваться во всех последующих работах. Системные вызовы могут быть объединены в следующие три категории
1. управление файлами;
2. управление процессами;
3. обработка ошибок;
Процесс в Unix/Linux представляет собой единицу работы вычислительной системы, которой операционная система выделяет ресурсы. С удовлетворительной степенью приближения можно определить процесс как выполняющуюся программу. Каждый процесс в системе имеет свой уникальный номер - идентификатор процесса (PID), представляемый целым числом.
Каждому процессу в операционной системе соответствует запись в таблице процессов и адресное пространство процесса, состоящее из областей кода, данных и стека. Запись в таблице процессов (и ее расширение в адресном пространстве процесса) содержит управляющую информацию о ресурсах, выделенных процессу, и о состоянии процесса. Создание нового процесса или запуск на выполнение новой программы называют с процессом порождения процессов. Процесс может порождать другой процесс. Порождение нового процесса в Unix/Linux реализовано копированием записи таблицы процессов, таким образом, процесс-потомок в момент своего порождения представляет собой точную копию процесса-предка. Процесс-предок и процесс-потомок далее выполняются параллельно, но процесс-предок может и ожидать завершения процесса- потомка.
Процессы в Unix/Linux выполняются в режиме разделения времени, это означает, что время центрального процессора равномерно (с учетом приоритетов) распределяется между всеми готовыми к выполнению процессами. Даже если процесс не переходит в состояние ожидания (например, ожидания выполнения операции ввода-вывода) по своей инициативе, по истечении выделенного процессу кванта времени выполнение процесса будет прервано операционной системой, и процессор будет отдан более приоритетному процессу. В ОС Unix/Linux используется планирование времени ЦП на основе динамическое вычисления приоритетов, приоритет выполняющегося процесса понижается, а приоритеты ожидающих процессов таким образом повышаются.
Текст программы mydup.c:
$cat mydup.c
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
main()
{int fd1, fd2, fd3; //файловые дескрипторы
fd1=open('text txt", 0_RDWR|0_TRUNC):
printf("fd1=%d\n", fd1);
write(fd1, "what's",6);
fd2=dup(fd1);
printf("fd2=%d\n", fd2);
write(fd2," up",3);
close(O);
fd3=dup(fd1);
printf("fd3=%d/n", fd3);
write(0," doc",4);
dup(2,3) //дублирование файловых дескрипторов
write(2, 7?\n, 2);
}
Выполнение программы mydup.c:
$mydup
fd1=3
fd2=4
fd3=0
$cat text.txt
what's up doc/?
Работа программы mydup.c:
Задаются 3 файловых дескриптора fdl, fd2, fd3. Далее открывается созданный файл text.txt, который при помощи макроса О RDWR можно читан, и писать. Команда write обеспечивает запись в наш файл. Команда dup() обеспечивает дублирование связанных дескрипторов. Числа 3, 4, 0 получается у нас. т.к. 3 - число после 0,1 - стандартный ввод/вывод, 2 - ошибка(занят). 4 - получается из прибавления на 1 процесс(dup). После выполнения команды close(0) берется 0 - т.к. освободили. Write(<файловый дескриптор, возвращаемый функцией open>,<данные для записи>,<количество написанных байт>) - функция, выполняющая запись в файл.
Лабораторная работа №2
Теоретические сведения.
Порождение процессов.
Новый процесс порождается системным вызовом fork (), который создает дочерний процесс - копию родительского. В дочернем процессе выполняется та же программа, что и в родительском, и когда дочерний процесс начинает выполняться, он выполняется с точки возврата из системного вызова fork () . Системный вызов fork () возвращает родительскому процессу PID дочернего процесса, а дочернему процессу - 0. По коду возврата вызова fork () дочерний процесс может "опознать" себя как дочерний. Свой PID процесс родитель может получить при помощи системного вызова getpid(), a PID родительского процесса - при помощи системного вызова getpp.idd (). Б.сли требуется, чтобы в дочернем процессе выполнялась программа, отличная от программы родительского процесса, процесс может сменить выполняемую в нем программу при помощи одного из системных вызовов семейства exec (). Все вызовы этого семейства загружают для выполнения в процессе программу из заданного в вызове файла и отличаются друг от друга способом передачи параметров этой программе. Таким образом, наиболее распространенный контекст применения системного вызова forк () выглядит примерно так:
/* порождение дочернего процесса и запоминание его PID */
if (!(ch_pid-fork())
/* загрузка другой программы в дочернем процессе */ exec(программа);
else
продолжение родительского процесса
Fork ()
порождение нового процесса
Синтаксис
#include <unistd.h>
pid_ t. fork (void) ;
Описание
fork создает новый процесс, почти точную копию родительского процесса.
Новый процесс отличается от родительского только значениями идентификатора текущего процесса (PID) и идентификатором родительского процесса (PPID), а также тем, что счетчики использования ресурсов установлены в 0.
Основные характеристики родительского процесса, наследуемые дочерним процессом следующие:
· Окружение.
· Способы обработки сигналов, адреса функций обработки сигналов.
· Приоритет.
· Все присоединенные разделяемые сегменты памяти.
· Идентификатор группы процессов.
· Идентификатор группы терминала.
· Текущий рабочий каталог.
· Корневой каталог.
Блокировки файлов и сигналы, ожидающие обработки, не наследуются.
Порожденный процесс получает свои собственные копии родительских дескрипторов файлов. Каждый дескриптор файла порожденного процесса разделяет с соответствующим родительским дескриптором файла общий указатель текущей позиции в файле.
Возвращаемое значение
При успешном завершении родителю возвращается PID дочернего процесса, а дочернему процессу возвращается 0. При неудаче родительскому процессу возвращается -1, дочерний процесса не создается, а переменной errno присваивается код ошибки.
Getpid(), getppid ()
получение идентификатора процесса
Синтаксис
#include <unistd.h>
pidt getpid(void); pid_t getppid(void);
Описание
fork возвращает идентификатор текущего процесса.
fork возвращает идентификатор родительского процесса
Синтаксис
#include <unistd.h>
extern char **environ;
int execl( const char *path, const char *arg, ...);
int execlp( const char *file, const char *arg, ...);
int execle( const char *path, const char *arg , …, char * const envp[]);
int execv( const char *path, char *const argv[]);
int execvp( const char *file, char *const argv[]);
Описание
Системные вызовы семейства exec заменяют текущий образ процесса на новый образ, считываемый из заданного файла.
Параметры
Первый аргумент всех функций является указателем на символьную строку, содержащую полное имя исполняемого файла (path). Для функций execlp и execvp имя файла может задаваться без пути (file). Если первый аргумент этих функций не содержит символа "/", то файл ищется в каталогах, определенных в переменной окружения PATH.
Аргументы arg, . . . функций execl, exelp, execle составляют список указателей на символьные строки, содержащие параметры, передаваемые программе. По соглашениям первый элемент чэтого списка должен содержать имя программного файла. Список параметров должен заканчиваться пустым указателем - NULL или (char *)0.
В функциях execv и execvp параметры, передаваемые программе, передаются через массив символьных строк. Аргумент argv является указателем на этот массив.
Аргумент envp функции функций execle также является массивом указателей на символьные строки. Эти строки представляют собой окружение среду для нового образа процесса. Последний элемент массива envp должен быть пустым указателем.
Возвращаемое значение
При нормальном выполнении функции возвращают 0. При ошибках выполнения функции возвращают -1 и устанавливают errno
Текст программы fork1.c:
'$cat fork1.c'
#include <stdio.h>
main()
{int pid;
Printf("I`m the original process with pid %d and ppid %d\n", getpid(), getppid());
pid = fork();
If (pid!=0)
{printf("I'm the parent process with pid %d and ppid %d\n", getpid(), getppid());
printf("my child's pid is %d\n", pid);
}
else
{/* esli pid=0, this is child process*/
printf("I'm the child process with pid %d and ppid %d/n", getpid(), getppid()); }
printf("/npid %d terminates./n", getpid());
}
Выполнение программы fork1.exe:
'$ps'
PID TTY TIME COMD
638 term/tty014 0:01 sh
1260 term/tty014 0:05 ps
$fork1
I'm the original process with pid 1265 and ppid 638
I'm the child process with pid 1266 and ppid 1265
pid 1266 terminates.
I'm the parent process with pid 1265 and ppid 638
My child's pid is 1266
pid 1265 terminates.
Описание работы программы fork1.exe:
Вводится переменная целого типа pid. Запуск программы fork1.exe имеет свой номер PID 1265 и родительский номер PPID 638 (номер процесса работы shell), что и прописывается на дисплее.
Далее системным вызовом fork() происходит создание нового процесса, а точнее дублирование существующего процесса под номером 1265, таким образом переменной pid присваивается значение дочернего процесса 0 и 1266. Таким образом, у нас получается два параллельно идущих процесса - родительский и дочерний.
Сначала проходить ветвь условного оператора при значении условия - ложь, т.е. значение PID=0, показывается надпись о дочернем процессе и он завершается.
Далее проходить ветвь условного оператора при значении условия - истина, т.е.значение РЮ=1266, и показывается надпись родительском процессе и его завершении.
Текст программы fork2.c:
‘$cat fork2.с'
#include <stdio.h>
main()
{int pid;
printf(“I`m the original process with %d and ppid %d\n", getpid(), getppid());
pid = fork();
if(pid!=0)
{printf(“I`m the parent process with pid %d and ppid %d\n", getpid(), getppidO);
printffMy child's pid is %d\n", pid);}
else
{/*esli pid=0, this is child process*/
sleep(5);
prinf("I'm the child process with pid %d and ppid %d\n", getpid(), getppid());
printf("\n was deserted");
}
printf("\n pid %d terminatesAn", getpid());}
Выполнение программы fork2.exe:
‘$ps’
PID TTY TIME COMD
638 term/tty014 0:01 sh
1387 term/tty014 0:05 ps
'$fork2’
I'm the original process with pid 1395 and ppid 638
I'm the parent process with pid 1395 and ppid 638
My child's pid is 1396
pid 1395 terminates
I'm the child process with pid 1396 and ppid 1
Was deserted
pid 1396 terminates.
Описание работы программы fork2.exe:
Вводится переменная целого типа pid. Запуск программы fork2.exe имеет свой номер PID 1395 и родительский номер РРГО 638 (номер процесса работы shell), что и прописывается на дисплее.
Далее системным вызовом fork() происходит создание нового дочернего процесса, а точнее дублирование существующего процесса под номером 1395, таким образом, переменной pid присваивается значение дочернего 1396 и родительского процесса 0. Соответственно, у нас тоже получается два параллельно работающих процесса.
Но т.к. в процессе выполнения программы включается функция sleep(5), которая осуществляет засыпание дочернего процесса на некоторое время и осуществляется работа условного оператора при значении условия - истина, т.е. PID=1396, далее показывается надпись о родительском процессе, который завершает свою работу.
Проходит работа оператора sleep(5), дочерний процесс оживает и продолжает идти по телу цикла. Но т.к. родительский процесс уже заверил свою работу, то его родителем становится процесс инициализации (init). Далее дочерний процесс завершает свою работу.
Текст программы myexec.c:
‘$cat myexec.с'
#include <stdio.h>
main()
{printf("I'm process of main program %d and I'm about to exec an ls -l\n", getpld());
exec(“/bin/ls", "ls", "-l", NULL);
printf("this is line should be never executed\n");
}
Выполнение программы myexec.exc:
‘myexec'
"I'm process of main program 1456 and I'm about tj exec an ls –l
…
…
…
Описание работы программы mуехес.ехе:
В процессе выполнения программы процесс программы под номером PID=1456 заменяется на новый процесс, указанный в функции ехес(). Таким образом, происходит создание нового процесса.
Синтаксис:
exec("const char*path, char*argl, ...char*argN, NULL) - сначала указывается путь, откуда берется значение аргумента, далее берегся значение аргумента.
Текст программы redirect.c:
‘$cat redirect.c'
#include <stdio.h>
#iriclude <fcntl.h>
main (argc, argv) //аргументы командной строки
int argc;
char *argv[2];
{int fd; //файловый дескриптор (номер файла, с которым происходит работа в программе, используемой ОС)
fd = open(argv[1],O_CREAT|O_TRUNC|O_WRONLY_0600);
dup2(fd,1);
close(fd);
execvp(argv[2], &argv[2]);
perror("main");
}
Выполнение программы rcdirect.c:
‘$redirect fff who'
'$cat fff'
User term/tty010 Jan 00:09
…
Описание работы программы redirect.c:
В программе происходит создание файла пользователем (если файла нет) или дополняет
файл(если файл существует), а затем перенаправление системного вызова, в нашем
примере - это системный вызов who, а файл называется fff. В программе используется
команда open, которая создает файл при помощи макросов:
O_CREAT - создать, если не существует,
O_JTRUNC - сократить (truncate) существующий,
O_WRONLY - только запись,
0600 - файл можно читать и писать.
Далее происходит дублирование в файл при помощи команды dup(). т.к. используя указанный файловый дескриптор fd. мы связали команды. Далее закрываем файл. А затем при помощи команды execvp() мы создаем процесс п добавляем его в файл.
open(<имя файла>,<режим открытия>,<права доступа к файлу>) - функция открытия файла, возвращает целое число, именуемое пользовательский дескриптор файла.
dup(<дескриптор файла, копируемый функцией>) - функция копирует дескриптор файла в первое свободное место в таблице пользовательских дескрипторов, возвращая новый дескриптор пользователю.
close(<файловый дескриптор открытого файла>) процесс закрывает открытый файл, когда процессу больше не нужно обращаться к файлу.
Лабораторная работа №3
Примеры программ с использованием сигналов.
Текст программы pulse.c:
'$cat pulse'
# include <stdio.h>
# include <signal.h>
main()
{int pid1, pid2;
pid1= fork();
{
while(1)
{printf("Pid1 is alive\n");
sleep(1);}
pid2=fork();
{while(1)
{iprintf ("Pid2 is alive\n");
sleep(1);
}
}
sleep(3);
kill(3);
kill(pid1, SIGSTOP);
sleep(3);
kill(pid1, SIGCONT);
sleep(3);
kill(pid1, SIGINT);
kill(pid2, SIGINT);
}
Выполнение программы pulse.c:
Pid1 is alive
Pid2 is alive
Pid1 is alive
Pid2 is alive
Pid1 is alive
Pid2 is alive
Pid2 is alive
Pid2 is alive
Pid2 is alive
Pid1 is alive
Pid1 is alive
Pid2 is alive
Pid1 is alive
Pid2 is alive
Описание работы программы pulse.c:
В программе создается новый пользовательский процесс, которому присваивается его PID (идентификатор порождаемого процесса), далее выполняется цикл while, в котором происходит написание фразы и засыпание (sleep()). Затем выполняется создание еще одного пользовательского процесса и для него выполняется такой же цикл, что и для предыдущего созданного процесса. Далее происходит остановка и восстановление сигналов, которые обеспечивают выполнение [циклов, определенных для каждого процесса.
SIGCONT - восстановление сигнала
kill(<адрес посылаемого сигнала>,<номер посылаемого сигнала>) - послать сигнал SIGINT - инициализация сигнального прерывания
Лабораторная работа №4
Тема: Сигналы POSIX.
Теоретические сведения.
Сигналы - общие сведения.
Сигнал или виртуальное прерывание является сообщением, которое система посылает процессу или один процесс посылает другому. Когда процесс получает сигнал, выполнение программы процесса прерывается, и управление передается на подпрограмму (функцию) - обработчик сигнала. После выполнения обработчика сигнала выполнение прерванной программы возобновляется с той точки, на которой она была прервана.
В операционной системе предусмотрено большое число типов сигналов, но большинство из этих типов зарезервировано для системных целей - это сигналы, которые операционная система посылает процессу. Однако есть и сигналы, которыми процессы могут обмениваться между собой.
По умолчанию реакция на большинство сигналов - прекращение процесса, получившего сигнал, то есть, если процесс получает сигнал, обработка которого в нем не предусмотрена, то процесс-получатель сигнала завершается. Однако для большинства типов сигналов процесс может установить обработчик данного сигнала или установить игнорирование данного сигнала.
Если процесс находится в состоянии "добровольного" приостанова (вызванного, например, выполнением системного вызова sleep ()), то получение сигнала "пробуждает процесс от сна", независимо от того, в чем состояла обработка сигнала, системный вызов sleep () заканчивается немедленно.
Sleep (3)
приостановка процесса
Синтаксис
#include <unistd.h>
unsigned int sleep(unsigned int sec);
Описание
sleep приостанавливает выполнение процесса на sec секунд. Приостановка переводит процесс в состояние ожидания, в котором он не требует использования центрального процессора.
Параметры
sec - число секунд ожидания.
Возвращаемое значение
0 - если заданный интервал ожидания отработан полностью, остаток интервала - в противном случае.
Обработчик сигнала в процессе имеет вид функции с прототипом:
void имя_функции(int sigtype);
Параметром данной функции является тип сигнала (один и тот же обработчик может быть установлен для обработки сигналов разных типов).
Для установки своего обработчика сигнала, для его отмены или для установки игнорирования сигнала используется системный вызов signal ().
signal () обработка сигнала |
Синтаксис
#include <signal.h>
void ( +signal ( int signum, void ( *handier) (int) ) ) (int) ;
Описание
Системный вызов signal устанавливает новый обработчик сигнала заданного типа.
Параметры
signum Тип сигнала. Некоторые типы сигналов в Linux:
SIGKILL Этот сигнал приводит к немедленному завершению процесса. Этот сигнал процесс не может игнорировать или установить для него новый обработчик.
SIGTERM Этот сигнал является запросом на завершение процесса.
SIGCHLD Система посылает этот сигнал процессу при завершении одного из его дочерних процессов.
SIGALRM Доставка этого сигнала планируется функциями alarm () и setitimer()
SIGUSR1,
SIGOSR2 Эти сигналы зарезервированы для прикладного использования
SIGHUP Система посылает этот сигнал, когда происходит отключение от терминала.
SIGINT Система посылает этот сигнал, когда пользователь нажимает комбинацию клавиш Ctrl+C.
SIGILL Система посылает этот сигнал при попытке выполнить недопустимую операцию.
SIGFPE Система посылает этот сигнал при попытке выполнить недопустимую операцию с плавающей точкой.
SIGSEGV Система посылает этот сигнал при выполнении программой недопустимого обращения к памяти.
SIGPIPE Система посылает этот сигнал при обращении программы к разрушенному потоку данных.
handler Обработчик сигнала или одно из следующих имен:
SIG_IGN - игнорировать сигнал;
SIG__DFL - установить обработку сигнала по умолчанию.
Обработчик сигнала должен быть функцией, объявляемой по такому прототипу:
void имя_обработчика(int signum);
Аргументом обработчика является тип сигнала. Один и тот же обработчик может быть назначен для сигналов разного типа.
Возвращаемое значение
При нормальном завершении вызов возвращает адрес старого обработчика данного сигнала, при ненормальном - ошибку SIG_ERR
Процесс может послать сигнал любому другому процессу, PID которого ему известен, при помощи системного вызова kill () (на самом деле имеет значение послать сигнал тому процессу, которому он адресован, а вовсе не убить, несмотря на грозное название). В некоторых случаях процессу бывает нужно послать сигнал самому себе, это можно сделать при помощи системного вызова raise().
Kill (2)
посылка сигнала процессу
Синтаксис
#include <sys/types.h>
#include <signal.h>
int kill(pid_t pid, int sig);
Описание
kill посылает заданному процессу определенный сигнал
Параметры
pid Если pid > 0, то он задает PID процесса, которому посылается сигнал.
Если pid = 0, то сигнал посылается всем процессам той группы, к которой принадлежит текущий процесс.
Если pid < 0, то сигнал посылается всем процессам группы, идентификатор которой равен -pid.
sig Тип посылаемого сигнала (см. signal ()).
Возвращаемое значение
При нормальном выполнении функция возвращает 0. При ошибках выполнения возвращает -1 и устанавливает errno ().
Raise ()
посылка сигнала текущему процессу
Синтаксис
#include <signal.h>
int raise (int sig);
Описание
raise посылает сигнал текущему процессу. Вызов:
raise(s);
эквивалентен вызову:
kill(getpid(),s);
Параметры
sig - тип сигнала (см.signal ()).
Возвращаемое значение
0 - при нормальном завершении, -1 - при ненормальном, в последнем случае устанавливается значение errno.
Лабораторная работа №5
Теоретические сведения.
Сигналы могут использоваться как средство межпроцессного взаимодействия. Примеры программ.
Текст программы alarm.с:
'$cat alarm. с'
main()
{alarm(3); //заведен будильник на 3 сек
printf("Looping forever\n”);
whiie(1);
printf("This line should never be executed\n”):
}
Выполнение программы alarm.e:
'$alarm'
Looping forever
Alarm clock
Описание работы программы aiarm.e:
Системным вызовом alarm() заводится будильник на 3 секунды. Далее печатается текст "Looping forever'". Далее начинается бесконечный цикл, поэтому текст не будет напечатан. Как только проходит 3 секунды, ставится отметка о будильнике текстом "Alarm clock". а1аrm(<время>) сигнал тревоги, посылаемый процессу, который выполняется в режиме задачи.
Лабораторная работа №6
Теоретические сведения.
Критической секцией называют такой участок кода, при вхождении в который процесс не может быть прерван. Существуют стандартные обработчики прерываний, созданные разработчиками системы. Вы можете создавать свои собственные обработчики прерываний и заменять ими стандартные.
Примеры программ с использованием обработчиков прерываний.
Текст программы critical.c:
‘$cat critical.с'#inciude <stdio.h>
#include <signal.h>
main()
{void (*oldHandler)(); //the index on function appears
printf(‘I can be <ctrl>+<c>\n");
sleep(3);
oldHandler=signal(S!GINT,S!GJGN);
printf('Tm protected from <ctrl>+<c> now\n"); sleep(3);
signal(SIGINT, oldHandler);
printf("l can be <ctrl>+<c> again\n");
sieep(3);
printf("bye");}
'Scritical'
I can be <ctrl>+<c>
I'm protected from <ctrl>+<c> now
Описание работы программы critical.c:
В программе происходит блокировка стандартного сигнала на другой (можно сделать и свой обработчик, будет свой сигнал). То есть происходит смена сигнала SIGINT на SIG_IGN.
SIGINT - сигнальное прерывание.
Signal(<номер сигнала, при получении которого будет выполнено действие, связанное с запуском пользовательской функции>,<адрес функции>) - системный вызов сигнала.
Текст программы handler.c:
‘$cat handler.c'
#include <stdio.h>
#include <signal.h>
int alarmFlag = 0;
void alarmHandler();
main ()
{signal(SIGALRM, alarmHandler);
alarm(3);
printf("Looping\n");
while(!alarmFlag)
{pause();
}
printf("Loop ends due to alarm signal\n");
}
void alarmHandler()
{printf("An alarm clock signal was received\n");
alarmFlag=1;
}
Выполнение программы handler.c:
Looping
An alarm clock signal was received
Loop ends due to alarm signal
Работа программы handler.c:
При использовании функции signal() используется написанная функция alarmHandler(), которая печатает строку и повышает флаг (индекс) на 1. Программа выполняется следующим образом: написана функция signal(), далее ставится будильник на 3 секунды, поэтому успевает напечататься текст и выполнится функция alarmHandler() и в самом конце печатается фраза.
Лабораторная работа №7
Теоретические сведения.
Как известно, семафор представляет собой неотрицательную целую неделимую переменную, над которой возможны только два вида операций: Р (операция уменьшения значения семафора на 1) и V (операция увеличения значения семафора на 1).
Семафоры являются гибким и удобным средством для синхронизации и взаимного исключения процессов,.
Semget
получение идентификатора массива семафоров
Синтаксис
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semget ( key_t key, int nsems, int semflg )
Описание
semget возвращает идентификатор массива из nsem семафоров, связанного с ключом, значение которого задано аргументом key. Если массива семафоров, связанного с таким ключом, нет и в параметре semflg имеется значение IPC_CREATE или значение ключа задано IPC_PRIVATE, создается новый массив семафоров. Значение ключа IPC_PRIVATE гарантирует уникальность идентификации нового массива семафоров.
Значение параметра semflg формируется как логическое ИЛИ одного из значений: IPC_CREATE (создать новый массив семафоров) или IPC EXCL (получить идентификатор существующего массива) и9 бит прав доступа (см. chmod).
Возвращаемое значение
При успешном завершении возвращается неотрицательное целое число - идентификатор множества семафоров. В случае ошибки возвращается -1 и устанавливается код ошибки errno.
Системные вызовы работают с массивами семафоров, это сделано только лишь для того, чтобы иметь общую идентификацию для всех семафоров одной задачи. Системные вызовы semctl и semop дают возможность отдельно оперировать с каждым семафором массива.
Системный вызов semctl позволяет выполнять управляющие операции над массивом семафоров и отдельными его элементами: читать и устанавливать значения, уничтожать массив семафоров.
Semctl
управляющие операции над семафорами
Синтаксис
include <sys/types . h>
include <sys/ipc.h>
include <sys/sem.h>
#if defined (__GNU_LIBRARY__) && !defined(_SEM_SEMUN_UNDEFINED)
/* union semun определен включением */
#else
/* в соответствии с X/OPEN мы должны определить его сами */
union semun {
int val; /* значение для SETVAL */
struct semid_ds *buf; /* буфер для 1PC_STAT, IPC_SET */
unsigned short int *array; /* массив для GETALL, SETALL */
struct seminfo *_buf; /* буфер для 1PC_1NE'0 */
};
#endif
int semctl (int semid, int semnum, int cmd, union semun arg)
Описание
semctl выполняет управляющие операции над семафорами. Семафор задается аргументами semid - идентификатор массива семафоров и semnum - номер семафора в массиве (нумерация начинается с 0). Выполняемая операция задается аргументом cmd. Аргумент arg служит для передачи параметров операции.
Операции, выполняемые системным вызовом semctl, следующие:
IPC STAT Копировать информацию из структуры данных массива семафоров в структуру, на которую указывает arg. buf.
IPC_SET Присвоить следующим полям структуры данных массива семафоров соответствующие значения, находящиеся в структуре, на которую указывает arg . buf:
sem perm.uid
sem_perm.gid
sem_perm.mode /* Только младшие 9 бит */
IPC RMID Удалить массив семафоров.
GETVAL Получить значение определенного семафора.
SETVAL Установить значение определенного семафора. Значение
задается в arg. buf.
GETALL Получить значения всех семафоров массива в arg. buf. Аргумент semnum игнорируется
SETALL Установить значения всех семафоров массива. Значения задаются в arg. buf. Аргумент semnum игнорируется
GETNCNT Получить значение числа процессов, ожидающих, увеличения
значения определенного семафора.
GETZNCNT Получить значение числа процессов, ожидающих, когда
значение определенного семафора станет 0.
GEТРID Получить значение идентификатора определенного семафора.
Возвращаемое значение
В случае ошибки возвращается -1 и устанавливается код ошибки в errno.
При успешном завершении возвращается:
GETVAL значение семафора
GETVAL идентификатор семафора
GETNCNT число процессов
GETZCNT число процессов
Системный вызов semop выполняет прикладные семафорные операции: аналоги Р- и V- операций, а также проверки состояния семафора.
Semop (2)
управляющие операции над семафорами
Синтаксис
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semop ( int semid, struct sembuf *sops, unsigned nsops )
Описание
semop выполняет операции над выбранными элементами массива семафоров, задаваемого идентификатором semid. Каждый из nsops элементов массива, на который указывает sops, задает одну операцию над одним семафором и содержит поля:
short sem_num; /* Номер семафора */
short sem_op; /* Операция над семафором */
short sem_flg; /* Флаги операции */
Значение поля sem_op возможны следующие:
1. Если значение sem op отрицательно, то:
· Если значение семафора больше или равно абсолютной величине sem_op, то абсолютная величина sem_ор вычитается из значения семафора.
· В противном случае процесс переводится в ожидание до тех пор, пока значение семафора не станет больше или равно абсолютной величине sem_op.
2. Если значение sem ор положительно, то оно добавляется к значение семафора.
3. Если значение sem_op равно нулю, то:
· Если значение семафора равно нулю, то управление сразу же возвращается вызывающему процессу.
· Если значение семафора не равно нулю, то выполнение вызывающего процесса приостанавливается до установки значения семафора в 0.
Флаг операции может принимать значения IPC_NOWAIT или/и SEM__UNDO. Первый из флагов определяет, что semop не переводит процесс в ожидание, когда этого требует выполнение семафорной операции, а заканчивается с признаком ошибки. Второй определяет, что операция должна откатываться при завершении процесса.
Возвращаемое значение
При успешном завершении возвращается 0. В случае ошибки возвращается -1 и устанавливается код ошибки в errno.
Семафоры в Unix/Linux не имеют внешних имен. При получении идентификатора семафора процесс пользуется числовым ключом. Разработчики несвязанных процессов могут договориться об общем значении ключа, который они будут использовать, но у них нет гарантии в том, что это же значение ключа не будет использовано кем-то еще. Гарантированно уникальный массив семафоров можно создать с использованием ключа IPC_PRIVATE, но такой ключ не может быть внешним. Поэтому семафоры используются, как правило, родственными процессами, которые имеют возможность передавать друг другу идентификаторы семафоров, например, через наследуемые ресурсы или через параметры вызова дочерней программы.
Постановка задачи.
Для заданной в Вашем варианте индивидуального задания предметной области разработайте программную модель ситуации предложенной преподавателем.
Лабораторная работа №8
Теоретические сведения.
Взаимодействие процессов (Interprocess communications,IPC) это общий термин, описывающий как два и более процесса могут обмениваться информацией. Вообще, два процесса могут работать как на одной, так ина разных машинах, хотя некоторые механизмы IPC могут поддерживать только локальное использование (например сигналы и конвейеры). IPC может представлять собой обмен данными, там где два или более процессов совместно обрабатывают данные или или другую синхронизированную информацию с целью помочь двум независимым, по связанным процессам запланировать работу так, чтобы они не перекрывались.
Каналы - общие сведения
Программный канал в Unix/Linux представляет собой одно из средств взаимодействия между процессами. Само название (pipe, дословно - трубка) достаточно точно передаст смысл функционирования этого средства. Канал подобен трубопроводу, проложенному между двумя процессами, и по этому трубопроводу процессы могут пересылать друг другу данные. Подобно трубопроводу, канал имеет собственную емкость, данные, направленные в канал процессом-отправителем, не обязательно должны быть немедленно прочитаны процессом-получателем, но могут накапливаться в канале. Как и у трубопровода, емкость канала конечна, когда она будет исчерпана, запись в канал становится невозможной.
Операционные системы Unix/Linux предоставляют в распоряжение программистам два вида каналов - именованные и неименованные. Работа с обоими видами во многом подобна работе с файлами.
Неименованные каналы
Неименованный канал является средством межпроцессного взаимодействия между двумя процессами - родительскими дочерним. Родительский процесс создает канал при помощи системного вызова: pipe () - создание неименованного программного канала
Синтаксис
int pipe(int fd[2]);
Описание
pipe создает неименованный программный канал и возвращает два файловых дескриптора: f d [ 0 ] - для чтения из канала и f d [ 1 ] - для заниси в канал.
Дальнейшая работа с каналом происходит так же, как с открытым файлом.
Возвращаемое значение
При нормальном выполнении возвращает 0. При ошибках выполнения возвращает -1 и устанавливает errno.
Массив из двух целых чисел является выходным параметром этого системного вызова. Если вызов выполнился нормально, то этот массив содержит два файловых дескриптора, fd [ 0 ] является дескриптором для чтения из канала, fd [ 1 ] - дескриптором для записи в канап. Когда процесс порождает другой процесс, дескрипторы родительского процесса наследуются дочерним процессом, и, таким образом, прокладывается трубопровод между двумя процессами. Естественно, что один из процессов использует канал только для чтения, а другой - только для записи (сами представьте себе, что произойдет, если это правило будет нарушаться). Поэтому, если, например, через канал должны передаваться данные из родительского процесса в дочерний, родительский процесс сразу после запуска дочернего процесса закрывает дескриптор канала для чтения, а дочерний процесс закрывает дескриптор для записи. Если нужен двунаправленный обмен данными между процессами, то родительский процесс создает два канала, один из которых используется для передачи данных в одну сторону, а другой - в другую. После получения процессами дескрипторов канала для работы с каналом использую