Описание

#include <sys/shm.h>

int shmctl(int shmid, int command, struct shmid ds *shm_stat);

Этот вызов в точности соответствует вызову msgctl, и параметр command мо­жет, наряду с другими, принимать значения IPC_STAT, IPC_SET и IPC_RMID. В следующем примере этот вызов будет использован с аргументом command рав­ным IPC_RMID.

Пример работы с разделяемой памятью: программа shmcopy

В этом разделе создадим простую программу shmcopy для демонстрации прак­тического использования разделяемой памяти. Программа shmcopy просто копи­рует данные со своего стандартного ввода на стандартный вывод, но позволяет избежать лишних простоев в вызовах read и write. При запуске программы shmcopy создаются два процесса, один из которых выполняет чтение, а другой - запись, и которые совместно используют два буфера, реализованные в виде сег­ментов разделяемой памяти. Когда первый процесс считывает данные в первый буфер, второй записывает содержимое второго буфера, и наоборот. Так как чтение и запись выполняются одновременно, пропускная способность возрастает. Этот подход используется, например, в программах, которые выводят информа­цию на ленточный накопитель.

Для согласования двух процессов (чтобы записывающий процесс не писал в буфер до тех пор, пока считывающий процесс его не заполнит) будем использо­вать два семафора. Почти во всех программах, использующих разделяемую па­мять, требуется дополнительная синхронизация, так как механизм разделяемой памяти не содержит собственных средств синхронизации.

Программа shmcopy использует следующий заголовочный файл share_ex.h: /* Заголовочный файл для примера работы с разделяемой памятью */

#include <stdio.h>

#include <signal.h>

#include <sys/types.h>

#include <sys/ipc.h>

#include <sys/shm.h>

#include <sys/sem.h>

#define SHMKEY1 (key_t)0x10 /* Ключ разделяемой памяти */

#define SHMKEY2 (key_t)0x15 /* Ключ разделяемой памяти */

#define SEMKEY (key_t)0x20 /* Ключ семафора */

/* Размер буфера для чтения и записи */

#define SIZ 5*BUFSIZ

/* В этой структуре будут находиться данные и счетчик чтения */

struct databuf

{

int d_nread;

char d_buf[SIZ];

};

typedef union semun

{

int val;

struct semid_ds *buf;

ushort *array;

} semun;

Напомним, что постоянная BUFSIZ определена в файле <stdio.h> и задает оптимальный размер порций данных при работе с файловой системой. Шаблон databuf показывает структуру, которая связывается с каждым сегментом раз­деляемой памяти. В частности, элемент d_nread позволит процессу, выполняю­щему чтение, передавать другому, осуществляющему запись, через участок разде­ляемой памяти число считанных символов.

Следующий файл содержит процедуры для инициализации двух участков раз­деляемой памяти и набора семафоров. Он также содержит процедуру remobj, ко­торая удаляет различные объекты межпроцессного взаимодействия в конце рабо­ты программы. Обратите внимание на способ вызова shmat для подключения участков разделяемой памяти к адресному пространству процесса.

/* Процедуры инициализации */

#include "share_ex.h"

#define IFLAGS (IPC_CREAT | IPC_EXCL)

#define ERR ((struct databuf *) -1)

static int shmid1, shmid2, semid;

void getseg(struct databuf **p1, struct databuf **p2)

{

/* Создать участок разделяемой памяти */

if((shmid1 = shmget(SHMKEY1, sizeof(struct databuf),

0600 | IFLAGS)) == -1)

fatal("shmget");

if((shmid2 = shmget(SHMKEY2, sizeof(struct databuf),

0600 | IFLAGS)) == -1)

fatal("shmget");

/* Подключить участки разделяемой памяти */

if((*p1 = (struct databuf *)shmat(shmid1,0,0)) == ERR)

fatal ("shmat");

if((*p2 = (struct databuf *)shmat(shmid2,0,0)) == ERR)

fatal ("shmat");

}

int getsem(void) /* Получить набор семафоров */

{

union semun x;

x.val = 0;

/* Создать два набора семафоров */

if((semid = semget(SEMKEY, 2, 0600 | IFLAGS)) == -1)

fatal("semget");

/* Задать начальные значения */

if(semctl(semid, 0, SETVAL, x) == -1)

fatal("semctl");

if(semctl(semid, 1, SETVAL, x) == -1)

fatal("semctl");

return(semid);

}

/* Удалить идентификаторы разделяемой памяти

и идентификатор набора семафоров */

void remobj(void)

