Уильям Стивенс - UNIX: взаимодействие процессов
//shra/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 filesize, mmapsize, pagesize;
8 if (argc != 4)
9 err_quit("usage: test1 <pathname> <filesize> <mmapsize>");
10 filesize = atoi(argv[2]);
11 mmapsize = atoi(argv[3]);
12 /* открытие файла, установка его размера */
13 fd = Open(argv[1], O_RDWR | O_CREAT | O_TRUNC, FILE_MODE);
14 Lseek(fd, filesize-1, SEEK_SET);
15 Write(fd, "", 1);
16 ptr = Mmap(NULL, mmapsize, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
17 Close(fd);
18 pagesize = Sysconf(_SC_PAGESIZE);
19 printf("PAGESIZE = %ldn", (long) pagesize);
20 for (i = 0; i < max(filesize, mmapsize); i += pagesize) {
21 printf("ptr[*d] = %dn", i, ptr[i]);
22 ptr[i] = 1;
23 printf("ptr[%d] = %dn", i + pagesize – 1, ptr[i + pagesize – 1]);
24 ptr[i + pagesize – 1] = 1;
25 }
26 printf("ptr[%d] = %dn", i, ptr[i]);
27 exit(0);
28 }
Аргументы командной строки8-11 Аргументы командной строки задают полное имя создаваемого и отображаемого в память файла, его размер и размер области памяти.
Создание, открытие, урезание файла; установка его размера12-15 Если файл не существует, он будет создан. Если он существует, его длина будет установлена равной нулю. Затем размер файла устанавливается равным указанному размеру путем вызова lseek для установки текущей позиции, равной трe-буемому размеру минус 1 и записи 1 байта.
Отображение файла в память16-17 Файл отображается в память, причем размер области задается последним аргументом командной строки. Затем дескриптор файла закрывается.
Вывод размера страницы памяти18-19 Размер страницы памяти получается вызовом sysconf и выводится на экран.
Чтение и запись в область отображения20-26 Считываются и выводятся данные из области памяти, в которую отображен файл. Считываются первый и последний байты каждой страницы этой области памяти. Все значения должны быть нулевыми. Затем первый и последний байты каждой страницы устанавливаются в 1. Одно из обращений к памяти может привести к отправке сигнала процессу, что приведет к его завершению. После завершения цикла for мы добавляем еще одно обращение к следующей странице памяти, что должно заведомо привести к ошибке и завершению пpoгрaммы (если ошибка не возникла раньше).
Рассмотрим первую ситуацию: размер файла совпадает с размером области памяти, но эта величина не кратна размеру страницы памяти в данной реализации:
solaris % ls –l foo
foo: No such file or directory
solaris % test1 foo 5000 5000
PAGESIZE = 4096
ptr[0] = 0
ptr[4095] = 0
ptr[4096] = 0
ptr[8191] = 0
Segmentation Fault(coredump)
solaris % ls-l foo
-rw-r--r-- 1 rstevens other1 5000 Mar 20 17:18 foo
solaris % od –b –A d foo
0000000 001 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000
0000016 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000
*
0004080 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000 001
0004096 001 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000
0004112 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000 000
0005000
Размер страницы памяти составляет 4096 байт, и мы смогли обратиться ко всему содержимому второй страницы (индексы 4096-8191), но обращение к третьей странице (8192) приводит к отправке сигнала SIGSEGV, о чем интерпретатор оповещает сообщением Segmentation Fault. Хотя мы и установили значение ptr[8191] = 1, оно не было записано в файл и его размер остался равным 5000 байт. Ядро позволяет считывать и записывать данные в ту часть последней страницы, которая не относится к отображенному файлу (поскольку защита памяти осуществляется ядром постранично), но изменения в этой области памяти не будут скопированы в файл. А вот относящиеся к файлу изменения (индексы 0, 4095 и 4096) были скопированы в него, в чем мы убедились, воспользовавшись программой od (параметр –b при вызове последней указывает на необходимость выводить значения байтов в восьмеричном формате, а параметр –Ad позволяет выводить адреса в десятичном формате). На рис. 12.8 изображена схема памяти для данного примера.
Рис. 12.8. Размер отображаемого файла совпадает с размером области памяти
Запустив этот пример в Digital Unix 4.0B, получим тот же результат, но размер страницы памяти в этой системе равняется 8192 байт:
alpha % ls –l foo
foo not found
alpha % test1 foo5000 5000
PAGESIZE = 8192
ptr[0] = 0
ptr[8191] = 0
Memory fault (coredump)
alpha % ls -l foo
-rw-r-r– 1 rstevens operator 5000 Mar 21 08:40 foo
Мы все так же можем обратиться к памяти за пределами отображенного файла, но не выходя за грaницы страницы памяти (индексы с 5000 по 8191). Обращение к ptr[8192] приводит к отправке SIGSEGV, на что мы и рассчитывали.
Вторая ситуация: размер области памяти (15000 байт) превышает размер файла (5000 байт):
solaris % rm foo
solaris % test1 foo 5000 15000
ptr[0] = 0
ptr[4095] = 0
ptr[4096] = 0
ptr[8191] = 0
Bus Error(coredump)
solaris % ls –l foo
-rw-r-r– 1 rstevens other1 5000 Mar 20 17:37 foo
Рис. 12.9. Размер области памяти больше размера отображаемого файла
Полученный результат аналогичен результату предыдущего примера, в котором размер файла равнялся размеру области отображения (5000 байт). Однако в данном примере генерируется сигнал SIGBUS (о чем интерпретатор оповещает сообщением Bus Error), тогда как в предыдущем примере отправлялся сигнал SIGSEGV. Отличие в том, что SIGBUS означает выход за грaницы отображенного файла внутри области отображения, a SIGSEGV — выход за грaницы области. Этим примером мы показали, что ядро хранит информацию о размере отображенного объекта, даже несмотря на то, что его дескриптор закрыт. Ядро позволяет указать при вызове mmap размер области памяти, больший размера файла, но не позволяет обратиться к адресам в этой области (кроме остатка последней страницы, в которой еще имеется содержимое собственно файла — индексы с 5000 по 8191 в данном случае). На рис. 12.9 приведена иллюстрация к этому примеру.
Следующая программа приведена в листинге 12.7. Ею мы иллюстрируем типичные методы работы с увеличивающимися в размерах файлами: при отображении в память заказывается большой размер области, текущий размер файла учитывается при всех операциях (чтобы не выйти за его пределы в памяти), а затем он просто увеличивается, по мере того как в файл записываются данные.
Листинг 12.7. Отображение увеличивающегося файла в память//shm/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 /* открытие, создание, урезание и установка размера файла, вызов mmap */
10 fd = Open(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 file size to %dn", i);
14 Ftruncate(fd, i);
15 printf("ptr[%d] = %dn", i-1, ptr[i-1]);
16 }
17 exit(0);
18 }
Открытие файла9-11 Мы создаем файл, если он еще не существует, или урезаем его до нулевой длины, если он существует. Затем файл отображается в область объемом 32 768 байт, хотя его текущий размер равен нулю.
Увеличение размера файла12-16 Мы увеличиваем размер файла на 4096 байт за один вызов ftruncate (раздел 13.3) и считываем из него последний байт в каждом проходе цикла.
Запустив эту программу, мы убедимся в возможности обращаться к новым данным по мере роста файла:
alpha % ls –l test.data
test.data: No such file or directory
alpha % test2
setting file size to 4096
ptr[4095] = 0
setting file size to 8192
ptr[8191] = 0
setting file size to 12288
ptr[12287] = 0
setting file size to 16384
ptr[16383] = 0
setting file size to 20480
ptr[20479] = 0
setting file size to 24576
ptr[24575] = 0
setting file size to 28672
ptr[28671] = 0
setting file size to 32768
ptr[32767] = 0
alpha % ls-l test.data
-rw-r--r-- 1 rstevens other1 32768 Mar 20 17:53 test.data
Этот пример показывает, что ядро всегда следит за размером отображаемого в память объекта (в данном примере это файл test.data), и мы всегда имеем возможность обратиться к байтам, лежащим внутри области, ограниченной размером файла и размером отображения. Те же результаты получаются при запуске этой программы в Solaris 2.6.
В этом разделе мы работали с отображением файлов в память с помощью mmap. В упражнении 13.1 нам придется немного изменить две наших программы для работы с разделяемой памятью Posix, и мы получим те же результаты.