UNIX: взаимодействие процессов — страница 23 из 35

Приведем текст трех программ, измеряющих задержку при передаче сообщений по каналам, очередям Posix и очередям System V. Данные о производительности, полученные с их помощью, приведены в табл. А.1.

Программа измерения задержки канала

Программа для измерения задержки канала приведена в листинге А.14.

Листинг А.14. Программа измерения задержки канала

//bench/lat_pipe.c

1  #include "unpipc.h"


2  void

3  doit(int readfd, int writefd)

4  {

5   char c;

6   Write(writefd, &c, 1);

7   if (Read(readfd, &c, 1) != 1)

8    err_quit("read error");

9  }


10 int

11 main(int argc, char **argv)

12 {

13  int i, nloop, pipe1[2], pipe2[2];

14  char c;

15  pid_t childpid;

16  if (argc != 2)

17   err_quit("usage: lat_pipe <#loops>");

18  nloop = atoi(argv[1]);

19  Pipe(pipe1);

20  Pipe(pipe2);

21  if ((childpid = Fork()) == 0) {

22   for(;;) { /* дочерний процесс */

23    if (Read(pipe1[0], &c, 1) != 1)

24     err_quit("read error");

25    Write(pipe2[1], &c, 1);

26   }

27   ехit(0);

28  }

29  /* родительский процесс */

30  doit(pipe2[0], pipe1[1]);

31  Start_time();

32  for (i = 0; i < nloop; i++)

33   doit(pipe2[0], pipe1[1]);

34  printf("latency: %.3f usec\n", Stop_time() / nloop);

35  Kill(childpid, SIGTERM);

36  exit(0);

37 }

Функция doit

2-9 Эта функция запускается родительским процессом. Мы измеряем время ее работы. Она помещает 1 байт в канал, из которого читает дочерний процесс, и считывает 1 байт из другого канала, в который сообщение помещается дочерним процессом. При этом измеряется именно то, что мы назвали задержкой, — время передачи небольшого сообщения туда и обратно.

Создание каналов

19-20 Создаются два канала, после чего вызов fork порождает дочерний процесс. При этом образуется схема, изображенная на рис. 4.6 (но без закрытия неиспользуемых дескрипторов каналов). Для этого теста требуются два канала, поскольку каналы являются односторонними, а мы хотим передавать сообщение в обе стороны.

Дочерний процесс отсылает обратно сообщение

22-27 Дочерний процесс представляет собой бесконечный цикл, в котором однобайтовое сообщение считывается и отсылается обратно.

Измерение времени работы родительского процесса

29-34 Родительский процесс вызывает функцию doit для отправки однобайтового сообщения дочернему процессу и получения ответа. После этого мы имеем гарантию, что оба процесса выполняются. Затем функция doit вызывается в цикле с измерением времени задержки.

На компьютере Sparc под управлением Solaris 2.6 при запуске программы пять раз подряд мы получим вот что:

solaris % lat_pipe 10000

latency: 278.633 usec

solaris % lat_pipe 10000

latency: 397.810 usec

solaris % lat_pipe 10000

latency: 392.567 usec

solaris % lat_pipe 10000

latency: 266.572 usec

solaris % lat_pipe 10000

latency: 284.559 usec

Среднее для пяти попыток составляет 324 микросекунды, и именно это значение приведено в табл. А.1. Это время учитывает два переключения контекста (от родительского процесса к дочернему и обратно), четыре системных вызова (write, read, write, read) и затраты на передачу 1 байта данных по каналу.

Программа измерения задержки очередей сообщений Posix

Пpoгрaммa измерения задержки для очередей сообщений Posix приведена в листинге А.15.

Листинг А. 15. Программа измерения задержки для очереди сообщений Posix

//bench/lat_pxmsg.с

1  #include "unpipc.h"

2  #define NAME1 "lat_pxmsg1"

3  #define NAME2 "lat_pxmsg2"

4  #define MAXMSG 4 /* место для 4096 байт в очереди */

5  #define MSGSIZE 1024


6  void

7  doit(mqd_t mqsend, mqd_t mqrecv)

8  {

9   char buff[MSGSIZE];

10  Mq_send(mqsend, buff, 1.0);

11  if (Mq_receive(mqrecv, buff, MSGSIZE, NULL) != 1)

12   err_quit("mq_receive error");

13 }


14 int

15 main(int argc, char **argv)

16 {

17  int i, nloop;

18  mqd_t mq1, mq2;

19  char buff[MSGSIZE];

20  pid_t childpid;

21  struct mq_attr attr;

22  if (argc != 2)

23   err_quit("usage: lat_pxmsg <#loops>");

24  nloop = atoi(argv[1]);

25  attr.mq_maxmsg = MAXMSG;

26  attr.mq_msgsize = MSGSIZE;

27  mq1 = Mq_open(Px_ipc_name(NAME1), O_RDWR | O_CREAT, FILE_MODE, &attr);

28  mq2 = Mq_open(Px_ipc_name(NAME2), O_RDWR | O_CREAT, FILE_MODE, &attr);

29  if ((childpid = Fork()) == 0) {

30   for(;;) { /* дочерний процесс */

31    if (Mq_receive(mq1, buff, MSGSIZE, NULL) != 1)

32     err_quit("mq_receive error");

33    Mq_send(mq2, buff, 1.0);

34   }

35   exit(0);

36  }

37  /* родительский процесс */

38  doit(mq1, mq2);

39  Start_time();

40  for (i = 0; i < nloop; i++)

41   doit(mq1, mq2);

42  printf("latency: %.3f usec\n", Stop_time() / nloop);

43  Kill(childpid, SIGTERM);

44  Mq_close(mq1);

45  Mq_close(mq2);

46  Mq_unlink(Px_ipc_name(NAMED);

47  Mq_unlink(Px_ipc_name (NAME2));

48  exit(0);

49 }

25-28 Создаются две очереди сообщений, каждая из которых используется для передачи данных в одну сторону. Хотя для очередей Posix можно указывать приоритет сообщений, функция mq_receive всегда возвращает сообщение с наивысшим приоритетом, поэтому мы не можем использовать лишь одну очередь для данного приложения.

Измерение задержки очередей сообщений System V

В листинге А.16 приведен текст программы измерения времени задержки для очередей сообщений System V.

Листинг А.16. Программа измерения времени задержки для очередей сообщений System V

//bench/lat_svmsg.c

1  #include "unpipc.h"

2  struct msgbuf p2child = { 1, { 0 } }; /* type = 1 */

3  struct msgbuf child2p = { 2, { 0 } }; /* type = 2 */

4  struct msgbuf inbuf;


5  void

6  doit(int msgid)

7  {

8   Msgsnd(msgid, &p2child, 0, 0);

9   if (Msgrcv(msgid, &inbuf, sizeof(inbuf.mtext), 2, 0) != 0)

10   err_quit("msgrcv error");

11 }


12 int

13 main(int argc, char **argv)

14 {

15  int i, nloop, msgid;

16  pid_t childpid;

17  if (argc != 2)

18   err_quit("usage: lat_svmsg <#loops>");

19  nloop = atoi(argv[1]);

20  msgid = Msgget(IPC_PRIVATE, IPC_CREAT | SVMSG_MODE);

21  if ((childpid = Fork()) == 0) {

22   for(;;) { /* дочерний процесс */

23    if (Msgrcv(msgid, &inbuf, sizeof(inbuf.mtext), 1, 0) != 0)

24     err_quit("msgrcv error");

25    Msgsnd(msgid, &child2p, 0, 0);

26   }

27   exit(0);

28  }

29  /* родительский процесс */

30  doit(msgid);

31  Start_time();

32  for (i = 0; i < nloop; i++)

33   doit(msgid);

34  printf("latency: %.3f usec\n", Stop_time() / nloop);

35  Kill(childpid, SIGTERM);

36  Msgctl(msgid, IPC_RMID, NULL);

37  exit(0);

38 }

Мы создаем одну очередь, по которой сообщения передаются в обоих направлениях. Сообщения с типом 1 передаются от родительского процесса дочернему, а сообщения с типом 2 — в обратную сторону. Четвертый аргумент при вызове msgrcv в функции doit имеет значение 2, что обеспечивает получение сообщений только данного типа. Аналогично в дочернем процессе четвертый аргумент msgrcv имеет значение 1.

ПРИМЕЧАНИЕ

В разделах 9.3 и 11.3 мы отмечали, что многие структуры, определенные в ядре, нельзя инициализировать статически, поскольку стандарты Posix.1 и Unix 98 гарантируют лишь наличие определенных полей в этих структурах, но не определяют ни их порядок, ни наличие других полей. В этой программе мы инициализируем структуру msgbuf статически, поскольку очереди сообщений System V гарантируют, что эта структура содержит поле типа сообщения long, за которым следуют передаваемые данные.

Программа измерения задержки интерфейса дверей

Пpoгрaммa измерения задержки для интерфейса дверей дана в листинге А.17. Дочерний процесс создает дверь и связывает с ней функцию server. Родительский процесс открывает дверь и вызывает door_call в цикле. В качестве аргумента передается 1 байт данных, и ничего не возвращается.

Листинг А.17. Программа измерения задержки интерфейса дверей

//bench/lat_door.c

1  #include "unpipc.h"


2  void

3  server(void *cookie, char *argp, size_t arg_size,

4   door_desc_t *dp, size_t n_descriptors)

5  {

6   char c;

7   Door_return(&c, sizeof(char), NULL, 0);

8  }


9  int

10 main(int argc, char **argv)

11 {

12  int i, nloop, doorfd, contpipe[2];

13  char c;

14  pid_t childpid;

15  door_arg_t arg;

16  if (argc != 3)

17   err_quit("usage: lat_door <#loops>");

18  nloop = atoi(argv[2]);

19  unlink(argv[1]);

20  Close(Open(argv[1], O_CREAT | O_EXCL | O_RDWR, FILE_MODE));

21  Pipe(contpipe);

22  if ((childpid = Fork()) == 0) {

23   doorfd = Door_create(server, NULL, 0);

24   Fattach(doorfd, argv[1]);

25   Write(contpipe[1], &c, 1);

26   for(;;) /* дочерний процесс = сервер */

27    pause();

28   exit(0);

29  }

30  arg.data_ptr = &c; /* родительский процесс = клиент */

31  arg.data_size = sizeof(char);

32  arg.desc_ptr = NULL;

33  arg.desc_num = 0;

34  arg.rbuf = &c;

35  arg.rsize = sizeof(char);

36  if (Read(contpipe[0], &c, 1) != 1) /* ждем создания */

37   err_quit("pipe read error");

38  doorfd = Open(argv[1], O_RDWR);

39  Door_call(doorfd, &arg); /* запуск */

40  Start_time();

41  for (i = 0; i < nloop; i++)

42   Door_call(doorfd, &arg);

43  printf("latency: %.3f usec\n", Stop_time() / nloop);

44  Kill(childpid, SIGTERM);

45  unlink(argv[1]);

46  exit(0);

47 }

Программа измерения времени задержки Sun RPC

Для измерения времени задержки Sun RPC мы напишем две программы: клиент и сервер, аналогично измерению полосы пропускания. Мы используем старый файл спецификации RPC, но на этот раз клиент вызывает нулевую процедуру сервера. Вспомните упражнение 16.11: эта процедура не принимает никаких аргументов и ничего не возвращает. Это именно то, что нам нужно, чтобы определить задержку. В листинге А.18 приведен текст клиента. Как и в решении упражнения 16.11, нам нужно воспользоваться clnt_call для вызова нулевой процедуры; в заглушке клиента отсутствует необходимая заглушка для этой процедуры.

Листинг А.18. Клиент Sun RPC для измерения задержки

//bench/lat_sunrpc_client.с

1  #include "unpipc.h"

2  #include "lat_sunrpc.h"


3  int

4  main(int argc, char **argv)

5  {

6   int i, nloop;

7   CLIENT *cl;

8   struct timeval tv;

9   if (argc != 4)

10   err_quit("usage: lat_sunrpc_client <#loops>");

11  nloop = atoi(argv[2]);

12  cl = Clnt_create(argv[1], BW_SUNRPC_PROG, BW_SUNRPC_VERS, argv[3]);

13  tv.tv_sec = 10;

14  tv.tv_usec = 0;

15  Start_time();

16  for (i = 0; i < nloop; i++) {

17   if (clnt_call(cl, NULLPROC, xdr_void, NULL,

18    xdr_void, NULL, tv) != RPC_SUCCESS)

19    err_quit("%s", clnt_sperror(cl, argv[1]));

20  }

21  printf("latency: %.3f usec\n", Stop_time() / nloop);

22  exit(0);

23 }

Мы компилируем сервер с функцией, приведенной в листинге А.13, но она все равно не вызывается. Поскольку мы используем rpcgen для построения клиента и сервера, нам нужно определить хотя бы одну процедуру сервера, но мы не обязаны ее вызывать. Причина, по которой мы используем rpcgen, заключается в том, что она автоматически создает функцию main сервера с нулевой процедурой, которая нам нужна.

А.5. Синхронизация потоков: программы