Взаимодействие процессов

Виды межпроцессного взаимодействия в Linux

Межпроцессное взаимодействие (IPC, Inter-Process Communication) может быть локальным или сетевым.

Виды IPC:

  • файлы (обмен данными через совместно используемые файлы)
  • сигналы (некий аналог процессорных прерываний)
  • каналы (pipes)
    • именованные
    • неименованные (перенаправление потоков ввода-вывода через | )
  • очереди сообщений (mq_open, mq_send, ...)
  • механизм разделямой памяти, shared memory (самый быстрый способ - диапазон адресов отображается на адресное пространство двух процессов, когда процесс изменяет память в этом участке - он одновременно изменяет память второго процесса)
  • сокеты
  • POSIX-семафоры
  • RPC (Remote Procedure Call) - man rpc
  • IPC (SystemV)

Каналы

Первоначально каналы были только неименованными. Передать дескриптор канала можно было только процессу-потомку. Для передачи данных через канал требуется в одном процессе получить дескриптор на чтение, в другом - на запись. Канал является односторонним, для двустороннего обмена данными требуется создавать два канала.

Для создания неименованного канала используется функция pipe, pipefd[0] будет дескриптором для чтения, pipefd[1] - для записи (пример рабочей программы есть в man-справке):

#include <unistd.h>

int pipe(int pipefd[2]);

Чтобы читать вывод команд, которые обычно используются в консоли, или передавать им данные на вход, используется функция popen, которая принимает команду для запуска другого приложения, тип канала (на запись или чтение) и возвращает дескриптор канала:

#include <stdio.h>

FILE *popen(const char *command, const char *type);
int pclose(FILE *stream);

Именованные каналы обладают именем в пространстве файловых имен. Для создания используется функция mkfifo:

#include <sys/types.h>
#include <sys/stat.h>

int mkfifo(const char *pathname, mode_t mode);

Сигналы

Каждый сигнал обладает номером и сигнатурой, которые определены в signal.h. Сигналы работают по принципу прерываний. Когда приложению поступает сигнал - оно обязано его сразу обработать. В каждом процессе есть обработчики сигналов. Стандартные обработчики добавляются в программу в процессе линковки. Часть сигналов может быть обработана собственными обработчиками, часть переопределить нельзя.

  • SIGINT (2)
  • SIGABRT (6) (man abort)
  • SIGTERM (15) (необязательная к исполнению, CTRL+C)
  • SIGKILL (9) (не переопределяется, прибивает процесс)
  • SIGCHILD (17) (при завершении потомка, см. waitpid/wait)

Сигналы используются для управления демонами. Для работы с сигналами используются две функции: kill отправляет процессу сигнал, signal определяет, как его обработать.

Из командной строки утилита kill отправяет сигнал процессу. kill -l выводит список сигналов. Среди них есть пользовательские SIGUSR1 и SIGUSR2, которые зарезервированы под использование пользователем и под которые можно писать свои обработчики.

Отправка сигнала с помощью функции kill:

#include <sys/types.h>
#include <signal.h>

int kill(pid_t pid, int sig);

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

#include <signal.h>

typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);

Пример обработки сигнала SIGINT:

#include <signal.h>
#include <stdio.h>
#include <unistd.h>

void mysignal_handler(int signalno)
{
    printf("Called from signal %d\n", signalno);
}

int main()
{    
    signal(SIGINT, mysignal_handler);
    int counter = 0;
    while(1)
    {
        printf("Hello %d\n", counter++);
        usleep(500000);
    }
    return 0;
}

Разделяемая память

Каждый процесс при создании получает одинаковое адресное пространство - диапазон адресов от 0 до ffffffff на 32-битной системе. В ОС есть механизм виртуальной памяти, который отображает память процесса частично на физическую память, частично на диск. Распределение адресного пространства процесса (memory layout) устроено так: "Сверху" в сторону уменьшения адресов располагается ядро, под которое зарезервирован 1 Гб адресного пространства. Снизу от младших адресов к старшим располагается процесс: сегмент кода, потом сегмент данных, диапазон адресов для кучи. От ядра в сторону уменьшения адресов растёт стек. Между кучей и стеком находится сегмент разделямой памяти, используемый для IPC.

Ядро ~1 Gb
Стек
Разделяемая память
Куча
Данные
Код

Для работы с разделяемой памятью служат функции:

  • shmget позволяет получать или создавать регион памяти
  • shmat позволяет подключаться к региону памяти
  • shmdt отключает от региона памяти
  • shmctl управляет параметрами регионов разделяемой памяти

Создание региона памяти с помощью shmget: в качестве параметров принимаются глобальный ключ key для доступа к региону, size округляется вверх до размера страницы памяти.

#include <sys/ipc.h>
#include <sys/shm.h>

int shmget(key_t key, size_t size, int shmflg);

Для получения информации о созданных регионах shared-памяти используется утилита ipcs.

Мультиплексирование ввода-вывода

results matching ""

    No results matching ""