Циклический обмен содержимым файлов
Содержание
Введение
Анализ задания и разработка алгоритма
Функции, используемые в работе
Листинг программы
Тестирование
Выводы
Литература
Введение
Целью выполнения работы является закрепление знаний умений и навыков в области взаимодействия модулей, использования системных вызовов и библиотечных функций управления процессами и файлами современных операционных систем для создания системных и пользовательских программ, процедур и функций на примере ОС семейства UNIX/Linux. В ходе выполнения работы студенту необходимо продемонстрировать знания функций, алгоритмов, механизмов управления процессами, разделяемыми ресурсами, файлами, вводом-выводом.
Файловая система Unix, это иерархическая, многопользовательская файловая система. Ее можно представить в виде дерева. В корне дерева находится “корневой каталог”, узлами, отличными от листьев дерева являются каталоги. Листьями могут являться: файлы (в традиционном понимании — именованные наборы данных), пустые каталоги (каталоги, с которыми не ассоциировано ни одного файла). В системе определено понятие имени файла — это имя, которое ассоциировано с набором данных в рамках каталога, которому принадлежит этот файл.
Процесс — понятие, которое определяется по-разному. Это может быть — “упорядоченный набор команд и принадлежащих ему ресурсов”. С точки зрения ОС Unix процесс — это объект, зарегистрированный в специальной таблице процессов.
Телом процесса называется набор команд и данных, которыми оперирует процесс.
Контекст процесса — атрибут, который присутствует практически во всех ОС, в разных ОС он может называться по-разному. Контексты всех процессов размещаются в адресном пространстве ОС и содержат оперативную информацию о состоянии процесса и текущую информацию, связанную с процессом и его запуском.
1. Анализ задания и разработка алгоритма
По заданию согласно варианта 5 по списку необходимо организовать циклический обмен содержимым всех файлов (1->2->3->4->1).
Рассмотрим основные принципы по которым будет создаваться программа:
Будут созданы 4-е процесса, а именно 4-ый процесс породит 1-ый процесс, 1-ый процесс, в свою очередь породит 2-ой процесс, 2-ой процесс породит 3-тий процесс.
Каждый процесс будет иметь файл с соответствующими именами – 1, 2, 3, 4.
Процессы между собой будут обмениваться через разделяемую память и временный (буферный) файл.
Обмен содержимым файлов будет происходить циклически.
Общая схема взаимодействия процессов показана на рис. 1.
Рис.1 Схема взаимодействия процессов.
2. Функции, используемые в работе
Директива #include включает в текст программы содержимое указанного файла. Эта директива имеет две формы:
#include "имя файла"
#include <имя файла>
Имя файла должно соответствовать соглашениям операционной системы и может состоять либо только из имени файла, либо из имени файла с предшествующим ему маршрутом. Если имя файла указано в кавычках, то поиск файла осуществляется в соответствии с заданным маршрутом, а при его отсутствии в текущем каталоге. Если имя файла задано в угловых скобках, то поиск файла производится в стандартных директориях операционной системы, задаваемых командой PATH.
Директива #include может быть вложенной, т.е. во включаемом файле тоже может содержаться директива #include, которая замещается после включения файла, содержащего эту директиву.
Директива #include широко используется для включения в программу так называемых заголовочных файлов, содержащих прототипы библиотечных функций, и поэтому большинство программ на СИ начинаются с этой директивы.
При написании программы согласно заданию, будем использовать функции различного вида.
Функция - это совокупность объявлений и операторов, обычно предназначенная для решения определенной задачи. Каждая функция должна иметь имя, которое используется для ее объявления, определения и вызова. В любой программе на СИ должна быть функция с именем main (главная функция), именно с этой функции, в каком бы месте программы она не находилась, начинается выполнение программы.
При вызове функции ей при помощи аргументов (формальных параметров) могут быть переданы некоторые значения (фактические параметры), используемые во время выполнения функции. Функция может возвращать некоторое (одно!) значение. Это возвращаемое значение и есть результат выполнения функции, который при выполнении программы подставляется в точку вызова функции, где бы этот вызов ни встретился. Допускается также использовать функции не имеющие аргументов и функции не возвращающие никаких значений. Действие таких функций может состоять, например, в изменении значений некоторых переменных, выводе на печать некоторых текстов и т.п..
С использованием функций в языке СИ связаны три понятия - определение функции (описание действий, выполняемых функцией), объявление функции (задание формы обращения к функции) и вызов функции.
Определение функции задает тип возвращаемого значения, имя функции, типы и число формальных параметров, а также объявления переменных и операторы, называемые телом функции, и определяющие действие функции. В определении функции также может быть задан класс памяти.
Функция fork:
int fork ()
Вызов fork приводит к созданию нового процесса (порожденного процесса) - точной копии процесса, сделавшего вызов (родительского процесса). Точнее, порожденный процесс наследует у родительского процесса следующие характеристики:
Окружение.
Флаг "закрыть при выполнении вызова exec"
Способы обработки сигналов (то есть SIG_DFL, SIG_IGN, SIG_HOLD, адреса функций обработки сигналов).
Разрешение переустанавливать действующий идентификатор пользователя.
Разрешение переустанавливать действующий идентификатор группы.
Состояние профилирования (включено/выключено).
Значение поправки к приоритету.
Все присоединенные разделяемые сегменты памяти.
Идентификатор группы процессов.
Идентификатор группы терминала.
Текущий рабочий каталог.
Корневой каталог.
Маска режима создания файлов.
Ограничение на размер файла.
Порожденный процесс отличается от родительского процесса следующим:
Порожденный процесс имеет свой уникальный идентификатор процесса.
Порожденный процесс имеет иной идентификатор родительского процесса, равный идентификатору процесса, его породившего.
Порожденный процесс имеет свои собственные копии родительских дескрипторов файлов. Каждый дескриптор файла порожденного процесса разделяет с соответствующим родительским дескриптором файла общий указатель текущей позиции в файле.
Все semadj значения сбрасываются.
Порожденный процесс не наследует у родительского процесса признаков удержания в памяти сегмента команд, данных или всего процесса целиком.
Обнуляются счетчики времени, потраченного для обслуживания этого процесса (tms_utime, tms_stime, tms_cutime, tms_cstime). Отменяется запрос к будильнику.
Системный вызов fork завершается неудачей и новый процесс не порождается, если выполнено хотя бы одно из следующих условий:
Создать процесс запрещает системное ограничение на общее количество процессов.
Создать процесс запрещает системное ограничение на количество процессов у одного пользователя.
Общее количество системной памяти, предоставленной для физического ввода/вывода, временно оказалось недостаточным.
Функция fopen:
#include <stdio.h>
FILE *fopen (filename, type)
char *filename, *type;
Функция fopen открывает файл с именем filename, ассоциирует с ним поток и возвращает указатель на структуру типа FILE, соответствующую этому потоку.
Аргумент filename является указателем на цепочку символов - имя открываемого файла.
Аргумент type - это указатель на цепочку символов, которая может принимать одно из следующих значений: r Открыть на чтение.
w Опустошить или создать для записи.
a Добавлять; открыть для записи в конец файла или создать для записи.
r+ Открыть для изменения (чтения и записи).
w+ Опустошить или создать для изменения.
a+ Добавлять; открыть или создать для изменения в конце файла.
Функция fclose:
#include <stdio.h>
int fclose (stream)
FILE *stream;
Функция fclose записывает в файл всю информацию, буферизованную в потоке stream (выталкивает буфера), и закрывает этот поток.
Функция fclose вызывается автоматически для всех открытых файлов при выполнении системного вызова exit(2).
Функция fflush выталкивает буфера потока stream в файл. Поток остается открытым.
Функция fputc:
#include <stdio.h>
int fputc (c, stream)
char c;
FILE *stream;
Запись в поток символа или машинного слова. Макрос putc записывает символ c в поток вывода stream (в позицию, задаваемую указателем файла, если он определен). Макрос putchar (c) определяется как putc (c, stdout).
Функция fputc выполняет те же действия, что и макрос putc; она работает медленнее, чем putc, но занимает меньше пространства на каждое использование. Кроме того, функцию можно передавать в качестве аргумента.
Функция fgetc:
#include <stdio.h>
int fgetc (stream)
FILE *stream;
Считывание символа или слова из потока. Макрос getc выдает в виде целого числа следующий символ (байт) из указанного потока ввода stream и перемещает текущую позицию (если она определена) на один символ вперед. Макрос getchar эквивалентен getc (stdin).
Функция fgetc действует аналогично getc, но реализована именно как функция, а не макрос. Функция fgetc выполняется медленнее, чем getc, но обращение к ней требует меньше памяти, и ее имя может передаваться в качестве аргумента функции.
Функция feof:
#include
int feof (stream)
FILE *stream;
Опрос состояния потока. Функция feof возвращает ненулевое значение, если ранее при чтении из потока steram фиксировался конец файла; в противном случае результат равен 0.
Функция malloc:
#include <malloc.h>
char *malloc (size)
unsigned size;
Быстрое выделение оперативной памяти.
Функции malloc и free предоставляют простой универсальный механизм выделения и освобождения памяти, работающий значительно быстрее пакета malloc(3C). Описываемые функции находятся в библиотеке malloc и подключаются при использовании опции -lmalloc команд cc(1) и ld(1).
Функция malloc возвращает указатель на блок размером не менее size байт, который можно использовать в произвольных целях.
Аргументом функции free является указатель на блок, предварительно выделенный с помощью malloc; после выполнения free блок может быть выделен вновь, а хранящаяся в нем информация теряется.
В случае выхода за границы блока, выделенного функцией malloc, а также при вызове функции free с аргументом, не являвшимся результатом malloc, эффект будет непредсказуем.
Каждая из функций выделения возвращает указатель на область памяти, подходящую (после возможного преобразования типа указателя) для хранения произвольных данных.
Структура определена во включаемом файле <malloc.h>.
3. Листинг программы
Программа состоит из главного модуля rgr.c, который использует подключаемый файл с функцией обмена file_obmen.h.
Файл rgr.c:
#include <stdlib.h>
#include <sys/wait.h>
#include <stdio.h>
#include "file_obmen.h"
int main()
{
char *a4 = "4", *a3 = "3", *a2 = "2", *a1 = "1";
int p1, p2, p3, p4;
printf ("Cozdanie vseh processov:\n");
printf ("\n");
printf ("Cozdanie processa 4 \n");
printf ("Porozhdenie processa 1 processom 4\n");
printf ("Porozhdenie processa 2 processom 1\n");
printf ("Porozhdenie processa 3 processom 2\n");
p4 = fork();
if(p4)
{
p1 = fork();
exit(0);
}
if(p1)
{
file_obmen(a4, a3);
printf("Write file 4 >>> file 3\n");
p2 = fork();
}
sleep(1);
if(p2)
{
file_obmen(a3, a2);
printf("Write file 3 >>> file 2\n");
p3 = fork();
exit(0);
}
sleep(1);
if(p3)
{
file_obmen(a2, a1);
printf("Write file 2 >>> file 1\n");
printf ("\n");
printf ("Obmen zachonchen!\n");
exit(0);
}
sleep(1);
exit(0);
}
Файл file_obmen.h:
void file_obmen(char *a4, char *a3)
{
FILE *f[3];
char *c=(char*)malloc(sizeof(char));
int p1, p2;
f[1] = fopen(a3, "rb");
f[0] = fopen(a4, "rb");
f[2] = fopen("buf", "wb");
while(!feof(f[0]))
{
c[0] = fgetc(f[0]);
fputc(c[0],f[2]);
}
fclose(f[0]);
f[0] = fopen(a4, "wb");
while(!feof(f[1]))
{
c[0] = fgetc(f[1]);
fputc(c[0], f[0]);
}
fclose(f[0]);
fclose(f[2]);
f[2] = fopen("buf", "rb");
f[1] = fopen(a3, "wb");
while(!feof(f[2]))
{
c[0] = fgetc(f[2]);
fputc(c[0], f[1]);
}
fclose(f[2]);
fclose(f[1]);
}
4. Тестирование
Результат выполнения программы в консоле:
Содержимое файлов при первом проходе:
Содержимое файлов при втором проходе:
Программа проверена и работает корректно.
Выводы
В данной работе частично описана структура системы UNIX, взаимоотношения между процессами, выполняющимися в режиме задачи и в режиме ядра. Процессы выполняются в режиме задачи или в режиме ядра, в котором они пользуются услугами системы благодаря наличию набора обращений к операционной системе. Архитектура системы поддерживает такой стиль программирования, при котором из небольших программ, выполняющих только отдельные функции, но хорошо, составляются более сложные программы, использующие механизм каналов и переназначение ввода-вывода.
Обращения к операционной системе позволяют процессам производить операции, которые иначе не выполняются. В дополнение к обработке подобных обращений ядро операционной системы осуществляет общие учетные операции, управляет планированием процессов, распределением памяти и защитой процессов в оперативной памяти, обслуживает прерывания, управляет файлами и устройствами и обрабатывает особые ситуации, возникающие в системе. В функции ядра системы UNIX намеренно не включены многие функции, являющиеся частью других операционных систем, поскольку набор обращений к системе позволяет процессам выполнять все необходимые операции на пользовательском уровне.
Литература
Дж. Такет (мл.), С.Барнет. Использование Linux/ Специальное издание.: 5-е изд.: Пер. с англ.: Уч.пос. – М.: Издательский дом «Вильямс», 2000. – 784 с.
Максимальная защита Linux. Искусство настройки.: Пер. с англ./ под.ред. Дж.Рея – СПб.: ООО «ДиаСофтЮП», 2002. – 752 с.
Браун С. Операционная система UNIX - М.: Мир, 1986 - 463 с.