Inter-Process Communication
By Hiran Ramankutty
Обзор
Inter-Process Communication - или IPC - облегчает взаимодействие между процессами.
Рассмотрим простой пример - пусть у нас 2 полных стакана , в одном горячая вода , в другом холодная.
Что нужно сделать для того , чтобы в обоих стаканах вода стала одинаковая ?
Нужен еще один стакан бОльшей емкости для смешивания .
В мире программ необходим механизм для взаимодействия между процессами.
Как взаимодействуют процессы ? Каждый процесс работает в собственном адресном пространстве.
Ядро в данном случае может выступить каналом между этими обособленными участками памяти.
Оно по аналогии с предыдущим примером играет роль стакана бОльшей емкости .
Основы IPC
IPC-механизм можно разбить на следующие категории :
- pipes
- fifos
- shared memory
- mapped memory
- message queues
- sockets
Pipes
Пайпы присутствуют в любой юникс-системе . Они обеспечивают передачу данных в одном направлении .
Пайп создается системным вызовом pipe , который создает пару файловых дескрипторов .
В этой паре filedes[0] используется для чтения и filedes[1] для записи.
Рассмотрим программу , которая читает ввод с клавиатуры .
Создадим 2 процесса : один будет читать эти символы , другой будет их проверять .
/***** KEYBOARD HIT PROGRAM *****/
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <pthread.h>
#include <ctype.h>
int filedes[2];
void *read_char()
{
char c;
printf("Entering routine to read character.........\n");
while(1) {
/* Get a character in 'c' except '\n'. */
c = getchar();
if(c == '\n')
c = getchar();
write(filedes[1], &c, 1);
if(isalnum(c)) {
sleep(2);
exit(1);
}
}
}
void *check_hit()
{
char c;
printf("Entering routine to check hit.........\n");
while(1) {
read(filedes[0], &c, 1);
if(isalnum(c)) {
printf("The key hit is %c\n", c);
exit(1);
} else {
printf("key hit is %c\n", c);
}
}
}
int main()
{
int i;
pthread_t tid1, tid2;
pipe(filedes);
/* Create thread for reading characters. */
i = pthread_create(&tid1, NULL, read_char, NULL);
/* Create thread for checking hitting of any keyboard key. */
i = pthread_create(&tid2, NULL, check_hit, NULL);
if(i == 0) while(1);
return 0;
}
Компиляция программы - cc filename.c -lpthread.
read_char читает символ и пишет его в filedes[1].
Нам нужен тред check_hit, который проверяет этот символ , и если он символьно-числовой,то программа прекращается .
Что происходит при системном вызове pipe ?
Ядро поддерживает пайпы через файловую систему .
Создав пру файловых дескрипторов и разместив их в специальной таблице , ядро позволяет пользователю
использовать по отношению к ним обычные файловые операции на чтение-запись,
причем один из них только на чтение и другой только на запись.
FIFO
FIFOs (first in, first out - первый входит-первый выходит) похож на пайп.
Отличие в том , что фифо-файл имеет имя . Поэтому можно сказать , что фифо - это именованный пайп.
Другое отличие в том , что пайпы живут внутри процесса , который их создал,а фифо - внутри всей системы.
Посмотрим , как фифо можно приспособить для предыдущей задачи .
/***** PROGRAM THAT READS ANY KEY HIT OF THE KEYBOARD*****/
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <pthread.h>
#include <ctype.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
extern int errno;
void *read_char()
{
char c;
int fd;
printf("Entering routine to read character.........\n");
while(1) {
c = getchar();
fd = open("fifo", O_WRONLY);
if(c == '\n')
c = getchar();
write(fd, &c, 1);
if(isalnum(c)) {
exit(1);
}
close(fd);
}
}
int main()
{
int i;
pthread_t tid1;
i = mkfifo("fifo", 0666);
if(i < 0) {
printf("Problems creating the fifo\n");
if(errno == EEXIST) {
printf("fifo already exists\n");
}
printf("errno is set as %d\n", errno);
}
i = pthread_create(&tid1, NULL, read_char, NULL);
if(i == 0) while(1);
return 0;
}
Откомпилируем - cc -o write_fifo filename.c.
Программа читает нажимаемые символы и пишет их в файл fifo.
Для создания fifo используется функция mkfifo.
Тред read_char читает символы с клавиатуры.
fifo открыт с O_WRONLY (write only) флагом .
Прочитанный символ при этом дописывается в конец fifo.
/***** KEYBOARD HIT PROGRAM *****/
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <pthread.h>
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/stat.h>
extern int errno;
void *check_hit()
{
char c;
int fd;
int i;
printf("Entering routine to check hit.........\n");
while(1) {
fd = open("fifo", O_RDONLY);
if(fd < 0) {
printf("Error opening in fifo\n");
printf("errno is %d\n", errno);
continue;
}
i = read(fd, &c, 1);
if(i < 0) {
printf("Error reading fifo\n");
printf("errno is %d\n", errno);
}
if(isalnum(c)) {
printf("The key hit is %c\n", c);
exit(1);
} else {
printf("key hit is %c\n", c);
}
}
}
int main()
{
int i;
i = mkfifo("fifo", 0666);
if(i < 0) {
printf("Problems creating the fifo\n");
if(errno == EEXIST) {
printf("fifo already exists\n");
}
printf("errno is set as %d\n", errno);
}
pthread_t tid2;
i = pthread_create(&tid2, NULL, check_hit, NULL);
if(i == 0) while(1);
return 0;
}
Эту программу откомпилируйте как cc -o detect_hit filename.c.
Теперь запустите 2 последних программы в разных терминалах,но в одном каталоге.
Наберите в первом терминале фразу . Ее же вы прочитаете во 2-м терминале.
То же самое можно проделать и внутри одной программы.
2 программы были созданы специально , чтобы показать , что фифо можно использовать для общения между разными процессами.
Если выйти из обоих программ и потом запустить 2-ю,будет получено сообщение,которое было набрано в прошлый раз.
Это говорит о том,что фифо живет на протяжении жизни всей системы.
Недостатком фифо является то,что их можно использовать только на одной машине.
Shared Memory
Расшареная память - это один из 3 основных механизмов System V IPC.
System V IPC можно описать в 4 шага:
- Инициализация идентификатора shared memory get - shmget
- Инициализация идентификатора shared memory attach - shmat
- Отсоединение памяти после использования - shmdt
- Финальный контроль shared memory control - shmctl
Напишем еще один вариант программы выше.В ней мы создадим участок памяти, в которой будет храниться информация о нажатых клавишах.
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <errno.h>
#include <string.h>
#include <ctype.h>
extern int errno;
#define SIZE 1
char *read_key;
int shmid;
int shared_init()
{
if((shmid = shmget(9999, SIZE, IPC_CREAT | 0666)) < 0) {
printf("Error in shmget. errno is: %d\n", errno);
return -1;
}
if((read_key = shmat(shmid, NULL, 0)) < 0) {
printf("Error in shm attach. errno is: %d\n", errno);
return -1;
}
return 0;
}
void read_char()
{
char c;
while(1) {
c = getchar();
if(c == '\n') {
c = getchar();
}
strncpy(read_key, &c, SIZE);
printf("read_key now is %s\n", read_key);
if(isalnum(*read_key)) {
shmdt(read_key);
shmctl(shmid, IPC_RMID, NULL);
exit(1);
}
}
}
int main()
{
if(shared_init() < 0) {
printf("Problems with shared memory\n");
exit(1);
}
read_char();
return 0;
}
Обратите внимание на переменную,названную read_key,которая инициализирует
создаваемую shared memory,это делается с помощью системного вызова shmget.
1-й параметр которой-9999,это ключ.Второй-SIZE=1.Указывает на размер хранимой информации.
3-й параметр - флаг IPC_CREAT указывает на read-write permissions.
Возвращается участок памяти,и его идентификатор хранится в errno.
Ключ генерится произвольно с помощью ftok.
Далее shared memory segment приаттачивается к конкретному адресу с помощью shmat system call,
который в качестве 1-го параметра использует сегментный идентификатор shmid.
2-й параметр - адрес и равен NULL для того,чтобы ядро само выбрало память.
После выделения памяти мы вызываем вызывается read_char и прочитанный символ копируется в выделенную память.
Теперь напишем другую программу , которую можно запустить вообще из другого каталога и которая будет читать данные из shared memory.
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <errno.h>
#include <string.h>
#include <ctype.h>
extern int errno;
#define SIZE 1
char *detect_key;
int shmid;
int shared_init()
{
if((shmid = shmget(9999, SIZE, 0444)) < 0) {
printf("Error in shmget. errno is: %d\n", errno);
return -1;
}
if((detect_key = shmat(shmid, NULL, SHM_RDONLY)) < 0) {
printf("Error in shm attach. errno is: %d\n", errno);
return -1;
}
// detect_key = NULL;
return 0;
}
void detect_hit()
{
char c;
c = *detect_key;
while(1) {
if(c != *detect_key) {
if(isalnum(detect_key[0])) {
printf("detect_key is %s\n", detect_key);
shmdt(detect_key);
shmctl(shmid, IPC_RMID, NULL);
exit(1);
} else {
printf("detect_key is %s\n", detect_key);
}
c = *detect_key;
}
}
}
int main()
{
if(shared_init() < 0) {
printf("Problems with shared memory\n");
exit(1);
}
detect_hit();
return 0;
}
В этой программе флаг IPC_CREAT указывает на то,что shared memory не создается.
Вместо этого имеется идентификатор,который приаттачивается к уже существующей.
Режим 0444 ограничивает доступ как 'read only'.
Ищется shared memory segment с ключом 9999.
Функция detect_hit проверяет нажимаемую клавишу.
Сначала запускаем 1-ю программу,потом 2-ю,которая выдаст ошибку инициализации,которую игнорируем.
Путь /proc/sysvipc/shm дает список задействованных shared mermory.
Описанный механизм предполагает , что один процесс читает из shared memory
параллельно с другим,который туда пишет.
Но этот же механизм можно использовать для процессов,которые выпоняются не обязательно параллельно во времени.
|