Метода по выполнению лабораторной работы 4 по СПО (ЛАБОРАТОРНАЯ РАБОТА №4)

Посмотреть архив целиком

14



ЛАБОРАТОРНАЯ РАБОТА №4

ПРОГРАММИРОВАНИЕ НА С В UNIX/LINUX


3.1 Цель работы

Целью данной работы является получение основных сведений о системных вызовах, базовых средств взаимодействия процессов, (порождение процесса, замена тела процесса, взаимодействие при помощи передачи/приема сигналов), отладка программы на С в UNIX/LINUX (анализ и исправление ошибок на стадии компиляции и в процессе работы программы).


3.2 Задание на выполнение работы

1) Установить компилятор GNU С Compiler под UNIX/LINUX*.

2) Написать и отладить программу, выводящую на экран строку «Hello UNIX»

  • с использованием стандартной библиотеки ввода-вывода;

  • с использованием функции непосредственной записи в стандартный поток вывода.

3) Написать и отладить программу, запрашивающую 2 числа и выводящую их наибольший общий делитель. Использовать алгоритм Евклида.

4) Написать и отладить программу, принимающую из командной строки 2 числа. Если эти числа равны, вывести на экран только одно число, если нет – вывести числа в порядке возрастания.

5) Написать и отладить программу, определяющую является ли файл исполняемым файлом формата ELF. Программа должна запросить у пользователя имя файла. Если файл не удастся открыть, проанализировать ошибку и выдать соответствующее сообщение.

6) Написать пример программы, которая запускает и связывает каналом два процесса: вывод содержимого каталога и подсчет количества строк (ls и wc).

7) Написать пользовательскую функцию обработки сигнала. Установка обработки сигнала происходит одноразово (обрабатывается только одно событие, связанное с появлением данного сигнала SIG_ALRM). Возврат из функции-обработчика происходит в точку прерывания процесса.

3.3 Краткие теоретические сведения

3.3.1 Установка компилятора

Компилятор GNU С Compiler представляет собой набор исполняемых файлов и библиотек. В дистрибутиве ASPLinux 7.3 он находится в пакете gсс-2.96-112asp.i386.rpm на диске 2. Для того чтобы поставить любой rpm-пакет в консоли надо набрать команду

rрт -i [имя_пакета] ,

в KDE следует использовать программу KPackager. Следует помнить, что между пакетами может существовать зависимость, т.е. один пакет не возможно поставить без предварительной установки другого. В случае с ASPLinux 7.3, при использовании типичной комплектации, при установке операционной системы gcc не ставится.

3.3.2 Компиляция

Процедура создания большинства приложений является общей. Первой фазой является стадия компиляции, когда файлы с исходными текстами программы, включая файлы заголовков, обрабатываются компилятором сс. Параметры компиляции задаются либо с помощью файла makefile, либо явным указанием необходимых опций компилятора в командной строке. В итоге компилятор создает набор промежуточных объектных файлов. Традиционно имена созданных объектных файлов имеют суффикс « .о».

На следующей стадии эти файлы с помощью редактора связей ld связываются друг с другом и с различными библиотеками, включая стандартную библиотеку по умолчанию и библиотеки, указанные пользователем в качестве параметров. При этом редактор связей может выполняться в двух режимах: статическом и динамическом, что задается соответствующими опциями. В статическом, наиболее традиционном режиме связываются все объектные модули и статические библиотеки (их имена имеют суффикс «.а»), производится разрешение всех внешних ссылок модулей и создается единый исполняемый файл, содержащий весь необходимый для выполнения код. Во втором случае, редактор связей по возможности подключает разделяемые библиотеки (имена этих библиотек имеют суффикс «.so»). В результате создается исполняемый файл, к которому в процессе запуска на выполнение будут подключены все разделяемые объекты. В обоих случаях по умолчанию создается исполняемый файл с именем a.out.

В UNIX/LINUX при использовании компилятора gcc исходник программы пишется в обычном текстовом редакторе (например встроенный в тс редактор) и сохраняется с расширением .с. Далее исходник транслируется:

$ сс -с [имя__исходного_файла]

после данной команды получается объектный файл с расширением .о (если не было ошибок) и компилируется:

$ сс -о [имя_исполняемого_файла] [имя_объектного файла]

после данной команды получается исполняемый файл.

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

$ make [имя исходного файла без расширения]

Заметим, что команда cc является программной оболочкой и компилятора, и редактора связей, которую и рекомендуется использовать при создании программ.

3.3.3 Форматы исполняемых файлов

В большинстве современных операционных систем UNIX используются два стандартных формата исполняемых файлов – COFF (Common Object File Format) и ELF (Executable and Linking Format).

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

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

Формат ELF

Формат ELF имеет файлы нескольких типов. Стандарт ELF различает следующие типы:

  1. Перемещаемый файл (relocatable file), хранящий инструкции и данные, которые могут быть связаны с другими объектными файлами. Резуль­татом такого связывания может быть исполняемый файл или разделяемый объектный файл.

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

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

ELF-файл

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

Таблица 3.1 –Поля заголовка ELF-файла

Поле

Описание

1

2

e_ident [ ]

Массив байт, каждый из которых определяет некоторую общую характеристику файла: формат файла (ELF), номер версии, архитектуру „ системы (32-разрядная или 64-разрядная) и т. д.

e_type

Тип файла

e_machine

Архитектура аппаратной платформы, для которой создан данный файл

e_version

Номер версии ELF-формата. Обычно определяется как EV_CURRENC (текущая), что означает последнюю версию

e_entry

Виртуальный адрес, по которому системой будет передано

управле­ние после загрузки программы (точка входа)

e_phoff

Р Расположение (смещение от начала файла) таблицы заголовков программы

e_shof f

Расположение таблицы заголовков секций

1

2

e_ehsize

Размер заголовка

e_phentsize

Размер каждого заголовка программы

e_phnum

Число заголовков программы

e_shentsize

Размер каждого заголовка сегмента (секции)


e_shnum

Число заголовков сегментов (секций)

e_shstrndx

Расположение сегмента, содержащего таблицу строк


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

Каждый заголовок сегмента программы описывает один сегмент и содержит следующую информацию:

- тип сегмента и действия операционной системы с данным сегментом;

  • расположение сегмента в файле;

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

  • размер сегмента в файле;

  • размер сегмента в памяти;

- флаги доступа к сегменту (запись, чтение, выполнение).

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

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

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

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

3.3.4 Утилита crash

Фактическую информацию о структурах управления адресным пространст­вом процесса можно получить с помощью команды crash(). В следую­щем примере определяется содержимое структур pregion процесса и характеристики соответствующих областей.


Как можно увидеть из вывода команды crash(), с рассматриваемым процессом связаны пять областей: сегмент кода, данных и стека, а также сегменты кода и данных подключенной библиотеки. Столбец reg# опре­деляет запись таблицы областей, где расположена адресуемая каждой pregion область region. Заметим, что значение в столбце REG# лишь от­части соответствует полю p_reg структуры pregion, поскольку последнее является указателем, а не индексом таблицы. Столбец regva содержит значения виртуальных адресов областей

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

$ region 12 22 23

SLOT PGSZ VALID SMEM NONE SOFF REF SWP NSW FORM BACK INOX TYPE FLAGS

12 1 1 1 0 0 11 0 0 15 5 154 stxt done

  1. 3 1 0 0 01 00 238 23 154 priv done

  2. 2 1 1 0 01 00 135 24 priv stack


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

Можно получить дополнительные сведения об этом файле:

$ inode 154


INODE TABLE SIZE = 472

SLOT MAJ/MIN FS INUMB RCNT LINK UID GID SIZE MODE MNT M/ST FLAGS

154 1,42 2 1562 3 1 123 56 8972 f---755 0 R130 tx

Из этой таблицы мы можем определить файловую систему, в которой рас­положен файл (maj/min), а также номер его дискового inode -inumb. В данном случае он равен 1562. Выполнив команду ncheck, можно узнать имя исполняемого файла, соответствующего исследуемому процессу:

$ ncheck -i 1562

/de/root:

1562 /home/andrei/CH3/test

3.3.5 Общая структура программы

Общая структура программы выглядит следующим образом:

#include <stdio.h> //стандартные заголовочные файлы

#include «my_file.h» //пользовательские заголовочные файлы

main (int argc, char* argv[]) //функция main и ее параметры

{

printfHello world!!!\n»); //стандартные функции my_printfHello_user!!!\n»); //пользовательские функции

return 0;

}

В приведенной структуре:

int argc - количество аргументов командной строки. Если программа запущена без аргументов argc = 1.

char* argv[] - массив указателей на аргументы командной строки,

argv[0] - всегда имя программы.

3.3.6 Программные каналы

Для организации взаимодействия между несколькими процессами путем передачи данных от одного процесса к другому в системе UNIX используются каналы. С точки зрения программы, канал есть некая сущность, обладающая двумя файловыми дескрипторами (ФД). Через один ФД процесс может записать информацию в канал, через другой ФД процесс может читать информацию из канала. Так как канал это нечто, связанное с файловыми дескрипторами, то канал может передаваться по наследству дочерним процессам. Это означает, что два родственных процесса могут обладать одним и тем же каналом, т.е. если один процесс запишет какую-то информацию в канал, то другой процесс может прочесть эту информацию из этого же канала.

Функции дублирования файловых дескрипторов

int dup(fd); int dup2(fd, to_fd);

int fd; int fd, to_fd,

Аргументом функции dup является файловый дескриптор открытого в данном процессе файла. Эта функция возвращает -1 в том случае, если обращение не проработало, и значение больше либо равное нулю, если работа функции успешно завершилась. Работа функции заключается в том, что осуществляется дублирование ФД в некоторый свободный ФД. Т.е. можно как бы продублировать открытый файл.

Функция dup2 дублирует файловый дескриптор fd в некоторый файловый дескриптор с номером to_fd. При этом, если при обращении к этой функции ФД, в который мы хотим дублировать, был занят, то происходит закрытие файла, работающего с этим ФД, и переопределение ФД. Рассмотрим пример:

int fd,

chars[80];

fd = open ("a.txt",O_RDONLY);

dup2 (fd,0);

close (fd);

gets (s,80);

Программа открывает файл с именем a.txt только на чтение. Файловый дескриптор, который будет связан с этим файлом, находится в fd. Далее программа обращается к функции dup2, в результате чего будет заменен стандартный ввод процесса на работу с файлом a.txt. Далее можно закрыть дескриптор fd. Функция gets прочтет очередную строку из файла a.txt.

Особенности работы с каналом

Под хранение информации передаваемой через канал выделяется некоторый фиксированный объем оперативной памяти. В некоторых системах этот буфер может быть продолжен на внешнюю память. Что происходит, если процесс хочет записать информацию в канал, но буфер переполнен, или прочесть информацию из канала, но в буфере нет еще данных? В обоих случаях процесс приостанавливает свое выполнение и дожидается, пока не освободится место либо, соответственно, пока в канале не появится информация. Надо заметить, что в этих случаях работа процесса может изменяться в зависимости от установленных параметров, которые можно менять программно. Для реализации каналов в системе используется функция pipe. Аргументом этой_функции должен быть указатель на массив двух целых переменных:

int pipe (pipes);

int pipes[2];

Нулевой элемент массива после обращения к функции pipe получает ФД для чтения, первый элемент этого массива получает ФД для записи. Если нет свободных ФД, то эта функция возвращает -1. Признак конца файла для считывающего дескриптора не будет получен до тех пор, пока не закрыты все дескрипторы, связанные с записью в этот канал.

Рассмотрим небольшой пример:

char *s = "Это пример";

char b[80];

int pipes[2];

pipe (pipes);

write (pipes[l],s, strlen(s)+l);

read (pipes[0],s, strlen(s)+l),

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

3.3.7 Сигналы

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

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

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

  • событие, связанное с приходом некоторой информации от устройства, например, событие, связанное с передачей от клавиатуры комбинации "Ctrl+C";

  • событие, связанное с воздействием одного процесса на другой, например, "SIG_KILL".

Система имеет фиксированный набор событий, которые могут возникать. Каждое событие имеет свое уникальное имя, эти имена обычно едины для всех версий Unix. Такие имена называются сигналами. Перечень сигналов находится в include-файле "signal.h".

Прототип функции обработки сигнала:

void (* signal (sig, fun)) ()

int sig;

void (* fun) ();

При обращении к signal передаем: sig — имя сигнала; fun — указатель на функцию, которая будет обрабатывать событие, связанное с возникновением этого сигнала. Она возвращает указатель на предыдущую функцию обработки данного сигнала.

Событие, связанное с возникновением сигнала может быть обработано в системе тремя способами:

SIG_DEF — стандартная реакция на сигнал, которая предусмотрена системой;

SIG_IGN — игнорирование сигнала (следует отметить, что далеко не все сигналы можно игнорировать, например, SIG_KILL).

Еще две функции, которые необходимы для организации взаимодействия между процессами:

  • int kifl(int pid, sig) — это функция передачи сигнала процессу. Она работает следующим образом: процессу с номером pid осуществляется попытка передачи сигнала, значение которого равно sig. Соответственно, сигнал может быть передан в рамках процессов, принадлежащих одной группе. Код ответа: -1, если сигнал передать не удалось. Функция kill может использоваться для проверки существования процесса с заданным идентификатором. Если функция выполняется с sig=0, то это тестовый сигнал, который определяет - можно или не тпередать процессу сигнал, если можно, то код ответа kill отличен от "-1". Если pid=0, то заданный сигнал передается всем процессам, входящим в группу.

  • int wait(int *wail_rel). Ожидание события в дочернем процессе. Если его нет, то управление возвращается сразу же с кодом ответа "-1". Если в дочернем процессе возникло событие, то анализируются младшие 16 бит в значении wait_ret:

- если дочерний процесс приостановлен (трассировка или получение сигнала), тогда старшие 8 бит wait_ret - код сигнала, который получил дочерний процесс, а младшие содержат код 0177.

- если дочерний процесс успешно завершился через обращение к функции exit. Тогда младшие 8 бит равны нулю, а старшие 8 бит равны коду, установленному функцией exit.

- если дочерний процесс завершился из-за возникновения у него необрабатываемого сигнала, то старшие 8 бит равны нулю, а младшие - номеру сигнала, который завершил процесс.

Функция wait возвращает идентификатор процесса в случае успешного выполнения и "-1" в противном случае. Если одно из перечисленных событий произошло до обращения к функции, то результат возвращается сразу же, то есть никакого ожидания не происходит, это говорит о том, что информация о событиях в процессе безвозвратно не теряется.

Рассмотрим пример многопроцессного взаимодействия.

alr()

{

printf ("\n Быстрее!!! \п");

signal (SIG_ALRM, alr);

}

main ()

{

char s[80];

int pid;

signal(SIG_ALRM, alr);

if (pid=fork())

for (;;)

{

sleep(5); kill(pid, SIG_ALRM); /* приостанавливаем процесс на 5 секунд

и отправляем сигнал SIG_ALRM

дочернему процессу */

}

printf ("имя?");

for (;;)

{

printf ("имя?");

if gets(s,80)!=NULL) break;

}

printf("OK!\n");

kill(getpid(), SIG_KILL); /* убиваем зациклившийся родительский процесс */

}

Следует заметить, что в разных версиях Unix имена сигналов могут различаться.

3.4 Порядок выполнения

1) * Выполняется под контролем преподавателя.

2) Вывод на экран «Hello world!!!»

2.1 #include

main ()

{

printf («Hello world!!!\n»);

return 0;

}

2.2 #include

main()

{

write(1,"Hello world!!!\n", 15);

return 0;

}

  1. Нахождение наибольшего общего делителя двух чисел по алгоритму

Евклида. Алгоритм Евклида по нахождению наибольшего общего делителя следующий: пусть есть два числа тип, т > п. Делим m на n, r- остаток от деления. Если остаток от деления равен нулю, то n- наибольший общий делитель. Если n не равен 0, заменяем m на n и n на r. Продолжаем деление, пока r не станет равным 0.

#include

unsigned int m,n,r;

main ()

{

scanf("%u %u", &m, &n);

if (n > m)

{

r=m;

m=n;

n=r;

}

printf ("Наибольший общий делитель %u и %u: ",m,n);

while (1)

{

r=m%n;

if ( r = = 0)

break;

else

{

m=n;

n=r;

}

}

printf ("%u\n",n); r

return 0;

}

4) Работа с аргументами командной строки

#include <stdio.h>

#include

#include

main (int argc, char* argv[])

{

if (argc < 3)

{

printf ("Недостаточно параметров\n");

return 0;

}

if (!strcmp(argv[l],argv[2]))

printf ("%s\n",argv[l] );

else

atoi(argv[l]) < atoi(argv[2]) ? printf ("%s%s\n", argv[l], argv[2]) :

printf ("%s "%s \n", argv[2] , argv[l] ) ;

return 0;

}

  1. Определение формата файла

#include <stdio.h>

#include

unsigned char szSig[]={'\x7F','\x45','\x4C','\x46'};

unsigned char szBuffer[4];

char szFileName[20];

FILE* hFile;

main ()

{

scanf("%s",szFileName);

hFile=fopen (szFileName, "r");

if (hFile ==NULL)

{

switch(errno)

{

case 2:

printf ("Файл не найден\n");

break;

case 13:

printf ("Нет фоступа к файлу\n") ;

break;

default:

printf ("Неизвестная ошибка\n") ;

}

return 1;

}

fread (szBuffer,1,4,hFile);

if (!memcmp(szSig,szBuffer, 4))

printf("Файл %s является исполняемым файлом формата ELF\n" ,szFileName);

else

printf("Файл %s не является исполняемым файлом формата ELF\n " ,szFileName);

fclose(hFile);

return 0;

}

6) Организация каналов

main ()

{

int fd[2];

pipe (fd); /*в родительском процессе образуем два дескриптора канала */

if (fork()) /* образуем дочерний процесс, у которого будут те же дескрипторы */

{ /* эта часть программы происходит в родительском процессе*/

dup2(fd[1],1); /* заменяем стандартный вывод выводом в канал */

close(fd[1]); /*закрываем дескрипторы канала*/ ,

close(fd[0]); /* теперь весь вывод будет происходить в канал */

exec1("/bin/ls”, ''ls", (char*)0); /* заменяем тело родителя на ls */

} /* отсюда начинает работать дочерний процесс */

dup2(fd[0],0), /* в дочернем процессе все делаем аналогично */

close(fd[0]);

close(fd[1]);
exe1l("/bin/wc", "wc", (char*)0);

}

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

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

  1. Пример программы "Будильник". Функция alrm инициализирует появление сигнала SIG_ALRM

main ()

{

chars[80];

signal (SIG_ALRM, alrm); /* установка режима связи с событием SIG_ALRM на

функцию alrm */

alarm(5); /* заводим будильник */

printf ("Введите имя \n");

for (;;)

{
printf{"имя:");

if (gets(s,80) != NULL) break;

}

printft ("OK! \n");

}

alrm()

{

printf ("\n жду имя \n");

alarm(5);

signal (SlG_ALRM, alrm);

}

В начале программы устанавливаем реакцию на сигнал SIG_ALRM на функцию alrm, далее заводим будильник, запрашиваем "Введите имя" и ожидаем ввода строки символов. Если ввод строки задерживается, то будет вызвана функция alrm, которая напомнит, что программа "ждет имя", опять заведет будильник и поставит себя на обработку сигнала SIG_ALRM еще раз.

И так будет до тех пор, пока не будет введена строка.

Если в момент выполнения системного вызова возникает событие, связанное с сигналом, то система прерывает выполнение системного вызова и возвращает код ответа, равный "-1". Это можно также проанализировать по функции еггnо.

3.5 Вопросы к защите лабораторной работы

1) Опишите схему компиляции программы.

2) Перечислите форматы исполняемых файлов.

3) Перечислите этапы выполнения программы в операционной системе UNIX.

4) Перечислите основные системные функции для работы с файлами.

5) Перечислите основные интерфейсы для файлового ввода/вывода.

6) Перечислите базовые средства взаимодействия процессов в UNIX.

7) Поясните особенности работы с каналами в UNIX.

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

  2. Как получить данные о сегментах кода, данных и стека?



4 Литература

1) А. Робачевский Операционная система UNIX.-СПб.:BHV-Санкт-Петербург, 2002- 528 с.

  1. FreeBSD/ Энциклопедия пользователя: Пер. с англ./ Майкл Эбен, Брайан Таймэн – К.: ООО ТИД ДС, 2002 - 736.

  2. Ю. Вахалия UNIX изнутри. – СПб.: Питер, 2003 – 844 с.



Случайные файлы

Файл
72855-1.rtf
149066.doc
44617.doc
28382.rtf
175243.rtf