121
выполняемого файла. Дополнительно, разрешается использовать и другие формы определения функции main(...):
void main (void); int main (void);
int main (int argc, char *argv [], char *envp []);
где envp — массив указателей на строки переменных среды ОС, заданных в формате:
Имя=Значение.
Поскольку все процессы ОС, кроме первого, создаются посредством системного вызова fork(...), то для запуска программ имеется набор системных функций семейства exec*(...):
#include <unistd.h> extern char **environ;
int execl (const char *path, const char *arg0, ..., (char *) 0); int execv (const char *path, char *const argv []);
int execle (const char *path, const char *arg0, ..., (char *)0, char *const envp []); int execve (const char *path, char *const argv [], char *const envp []);
int execlp (const char *file, const char *arg0, ..., (char *) 0); int execvp (const char *file, char *const argv []);
где внешняя переменная environ - указатель на массив указателей, которые адресуют переменные среды ОС, в виде: Имя=Значение
path — указатель на маршрутное имя файла, с новым образом процесса; file — указатель на имя файла загружаемой программы;
arg0, ..., - указатели на соответствующие аргументы, при вызове нового образа процесса; причем, последним в списке располагается пустой указатель, а аргумент arg0 должен указывать на имя файла-образа;
envp — то же, что и внешняя переменная environ.
Замечание.
Все массивы argv, envp и environ должны завершаться пустым указателем NULL.
Во многих системах разрабоки ОС Linux, как и в ОС УПК АСУ, вместо переменной environ используется переменная __environ.
Все функции семейства exec*(...):
•заменяют текущий образ процесса новым;
•случае успешного завершения, возврат в вызывающий процесс невозможен;
новый образ создается на основе выполнимого файла, который называется
файлом образа процесса;
•в случае ошибки, возвращаемым значением будет -1 и глобальной переменной errno будет присвоен код: (см. man execve).
122
Замечание
Файловые дескрипторы остаются открытыми в новом образе, если только они не были снабжены флагом FD_CLOEXEC.
Действующий идентификатор пользователя процесса переустанавливается равным идентификатору владельца файла (аналогично для группы).
Следующие атрибуты процесса остаются неизменными:
•идентификатор процесса;
•идентификатор родительского процесса;
•идентификатор группы процессов;
•членство в сеансе;
•реальные идентификаторы пользователя и группы процесса;
•идентификаторы дополнительных групп;
•текущий и корневой каталоги;
•маска режима создания файлов;
•атрибуты, связанные с обработкой сигналов.
Для демострации возможностей системных вызовов по смене образов процессов, рассмотрим задачу «Имитация конвейеров языка shell»:
•родителский процесс запрашивает ввод строки, содержащей не менее двух команд, например, ls -l /home/upk | grep d, а затем, - проводит формирование массивов указателей для аргументов вызова образов дочерних процессов;
•далее, родительский процесс создает массив каналов, число которых на
единицу меньше числа заявленных команд, и, в цикле по числу команд, запускает дочерние процессы, отслеживая их завершение;
•каждый дочерний процесс, в зависимости от своего номера, связывает свои
стандартные ввод и вывод с номером канала, - как уже было рассмотрено в программе на листинге 1.4;
•далее, дочерний процесс загружает новый образ, используя в качестве аргу-
ментов соотвествующий массив указателей, подготовленный для него родительским процессом, или аварийно завершается, в случае ошибки.
На листинге 3.5 приведен пример реализации такой программы.
Листинг 3.5 — Имитация конвейеров языка shell
#include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <sys/wait.h> #include <unistd.h>
// Функция, обрабатывающая вызов завершения процесса void exit_process(void){
printf("Обработчик exit(): Процесс pid = %i - завершается...\n", getpid());
}
// Основная программа
123
int main(int argc, char *argv[], char *envp[]) { puts("Проект lab9.5 ...");
//Вывод системных переменных
//Вариант 1
//char |
**p1 = |
__environ; |
// Раскомментируйте |
операторы, |
||
//while (*p1) |
puts(*p1++); |
// если |
хотите вывести |
системные переменные |
||
// Вариант 2 |
|
|
|
|
|
|
//char |
**p2 = envp; |
// Раскомментируйте |
операторы, |
|||
//while (*p2) |
puts(*p2++); |
// если |
хотите вывести |
системные переменные |
||
char buf[BUFSIZ]; |
// Буфер ввода командной |
строки |
|
|||
char msg[BUFSIZ]; |
// Буфер ввода сообщений |
для |
дочерних процессов |
|||
#define MAX_PROC 10 |
// |
Максимальное число возможных дочерних |
процессов |
|||
int |
fd[MAX_PROC - 1][2];// Максимальное число каналов |
|
||||
int |
N = 0; |
// |
Реальное число процессов: |
2 <= N+1 <= |
MAX_PROC |
|
char *parg[MAX_PROC][MAX_PROC + 1];// Аргументы для вызова дочернего процесса
pid_t pid, cpid;
int i = 0, j = 2, k = 0, status; ssize_t m;
puts("Введи командную строку, разделяя слова одним пробелом, и нажми Enter:"); if ((m = read(0, buf, BUFSIZ)) < 2){
puts("Ошибка: Командная строка должна быть больше 2-х символов..."); exit(EXIT_FAILURE);
}
buf[m-1] = '\0'; // Вместо символа перевода строки printf("Введена строка: %s\n", buf);
while(buf[i] != '\0') { // Разбор командной строки if (buf[i] == ' ') {
buf[i] = '\0';
if (j < 2) j = 1;
}
if ((buf[i] != '\0') && (j == 2)) { if (N >= (MAX_PROC - 1)) break; parg[N][0] = &buf[i];
j = 0; k = 1;
}
if (buf[i] == '|') { buf[i] = '\0'; parg[N][k] = NULL; N++;
j = 2;
}
if ((buf[i] != '\0') && (j == 1)) { if (k >= MAX_PROC) break; parg[N][k] = &buf[i];
k++; j = 0;
} |
|
i++; |
|
} |
|
parg[N][k] = NULL; |
// Для последней команды |
printf("Введено %i команды:\n", N + 1); for(i = 0; i <= N; i++) {
printf("%s -- %s\n", parg[i][0], parg[i][1]);
}
if ((N < 1) || (N >= MAX_PROC - 1)){
printf("Число команд дожно быть > 1 или <= %i", MAX_PROC); exit(EXIT_FAILURE);
}
124
printf("Создание %i дуплексных каналов...\n", N); for (i = 0; i < N; i++) {
if (pipe(fd[i]) == -1) { perror("pipe"); exit(EXIT_FAILURE);
}
}
printf("Запуск %i дочерних процессов:\n", N + 1); for (i=0; i<=N; i++) {
if ((pid = fork()) < 0) { perror("fork():"); exit(EXIT_FAILURE);
}
if (pid == 0) { // Дочерний процесс
if (i == 0) { |
// Первый - 0 |
dup2 (fd[i][1], 1); |
|
close(fd[i][0]); |
|
close(fd[i][1]); |
|
}else { |
|
if(i == N) { |
// Последний - N |
dup2 (fd[i |
- 1][0], 0); |
close(fd[i |
- 1][0]); |
close(fd[i |
- 1][1]); |
}else{ |
// Другие - i |
dup2 (fd[i |
- 1][0], 0); |
dup2 (fd[i |
][1], 1); |
close(fd[i |
- 1][0]); |
close(fd[i |
- 1][1]); |
close(fd[i |
][0]); |
close(fd[i |
][1]); |
} |
|
}
// Меняем тело процесса
if (execvp((char *)parg[i][0], parg[i]) < 0) {
j = sprintf(msg, "\nАварийное завершение процесса pid = %i", getpid()); msg[j] = '\0';
perror(msg);
}
exit(i + 1);
}
sleep(1); // Нужно дать поработать дочернему процессу
}
//puts("Ожидание завершения дочерних процессов:"); for (i = 0; i < N; i++) {
close(fd[i][1]); // Чтобы дочерний процесс ничего не ожидал
}
while((cpid = wait(&status)) > 0) { if (WIFEXITED(status)) {
printf(" Процесс pid = %i завершился со статусом = %i\n", cpid, WEXITSTATUS(status));
}
}
while(wait(NULL) > 0){ // Ожидание, если остались незавершенные процессы puts(".");
}
puts("Завершение проекта lab9.5 ..."); return EXIT_SUCCESS;
}
На рисунке 3.5 показан один из результатов взаимодействия двух команд через канал.
125
Замечание
При указании файлов не следует использовать шаблоны их имен!
Рисунок 3.5 — Пример результата вывода конкретных двух команд
Взаимодействие процессов посредством каналов, является мощным инструментальным средством ОС, но имеет ряд существенных недостатков:
•необходимость синхронизации операций чтения и записи в каналы для разных асинхнонно выполняющихся процессов;
•необходимость форматирования передаваемых в канал данных;
•необходимость дешифровки читаемых из канала данных.
Во многих современных ОС существует расширенная реализация понятия процесс, когда исполняемый образ программы представляет собой не только совокупность