Search     or:     and:
 LINUX 
 Language 
 Kernel 
 Package 
 Book 
 Test 
 OS 
 Forum 
iakovlev.org

Inter-Process Communication

By Hiran Ramankutty

Обзор

Inter-Process Communication - или IPC - облегчает взаимодействие между процессами. Рассмотрим простой пример - пусть у нас 2 полных стакана , в одном горячая вода , в другом холодная. Что нужно сделать для того , чтобы в обоих стаканах вода стала одинаковая ? Нужен еще один стакан бОльшей емкости для смешивания . В мире программ необходим механизм для взаимодействия между процессами. Как взаимодействуют процессы ? Каждый процесс работает в собственном адресном пространстве. Ядро в данном случае может выступить каналом между этими обособленными участками памяти. Оно по аналогии с предыдущим примером играет роль стакана бОльшей емкости .

Основы IPC

IPC-механизм можно разбить на следующие категории :
  1. pipes
  2. fifos
  3. shared memory
  4. mapped memory
  5. message queues
  6. 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 параллельно с другим,который туда пишет. Но этот же механизм можно использовать для процессов,которые выпоняются не обязательно параллельно во времени.
Оставьте свой комментарий !

Ваше имя:
Комментарий:
Оба поля являются обязательными

 Автор  Комментарий к данной статье