• Глава 1
  • Глава 2
  • Глава 3
  • Глава 4
  • Глава 5
  • Глава 6
  • Глава 7
  • Глава 9
  • Глава 10
  • Глава 11
  • Глава 12
  • Глава 13
  • Глава 14
  • Глава 15
  • Глава 16
  • ПРИЛОЖЕНИЕ Г

    Решения некоторых упражнений

    Глава 1

    1. В обоих процессах нужно лишь указать флаг O_APPEND при вызове функции open или режим дополнения файла при вызове fopen. Ядро гарантирует, что данные будут дописываться в конец файла. Это самая простая форма синхронизации доступа к файлу. На с. 60-61 [21] об этом рассказывается более подробно. Синхронизация становится проблемой при обновлении имеющихся в файле данных, как это происходит в базах данных.

    2. Обычно встречается что-нибудь вроде:

    #ifdef REENTRANT

    #define errno (*_errno())

    #else

    extern int errno;

    #endif

    Если определена константа _REENTRANT, обращение к errno приводит к вызову функции _errno, возвращающей адрес переменной errno вызвавшего потока. Эта переменная, скорее всего, хранится в области собственных данных этого потока (раздел 23.5 [24]). Если константа REENTRANT не определена, переменная errno является глобальной.

    Глава 2

    1. Эти два бита могут менять действующий идентификатор пользователя и/или группы выполняющейся программы. Идентификаторы используются в разделе 2.4.

    2. Сначала следует указать флаги O_CREAT | O_EXCL, и если вызов окажется успешным, будет создан новый объект. Если вызов вернет ошибку EEXIST, объект уже существует и программа должна вызвать open еще раз, без флага O_CREAT или O_EXCL Второй вызов должен оказаться успешным, но есть вероятность, что он вернет ошибку ENOENT, если какой-либо другой поток или процесс удалит объект в промежутке между этими двумя вызовами.

    Глава 3

    1. Текст пpoгрaммы приведен в листинге Г.1.[1]

    Листинг Г.1. Вывод идентификатора и порядкового номера слота

    //svmsg/slotseq.c

    1  #include "unpipc.h"


    2  int

    3  main(int argc, char **argv)

    4  {

    5   int i, msqid;

    6   struct msqid_ds info;

    7   for (i = 0; i < 10; i++) {

    8    msqid = Msgget(IPC_PRIVATE, SVMSG_MODE | IPC_CREAT);

    9    Msgctl(msqid, IPC_STAT, &info);

    10   printf("msqid = %d, seq = %lu\n", msqid, info.msg_perm.seq);

    11   Msgctl(msqid, IPC_RMID, NULL);

    12  }

    13  exit(0);

    14 }

    2. Первый вызов msgget задействует первую свободную очередь сообщений, порядковый номер которой имеет значение 20 после двукратного запуска программы из листинга 3.2, и вернет идентификатор 1000. Если предположить, что следующая доступная очередь сообщений никогда ранее не использовалась, ее порядковый номер будет иметь значение 0, а возвращаться будет идентификатор 1.

    3. Программа приведена в листинге Г.2.

    Листинг Г.2. Проверка использования маски создания файла функцией msgget

    //svmsg/testumask.c

    1 #include "unpipc.h"


    2 int

    3 main(int argc, char **argv)

    4 {

    5  Msgget(IPC_PRIVATE, 0666 | IPC_CREAT | IPC_EXCL);

    6  unlink("/tmp/fifo.1");

    7  Mkfifo("/tmp/fifo.1", 0666);

    8  exit(0);

    9 }

    Запустив эту пpoгрaммy, мы увидим, что маска создания файла имеет значение 2 (снять бит записи для прочих пользователей) и этот бит оказывается снятым для канала FIFO, но не для очереди сообщений:

    solaris % umask

    02

    solaris % testumask

    solaris % ls –l /tmp/fifo.1

    prw-rw-r-- 1 rstevens other1 0 Mar 25 16:05 /tmp/fifo.1

    solaris % ipcs –q

    IPC status from <running system> as of Wed Mar 25 16:06:03 1998

    T ID  KEY       MODE      OWNER    GROUP

    Message Queues:

    q 200 00000000 –rw-rw-rw– rstevens other1

    4. При использовании ftok имеется вероятность того, что для двух полных имен получится один и тот же ключ. При использовании IPC_PRIVATE сервер знает, что он создает новую очередь, но в этом случае ему нужно записать ее идентификатор в какой-либо файл, чтобы клиенты могли его считать.

    5. Вот один из способов обнаружения коллизий:

    solaris % find / –links 1 –not –type l – print | xargs –n1 ftok1 > temp.1

    solaris % wc –l temp.1

    109351 temp.1

    solaris % sort +0 –1 temp.1 | nawk '{ if (lastkey== $1) print lastline, $0 lastline = $0 lastkey = $1 }' > temp.2

    solaris % wc –l temp.2 82188 temp.2

    Программа find игнорирует файлы, на которые имеется несколько ссылок (поскольку у всех ссылок одинаковый номер узла), и символические ссылки (поскольку функция stat возвращает информацию для файла, на который ссылка указывает). Большой процент коллизий (75,2%) вызван тем, что в Solaris 2.x используется только 12 бит номера узла. Поэтому в файловых системах с числом файлов более 4096 количество коллизий может быть велико. Например, файлы с номерами 4096, 8192, 12288 и 16384 будут иметь один и тот же ключ IPC (если все они принадлежат одной файловой системе).

    Мы запустили эту программу в той же файловой системе, но используя функцию ftok из BSD/OS, которая добавляет номер узла к ключу целиком, и получили всего 849 коллизий (менее 1%).

    Глава 4

    1. Если бы дескриптор fd[1] остался открытым в дочернем процессе при завершении родительского, его операция read для этого дескриптора не вернула бы признак конца файла, потому что дескриптор был бы еще открыт в дочернем процессе. Закрытие fd[1] гарантирует, что после завершения родительского процесса все его дескрипторы закрываются и вызов read для fd[1] возвращает 0.

    2. Если поменять местами порядок вызовов, другой процесс сможет создать канал FIFO в промежутке между вызовами open и mkfifo, в результате чего последний вернет ошибку.

    3. Если выполнить

    solaris % mainopen 2>temp.stderr

    /etc/ntp.conf > /myfile

    solaris % cat temp.stderr

    sh: /myfile: cannot create

    мы увидим, что popen срабатывает успешно, но fgets считывает символ конца файла. Сообщение об ошибке записывается интерпретатором в стандартный поток сообщений об ошибках.

    5. Измените первый вызов open, указав флаг отключения блокировки:

    readfifo = Open(SERV_FIFO, O_RDONLY | O_NONBLOCK, 0);

    Возврат из этого вызова произойдет немедленно, как и из следующего вызова open, поскольку канал уже открыт на чтение. Чтобы избежать ошибки при вызове readline, флаг O_NONBLOCK для дескриптора readfifo следует снять, перед тем как вызывать эту функцию.

    6. Если клиент откроет свой канал на чтение перед открытием канала сервера, все зависнет. Единственный способ избежать блокировки заключается в вызове open для этих двух каналов в порядке, показанном в листинге 4.11, или в использовании флага отключения блокировки.

    7. Исчезновение пишущего процесса воспринимается считывающим как конец файла.

    8. В листинге Г.3 приведен текст соответствующей программы.

    Листинг Г.З. Возвращает ли fstat количество байтов в канале FIFO?

    //pipe/test1l.c

    1  #include "unpipc.h"


    2  int

    3  main(int argc, char **argv)

    4  {

    5   int fd[2],

    6   char buff[7];

    7   struct stat info;

    8   if (argc != 2)

    9    err_quit("usage: test1 <pathname>");

    10  Mkfifo(argv[1], FILE_MODE);

    11  fd[0] = Open(argv[1], O_RDONLY | O_NONBLOCK);

    12  fd[1] = Open(argv[1], O_WRONLY | O_NONBLOCK);

    13  /* 4check sizes when FIFO is empty */

    14  Fstat(fd[0], &info);

    15  printf("fd[0]: st_size = %ld\n", (long) info.st_size);

    16  Fstat(fd[1], &info);

    17  printf("fd[1]: st_size = %ld\n", (long) info.st_size);

    18  Write(fd[1], buff, sizeof(buff));

    19  Fstat(fd[0], &info);

    20  printf("fd[0]: st_size = %ld\n", (long) info.st_size);

    21  Fstat(fd[1], &info);

    22  printf("fd[1]: st_size = %ld\n", (long) info.st_size);

    23  exit(0);

    24 }

    9. Вызов select возвращает информацию о возможности записи в дескриптор, но вызов write приводит к отправке сигнала SIGPIPE. Это описано в книге [24, с. 153-155]; когда возникает ошибка чтения или записи, select возвращает информацию о том, что дескриптор доступен, а собственно ошибка возвращается уже вызовами read или write. В листинге Г.4 приведен текст соответствующей пpoгрaммы.

    Листинг Г.4. Что возвращает select при закрытии другого конца канала?

    //pipe/test2.c

    1  #include "unpipc.h"


    2  int

    3  main(int argc, char **argv)

    4  {

    5   int fd[2], n;

    6   pid_t childpid;

    7   fd_set wset;

    8   Pipe(fd);

    9   if ((childpid = Fork()) == 0) { /* child */

    10   printf("child closing pipe read descriptor\n");

    11   Close(fd[0]);

    12   sleep(6);

    13   exit(0);

    14  }

    15  /* 4parent */

    16  Close(fd[0]); /* для двустороннего канала */

    17  sleep(3);

    18  FD_ZERO(&wset);

    19  FD_SET(fd[1], &wset);

    20  n = select(fd[1] + 1, NULL, &wset, NULL, NULL);

    21  printf("select returned %d\n", n);

    22  if (FD_ISSET(fd[1], &wset)) {

    23   printf("fd[1] writable\n");

    24   Write(fd[1], "hello", 5);

    25  }

    26  exit(0);

    27 }

    Глава 5

    1. Сначала создайте очередь, не указывая никаких атрибутов, а затем вызовите mq_getattr для получения атрибутов по умолчанию. Затем удалите очередь и создайте ее снова, используя значения по умолчанию для всех неуказанных атрибутов.

    2. Для второго сообщения сигнал не отправляется, поскольку регистрация снимается после отправки первого сигнала.

    3. Для второго сообщения сигнал не отправляется, поскольку в момент отправки этого сообщения очередь не была пуста.

    4. Компилятор GNU С в системе Solaris 2.6 (в котором обе константы определены как вызовы sysconf) возвращает ошибки:

    test1.c:13: warning: int format, long int arg (arg 2)

    test1.c:13: warning: int format, long int arg (arg 3)

    5. В Solaris 2.6 мы указываем 1000000 сообщений по 10 байт в каждом. При этом создается файл размером 20000536 байт, что соответствует результатам, полученным с помощью пpoгрaммы 5.4: 10 байт данных на сообщение, 8 байт дополнительной информации (возможно, указатели), еще 2 байта добавочной информации (возможно, дополнение до кратного 4) и 536 байт добавочных данных на весь файл. Перед вызовом mq_open размер программы, выводимый ps, равнялся 1052 Кбайт, а после создания очереди размер вырос до 20 Мбайт. Это заставляет предположить, что очереди сообщений Posix реализованы через отображение файлов в память, и mq_open отображает файл в адресное пространство вызвавшего процесса. Аналогичные результаты получаются в Digital Unix 4.0B.

    6. Размер аргумента, равный нулю, не вызывает проблем с функциями ANSI С memXXX. В оригинале стандарта 1989 года Х3.159-1989 (ISO/IEC 9899:1990) ничего не говорилось по этому поводу (как и в документации), но в Technical Corrigendum Number 1 явно говорится, что указание размера 0 не вызовет проблем (но аргументы и указатели при этом должны быть правильными). Вообще, за информацией по языку С лучше всего обращаться по адресу http://www.lysator.liu.se/c/.

    7. Для двустороннего взаимодействия процессов требуется наличие двух очередей сообщений (см. например, листинг А.15). Если бы мы изменили листинг 4.4 для использования очередей сообщений Posix вместо каналов, мы бы увидели, что родительский процесс считывает то, что сам же и отправил.

    8. Взаимное исключение и условная переменная помещаются в отображаемый файл, совместно используемый всеми процессами, открывающими очередь. Очередь может быть открыта и другими процессами, поэтому при закрытии дескриптора очереди взаимное исключение и условная переменная не уничтожаются.

    9. Массиву нельзя присвоить значение другого массива с помощью знака равенства в языке С, а вот структуре можно.

    10. Функция main проводит большую часть времени заблокированной в вызове select, ожидая возможности чтения из конца канала. Каждый раз при получении сигнала возврат из обработчика прерывает вызов select, что приводит к возврату ошибки EINTR. Чтобы избежать этой проблемы, функция-обертка Select проверяет возврат данного кода ошибки и снова вызывает select, как показано в листинге Г.5. В книге [24, с. 124] вы можете найти более подробный рассказ о прерывании системных вызовов.

    Листинг Г.5. Обертка Select, обрабатывающая возврат EINTR

    //lib/wrapunix.c

    313 int

    314 Select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds,

    315  struct timeval *timeout)

    316 {

    317  int n;

    318 again:

    319  if ( (n = select(nfds, readfds, writefds, exceptfds, timeout)) < 0) {

    320   if (errno == EINTR)

    321    goto again;

    322  else

    323   err_sys("select error");

    324  } else if (n == 0 && timeout == NULL)

    325   err_quit("select returned 0 with no timeout");

    326  return(n); /* возвращаем 0 по тайм-ауту */

    327 }

    Глава 6

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

    2. Передача сообщений с типом 0 запрещена, а клиент никогда не может иметь идентификатор 1, поскольку этот идентификатор обычно принадлежит процессу init.

    3. При использовании единственной очереди на рис. 6.2 злоумышленник мог повлиять на все прочие процессы-клиенты. Если у каждого клиента есть своя очередь (рис. 6.3), злоумышленник портит только свою.

    Глава 7

    2. Вероятно, процесс завершит работу, прежде чем потребитель успеет сделать все, что нужно, поскольку вызов exit завершает все выполняющиеся потоки.

    3. В Solaris 2.6 удаление вызова функций типа destroy приводит к утечке памяти, из чего становится ясно, что функции init осуществляют динамическое выделение памяти. В Digital Unix 4.0B такого не наблюдается, что указывает на разницу в реализации. Тем не менее вызывать функции destroy все равно нужно. С точки зрения реализации в Digital Unix 4.0B используется переменная типа attr_t как объект, содержащий атрибуты, а в Solaris 2.6 эта переменная представляет собой указатель на динамически создаваемый объект.

    Глава 9

    1. В зависимости от системы может потребоваться увеличивать счетчик до значений, больших 20, чтобы наблюдать ошибку.

    2. Для отключения буферизации стандартного потока мы добавляем строку

    setvbuf(stdout, NULL, _IONBF, 0);

    к функции main перед циклом for. Это не должно ни на что влиять, поскольку вызов printf только один и строка завершается символом перевода. Обычно стандартный поток вывода буферизуется построчно, поэтому в любом случае один вызов printf превращается в один системный вызов write.

    3. Заменим printf на

    snprintf(line, sizeof(line), "%s: pid = 3.1d, seq# = %d\n", argv[0], (long) pid, seqno);

    for (ptr = line; (c = *ptr++) != 0) putchar(c);

    и объявим с как целое, a ptr — как char*. Если мы вызвали setvbuf для отключения буферизации стандартного потока вывода, библиотека будет делать системный вызов для вывода каждого символа. На это требуется больше времени, что дает ядру больше возможностей на переключение контекста между процессами. Такая программа должна давать больше ошибок.

    4. Поскольку несколько процессов могут заблокировать на чтение одну и ту же область файла, в нашем примере это эквивалентно полному отсутствию блокировок.

    5. Ничего не изменится, поскольку флаг отключения блокировки для дескриптора никак не влияет на работу рекомендательной блокировки fcntl. Блокирование процесса при вызове fcntl определяется типом команды: F_SETLKW (которая блокируется всегда) или F_SETLK (которая не блокируется никогда).

    6. Пpoгрaммa loopfcntlnonb работает как положено, поскольку, как мы показали в предыдущем примере, флаг отключения блокировки никак не влияет на блокировку fcntl. Однако этот флаг влияет на работу loopnonenonb, которая не пользуется блокировкой. Как говорилось в разделе 9.5, неблокируемый вызов write или read для файла с включенной обязательной блокировкой приводит к возврату ошибки EAGAIN. Мы увидим одно из следующих сообщений:

    read error: Resource temporarily unavailable

    write error: Resource temporarily unavailable

    и мы можем проверить, что это сообщение соответствует EAGAIN, выполнив

    solaris % grep Resource /usr/include/sys/errno.h

    #define EAGAIN 11 /* Resource temporarily unavailable */

    7. В Solaris 2.6 обязательная блокировка увеличивает время работы на 16%, а время процессора — на 20%. Пользовательское время процессора остается прежним, поскольку проверка осуществляется в ядре, а не в процессе.

    8. Блокировки выдаются процессам, а не потокам.

    9. Если бы работала другая копия демона, а мы открыли бы файл с флагом O_TRUNC, это привело бы к удалению идентификатора процесса из файла. Мы не имеем права укорачивать файл, пока не убедимся, что данная копия является единственной.

    10. Лучше использовать SEEK_SET. Проблема с SEEK_CUR заключается в том, что этот вариант зависит от текущего положения в файле, устанавливаемого 1 seek. Однако если мы вызываем 1 seek, а потом fcntl, мы делаем одну операцию в два вызова и существует вероятность, что другой процесс в промежутке между вызовами изменит текущий сдвиг вызовом lseek. Вспомните, что все потоки используют общие дескрипторы. Аналогично, если указать SEEK_END, другой процесс может дописать данные к файлу, прежде чем мы получим блокировку, и тогда она уже не будет распространяться на весь файл.

    Глава 10

    1. Вот результат работы в Solaris 2.6:

    solaris % deadlock 100

    prod: calling sem_wait(nempty) i=0 у производителя

    prod: got sem_wait(nempty)

    prod: calling sem_wait(mutex)

    prod: got sem_wait(mutex), storing 0

    prod: calling sem_wait(nempty) i=1 у производителя

    prod: got sem_wait(nempty)

    prod: calling sem_wait(mutex)

    prod: got sem_wait(mutex), storing 1

    prod: calling sem_wait(nempty) начало следующего цикла, но места больше нет,

                                   поэтому происходит переключение контекста

    cons: calling sem_wait(mutex) i=0

    cons: got sem_wait(mutex)

    cons: calling sem_wait(nstored)

    cons: got sem_wait(nstored)

    cons: fetched 0

    cons: calling sem_wait(mutex) i=1

    cons: got sem_wait(mutex)

    cons: calling sem_wait(nstored)

    cons: got sem_wait(nstored)

    cons: fetched 1

    cons: calling sem_wait(mutex)

    cons: got sem_wait(mutex)

    cons: calling sem_wait(nstored) здесь блокируется потребитель. Навсегда.

    prod: got sem_wait(nempty)

    prod: calling sem_wait(mutex)   а здесь блокируется производитель.

    2. Это не вызывает проблем с учетом правил, которые были указаны при описании sem_open: если семафор уже существует, он не инициализируется. Поэтому только первая из четырех программ, вызывающих sem_open, инициализирует семафор.

    3. Это проблема. Семафор автоматически закрывается при завершении процесса, но значение его не изменяется. Это не дает другим пpoгрaммaм получить блокировку, и все зависает.

    4. Если мы не инициализируем дескрипторы значением –1, их значение оказывается неизвестным, поскольку malloc не инициализирует выделяемую память. Поэтому если один из вызовов open возвращает ошибку, вызовы close под меткой error могут закрыть какой-нибудь используемый процессом дескриптор. Инициализируя дескрипторы значением –1, мы можем быть уверены, что вызовы close не дадут результата (помимо возвращения игнорируемой ошибки), если дескриптор еще не был открыт.

    5. Существует вероятность, что close будет вызвана для нормального дескриптора и вернет ошибку, изменив значение errno. Поэтому нам нужно сохранить это значение в другой переменной, чтобы оно не изменилось из-за побочного эффекта.

    6. В этой функции ситуация гонок не возникает, поскольку mkfifo возвращает ошибку, если канал уже существует. Если два процесса вызывают эту функцию одновременно, канал FIFO создается только один раз. Второй вызов mkfifо приведет к возврату EEXIST.

    7. В пpoгрaммe из листинга 10.22 ситуация гонок, описанная в связи с листингом 10.28, не возникает, поскольку инициализация семафора осуществляется записью данных в канал. Если процесс, создавший канал, приостанавливается ядром после создания, но перед записью данных, второй процесс откроет этот канал и заблокируется в вызове sem_wait, поскольку только что созданный канал будет пуст (пока первый процесс не поместит в него данные).

    8. В листинге Г.6 приведена тестовая программа. Реализации Solaris 2.6 и Digital Unix 4.0B обнаруживают прерывание перехватываемым сигналом и возвращают ошибку EINTR.

    Листинг Г.6. Возвращает ли sem_wait ошибку EINTR?

    //pxsem/testeintr.c

    1  #include "unpipc.h"

    2  #define NAME "testeintr"

    3  static void sig_alrm(int);


    4  int

    5  main(int argc, char **argv)

    6  {

    7   sem_t *sem1, sem2;

    8   /* именованный семафор */

    9   sem_unlink(Px_ipc_name(NAME));

    10  sem1 = Sem_open(Px_ipc_name(NAME), O_RDWR | O_CREAT | О_EXCL,

    11   FILE_MODE, 0);

    12  Signal(SIGALRM, sig_alrm);

    13  alarm(2);

    14  if (sem_wait(sem1) == 0)

    15   printf("sem_wait returned 0?\n");

    16  else

    17   err_ret("sem_wait error");

    18  Sem_close(sem1);

    19  /* размещаемый в памяти семафор */

    20  Sem_init(&sem2, 1, 0);

    21  alarm(2);

    22  if (sem_wait(&sem2) == 0)

    23   printf("sem_wait returned 0?\n");

    24  else

    25   err_ret("sem_wait error");

    26  Sem_destroy(&sem2);

    27  exit(0);

    28 }


    29 static void

    30 sig_alrm(int signo)

    31 {

    32  printf("SIGALRM caught\n");

    33  return;

    34 }

    Реализация с использованием FIFO возвращает EINTR, поскольку sem_wait блокируется в вызове read, который должен возвращать такую ошибку. Реализация с использованием отображения в память ошибки не возвращает, поскольку sem_wait блокируется в вызове pthread_cond_wait, а эта функция не возвращает такой ошибки. Реализация с использованием семафоров System V возвращает ошибку EINTR, поскольку sem_wait блокируется в вызове semop, которая возвращает эту ошибку.

    9. Реализация с использованием каналов (листинг 10.25) является защищенной, поскольку таковой является операция write. Реализация с отображением в память защищенной не является, поскольку функции pthread_XXX не являются защищенными и не могут вызываться из обработчика сигналов. Реализация с семафорами System V (листинг 10.41) также не является защищенной, поскольку semop не является защищенной функцией согласно Unix 98.

    Глава 11

    1. Нужно изменить только одну строку:

    < semid = Semget(Ftok(argv[optind], 0), 0, 0);

    > semid = atol(argv[optind]);

    2. Вызов ftok вернет ошибку, что приведет к завершению работы обертки Ftok. Функция my_lock могла бы вызывать ftok перед semget, проверять, не возвращается ли ошибка ENOENT, а затем создавать файл, если он не существует.

    Глава 12

    1. Размер файла увеличится еще на 4096 байт (до 36864), но обращение к новому концу файла (36863) может привести к отправке сигнала SIGSEGV, поскольку размер области отображения в памяти равен 32768 байт. Причина, по которой мы говорим «может», а не «должен», — в неопределенности размера страницы памяти.

    2. На рис. Г.1 показана схема с очередью сообщений System V, а на рис. Г.2 — с очередью сообщений Posix. Вызовы memcpy в отправителе происходят внутри функций mq_send (листинг 5.26), а в получателе — внутри mq_receive (листинг 5.28).

    Рис. Г.1. Отправка сообщений в очередь System V


    Рис. Г.2. Отправка сообщений через очередь Posix, реализованную с mmap


    3. Любой вызов read для /dev/zero возвращает запрошенное количество нулей. Данные, помещаемые в этот файл, попросту сбрасываются (аналогично /dev/null).

    4.  В результате в файле получится 4 байта — все нули (предполагается 32-разрядное целое).

    5. В листинге Г.7 приведен текст нашей программы.

    Листинг Г.7. Использование select с очередями System V

    //shm/svmsgread.c

    1  #include "unpipc.h"

    2  #define MAXMSG (8192 + sizeof(long))


    3  int

    4  main(int argc, char **argv)

    5  {

    6   int pipe1[2], pipe2[2], mqid;

    7   char c;

    8   pid_t childpid;

    9   fd_set rset;

    10  ssize_t n, nread;

    11  struct msgbuf *buff;

    12  if (argc != 2)

    13   err_quit("usage: svmsgread <pathname>");

    14  Pipe(pipe1); /* двусторонняя связь */

    15  Pipe(pipe2);

    16  buff = My_shm(MAXMSG); /* неименованная разделяемая память */

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

    18   Close(pipe1[1]); /* child */

    19   Close(pipe2[0]);

    20   mqid = Msgget(Ftok(argv[1], 0), MSG_R);

    21   for(;;) {

    22    /* блокируется в ожидании, извещает родительский процесс */

    23    nread = Msgrcv(mqid, buff, MAXMSG, 0, 0);

    24    Write(pipe2[1], &nread, sizeof(ssize_t));

    25    /* ожидает разрешения родительского процесса */

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

    27     err_quit("child: read on pipe returned %d", n);

    28   }

    29   exit(0);

    30  } /* $$.bp$$ */

    31  /* parent */

    32  Close(pipe1[0]);

    33  Close(pipe2[1]);

    34  FD_ZERO(&rset);

    35  FD_SET(pipe2[0], &rset);

    36  for(;;) {

    37   if ((n = select(pipe2[0] + 1, &rset, NULL, NULL, NULL)) != 1)

    38    err_sys("select returned %d", n);

    39   if (FD_ISSET(pipe2[0], &rset)) {

    40    n = Read(pipe2[0], &nread, sizeof(ssize_t)); /* *INDENT-OFF* */

    41    if (n != sizeof(ssize_t))

    42     err_quit("parent: read on pipe returned %d", n); /* *INDENT-ON* */

    43    printf("read %d bytes, type = %ld\n", nread, buff->mtype);

    44    Write(pipe1[1], &c, 1);

    45   } else

    47  }

    46  err_quit("pipe2[0] not ready");

    48  Kill(childpid, SIGTERM);

    49  exit(0);

    50 }

    Глава 13

    1. В листинге Г.8 приведен текст измененной версии листинга 12.6, а в листинге Г.9 — текст новой версии листинга 12.7. Обратите внимание, что в первой пpoгрaммe мы устанавливаем размер объекта вызовом ftruncate; lseek и write использовать для этого нельзя.

    Листинг Г.8. Отображение с точным указанием размера файла

    //pxshra/test1.c

    1  #include "unpipc.h"


    2  int

    3  main(int argc, char **argv)

    4  {

    5   int fd, i;

    6   char *ptr;

    7   size_t shmsize, mmapsize, pagesize;

    8   if (argc != 4)

    9    err_quit("usage: test1 <name> <shmsize> <mmapsize>");

    10  shmsize = atoi(argv[2]);

    11  mmapsize = atoi(argv[3]);

    12  /* открываем shm: создание или урезание; установка размера */

    13  fd = Shm_open(Px_ipc_name(argv[1]), O_RDWR | O_CREAT | O_TRUNC,

    14   FILE_MODE);

    15  Ftruncate(fd, shmsize); /* $$.bp$S */

    16  ptr = Mmap(NULL, mmapsize, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);

    17  Close(fd);

    18  pagesize = Sysconf(_SC_PAGESIZE);

    19  printf("PAGESIZE = %ld\n", (long) pagesize);

    20  for (i = 0; i < max(shmsize, mmapsize); i += pagesize) {

    21   printf("ptr[%d] = %d\n", i, ptr[i]);

    22   ptr[i] = 1;

    23   printf("ptr[%d] = %d\n", i + pagesize – 1, ptr[i + pagesize – 1]);

    24   ptr[i + pagesize – 1] = 1;

    25  }

    26  printf("ptr[%d] =%d\n", i, ptr[i]);

    27  exit(0);

    28 }

    Листинг Г.9. Отображение в память с возможностью роста файла

    //pxshm/test2.c

    1  #include "unpipc.h"

    2  #define FILE "test.data"

    3  #define SIZE 32768


    4  int

    5  main(int argc, char **argv)

    6  {

    7   int fd, i;

    8   char *ptr;

    9   /* создаем или урезаем, отображаем */

    10  fd = Shm_open(Px_ipc_name(FILE), O_RDWR | O_CREAT | O_TRUNC, FILE_MODE);

    11  ptr = Mmap(NULL, SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);

    12  for (i = 4096; i <= SIZE; i += 4096) {

    13   printf("setting shm size to %d\n", i);

    14   Ftruncate(fd, i);

    15   printf("ptr[%d] = %d\n", i-1, ptr[i-1]);

    16  }

    17  exit(0);

    18 }

    2. Одна из возможных проблем при использовании *ptr++ заключается в том, что указатель, возвращенный mmap, изменяется, что может помешать впоследствии вызвать munmap. Если этот указатель еще понадобится, лучше его сохранить или вовсе не изменять.

    Глава 14

    1. Нужно изменить только одну строку:

    13с13

    < id = Shmget(Ftok(argv[1], 0), 0, SVSHM_MORE);

    > id = atol(argv[1]);

    Глава 15

    1. Аргументы будут иметь размер data_size + (desc_numxsizeof(door_desc_t)) байт.

    2. Вызывать fstat не нужно. Если дескриптор не указывает на дверь, вызов door_infо возвращает ошибку EBADF:

    solaris % doorinfo /etc/passwd

    door_info error: Bad file number

    3. Документация содержит неверные сведения. Стандарт Posix утверждает, что функция sleep приведет к приостановке вызвавшего потока.

    4. Результат непредсказуем (хотя, скорее всего, будет выполнен дамп памяти), поскольку адрес процедуры сервера, связанной с дверью, в новом процессе будет указывать на совершенно случайную область памяти.

    5. При завершении door_call из-за перехвата сигнала сервер следует уведомить об этом, поскольку поток, работающий с этим клиентом, получит запрос на отмену выполнения. В связи с листингом 15.18 мы говорили, что отмена для создаваемых библиотекой потоков по умолчанию отключена, следовательно, поток завершен не будет. Вместо этого происходит досрочный возврат из вызова sleep(6) в процедуре сервера, но она продолжает выполняться.

    6. Вот что мы увидим:

    solaris % server6 /tmp/door6

    my_thread: created sever thread 4

    door_bind error: Bad file number

    Запустив сервер 20 раз подряд, мы получим 5 сообщений об ошибке. Предсказать такую ошибку заранее нельзя.

    7. Нет. Все, что нужно, — включать возможность отмены каждый раз при вызове процедуры сервера, как мы делали в листинге 15.26. Хотя в этом случае и приходится вызывать pthread_setcancelstate каждый раз при запуске процедуры сервера, накладные расходы, скорее всего, будут невелики.

    8. Чтобы проверить это, изменим код одного из серверов (скажем, из листинга 15.6) так, чтобы он вызывал door_revoke из процедуры сервера. Поскольку аргументом door_revoke является дескриптор двери, его придется сделать глобальным. Вот что получится при запуске клиента (из листинга 15.1) два раза подряд:

    solaris % client8 /tmp/door8 88

    result: 7744

    solaris % client8 /tmp/door8 99

    door_call error: Bad file number

    Первый вызов завершается успешно, что подтверждает наше предположение насчет door_revoke. Второй вызов возвращает ошибку EBADF.

    9. Чтобы не делать дескриптор fd глобальным, мы воспользуемся указателем cookiе, который можем передать door_create и который затем будет передаваться процедуре сервера при каждом вызове. В листинге Г.10 приведен текст сервера.

    Листинг Г.10. Использование указателя cookie для избавления от глобальных переменных

    //doors/server9.c

    1  #include "unpipc.h"


    2  void

    3  servproc(void *cookie, char *dataptr, size_t datasize,

    4   door_desc_t *descptr, size_t ndesc)

    5  {

    6   long arg, result;

    7   Door_revoke(*((int *) cookie));

    8   arg = *((long *) dataptr);

    9   printf("thread id %ld, arg = %ld\n", pr_thread_id(NULL), arg);

    10  result = arg * arg;

    11  Door_return((char *) &result, sizeof(result), NULL, 0);

    12 }


    13 int

    14 main(int argc, char **argv)

    15 {

    16  int fd;

    17  if (argc != 2)

    18   err_quit("usage: server9 <server-pathname>");

    19  fd = Door_create(servproc, &fd, 0);

    20  unlink(argv[1]);

    21  Close(Open(argv[1], O_CREAT | O_RDWR, FILE MODE));

    22  Fattach(fd, argv[1]);

    23  for(;;)

    24   pause();

    25 }

    Мы легко могли бы изменить листинги 5.17 и 5.18, поскольку указатель cookie доступен функции my_thread (через структуру door_info_t), которая передает указатель на эту структуру создаваемому потоку (которому нужно знать дескриптор для вызова door_bind).

    10. В этом примере атрибуты потока не меняются, поэтому их достаточно инициализировать лишь единожды (в функции main).

    Глава 16

    1. Программа отображения портов (port mapper) не проверяет серверы на работоспособность во время регистрации. После завершения сервера отображения остаются в силе, в чем мы можем убедиться с помощью пpoгрaммы rpcinfо. Поэтому клиент, связывающийся с программой отображения портов, получит информацию, которая была актуальной до завершения сервера. Когда клиент попытается связаться с сервером по TCP, библиотека RPC получит RST в ответ на пакет SYN (предполагается, что другие процессы не успели подключиться к порту завершенного сервера), что приведет к возврату ошибки функцией clnt_create. Вызов по протоколу UDP будет успешен (поскольку устанавливать соединение не нужно), но при отправке дейтаграмм через устаревший порт ответ получен не будет и функция клиента выйдет по тайм-ауту.

    2. Библиотека RPC возвращает первый ответ сервера клиенту сразу по получении, то есть через 20 секунд после вызова клиента. Следующий ответ будет храниться в сетевом буфере для данной конечной точки до тех пор, пока эта точка не будет закрыта или не будет выполнена операция чтения из нее библиотекой RPC. Предположим, что клиент отправит второй вызов серверу сразу после получения первого ответа. Если потерь в сети не произойдет, следующей прибывшей дeйтaгрaммoй будет ответ сервера на повторно переданную клиентом дейтаграмму. Но библиотека RPC проигнорирует этот ответ, поскольку XID будет совпадать с первым вызовом процедуры, который не может быть равным XID для второго вызова.

    3. Соответствующая структура в С — это char c[10], но она будет закодирована XDR как десять 4-байтовых целых. Если вы хотите использовать строку фиксированной длины, используйте скрытый тип данных фиксированной длины.

    4. Вызов xdr_data возвращает FALSE, поскольку вызов xdr_string (прочитайте содержимое файла data_xdr.c) возвращает FALSE.

    Максимальная длина строки указывается в качестве последнего аргумента xdr_string. Если максимальная длина не указана, этот аргумент принимает значение 0 в дополнительном коде (2³²–1 для 32-разрядного целого).

    5. Пoдпpoгрaммы XDR проверяют наличие достаточного объема свободной памяти в буфере для кодирования данных и возвращают ошибку FALSE при переполнении буфера. К сожалению, отличить одну ошибку от другой для подпpoгрaмм XDR невозможно.

    6. В принципе, можно сказать, что использование последовательных номеров в TCP для обнаружения повторов эквивалентно кэшу повторных запросов, поскольку эти последовательные номера позволяют обнаружить любой устаревший сегмент. Для конкретного соединения (IP-адреса и порта клиента) размер этого кэша соответствует половине 32-разрядного последовательного номера TCP, то есть 2³¹ или 2 Гбайт.

    7. Поскольку все пять значений для конкретного запроса должны в точности равняться пяти значениям в кэше, первое сравнение должно выполняться для того поля, которое может отличаться с наибольшей вероятностью. Реальный порядок сравнений в пакете TI-RPC таков: (1) XID, (2) номер процедуры, (3) номер версии, (4) номер программы, (5) адрес клиента. Разумно сравнивать XID в первую очередь, поскольку именно это значение меняется от запроса к запросу.

    8. На рис. 16.5 имеется двенадцать 4-байтовых полей, начиная с поля флага и длины и включая 4 байта на аргумент типа long. Получается 48 байт. При использовании нулевой аутентификации данные о пользователе и проверочные данные будут отсутствовать. При этом они займут по 8 байтов: 4 байта на тип аутентификации (AUTH_NONE) и 4 байта на длину аутентификационных данных (0).

    В переданном ответе (взгляните на рис. 16.7, но помните, что используется протокол TCP, поэтому 4 байта флага и длины будут идти перед XID) будет восемь 4-байтовых полей, начиная с поля флага и длины и заканчивая результатом типа long. Вместе они дают 32 байта.

    При использовании UDP единственное отличие будет заключаться в отсутствии поля флага и длины (4 байта). При этом размер запроса будет 44 байта, а ответа — 28 байтов, что можно проверить с помощью tcpdump.

    9. Да. Отличие в обработке аргументов у клиента и сервера не зависит от пакетов, передаваемых по сети. Функция main клиента вызывает функцию заглушки для отправки пакета, а функция main сервера вызывает функцию заглушки сервера для обработки этого пакета. Передаваемая по сети запись RPC определяется протоколом RPC, и ее содержимое остается неизменным вне зависимости от того, поддерживается ли многопоточность.

    10. Библиотека XDR выделяет место под эти строки (динамически). Мы можем проверить это, добавив следующую строку к пpoгрaммe read:

    printf(sbrk()= %p, buff = %p, in.vstring_arg = %p\n", sbrk(NULL), buff, in.vstring_arg);

    Функция sbrk возвращает текущий адрес вершины сегмента данных программы, а функция malloc обычно выделяет память непосредственно под этим адресом. Запустив программу, получим:

    sbrk() = 29638, buff = 25е48, in.vstring_arg = 27e58

    Это показывает, что указатель vstring_arg указывает на область, выделенную mallос. Буфер buff размером 8192 байта занимает адреса с 0х25е48 по 0х27е47, а строка помещается непосредственно под ним.

    11. В листинге Г.11 приведен текст программы-клиента. Обратите внимание, что последним аргументом clnt_call является сама структура timeval, а не указатель на нее. Также отметьте, что третий и пятый аргументы clnt_call должныбыть ненулевыми указателями на подпрограммы XDR, поэтому мы указываем в этих аргументах xdr_void (функция, которая ничего не делает). Вы можете проверить, что именно так нужно вызывать функцию без аргументов и возвращаемых значений, написав тривиальный файл спецификации RPC, определяющий такую функцию, запустив rpcgen и посмотрев на содержимое созданной заглушки клиента.

    Листинг Г.11. Клиент, вызывающий нулевую процедуру сервера

    //sunrpc/square10/client.c

    1  #include "unpipc.h" /* our header */

    2  #include "square.h" /* generated by rpcgen */


    3  int

    4  main(int argc, char **argv)

    5  {

    6   CLIENT *cl;

    7   struct timeval tv;

    8   if (argc != 3)

    9    err_quit("usage: client <hostname> <protocol>");

    10  cl = Clnt_create(argv[1], SQUARE_PROG, SQUARE_VERS, argv[2]);

    11  tv.tv_sec = 10;

    12  tv.tv_usec = 0;

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

    14   xdr_void, NULL, tv) != RPC_SUCCESS)

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

    16  exit(0);

    17 }

    12. Получающийся размер дейтаграммы UDP (65536+20+дополнительные расходы RPC) превосходит 65535 — максимальный размер дейтаграммы в IPv4. В табл. А.2 отсутствуют значения для Sun RPC с использованием UDP для сообщений размером 16384 и 32768, поскольку старая реализация RPCSRC 4.0 ограничивала размер дейтаграммы UDP некоторым значением около 9000 байт.


    Примечания:



    1

    Все исходные тексты, опубликованные в этой книге, вы можете найти по адресу http://www.piter.com/download.








    Главная | В избранное | Наш E-MAIL | Прислать материал | Нашёл ошибку | Наверх