Все построение операционной системы UNIX основано на использовании концепции процессов, которая обсуждалась на лекции. Контекст процесса складывается из
malloc(), calloc(), realloc () ).Исполняемый код и инициализируемые данные составляют содержимое файла программы, который исполняется в контексте процесса. Пользовательский стек применяется при работе процесса в пользовательском режиме (user-mode).
(рис 3-4.1) Контекст процесса в UNIXUID, групповой идентификатор пользователя — GID,
kernel при старте операционной системы. Этот номер впоследствии не может быть присвоен никакому другому процессу. Максимально возможное значение для номера процесса в Linux на базе 32-разрядных процессоров Intel составляет 231-1.
Модель состояний процессов в операционной системе UNIX представляет собой детализацию модели состояний, принятой в лекционном курсе. Краткая диаграмма состояний процессов в операционной системе UNIX изображена на рисунке 3-4.2.
(рис 3-4.2) Сокращенная диаграмма состояний процесса в UNIXКак мы видим, состояние процесса исполнение расщепилось на два состояния:
Приведенная выше диаграмма состояний процессов в UNIX не является полной. Она показывает только состояния, для понимания которых достаточно уже полученных знаний. Пожалуй, наиболее полную диаграмму состояний процессов в операционной системе UNIX можно найти в книге (рисунок 6.1.).
В операционной системе UNIX все процессы, кроме одного, создающегося при старте операционной системы, могут быть порождены только какими-либо другими процессами. В качестве прародителя всех остальных процессов в подобных UNIX системах могут выступать процессы с номерами 1 или 0. В операционной системе Linux таким родоначальником, существующим только при загрузке системы, является процесс kernel с идентификатором 0.
Таким образом, все процессы в UNIX связаны отношениями процесс-родитель – процесс-ребенок и образуют генеалогическое дерево процессов. Для сохранения целостности генеалогического дерева в ситуациях, когда процесс-родитель завершает свою работу до завершения выполнения процесса-ребенка, 1, соответствующее init, время жизни которого определяет время функционирования операционной системы. Тем самым процесс init как бы усыновляет осиротевшие процессы. Наверное, логичнее было бы заменять 1, а на значение идентификатора ближайшего существующего процесса-прародителя умершего процесса-родителя,
но в UNIX почему-то такая схема реализована не была.
Данные ядра, находящиеся в <sys/types.h> и <unistd.h>. Системные вызовы не имеют параметров и возвращают
Системные вызовы getpid() и getppid() Прототипы системных вызовов #include <sys/types.h> #include <unistd.h> pid_t getpid(void); pid_t getppid(void); Описание системных вызовов Системный вызов Системный вызов Тип данных |
В качестве примера использования системных вызовов и самостоятельно напишите программу, печатающую значения и для текущего процесса. Запустите ее несколько раз подряд. Посмотрите, как меняется идентификатор текущего процесса. Объясните наблюдаемые изменения.
. При этом вновь созданный процесс будет являться практически полной копией родительского процесса
PID ; PPID .Дополнительно может измениться поведение порожденного процесса по отношению к некоторым сигналам, о чем подробнее будет рассказано на семинарах 13–14, когда мы будем говорить о сигналах в операционной системе UNIX.
Системный вызов для порождения нового процесса Прототип системного вызова #include <sys/types.h> #include <unistd.h> pid_t fork(void); Описание системного вызова Системный вызов SIGALRM ;При однократном системном вызове возврат из него может произойти дважды: один раз в родительском процессе, а второй раз в порожденном процессе. Если создание нового процесса произошло успешно, то в порожденном процессе системный вызов вернет значение Системный вызов |
В процессе выполнения системного вызова порождается копия родительского процесса и возвращение из системного вызова будет происходить уже как в родительском, так и в порожденном процессах. Этот системный вызов является единственным, который вызывается один раз, а при успешной работе возвращается два раза (один раз в процессе-родителе и один раз в процессе-ребенке)! После выхода из системного вызова оба процесса продолжают выполнение регулярного пользовательского кода, следующего за системным вызовом.
Для иллюстрации сказанного давайте рассмотрим следующую программу:
/* Программа 03-1.с – пример создания нового
процесса с одинаковой работой процессов
ребенка и родителя */
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
int main()
{
pid_t pid, ppid;
int a = 0;
(void)fork();
/* При успешном создании нового процесса
с этого места псевдопараллельно
начинают работать два процесса: старый
и новый */
/* Перед выполнением следующего выражения
значение переменной a в обоих процессах
равно 0 */
a = a+1;
/* Узнаем идентификаторы текущего и роди-
тельского процесса (в каждом из
процессов !!!) */
pid = getpid();
ppid = getppid();
/* Печатаем значения PID, PPID и вычислен-
ное значение переменной a (в каждом из
процессов !!!) */
printf("My pid = %d, my ppid = %d,
result = %d\n", (int)pid, (int)ppid, a);
return 0;
}
Наберите эту программу, откомпилируйте ее и запустите на исполнение (лучше всего это делать не из оболочки mc, так как она не очень корректно сбрасывает буферы ввода-вывода). Проанализируйте полученный результат.
Для того чтобы после возвращения из системного вызова процессы могли определить, кто из них является ребенком, а кто родителем, и, соответственно, по-разному организовать свое поведение, системный вызов возвращает в них разные значения. При успешном создании нового процесса в процесс-родитель возвращается положительное значение, равное идентификатору процесса-ребенка. В процесс-ребенок же возвращается значение 0. Если по какой-либо причине создать новый процесс не удалось, то системный вызов вернет в инициировавший его процесс значение -1. Таким образом, общая схема организации различной работы процесса-ребенка и процесса-родителя выглядит так:
pid = fork();
if(pid == -1){
...
/* ошибка */
...
} else if (pid == 0){
...
/* ребенок */
...
} else {
...
/* родитель */
...
}
Измените предыдущую программу с так, чтобы родитель и ребенок совершали разные действия (какие – не важно).
Существует два способа корректного завершения процесса в программах, написанных на языке C. Первый способ мы использовали до сих пор: процесс корректно завершался по достижении конца функции main() или при выполнении оператора return в функции main(), второй способ применяется при необходимости завершить процесс в каком-либо другом месте программы. Для этого используется функция из стандартной библиотеки функций для языка C.
Возврата из функции в текущий процесс не происходит и функция ничего не возвращает.
Значение параметра функции – кода завершения процесса – передается ядру операционной системы и может быть затем получено процессом, породившим завершившийся процесс. На самом деле при достижении конца функции main() также неявно вызывается эта функция со значением параметра 0.
Функция для нормального завершения процесса Прототип функции #include <stdlib.h> void exit(int status); Описание функции Функция Возврата из функции в текущий процесс не происходит, и функция ничего не возвращает. Значение параметра |
Если процесс завершает свою работу раньше, чем его родитель, и родитель явно не указал, что он не хочет получать информацию о статусе zombie, defunct ).
У функции main() в языке программирования C существует три main() выглядит следующим образом:
int main(int argc, char *argv[],
char *envp[]);
Первые два передается количество слов в командной строке, которой была запущена программа. Параметр является массивом указателей на отдельные слова. Так, например, если программа была запущена командой
a.out 12 abcd
то значение параметра будет равно 3, argv[0] будет указывать на имя программы — первое слово — "a.out", argv[1] — на слово "12", argv[2] — на слово "abcd". Так как имя программы всегда присутствует на первом месте в командной строке, то argc всегда больше 0, а argv[0] всегда указывает на имя запущенной программы.
Анализируя в программе gcc, вызванный командой gcc 1.c будет генерировать исполняемый файл с именем a.out, а при вызове командой gcc 1.c –o 1.exe – файл с именем 1.exe.
Третий параметр – – является массивом указателей на параметры окружающей среды процесса. Начальные параметры окружающей среды процесса задаются в специальных конфигурационных файлах для каждого пользователя и устанавливаются при входе пользователя в систему. В дальнейшем они могут быть изменены с помощью специальных команд операционной системы UNIX. Каждый параметр имеет вид: переменная=строка. Такие переменные используются для изменения долгосрочного поведения процессов, в отличие от TERM=vt100 может говорить процессам, осуществляющим вывод на экран дисплея, что работать им придется с терминалом vt100. Меняя значение TERM, например на TERM=console, мы сообщаем таким процессам,
что они должны изменить свое поведение и осуществлять вывод для
Размер массива мы получали в качестве ее параметра. Так как для массива ссылок на параметры окружающей среды такого параметра нет, то его размер определяется другим способом. Последний элемент этого массива содержит указатель NULL.
В качестве примера самостоятельно напишите программу, распечатывающую значения
, который пользователь не может вызвать непосредственно заменяет , , и, , , , отличающихся друг от друга представлением параметров, необходимых для работы системного вызова . Взаимосвязь указанных выше функций изображена на рисунке 3–4.3.
(рис 3-4.3) Взаимосвязь различных функций для выполнения системного вызова exec()Функции изменения пользовательского контекста процесса Прототипы функций #include <unistd.h>
int execlp(const char *file,
const char *arg0,
... const char *argN,(char *)NULL)
int execvp(const char *file, char *argv[])
int execl(const char *path,
const char *arg0,
... const char *argN,(char *)NULL)
int execv(const char *path, char *argv[])
int execle(const char *path,
const char *arg0,
... const char *argN,(char *)NULL,
char * envp[])
int execve(const char *path, char *argv[],
char *envp[])
Описание функций Для загрузки новой программы в Аргумент Аргументы Аргумент Поскольку вызов функции не изменяет SIGALRM ; ").В случае успешного выполнения возврата из функций в программу, осуществившую вызов, не происходит, а управление передается загруженной программе. В случае неудачного выполнения в программу, инициировавшую вызов, возвращается отрицательное значение. |
Поскольку остается практически неизменным, большинство , UID, GID, и другие, смысл которых станет понятен по мере углубления наших знаний на дальнейших занятиях), после запуска новой программы также не изменяется.
Важно понимать разницу между системными вызовами и . Системный вызов создает новый процесс, у которого пользовательский контекст совпадает с пользовательским контекстом процесса-родителя. Системный вызов изменяет пользовательский контекст текущего процесса, не создавая новый процесс.
Для иллюстрации использования системного вызова давайте рассмотрим следующую программу
/* Программа 03-2.с, изменяющая пользователь-
ский контекст процесса (запускающая
другую программу) */
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
int main(int argc, char *argv[],
char *envp[]){
/* Мы будем запускать команду cat c аргументом
командной строки 03-2.с без изменения
параметров среды, т.е. фактически выполнять
команду "cat 03-2.c", которая должна выдать
содержимое данного файла на экран. Для
функции execle в качестве имени программы
мы указываем ее полное имя с путем от
корневой директории —/bin/cat.
Первое слово в командной строке у нас
должно совпадать с именем запускаемой
программы. Второе слово в командной строке
– это имя файла, содержимое которого мы
хотим распечатать. */
(void) execle("/bin/cat", "/bin/cat",
"03-2.c", 0, envp);
/* Сюда попадаем только при
возникновении ошибки */
printf("Error on program start\n");
exit(-1);
return 0; /* Никогда не выполняется, нужен
для того, чтобы компилятор не
выдавал warning */
}
Откомпилируйте ее и запустите на исполнение. Поскольку при нормальной работе будет распечатываться содержимое файла с именем 03-2.c, такой файл при запуске должен присутствовать в текущей директории (проще всего записать исходный текст программы под этим именем). Проанализируйте результат.
Для закрепления полученных знаний модифицируйте программу, созданную при выполнении задания раздела "Написание, компиляция и запуск программы с использованием вызова с разным поведением процессов ребенка и родителя" так, чтобы порожденный процесс запускал на исполнение новую (любую) программу.
Все построение операционной системы UNIX основано на использовании концепции процессов, которая обсуждалась на лекции. Контекст процесса складывается из
malloc(), calloc(), realloc () ).Исполняемый код и инициализируемые данные составляют содержимое файла программы, который исполняется в контексте процесса. Пользовательский стек применяется при работе процесса в пользовательском режиме (user-mode).
(рис 3-4.1) Контекст процесса в UNIXUID, групповой идентификатор пользователя — GID,
kernel при старте операционной системы. Этот номер впоследствии не может быть присвоен никакому другому процессу. Максимально возможное значение для номера процесса в Linux на базе 32-разрядных процессоров Intel составляет 231-1.
Модель состояний процессов в операционной системе UNIX представляет собой детализацию модели состояний, принятой в лекционном курсе. Краткая диаграмма состояний процессов в операционной системе UNIX изображена на рисунке 3-4.2.
(рис 3-4.2) Сокращенная диаграмма состояний процесса в UNIXКак мы видим, состояние процесса исполнение расщепилось на два состояния:
Приведенная выше диаграмма состояний процессов в UNIX не является полной. Она показывает только состояния, для понимания которых достаточно уже полученных знаний. Пожалуй, наиболее полную диаграмму состояний процессов в операционной системе UNIX можно найти в книге (рисунок 6.1.).
В операционной системе UNIX все процессы, кроме одного, создающегося при старте операционной системы, могут быть порождены только какими-либо другими процессами. В качестве прародителя всех остальных процессов в подобных UNIX системах могут выступать процессы с номерами 1 или 0. В операционной системе Linux таким родоначальником, существующим только при загрузке системы, является процесс kernel с идентификатором 0.
Таким образом, все процессы в UNIX связаны отношениями процесс-родитель – процесс-ребенок и образуют генеалогическое дерево процессов. Для сохранения целостности генеалогического дерева в ситуациях, когда процесс-родитель завершает свою работу до завершения выполнения процесса-ребенка, 1, соответствующее init, время жизни которого определяет время функционирования операционной системы. Тем самым процесс init как бы усыновляет осиротевшие процессы. Наверное, логичнее было бы заменять 1, а на значение идентификатора ближайшего существующего процесса-прародителя умершего процесса-родителя,
но в UNIX почему-то такая схема реализована не была.
Данные ядра, находящиеся в <sys/types.h> и <unistd.h>. Системные вызовы не имеют параметров и возвращают
Системные вызовы getpid() и getppid() Прототипы системных вызовов #include <sys/types.h> #include <unistd.h> pid_t getpid(void); pid_t getppid(void); Описание системных вызовов Системный вызов Системный вызов Тип данных |
В качестве примера использования системных вызовов и самостоятельно напишите программу, печатающую значения и для текущего процесса. Запустите ее несколько раз подряд. Посмотрите, как меняется идентификатор текущего процесса. Объясните наблюдаемые изменения.
. При этом вновь созданный процесс будет являться практически полной копией родительского процесса
PID ; PPID .Дополнительно может измениться поведение порожденного процесса по отношению к некоторым сигналам, о чем подробнее будет рассказано на семинарах 13–14, когда мы будем говорить о сигналах в операционной системе UNIX.
Системный вызов для порождения нового процесса Прототип системного вызова #include <sys/types.h> #include <unistd.h> pid_t fork(void); Описание системного вызова Системный вызов SIGALRM ;При однократном системном вызове возврат из него может произойти дважды: один раз в родительском процессе, а второй раз в порожденном процессе. Если создание нового процесса произошло успешно, то в порожденном процессе системный вызов вернет значение Системный вызов |
В процессе выполнения системного вызова порождается копия родительского процесса и возвращение из системного вызова будет происходить уже как в родительском, так и в порожденном процессах. Этот системный вызов является единственным, который вызывается один раз, а при успешной работе возвращается два раза (один раз в процессе-родителе и один раз в процессе-ребенке)! После выхода из системного вызова оба процесса продолжают выполнение регулярного пользовательского кода, следующего за системным вызовом.
Для иллюстрации сказанного давайте рассмотрим следующую программу:
/* Программа 03-1.с – пример создания нового
процесса с одинаковой работой процессов
ребенка и родителя */
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
int main()
{
pid_t pid, ppid;
int a = 0;
(void)fork();
/* При успешном создании нового процесса
с этого места псевдопараллельно
начинают работать два процесса: старый
и новый */
/* Перед выполнением следующего выражения
значение переменной a в обоих процессах
равно 0 */
a = a+1;
/* Узнаем идентификаторы текущего и роди-
тельского процесса (в каждом из
процессов !!!) */
pid = getpid();
ppid = getppid();
/* Печатаем значения PID, PPID и вычислен-
ное значение переменной a (в каждом из
процессов !!!) */
printf("My pid = %d, my ppid = %d,
result = %d\n", (int)pid, (int)ppid, a);
return 0;
}
Наберите эту программу, откомпилируйте ее и запустите на исполнение (лучше всего это делать не из оболочки mc, так как она не очень корректно сбрасывает буферы ввода-вывода). Проанализируйте полученный результат.
Для того чтобы после возвращения из системного вызова процессы могли определить, кто из них является ребенком, а кто родителем, и, соответственно, по-разному организовать свое поведение, системный вызов возвращает в них разные значения. При успешном создании нового процесса в процесс-родитель возвращается положительное значение, равное идентификатору процесса-ребенка. В процесс-ребенок же возвращается значение 0. Если по какой-либо причине создать новый процесс не удалось, то системный вызов вернет в инициировавший его процесс значение -1. Таким образом, общая схема организации различной работы процесса-ребенка и процесса-родителя выглядит так:
pid = fork();
if(pid == -1){
...
/* ошибка */
...
} else if (pid == 0){
...
/* ребенок */
...
} else {
...
/* родитель */
...
}
Измените предыдущую программу с так, чтобы родитель и ребенок совершали разные действия (какие – не важно).
Существует два способа корректного завершения процесса в программах, написанных на языке C. Первый способ мы использовали до сих пор: процесс корректно завершался по достижении конца функции main() или при выполнении оператора return в функции main(), второй способ применяется при необходимости завершить процесс в каком-либо другом месте программы. Для этого используется функция из стандартной библиотеки функций для языка C.
Возврата из функции в текущий процесс не происходит и функция ничего не возвращает.
Значение параметра функции – кода завершения процесса – передается ядру операционной системы и может быть затем получено процессом, породившим завершившийся процесс. На самом деле при достижении конца функции main() также неявно вызывается эта функция со значением параметра 0.
Функция для нормального завершения процесса Прототип функции #include <stdlib.h> void exit(int status); Описание функции Функция Возврата из функции в текущий процесс не происходит, и функция ничего не возвращает. Значение параметра |
Если процесс завершает свою работу раньше, чем его родитель, и родитель явно не указал, что он не хочет получать информацию о статусе zombie, defunct ).
У функции main() в языке программирования C существует три main() выглядит следующим образом:
int main(int argc, char *argv[],
char *envp[]);
Первые два передается количество слов в командной строке, которой была запущена программа. Параметр является массивом указателей на отдельные слова. Так, например, если программа была запущена командой
a.out 12 abcd
то значение параметра будет равно 3, argv[0] будет указывать на имя программы — первое слово — "a.out", argv[1] — на слово "12", argv[2] — на слово "abcd". Так как имя программы всегда присутствует на первом месте в командной строке, то argc всегда больше 0, а argv[0] всегда указывает на имя запущенной программы.
Анализируя в программе gcc, вызванный командой gcc 1.c будет генерировать исполняемый файл с именем a.out, а при вызове командой gcc 1.c –o 1.exe – файл с именем 1.exe.
Третий параметр – – является массивом указателей на параметры окружающей среды процесса. Начальные параметры окружающей среды процесса задаются в специальных конфигурационных файлах для каждого пользователя и устанавливаются при входе пользователя в систему. В дальнейшем они могут быть изменены с помощью специальных команд операционной системы UNIX. Каждый параметр имеет вид: переменная=строка. Такие переменные используются для изменения долгосрочного поведения процессов, в отличие от TERM=vt100 может говорить процессам, осуществляющим вывод на экран дисплея, что работать им придется с терминалом vt100. Меняя значение TERM, например на TERM=console, мы сообщаем таким процессам,
что они должны изменить свое поведение и осуществлять вывод для
Размер массива мы получали в качестве ее параметра. Так как для массива ссылок на параметры окружающей среды такого параметра нет, то его размер определяется другим способом. Последний элемент этого массива содержит указатель NULL.
В качестве примера самостоятельно напишите программу, распечатывающую значения
, который пользователь не может вызвать непосредственно заменяет , , и, , , , отличающихся друг от друга представлением параметров, необходимых для работы системного вызова . Взаимосвязь указанных выше функций изображена на рисунке 3–4.3.
(рис 3-4.3) Взаимосвязь различных функций для выполнения системного вызова exec()Функции изменения пользовательского контекста процесса Прототипы функций #include <unistd.h>
int execlp(const char *file,
const char *arg0,
... const char *argN,(char *)NULL)
int execvp(const char *file, char *argv[])
int execl(const char *path,
const char *arg0,
... const char *argN,(char *)NULL)
int execv(const char *path, char *argv[])
int execle(const char *path,
const char *arg0,
... const char *argN,(char *)NULL,
char * envp[])
int execve(const char *path, char *argv[],
char *envp[])
Описание функций Для загрузки новой программы в Аргумент Аргументы Аргумент Поскольку вызов функции не изменяет SIGALRM ; ").В случае успешного выполнения возврата из функций в программу, осуществившую вызов, не происходит, а управление передается загруженной программе. В случае неудачного выполнения в программу, инициировавшую вызов, возвращается отрицательное значение. |
Поскольку остается практически неизменным, большинство , UID, GID, и другие, смысл которых станет понятен по мере углубления наших знаний на дальнейших занятиях), после запуска новой программы также не изменяется.
Важно понимать разницу между системными вызовами и . Системный вызов создает новый процесс, у которого пользовательский контекст совпадает с пользовательским контекстом процесса-родителя. Системный вызов изменяет пользовательский контекст текущего процесса, не создавая новый процесс.
Для иллюстрации использования системного вызова давайте рассмотрим следующую программу
/* Программа 03-2.с, изменяющая пользователь-
ский контекст процесса (запускающая
другую программу) */
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>
int main(int argc, char *argv[],
char *envp[]){
/* Мы будем запускать команду cat c аргументом
командной строки 03-2.с без изменения
параметров среды, т.е. фактически выполнять
команду "cat 03-2.c", которая должна выдать
содержимое данного файла на экран. Для
функции execle в качестве имени программы
мы указываем ее полное имя с путем от
корневой директории —/bin/cat.
Первое слово в командной строке у нас
должно совпадать с именем запускаемой
программы. Второе слово в командной строке
– это имя файла, содержимое которого мы
хотим распечатать. */
(void) execle("/bin/cat", "/bin/cat",
"03-2.c", 0, envp);
/* Сюда попадаем только при
возникновении ошибки */
printf("Error on program start\n");
exit(-1);
return 0; /* Никогда не выполняется, нужен
для того, чтобы компилятор не
выдавал warning */
}
Откомпилируйте ее и запустите на исполнение. Поскольку при нормальной работе будет распечатываться содержимое файла с именем 03-2.c, такой файл при запуске должен присутствовать в текущей директории (проще всего записать исходный текст программы под этим именем). Проанализируйте результат.
Для закрепления полученных знаний модифицируйте программу, созданную при выполнении задания раздела "Написание, компиляция и запуск программы с использованием вызова с разным поведением процессов ребенка и родителя" так, чтобы порожденный процесс запускал на исполнение новую (любую) программу.
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.