{

if(shmctl(shmid1, IPC_RMID, NULL) == -1)

fatal("shmctl");

if(shmctl(shmid2, IPC_RMID, NULL) == -1)

fatal("shmctl");

if(semctl(semid, 0, IPC_RMID, NULL) == -1)

fatal("semctl");

}

Ошибки в этих процедурах обрабатываются при помощи процедуры fatal, ко­торая использовалась в предыдущих примерах. Она просто вызывает perror, а затем exit.

Ниже следует функция main для программы strcopy. Она вызывает процедуры инициализации, а затем создает процесс для чтения (родительский) и для за­писи (дочерний). Обратите внимание на то, что именно выполняющий запись процесс вызывает процедуру remobj при завершении программы.

/* Программа shmcopy - функция main */

#nclude "share_ex.h"

main()

{

int semid;

pid_t pid;

struct databuf *buf1, *buf2;

/* Инициализация набора семафоров */

semid = getsem();

/* Создать и подключить участки разделяемой памяти */

getseg(&buf1, &buf2);

switch (pid = fork()){

case -1:

fatal("fork");

case 0: /* Дочерний процесс */

writer(semid, buf1, buf2);

remobj();

break;

default: /* Родительский процесс */

reader(semid, buf1, buf2);

break;

}

exit (0);

}

Функция main создает объекты межпроцессного взаимодействия до вызова fork. Обратите внимание на то, что адреса, определяющие сегменты разделяемой памяти (которые находятся в переменных buf1 и buf 2), будут заданы в обоих про­цессах.

Процедура reader принимает данные со стандартного ввода, то есть из дескриптора файла 0, и является первой функцией, представляющей интерес. Ей пе­редается идентификатор набора семафоров в параметре semid и адреса двух уча­стков разделяемой памяти в переменных buf1 и buf2.

/* Процедура reader - выполняет чтение из файла */

#include "share_ex.h"

/* Определения процедур р() и v() для двух семафоров */

struct sembuf p1 = {0,-1,0}, p2 = {1,-1,0};

struct sembuf v1 = {0,1,0}, v2 = {1,1,0};

void reader(int semid, struct databuf *buf1,

struct databuf *buf2)

{

for(;;)

{

/* Считать в буфер buf1 */

buf1->d_nread = read(0, buf1->d_buf, SIZ);

/* Точка синхронизации */

semop(semid, &v1, 1);

semop(semid, &p2, 1);

/* Чтобы процедура writer не была приостановлена */

if (buf1->d_nread <=0)

return;

buf2->d_nread = read(0, buf2->d_buf, SIZ);

semop(semid, &v2, 1);

semop(semid, &p1, 1);

if (buf2->d_nread <=0)

return;

}

}

Структуры sembuf просто определяют операции p () и v () для набора из двух семафоров. Но на этот раз они используются не для блокировки критических участков кода, а для синхронизации процедур, выполняющих чтение и запись. Процедура reader использует операцию v2 для сообщения о том, что она завершила чтение и ожидает, вызвав semop с параметром p1, пока процедура writer не сообщит о завершении записи. Это станет более очевидным при описании проце­дуры writer. Возможны другие подходы, включающие или четыре бинарных се­мафора, или семафоры, имеющие более двух значений.

Последней процедурой, вызываемой программой shmcopy, является процедура writer:

/* Процедура writer - выполняет запись */

#include "share_ex.h"

extern struct sembuf p1, p2; /* Определены в reader.с */

extern struct sembuf v1, v2; /* Определены в reader.с */

void writer(int semid, struct databuf *buf1, struct databuf *buf2)

{

for(;;)

{

semop(semid, &p1, 1);

semop(semid, &v2, 1);

if(buf1->d_nread <= 0)

return;

write(1, buf1->d_buf, buf1->d_nread);

semop(semid, &p2, 1);

semop(semid, &v1, 1);

if(buf2->d_nread <= 0)

return;

write(1, buf2->d_buf, buf2->d_nread);

}

}

И снова следует обратить внимание на использование набора семафоров для согласования работы процедур reader и writer. На этот раз процедура writer использует операцию v2 для сигнализации и ждет p1. Важно также отметить, что значения bufl->d_nread и buf2->d_nread устанавливаются процессом, выполняющим чтение.

После компиляции можно использовать программу shmcopy при помощи по­добной команды:

$ shmcopy < big > /tmp/big.

Потоки


Понравилась статья? Добавь ее в закладку (CTRL+D) и не забудь поделиться с друзьями:  



double arrow
Сейчас читают про: