Основы операционных систем

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

Рубрика Программирование, компьютеры и кибернетика
Вид книга
Язык русский
Дата добавления 12.01.2010
Размер файла 2,6 M

Отправить свою хорошую работу в базу знаний просто. Используйте форму, расположенную ниже

Студенты, аспиранты, молодые ученые, использующие базу знаний в своей учебе и работе, будут вам очень благодарны.

0400 - право чтения для пользователя, создавшего файл;

0200 - право записи для пользователя, создавшего файл;

0100 - право исполнения для пользователя, создавшего файл;

0040 - право чтения для группы пользователя, создавшего файл;

0020 - право записи для группы пользователя, создавшего файл;

0010 - право исполнения для группы пользователя, создавшего файл;

0004 - право чтения для всех остальных пользователей;

0002 - право записи для всех остальных пользователей;

0001 - право исполнения для всех остальных пользователей.

Установление значения какого-либо бита равным 1 запрещает инициализацию соответствующего права доступа для вновь создаваемого файла. Значение маски создания файлов может изменяться с помощью системного вызова umask () или команды umask. Маска создания файлов наследуется процессом-ребенком при порождении нового процесса системным вызовом fork () и входит в состав неизменяемой части системного контекста процесса при системном вызове exec (). В результате этого наследования изменение маски с помощью команды umask окажет влияние на атрибуты доступа к вновь создаваемым файлам для всех процессов, порожденных далее командной оболочкой.

Изменить текущее значение маски для программы-оболочки или посмотреть его можно с помощью команды шпавк.

Команда umask

Синтаксис команды

umask [value]

Описание команды

Команда umask предназначена для изменения маски создания файлов командной оболочки или просмотра ее текущего значения. При отсутствии параметра команда выдает значение установленной маски создания файлов в восьмеричном виде. Для установления нового значения оно задается как параметр value в восьмеричном виде.

Если вы хотите изменить его для Midnight Commander, необходимо выйти из тс, выполнить команду umask и запустить тс снова. Маска создания файлов не сохраняется между сеансами работы в системе. При новом входе в систему значение маски снова будет установлено по умолчанию.

Системные вызовы дет.шс1 и де!д1с1

Узнать идентификатор пользователя, запустившего программу на исполнение, -- иго и идентификатор группы, к которой он относится, --

можно с помощью системных вызовов десигс! () и десдгс! (), применив их внутри этой программы.

Системные вызовы getuid() и getgidQ Прототипы системных вызовов

