13.3. Функции ftruncate и fstat
Размер файла или объекта разделяемой памяти можно изменить вызовом ftruncate:
#include <unistd.h>
int ftruncate(int fd, off_t length);
/* Возвращает 0 в случае успешного завершения, –1 – в случае ошибки */
Стандарт Posix делает некоторые различия в определении действия этой функции для обычных файлов и для объектов разделяемой памяти.
1. Для обычного файла: если размер файла превышает значение length, избыточные данные отбрасываются. Если размер файла оказывается меньше значения length, действие функции не определено. Поэтому для переносимости следует использовать следующий способ увеличения длины обычного файла: вызов 1 seek со сдвигом length-1 и запись 1 байта в файл. К счастью, почти все реализации Unix поддерживают увеличение размера файла вызовом ftruncate.
2. Для объекта разделяемой памяти: ftruncate устанавливает размер объекта равным значению аргумента length.
Итак, мы вызываем ftruncate для установки размера только что созданного объекта разделяемой памяти или изменения размера существующего объекта. При открытии существующего объекта разделяемой памяти следует воспользоваться fstat для получения информации о нем:
#include <sys/types.h>
#include <sys/stat.h>
int fstat(int fd, struct stat *buf);
/* Возвращает 0 в случае успешного завершения. –1 – в случае ошибки */
В структуре stat содержится больше десятка полей (они подробно описаны в главе 4 [21]), но только четыре из них содержат актуальную информацию, если fd представляет собой дескриптор области разделяемой памяти:
struct stat {
…
mode_t st_mode; /* mode: S_I{RW}{USR,GRP,OTH} */
uid_t st_uid; /* UID владельца */
gid_t st_gid; /* GID владельца */
off_t st_size; /* размер в байтах */
…
};
Пример использования этих двух функций будет приведен в следующем разделе.
ПРИМЕЧАНИЕ
К сожалению, стандарт Posix никак не оговаривает начальное содержимое разделяемой памяти. Описание функции shm_open гласит, что «объект разделяемой памяти будет иметь нулевой размер». Описание ftruncate гласит, что для обычных файлов (не объектов разделяемой памяти) «при увеличении размера файла он будет дополнен нулями». Однако в этом описании ничего не говорится о содержимом разделяемой памяти. Обоснование Posix.1 (Rationale) говорит, что «разделяемая память при расширении дополняется нулями», но это не официальный стандарт. Когда автор попытался уточнить этот вопрос в конференции comp.std.unix, он узнал, что некоторые производители протестовали против введения требования на заполнение памяти нулями из-за возникающих накладных расходов. Если новая область памяти не инициализируется каким-то значением (то есть содержимое остается без изменения), это может угрожать безопасности системы.
13.4. Простые программы
Приведем несколько примеров программ, работающих с разделяемой памятью Posix.
Программа shmcreate
Программа shmcreate, текст которой приведен в листинге 13.1,[1] создает объект разделяемой памяти с указанным именем и длиной.
Листинг 13.1. Создание объекта разделяемой памяти Posix указанного размера
//pxshm/shmcreate.c
1 #include "unpipc.h"
2 int
3 main(int argc, char **argv)
4 {
5 int c, fd, flags;
6 char *ptr;
7 off_t length;
8 flags = O_RDWR | O_CREAT;
9 while ((c = Getopt(argc, argv, "e")) != –1) {
10 switch (c) {
11 case 'e':
12 flags |= O_EXCL;
13 break;
14 }
15 }
16 if (optind != argc – 2)
17 err_quit("usage: shmcreate [ –e ] <name> <length>");
18 length = atoi(argv[optind + 1]);
19 fd = Shm_open(argv[optind], flags, FILE_MODE);
20 Ftruncate(fd, length);
21 ptr = Mmap(NULL, length, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
22 exit(0);
23 }
19-22 Вызов shm_open создает объект разделяемой памяти. Если указан параметр –е, будет возвращена ошибка в том случае, если такой объект уже существует. Вызов ftruncate устанавливает длину (размер объекта), a mmap отображает его содержимое в адресное пространство процесса. Затем программа завершает работу. Поскольку разделяемая память Posix обладает живучестью ядра, объект разделяемой памяти при этом не исчезает.
Программа shmunlink
В листинге 13.2 приведен текст тривиальной программы, удаляющей имя объекта разделяемой памяти из системы.
Листинг 13.2. Удаление имени объекта разделяемой памяти Posix
//pxshm/shmunlink.c
1 #include "unpipc.h"
2 int
3 main(int argc, char **argv)
4 {
5 if (argc != 2)
6 err_quit("usage: shmunlink <name>");
7 Shm_unlink(argv[1]);
8 exit(0);
9 }
Программа shmwrite
В листинге 13.3 приведен текст программы shmwrite, записывающей последовательность 0, 1, 2 254, 244, 0, 1 и т. д. в объект разделяемой памяти.
Листинг 13.3. Заполнение разделяемой памяти
//pxshm/shmwrite.с
1 #include "unpipc.h"
2 int
3 main(int argc, char **argv)
4 {
5 int i, fd;
6 struct stat stat;
7 unsigned char *ptr;
8 if (argc != 2)
9 err_quit("usage: shmwrite <name>");
10 /* open, определяем размер, отображаем в память */
11 fd = Shm_open(argv[1], O_RDWR, FILE_MODE);
12 Fstat(fd, &stat);
13 ptr = Mmap(NULL, stat.st_size, PROT_READ | PROT_WRITE,
14 MAP_SHARED, fd, 0);
15 Close(fd);
16 /* присваиваем: ptr[0] = 0, ptr[1] = 1 и т. д. */
17 for (i = 0; i < stat.st_size; i++)
18 *ptr++ = i % 256;
19 exit(0);
20 }
10-15 Объект разделяемой памяти открывается вызовом shm_open. Его размер мы узнаем с помощью fstat. Затем файл отображается в память вызовом mmap, после чего его дескриптор может быть закрыт.
16-18 Последовательность записывается в разделяемую память.
Программа shmread
Программа shmread (листинг 13.4) проверяет значения, помещенные в разделяемую память программой shmwrite.
Листинг 13.4. Проверка значений в разделяемой памяти
//pxshm/shmread.c
1 #include "unpipc.h"
2 int
3 main(int argc, char **argv)
4 {
5 int i, fd;
6 struct stat stat;
7 unsigned char c, *ptr;
8 if (argc != 2)
9 err_quit("usage: shmread <name>");
10 /* вызываем open, узнаем размер, отображаем в память*/
11 fd = Shm_open(argv[1], O_RDONLY, FILE_MODE);
12 Fstat(fd, &stat);
13 ptr = Mmap(NULL, stat.st_size, PROT_READ,
14 MAP_SHARED, fd, 0);
15 Close(fd);
16 /* проверяем равенства ptr[0] = 0, ptr[1] = 1 и т. д. */
17 for (i = 0; i < stat.st_size; i++)
18 if ((c = *ptr++) != (i % 256))
19 err_ret("ptr[%d] = %d", i, c);
20 exit(0);
21 }
10-15 Объект разделяемой памяти открывается только для чтения, его размер получается вызовом fstat, после чего он отображается в память с доступом только на чтение, а дескриптор закрывается.
16-19 Проверяются значения, помещенные в разделяемую память вызовом shmwrite.
Примеры
Создадим объект разделяемой памяти с именем /tmp/myshm объемом 123 456 байт в системе Digital Unix 4.0B:
alpha % shmcreate /tmp/myshm 123456
alpha % ls –l /tmp/myshm
-rw-r--r-- 1 rstevens system 123456 Dec 10 14:33 /tmp/myshm
alpha % od –c /tmp/myshm
0000000
*
0361100
Мы видим, что файл с указываемым при создании объекта разделяемой памяти именем появляется в файловой системе. Используя программу od, мы можем выяснить, что после создания файл целиком заполнен нулями (восьмеричное число 0361100 — сдвиг, соответствующий байту, следующему за последним байтом файла, — эквивалентно десятичному 123 456).
Запустим программу shmwrite и убедимся в правильности записываемых значений с помощью программы od:
alpha % shmwrite /tmp/myshm
alpha * od –x /tmp/myshm | head-4
0000000 0100 0302 0504 0706 0908 0b0a 0d0c 0f0e
0000020 1110 1312 1514 1716 1918 1b1a 1d1c 1f1e
0000040 2120 2322 2524 2726 2928 2b2a 2d2c 2f2e
0000060 3130 3332 3534 3736 3938 3b3a 3d3c 3f3e
alpha % shmread /tmp/myshm
alpha % shmunlink /tmp/myshm
Мы проверили содержимое разделяемой памяти и с помощью shmread, а затем удалили объект, запустив программу shmunlink.
Если теперь мы запустим программу shmcreate в Solaris 2.6, то увидим, что файл указанного размера создается в каталоге /tmp: