Материал: 6262

Внимание! Если размещение файла нарушает Ваши авторские права, то обязательно сообщите нам

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 — Пример результата вывода конкретных двух команд

3.6 Нити (Threads)

Взаимодействие процессов посредством каналов, является мощным инструментальным средством ОС, но имеет ряд существенных недостатков:

необходимость синхронизации операций чтения и записи в каналы для разных асинхнонно выполняющихся процессов;

необходимость форматирования передаваемых в канал данных;

необходимость дешифровки читаемых из канала данных.

Во многих современных ОС существует расширенная реализация понятия процесс, когда исполняемый образ программы представляет собой не только совокупность

Источник: https://studfile.net/preview/16438530/