((include <sys/types.h> #include <unis.td.h> uid_t getuid(void); gid_t getgid(void);

Описание системных вызовов

Системный вызов getuid возвращает идентификатор пользователя для текущего процесса. Системный вызов getgid возвращает идентификатор группы пользователя для текущего процесса.

Типы данных uid_t и gid_t являются синонимами для одного из целочисленных типов языка С.

Компиляция программ на языке С в UNIX и запуск их на счет

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

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

В простейшем случае откомпилировать программу можно, запуская компилятор командой

дсс имя_исходного_файла

Если программа была написана без ошибок, то компилятор создаст исполняемый файл с именем a.out. Изменить имя создаваемого исполняемого файла можно, задав его с помощью опции -о.

дсс имя_исходного_файла -о имя_исполняемого_файла

Компилятор дсс имеет несколько сотен возможных опций. Получить информацию о них вы можете в UNIX Manual.

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

Запустить программу на исполнение можно, набрав имя исполняемого файла и нажав клавишу <Enter>.

Написание, компиляция и запуск программы с использованием системных вызовов getuid() и

getgid()

Напишите, откомпилируйте и запустите программу, которая печатала бы идентификатор пользователя, запустившего программу, и идентификатор его группы.

Семинары 3-4. Процессы в операционной системе UNIX

Понятие процесса в UNIX, его контекст. Идентификация процесса. Состояния процесса. Краткая диаграмма состояний. Иерархия процессов. Системные вызовы getpid(), getppid(). Создание процесса в UNIX. Системный вызов fork (). Завершение процесса. Функция exit (). Параметры функции main () в языке С. Переменные среды и аргументы командной строки. Изменение пользовательского контекста процесса. Семейство функций для системного вызова exec ().

Ключевые слова: пользовательский контекст процесса, контекст ядра, идентификатор процесса, PID, идентификатор родительского процесса, PPID, исполнение в режиме пользователя, исполнение в режиме ядра, системные вызовы getpid, getppid, fork, exec, создание процесса, завершение процесса, функции exit, execl, execv, execlp, execvp, execle, execve, параметры функции main, аргументы командной строки, переменные среды.

Понятие процесса в UNIX. Его контекст

Все построение операционной системы UNIX основано на использовании концепции процессов, которая обсуждалась налекции. Контекст процесса складывается из пользовательского контекста и контекста ядра, как изображено на рис. 3-4.1.

Под пользовательским контекстом процесса понимают код и данные, расположенные в адресном пространстве процесса. Все данные подразделяются на:

инициализируемые неизменяемые данные (например, константы);

инициализируемые изменяемые данные (все переменные, начальные значения которых присваиваются на этапе компиляции);

^инициализируемые изменяемые данные (все статические переменные, которым не присвоены начальные значения на этапе компиляции);

стек пользователя;

данные, расположенные в динамически выделяемой памяти (например, с помощью стандартных библиотечных С функций malloc (), calloc(), real loc ()).

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

Пользовательский контекст Контекст ядра

Стек пользователя t

Т

Динамически выделяемая память

Неинициал изирумые изменяемые данные

Стек ядра

Данные ядра

I

Аналог РСВ

Инициал изирумые изменяемые данные

Инициал изирумые неизменяемые данные

Загружаются из исполняемого файла

Исполняемый код

Рис. 3-4.1. Контекст процесса в UNIX

Под понятием «контекст ядра» объединяются системный контекст и регистровый контекст, рассмотренные на лекции. Мы будем выделять в контексте ядра стек ядра, который используется при работе процесса в режиме ядра (kernel mode), и данные ядра, хранящиеся в структурах, являющихся аналогом блока управления процессом -- РСВ. Состав данных ядра будет уточняться на последующих семинарах. На этом занятии нам достаточно знать, что в данные ядра входят: идентификатор пользователя -- UID, групповой идентификатор пользователя -- GID, идентификатор процесса -- PID, идентификатор родительского процесса -- PPID.

Идентификация процесса

Каждый процесс в операционной системе получает уникальный идентификационный номер -- PID (Process IDentificator). При создании нового процесса операционная система пытается присвоить ему свободный номер больший, чем у процесса, созданного перед ним. Если таких свободных номеров не оказывается (например, мы достигли максимально возможного номера для процесса), то операционная система выбирает минимальный номер из всех свободных номеров. В операционной системе Linux присвоение идентификационных номеров процессов начинается с номера 0, который получает процесс kernel при старте операционной системы. Этот номер впоследствии не может быть присвоен никакому другому процессу. Максимально возможное значение для номера процесса в Linux на базе 32-разрядных процессоров Intel составляет 231 -- 1.

Состояния процесса. Краткая диаграмма состояний

Модель состояний процессов в операционной системе UNIX представляет собой детализацию модели состояний, принятой в лекционном курсе. Краткая диаграмма состояний процессов в операционной системе UNIX изображена на рис. 3-4.2.

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

Приведенная выше диаграмма состояний процессов в UNIX не является полной. Она показывает только состояния, для понимания которых достаточно уже полученных знаний. Пожалуй, наиболее полную диаграмму состояний процессов в операционной системе UNIX можно найти в книге [Bach, 1986] (рис. 6.1).

Иерархия процессов

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

Таким образом, все процессы в UNIX связаны отношениями процесс-родитель -- процесс-ребенок и образуют генеалогическое дерево процессов. Для сохранения целостности генеалогического дерева в ситуациях, когда процесс-родитель завершает свою работу до завершения выполнения процесса-ребенка, идентификатор родительского процесса в данных ядра процесса-ребенка (ppid -- Parent Process IDentificator) изменяет свое значение на значение 1, соответствующее идентификатору процесса init, время жизни которого определяет время функционирования операционной системы. Тем самым процесс init как бы усыновляет осиротевшие процессы. Наверное, логичнее было бы заменять ppid не на значение 1, а на значение идентификатора ближайшего существующего процесса-прародителя умершего процесса-родителя, но в UNIX почему-то такая схема реализована не была.

Системные вызовы getppid() и getpid()

Данные ядра, находящиеся в контексте ядра процесса, не могут быть прочитаны процессом непосредственно. Для получения информации о них процесс должен совершить соответствующий системный вызов. Значение идентификатора текущего процесса может быть получено с помощью системного вызова getpid(), а значение идентификатора родительского процесса для текущего процесса -- с помощью системного вызова getppid(). Прототипы этих системных вызовов и соответствующие типы данных описаны в системных файлах ^уБ^уреБ. п> и <unistd.h>. Системные вызовы не имеют параметров и возвращают идентификатор текущего процесса и идентификатор родительского процесса соответственно.

Системные вызовы getpid() и getppid() Прототипы системных вызовов

¦include <sys/types.h> ¦include <unistd.h> pid_t getpid(void); pid_t getppid(void);

Описание системных вызовов

Системный вызов getpid возвращает идентификатор текущего процесса.

Системный вызов getppid возвращает идентификатор гюоцесса-родителя для текущего процесса.

Тип данных pid_t является синонимом для одного из целочисленных типов языка С.

Написание программы с использованием getpidQ и getppid()

В качестве примера использования системных вызовов getpid () и getppid () самостоятельно напишите программу, печатающую значения PID и PPID для текущего процесса. Запустите ее несколько раз подряд. Посмотрите, как меняется идентификатор текущего процесса. Объясните наблюдаемые изменения.

Создание процесса в UNIX. Системный вызов fork()

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

* идентификатор процесса -- PID;

* идентификатор родительского процесса -- PPID. Дополнительно может измениться поведение порожденного процесса по отношению к некоторым сигналам, о чем подробнее будет рассказано на семинарах 13--14, когда мы будем говорить о сигналах в операционной системе UNIX.

Системный вызов для порождения нового процесса Прототип системного вызова

ttinclude <sys/types.h> #include <unistd.h> pid_t fork(void);

Описание системного вызова

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

идентификатор процесса;

идентификатор родительского процесса;

время, оставшееся до получения сигнала SIGALRM;

сигналы, ожидавшие доставки родительскому процессу, не будут доставляться порожденному процессу.

При однократном системном вызове возврат из него может произойти дважды: один раз в родительском процессе, а второй раз в порожденном процессе. Если создание нового процесса произошло успешно, то в порожденном процессе системный вызов вернет значение 0, а в родительском процессе - положительное значение, равное идентификатору процесса-ребенка. Если создать новый процесс не удалось, то системный вызов вернет в инициировавший его процесс отрицательное значение.

Системный вызов fork является единственным способом породить новый процесс после инициализации операционной системы UNIX.

В процессе выполнения системного вызова fork () порождается копия родительского процесса и возвращение из системного вызова будет происходить уже как в родительском, так и в порожденном процессах. Этот системный вызов является единственным, который вызывается один раз, а при успешной работе возвращается два раза (один раз в процессе-родителе и один раз в процессе-ребенке)! После выхода из системного вызова оба процесса продолжают выполнение регулярного пользовательского кода, следующего за системным вызовом.

Прогон программы с fork() с одинаковой работой родителя и ребенка

Для иллюстрации сказанного давайте рассмотрим следующую программу

/* Программа 03-1.с - пример создания нового процесса

с одинаковой работой процессов ребенка и родителя */

#include <sys/types.h>

#include <unistd.h>

#include <stdio.h>

int main()

{

pid_t pid, ppid; int a = 0; (void)fork();

/* При успешном создании нового процесса с этого места псевдопараллельно начинают работать два процесса: старый и новый */

/* Перед выполнением следующего выражения значение переменной а в обоих процессах равно 0 */ а = а+1;

/* Узнаем идентификаторы текущего и родительского процесса (в каждом из процессов !!!) */ pid = getpid(); ppid = getppidO;

/* Печатаем значения PID, PPID и вычисленное значение переменной а (в каждом из процессов !!!) */ printf("My pid = %d, my ppid = %d, result = %d\n",

(int)pid, (int)ppid, a); return 0;

Наберите эту программу, откомпилируйте ее и запустите на исполнение (лучше всего это делать не из оболочки тс, так как она не очень корректно сбрасывает буферы ввода-вывода). Проанализируйте полученный результат.

Системный вызов fork() (продолжение)

Для того чтобы после возвращения из системного вызова fork () процессы могли определить, кто из них является ребенком, а кто родителем, и, соответственно, по-разному организовать свое поведение, системный вызов возвращает в них разные значения. При успешном создании нового процесса в процесс-родитель возвращается положительное значение, равное идентификатору процесса-ребенка. В процесс-ребенок же возвращается значение 0. Если по какой-либо причине создать новый процесс не удалось, то системный вызов вернет в инициировавший его процесс значение --1. Таким образом, общая схема организации различной работы процесса-ребенка и процесса-родителя выглядит так:

pid = fork(); if(pid == -1){

/* ошибка */ } else if (pid == 0) {

/* ребенок */ } else {

/* родитель */

}

Написание, компиляция и запуск программы с использованием вызова fork() с разным поведением процессов ребенка и родителя

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

Завершение процесса. Функция exit()

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

Возврата из функции в текущий процесс не происходит и функция ничего не возвращает.

Значение параметра функции exit {) -- кода завершения процесса -- передается ядру операционной системы и может быть затем получено процессом, породившим завершившийся процесс. На самом деле при достижении конца функции main() также неявно вызывается эта функция со значением параметра 0.

Функция для нормального завершения процесса Прототип функции

¦include <stdlib.h> void exitfint status);

Описание функции

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

Возврата из функции в текущий процесс не происходит, и функция ничего не возвращает.

Значение параметра status - кода завершения процесса - передается ядру операционной системы и может быть затем получено процессом, породившим завершившийся процесс. При этом используются только младшие 8 бит параметра, так что для кода завершения допустимы значения от 0 до 255. По соглашению, код завершения 0 означает безошибочное завершение процесса.

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

Параметры функции main() в языке С. Переменные среды и аргументы командной строки

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

int main(int argc, char *argv[], char *envp[]);

Первые два параметра при запуске программы на исполнение командной строкой позволяют узнать полное содержание командной строки. Вся командная строка рассматривается как набор слов, разделенных пробелами. Через параметр argc передается количество слов в командной строке, которой была запущена программа. Параметр argv является массивом указателей на отдельные слова. Так, например, если программа была запущена командой

а.out 12 abed

то значение параметра argc будет равно 3, argv [ 0 ] будет указывать на имя программы -- первое слово -- "a.out", argv[l] -- на слово "12", argv[2] -- на слово "abed". Так как имя программы всегда присутствует на первом месте в командной строке, то argc всегда больше О, a argv [ 0 ] всегда указывает на имя запущенной программы.

Анализируя в программе содержимое командной строки, мы можем предусмотреть ее различное поведение в зависимости от слов, следующих за именем программы. Таким образом, не внося изменений в текст программы, мы можем заставить ее работать по-разному от запуска к запуску. Например, компилятор дес, вызванный командой gec 1. с будет генерировать исполняемый файл с именем a. out, а при вызове командой gec 1.с -о 1. ехе -- файл с именем 1. ехе.

Третий параметр -- envp -- является массивом указателей на параметры окружающей среды процесса. Начальные параметры окружающей среды процесса задаются в специальных конфигурационных файлах для каждого пользователя и устанавливаются при входе пользователя в систему. В дальнейшем они могут быть изменены с помощью специальных команд операционной системы UNIX. Каждый параметр имеет вид: перемен-ная=строка. Такие переменные используются для изменения долгосрочного поведения процессов, в отличие от аргументов командной строки. Например, задание параметра TERM=vtlOO может говорить процессам, осуществляющим вывод на экран дисплея, что работать им придется с терминалом vtlOO. Меняя значение переменной среды TERM, например на TERM=console, мы сообщаем таким процессам, что они должны изменить свое поведение и осуществлять вывод для системной консоли.

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

Написание, компиляция и запуск программы, распечатывающей аргументы командной строки и параметры среды

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

Изменение пользовательского контекста процесса. Семейство функций для системного вызова ехес()

Для изменения пользовательского контекста процесса применяется системный вызов exec (), который пользователь не может вызвать непосредственно. Вызов exec () заменяет пользовательский контекст текущего процесса на содержимое некоторого исполняемого файла и устанавливает начальные значения регистров процессора (в том числе устанавливает программный счетчик на начало загружаемой программы). Этот вызов требует для своей работы задания имени исполняемого файла, аргументов командной строки и параметров окружающей среды. Для осуществления вызова программист может воспользоваться одной из шести функций: execlp (), execvp (), execl(), execv (), execle(), execve (), отличающихся друг от друга представлением параметров, необходимых для работы системного вызова exec (). Взаимосвязь указанных выше функций изображена на рисунке 3-4.3.

I 1

execlp(file,arg,...,0)

execl(path,arg,...,0)

execle(path,arg,...,0,envp)

Формирование массива argv

Формирование массива argv

execvp(file,argv)

execv(path,argv)

execve(path,arg,envp)

Преобразование файла Добавление

в путь параметров среды

Рис. 3-4.3. Взаимосвязь различных функций для выполнения системного вызова exec ()

Функции изменения пользовательского контекста процесса Прототипы функций

#include <unistd.h>

int execlp(const char *file, const char *argO,

... const char *argN,(char *)NULL) int execvp(const char *file, char *argv[]) int execl(const char *path, const char *argO,

... const char *argN,(char *)NULL) int execv(const char *path, char *argv[]) int execle(const char *path, const char *argO,

... const char *argN,(char *)NULL, char * envp[]) int execve(const char *path, char *argv{], char *envp[])

Описание функций

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

Аргумент file является указателем на имя файла, который должен быть загружен. Аргумент path - это указатель на полный путь к файлу, который должен быть загружен.

Аргументы argO, ..., argN представляют собой указатели на аргументы командной строки. Заметим, что аргумент argO должен указывать на имя загружаемого файла. Аргумент argv представляет собой массив из указателей на аргументы командной строки. Начальный элемент массива должен указывать на имя загружаемой программы, а заканчиваться массив должен элементом, содержащим указатель NULL.

Аргумент envp является массивом указателей на параметры окружающей среды, заданные в виде строк <переменная=строка». Последний элемент этого массива должен содержать указатель NULL.

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

идентификатор процесса;

идентификатор родительского процесса;

групповой идентификатор процесса; ¦ идентификатор сеанса;

время, оставшееся до возникновения сигнала SIGALRM; .

текущую рабочую директорию;

маску создания файлов;

идентификатор пользователя;

групповой идентификатор пользователя;

* явное игнорирование сигналов;

* таблицу открытых файлов (если для файлового дескриптора не устанавливался признак «закрыть файл при выполнении exec ()»).

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

Поскольку системный контекст процесса при вызове exec () остается практически неизменным, большинство атрибутов процесса, доступных пользователю через системные вызовы (PID, UID, GID, PPID и другие, смысл которых станет понятен по мере углубления наших знаний на дальнейших занятиях), после запуска новой программы также не изменяется.

Важно понимать разницу между системными вызовами fork() и exec (). Системный вызов fork() создает новый процесс, у которого пользовательский контекст совпадает с пользовательским контекстом процесса-родителя. Системный вызов exec () изменяет пользовательский контекст текущего процесса, не создавая новый процесс.

Прогон программы с использованием системного вызова exec ()

Для иллюстрации использования системного вызова exec () давайте рассмотрим следующую программу:

/* Программа 03-2.с, изменяющая пользовательский контекст процесса (запускающая другую программу) */ #include <sys/types.h> #include <unistd.h> #include <stdio.h>

int main(int argc, char *argv[], char *envp[]){

/* Мы будем запускать команду cat с аргументом командной строки 03-2.с без изменения параметров среды, т.е. фактически выполнять команду "cat 03-2.с", которая должна выдать содержимое данного файла на экран. Для функции execle в качестве имени программы мы указываем ее полное имя с путем от корневой директории --/bin/cat. Первое слово в командной строке у нас должно совпадать с именем запускаемой программы. Второе слово в командной строке - это имя файла, содержимое которого мы хотим распечатать. */ (void) execle("/bin/cat", "/bin/cat", "03-2.c", О, envp);

/* Сюда попадаем только при возникновении ошибки */ printf("Error on program start\n"); exit (-lb-return 0; /* Никогда не выполняется, нужен для

того, чтобы компилятор не выдавал

warning */

}

Откомпилируйте ее и запустите на исполнение. Поскольку при нормальной работе будет распечатываться содержимое файла с именем 03-2.с, такой файл при запуске должен присутствовать в текущей директории (проще всего записать исходный текст программы под этим именем). Проанализируйте результат.

Написание, компиляция и запуск программы для изменения пользовательского контекста в порожденном процессе

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

Семинар 5. Организация взаимодействия процессов через pipe и FIFO в UNIX

Понятие потока ввода-вывода. Представление о работе с файлами через системные вызовы и стандартную библиотеку ввода-вывода. Понятие файлового дескриптора. Открытие файла. Системный вызов open (). Системные вызовы close (), read (), write (). Понятие pipe. Системный вызов pipe (). Организация связи через pipe между процессом-родителем и процессом-потомком. Наследование файловых дескрипторов при вызовах fork () и exec (). Особенности поведения вызовов read () и write () для pip'a. Понятие FIFO. Использование системного вызова mknod () для создания FIFO. Функция mkf if о (). Особенности поведения вызова open () при открытии FIFO.

Ключевые слова: поток ввода-вывода, файловый дескриптор, таблица открытых файлов процесса, стандартный поток ввода, стандартный поток вывода, стандартный поток вывода для ошибок, открытие файла, закрытие файла, pipe, FIFO, файл типа FIFO, системные вызовы open, read, write, close, pipe, mknod, mkf if o.

Понятие о потоке ввода-вывода

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

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

Понятие о работе с файлами через системные вызовы и стандартную библиотеку ввода-вывода для языка С

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

Как мы надеемся, из курса программирования на языке С вам известны функции работы с файлами из стандартной библиотеки ввода-вывода, такие как fopen(), fread(), fwrite(), fprintf(),fscanf(), f gets () и т. д. Эти функции входят как неотъемлемая часть в стандарт ANSI на язык С и позволяют программисту получать информацию из файла или записывать ее в файл при условии, что программист обладает определенными знаниями о содержимом передаваемых данных. Так, например, функция fgets () используется для ввода из файла последовательности символов, заканчивающейся символом '\п -- перевод каретки. Функция f scanf () производит ввод информации, соответствующей заданному формату, и т.д. С точки зрения потоковой модели операции, определяемые функциями стандартной библиотеки ввода-вывода, не являются потоковыми операциями, так как каждая из них требует наличия некоторой структуры передаваемых данных.

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

Файловый дескриптор

В лекции 2 мы говорили, что информация о файлах, используемых процессом, входит в состав его системного контекста и хранится в его блоке управления -- РСВ. В операционной системе UNIX можно упрощенно полагать, что информация о файлах, с которыми процесс осуществляет операции потокового обмена, наряду с информацией о потоковых линиях связи, соединяющих процесс с другими процессами и устройствами ввода-вывода, хранится в некотором массиве, получившем название таблицы открытых файлов или таблицы файловых дескрипторов. Индекс элемента этого массива, соответствующий определенному потоку ввода-вывода, получил название файлового дескриптора для этого потока. Таким образом, файловый дескриптор представляет собой небольшое целое неотрицательное число, которое для текущего процесса в данный момент времени однозначно определяет некоторый действующий канал ввода-вывода. Некоторые файловые дескрипторы на этапе старта любой программы ассоциируются со стандартными потоками ввода-вывода. Так, например, файловый дескриптор 0 соответствует стандартному потоку ввода, файловый дескриптор 1 -- стандартному потоку вывода, файловый дескриптор 2 -- стандартному потоку для вывода ошибок. В нормальном интерактивном режиме работы стандартный поток ввода связывает процесс с клавиатурой, а стандартные потоки вывода и вывода ошибок -- с текущим терминалом.

Более детально строение структур данных, содержащих информацию о потоках ввода-вывода, ассоциированных с процессом, мы будем рассматривать позже, при изучении организации файловых систем в UNIX (семинары 11-12 и 13-14).

Открытие файла. Системный вызов ореп()

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

Системный вызов open Прототип системного вызова

Mnclude <fcntl.h>

int open(char *path, int flags);

int open(char *path, int flags, int mode);

Описание системного вызова

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

Параметр path является указателем на строку, содержащую полное или относительное имя файла.

Параметр flags может принимать одно из следующих трех значений:

0_RD0NLY - если над файлом в дальнейшем будут совершаться только операции чтения;

0_WR0NLY - если над файлом в дальнейшем будут осуществляться только операции записи;

0_RDWR - если над файлом будут осуществляться и операции чтения, и операции записи.

Каждое из этих значений может быть скомбинировано посредством операции «побитовое или (|)»с одним или несколькими флагами:

0_CREAT - если файла с указанным именем не существует, он должен быть создан;

0_EXCL - применяется совместно с флагом 0_CREAT. При совместном их использовании и существовании файла с указанным именем, открытие файла не производится и констатируется ошибочная ситуация;

QJJDELAY - запрещает перевод процесса в состояние ъ ъ при выполнении операции открытия и любых последующих операциях над этим файлом;

0_APPEND - при открытии файла и перед выполнением каждой операции записи (если она, конечно, разрешена) указатель текущей позиции в файле устанавливается на конец файла;

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

0_SYNC - любая операция записи в файл будет блокироваться (т. е. процесс будет переведен в состояние L L ) до тех пор, пока записанная информация не будет физически помещена на соответсвующий нижестоящий уровень hardware;

0_NOCTTY - если имя файла относится к терминальному устройству, оно не становится управляющим терминалом процесса, даже если до этого процесс не имел управляющего терминала.

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

0400 - разрешено чтение для пользователя, создавшего файл;

0200 - разрешена запись для пользователя, создавшего файл;

0100 - разрешено исполнение для пользователя, создавшего файл;

0040 - разрешено чтение для группы пользователя, создавшего файл;

0020 - разрешена запись для группы пользователя, создавшего файл;

0010 - разрешено исполнение для группы пользователя, создавшего файл;

0004 - разрешено чтение для всех остальных пользователей;

0002 - разрешена запись для всех остальных пользователей;

0001 - разрешено исполнение для всех остальных пользователей.

При создании файла реально устанавливаемые права доступа получаются из стандартной комбинации параметра mode и маски создания файлов текущего процесса umask, а именно -они равны mode & ~umask.

При открытии файлов типа FIFO системный вызов имеет некоторые особенности поведения по сравнению с открытием файлов других типов, Если FIFO открывается только для чтения, и не задан флаг 0_NDELAY, то процесс, осуществивший системный вызов, блокируется до тех пор, пока какой-либо другой процесс не откроет FIFO на запись. Бели флаг 0JTOELAY задан, то возвращается значение файлового дескриптора, ассоциированного с RFO. Бели RFO открывает--! ся только для записи, и не задан флаг 0_NDELAY, то процесс, осуществивший системный вызов, блокируется до тех пор, пока какой-либо другой процесс не откроет FIFO на чтение. Боли флаг 0JJDELAY задан, то констатируется возникновение ошибки и возвращается значение -1.

Возвращаемое значение

Системный вызов возвращает значение файлового дескриптора для открытого файла при нормальном завершении и значение -1 при возникновении ошибки.

Системный вызов open () использует набор флагов для того, чтобы специфицировать операции, которые предполагается применять к файлу в дальнейшем или которые должны быть выполнены непосредственно в момент открытия файла. Из всего возможного набора флагов на текущем уровне знаний нас будут интересовать только флаги 0_RDONLY, 0_WRONLY, 0_RDWR( 0_CREAT и 0_EXCL. Первые три флага являются взаимоисключающими: хотя бы один из них должен быть применен и наличие одного из них не допускает наличия двух других. Эти флаги описывают набор операций, которые, при успешном открытии файла, будут разрешены над файлом в дальнейшем: только чтение, только запись, чтение и запись. Как вам известно из материалов семинаров 1--2, у каждого файла существуют атрибуты прав доступа для различных категорий пользователей. Если файл с заданным именем существует на диске, и права доступа к нему для пользователя, от имени которого работает текущий процесс, не противоречат запрошенному набору операций, то операционная система сканирует таблицу открытых файлов от ее начала к концу в поисках первого свободного элемента, заполняет его и возвращает индекс этого элемента в качестве файлового дескриптора открытого файла. Если файла на диске нет, не хватает прав или отсутствует свободное место в таблице открытых файлов, то констатируется возникновение ошибки.

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

В случае, когда мы требуем, чтобы файл на диске отсутствовал и был создан в момент открытия, флаг для набора операций должен использоваться в комбинации с флагами 0_CREAT и 0_EXCL.

Подробнее об операции открытия файла и ее месте среди набора всех файловых операций будет рассказываться на лекции 7 «Файловая система с точки зрения пользователя». Работу системного вызова open () с флагами 0_APPEND и 0_TRUNC мы разберем на семинарах 11--12, посвященных организации файловых систем в UNIX.

Системные вызовы read(), write(), close()

Для совершения потоковых операций чтения информации из файла и ее записи в файл применяются системные вызовы read () и write ().

Системные вызовы read и write Прототипы системных вызовов

#include <sys/types.h> Sinclude <unistd.h>

size_t readfint fd, void *addr, size_t nbytes); size_t write(int fd, void *addr, size_t nbytes);

Описание системных вызовов

Системные вызовы read и write предназначены для осуществления потоковых операций ввода (чтения) и вывода (записи) информации над каналами связи, описываемыми файловыми дескрипторами, т.е. для файлов, pipe, FIFO и socket.

Параметр f d является файловым дескриптором созданного ранее потокового канала связи, через который будет отсылаться или получаться информация, т. е. значением, которое вернул один из системных вызовов open {), pipe () или socket ().

Параметр addr представляет собой адрес области памяти, начиная с которого будет браться информация для передачи или размещаться принятая информация.

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

Возвращаемые значения

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

Особенности поведения при работе с файлами

При работе с файлами информация записывается в файл или читается из файла, начиная с места, определяемого указателем текущей позиции в файле. Значение указателя увеличивается на количество реально прочитанных или записанных байт. При чтении информации из файла она не пропадает из него. Если системный вызов read возвращает значение 0, то это означает, что файл прочитан до конца.

Мы сейчас не акцентируем внимание на понятии указателя текущей позиции в файле и взаимном влиянии значения этого указателя и поведения системных вызовов. Этот вопрос будет обсуждаться в дальнейшем на семинарах 11 -- 12.

После завершения потоковых операций процесс должен выполнить операцию закрытия потока ввода-вывода, во время которой произойдет окончательный сброс буферов на линии связи, освободятся выделенные ресурсы операционной системы, и элемент таблицы открытых файлов, соответствующий файловому дескриптору, будет отмечен как свободный. За эти действия отвечает системный вызов close (). Надо отметить, что при завершении работы процесса (см. семинар 3--4) с помощью явного или неявного вызова функции exit() происходит автоматическое закрытие всех открытых потоков ввода-вывода.

Системный вызов close Прототип системного вызова

#include <unistd.h> int close(int fd);

Описание системного вызова

Системный вызов close предназначен для корректного завершения работы с файлами и другими объектами ввода-вывода, которые описываются в операционной системе через файловые дескрипторы: pipe, FIFO, socket.

Параметр f d является дескриптором соответствующего объекта, т. е. значением, которое вернул один из системных вызовов open (), pipe () или socket ().

Возвращаемые значения

Системный вызов возвращает значение 0 при нормальном завершении и значение -1 при возникновении ошибки.

Прогон программы для записи информации в файл

Для иллюстрации сказанного давайте рассмотрим следующую программу:

/* Программа 05-1-е, иллюстрирующая использование

системных вызовов open(), write() и close() для

записи информации в файл*/

ttinclude <sys/types.h>

ttinclude <fcntl.h>

ttinclude <stdio.h>

int main(){

int fd;

s i z e_t size;

char string[] = "Hello, world!";

/* Обнуляем маску создания файлов текущего процесса для того, чтобы права доступа у создаваемого файла точно соответствовали параметру вызова ореп() */ (void)umask(0);

/* Попытаемся открыть файл с именем myfile в текущей директории только для операций вывода. Если файла не существует, попробуем его создать с правами доступа 0666, т. е. read-write для всех категорий пользователей */

if((fd = open("myfile", 0_WRONLY I 0_CREAT, 0666)) < 0) {

/* Если файл открыть не удалось, печатаем об этом сообщение и прекращаем работу */ printf("Can\'t open file\n"); exit(-1) ;

}

/* Пробуем записать в файл 14 байт из нашего массива, т.е. всю строку "Hello, world!" вместе с признаком конца строки */ size = write(fd, string, 14); if(size != 14){

/* Если записалось меньшее количество байт,

сообщаем об ошибке */

printf("Can\'t write all string\n");

exit(-1);

/* Закрываем файл */ if (close (fd)- < 0) {

printf("Can\'t close file\n");

}

return 0 ;

}

Наберите, откомпилируйте эту программу и запустите ее на исполнение. Обратите внимание на использование системного вызова umask () с параметром 0 для того, чтобы права доступа к созданному файлу точно соответствовали указанным в системном вызове open ().

Написание, компиляция и запуск программы для чтения информации из файла

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

Понятие о pipe. Системный вызов pipe()


Подобные документы

  • Основные понятия об операционных системах. Виды современных операционных систем. История развития операционных систем семейства Windows. Характеристики операционных систем семейства Windows. Новые функциональные возможности операционной системы Windows 7.

    курсовая работа [60,1 K], добавлен 18.02.2012

  • Понятие виртуальной памяти, ее реализация. Особенности страничной организации по требованию. Этапы обработки ситуации отсутствия страницы в памяти. Стратегии (алгоритмы) замещения страниц. Особенности некоторых операционных систем: Windows NT и Solaris.

    презентация [2,2 M], добавлен 24.01.2014

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

    презентация [1,3 M], добавлен 22.04.2014

  • Общая характеристика преимуществ взаимодействующих процессов: модульность, ускорение вычислений. Знакомство с основами современных операционных систем. Анализ особенностей использования общего почтового ящика, рассмотрение способов создания и удаления.

    презентация [1,6 M], добавлен 24.01.2014

  • Характеристика сущности, назначения, функций операционных систем. Отличительные черты их эволюции. Особенности алгоритмов управления ресурсами. Современные концепции и технологии проектирования операционных систем, требования, предъявляемые к ОС XXI века.

    курсовая работа [36,4 K], добавлен 08.01.2011

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

    реферат [16,6 K], добавлен 25.02.2011

  • Важность операционной системы для мобильных устройств. Популярность операционных систем. Доля LINUX на рынке операционных систем. История OS Symbian, BlackBerry OS, Palm OS. Отличия смартфона от обычного мобильного телефона. Учет ограничений по памяти.

    презентация [477,3 K], добавлен 01.12.2015

  • Основные понятия операционных систем. Современное оборудование компьютера. Преимущества и недостатки операционной системы Linux. Функциональные возможности операционной системы Knoppix. Сравнительная характеристика операционных систем Linux и Knoppix.

    реферат [1,5 M], добавлен 17.12.2014

  • Основные понятия операционных систем. Синхронизация и критические области. Сигналы и взаимодействие между процессами. Управление памятью. Драйверы устройств. Особенности современных операционных систем. Центральный процессор, микросхемы часов и таймеров.

    учебное пособие [1,2 M], добавлен 24.01.2014

  • Использование операционных систем Microsoft Windows. Разработка операционной системы Windows 1.0. Возможности и характеристика последующих версий. Выпуск пользовательских операционных систем компании, доработки и нововведения, версии Windows XP и Vista.

    реферат [23,3 K], добавлен 10.01.2012

Работы в архивах красиво оформлены согласно требованиям ВУЗов и содержат рисунки, диаграммы, формулы и т.д.
PPT, PPTX и PDF-файлы представлены только в архивах.
Рекомендуем скачать работу.