Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Text111

.pdf
Скачиваний:
49
Добавлен:
06.02.2018
Размер:
1.16 Mб
Скачать

printf(“\nPause after writing word into 4096 offset.\n”); getchar();

n=*(int*)(pm+4096);

printf(“Reading from 4096 offset number=%d\n”, n); VirtualFree(pm, 16384 ,MEM_RELEASE); ExitProcess(0);

}

void QueryMem(void *pmem, DWORD region) {DWORD rc;

DWORD state, type, protect; MEMORY_BASIC_INFORMATION rec; void *pbase, *pregion;

rc=VirtualQuery(pmem, &rec, sizeof(rec)); state=rec.State; protect=rec.Protect; region=rec.RegionSize;

pbase=rec.AllocationBase; pregion=rec.BaseAddress; type=rec.Type;

if (rc= =0) {printf(“Error QueryMem with RC=%ld\n”, rc); exit(0);} printf(“Base address_=%08X, region=%ld, BaseRegion=%ld\n”,

pbase, region, pregion);

if (protect&PAGE_READWRITE) printf(“PAGE_READWRITE “); if (protect&PAGE_WRITECOPY) printf(“PAGE_WRITECOPY “); if (protect&PAGE_READONLY) printf(“PAGE_READONLY “); if (protect&PAGE_EXECUTE) printf(“PAGE_EXECUTE “);

if (protect&PAGE_EXECUTE_READWRITE) printf(“PAGE_EXECUTE_READWRITE “);

if (protect&PAGE_EXECUTE_WRITECOPY) printf(“PAGE_EXECUTE_WRITECOPY “);

if (protect&PAGE_GUARD) printf(“PAGE_GUARD “);

if (protect&PAGE_NOACCESS) printf(“PAGE_NOACCESS “); if (state&MEM_COMMIT) printf(“MEM_COMMIT “);

if (state&MEM_RESERVE) printf(“MEM_RESERVE “); if (state&MEM_FREE) printf(“MEM_FREE “);

if (type&MEM_IMAGE) printf(“MEM_IMAGE “);

if (type&MEM_MAPPED) printf(“MEM_MAPPED “); if (type&MEM_PRIVATE) printf(“MEM_PRIVATE “);

}

Листинг 10.3.1. Распределение памяти в Windows

В последней программе вначале запрашивается резервирование региона памяти размером в 16 000 байтов, причем сразу задается, что доступ к этому региону будет только по чтению. Затем служебной подпрограммой, построенной на основе функции VirtualQuery, запрашивается информация об участке виртуальной памяти, начинающейся с возвращенного базового адреса, но охватывающего несколько больше запрошенного, а именно 4 страницы. Отображаемая подпрограммой информация свидетельствует, что действительно выделено больше чем 16000, а 16384 байта, т.е.- ровно четыре страницы.

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

После этого в байты распределенного региона, имеющие смещение от его начала 999, 1000 и 4095, записываются данные. Несмотря на то, что и байт по смещению 1000 и тем более байт по смещению 4095 находятся за пределами участка в 1000 байтов, на котором явно задан доступ по записи и который, казалось бы, только и введен в действие, эти записи свободно осуществляются. Тем самым демонстрируется, что заказанные действия неизбежно распространяются на всю текущую страницу памяти.

Далее в программе функцией VirtualProtect меняется вид доступа к первой странице региона, а именно меняется на "только для чтения". Затем опрашивается состояние первых четырех страниц, получается информация только о первой, которая и введена в действие, несмотря на то, что и дальнейшие и эта страница имеют доступ только для чтения. Из записанных ранее байтов читается информация, что удается сделать, так как такой доступ разрешен и теперь.

Затем на 1000 байтов второй страницы устанавливается доступ по записи, и они вводятся в действие. Опрос с помощью служебной подпрограммы подтверждает, что состояние первой страницы региона не изменилось, а вся вторая страница введена в действие и имеет доступ и по записи. Для проверки в начало этой страницы записывается байт данных, который после этого читается и выводится на экран. Перед завершением программа освобождает весь регион, задавая константой MEM_RELEASE его полное освобождение.

10.4. Совместное использование памяти

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

кооперативными.

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

Изучение средств совместного использования памяти начнем с ОС Unix. Здесь разделяемая память находится под непосредственным управлением ядра, которое содержит таблицу описания областей разделяемой памяти. Каждая из областей обозначается в этой таблице целочисленным идентификатором (а не текстовым именем, как в других ОС). Кроме того, каждая такая область описывается в этой таблице атрибутами доступа и размером. Области разделяемой памяти относятся к адресному пространству ядра ОС.

Доступ к разделяемой памяти со стороны процесса осуществляется в два этапа. На первом из них получается хэндл области памяти, причем на этом этапе либо открывается доступ к уже имеющейся в ОС области памяти, либо такая область создается операционной системой. (Формально ситуация очень напоминает предварительные действия перед непосредственной работой с файлом.) На втором этапе процесс подключается к разделяемой области (to attach), используя ранее полученный хэндл. (Заметим, что сам термин хэндл в первоисточниках по Unix не используется, а применяется термин идентификатор, который в данном тексте действительно точнее. Мы же будем использовать термин хэндл для единообразного рассмотрения средств разделяемой памяти в различных ОС.)

На этапе подключения происходит подсоединение указанной области памяти, находящейся в ведении ОС, к виртуальному адресному пространству процесса, запросившего такое подключение. Результатом этой операции является базовый виртуальный адрес, начиная с которого в текущем процессе можно обращаться к разделяемой памяти.

Для получения разделяемой памяти предназначена функция с прототипом int shmget(key_t key, int size, int flag),

возвращающая при удачном выполнении целочисленное значение требуемого идентификатора. Первым аргументом этой функции является уникальный номер (внешний идентификатор) области памяти, по своему смыслу аналогичный уникальному имени этого объекта. Вместо решения проблемы уникальности этого значения для новой области разделяемой памяти можно использовать специальную символьную константу IPC_PRIVATE, которая автоматически обеспечит создание внутри вызова уникального идентификатора исключительно для индивидуального пользования текущим процессом и его потомками. Кроме того, в качестве такого обозначения области разделяемой памяти может использоваться значение, полученное в другом процессе и как-то переданное в данный.

Аргумент size задает размер создаваемой по запросу области или ограничение по размеру на уже имеющуюся (больше, чем заданный размер по идентификатору, полученному от функции shmget, использовать будет нельзя). Аргумент flag в простейшем случае задается нулевым, но когда вызывающему процессу нужно создать область с заданным значением key, и области с таким идентификатором нет, то значение этого аргумента должно быть результатом логического сложения символьной константы IPC_CREAT и прав доступа по чтению и записи к новой области памяти. При неудаче функция возвращает значение -1.

Для подключения процесса к запрошенной ранее области разделяемой памяти служит функция с прототипом

void* shmat(int shmid, void* addr, int flag),

первый аргумент которой должен быть получен от предварительного вызова функции shmget, рассмотренной выше. Второй аргумент, обозначенный addr, задает виртуальный начальный адрес вызывающего процесса, в который следует отобразить разделяемую память. Для поручения ОС самой выбрать базовый адрес в виртуальном пространстве вызывающего процесса этот аргумент задается равным нулю (именно это значение рекомендуется для большинства ситуаций). Ненулевые значения следует выбирать только по крайней необходимости, например, когда в разделяемой памяти хранятся ссылки на адреса, что имеет место в связном списке.

При ненулевом аргументе addr последний аргумент может содержать флаг SHM_RND, приказывающий обнулить заданное значение базового адреса до границы страницы. Аргумент flag может также содержать флаг SHM_RDONLY, задающий режим доступа к подключаемой памяти только по чтению. При отсутствии такого флага доступ в дальнейшем будет осуществляться как по чтению, так и по записи. Функция в качестве собственного значения возвращает виртуальный адрес отображения разделяемой памяти, а при неудаче – значение -1.

Для отсоединения разделяемой памяти должна использоваться функция с прототипом

int shmdt(void* addr),

аргументом которой является адрес, ранее полученный от функции shmat. При успешном выполнении она возвращает 0, а при неудаче -1.

Кроме рассмотренных базовых функций для разделяемой памяти, в Unix имеется функция расширенного управления разделяемой памятью с прототипом

int shmctl(int shmid, int cmd, struct shmid_ds *buf).

Первым аргументом этой функции служит идентификатор, полученный от функции shmget. Собственно операции данной функции задаются вторым аргументом, который может задаваться константами IPC_STAT, IPC_SET и IPC_RMID. Первая из них задает операцию получения информации об области разделяемой памяти, вторая – изменение характеристик этой области (изменение прав доступа), а последняя служит для указания освобождения области разделяемой памяти. Третий аргумент используется только с операциями запроса информации и изменения характеристик, а для IPC_RMID задается значением NULL.

Следующий пример, приведенный в листинге 10.4.1а и 10.4.1b двумя исходными текстами программ для Unix, демонстрирует рассмотренное построение разделяемой памяти.

#include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/stat.h> #include <sys/shm.h>

void main() {char *pm; int hmem;

printf(“Begin work\n”);

hmem=shmget(18011970, 16000, IPC_CREAT | 0600); if (hmem = = -1)

{perror(“Error AllocSharedMem with:”); getchar(); exit(0);} pm=shmat(hmem, NULL, 0);

if (pm= =NULL) {perror(“shmat”); exit(3);} strcpy(pm, “—”);

sleep(8);

 

 

strcpy(pm, “Privet Shara !!!”);

printf(“Middle work\n”);

sleep(10);

printf(“Two step\n”);

strcpy(pm+7, “- Good Bye !!!”);

sleep(10);

shmdt(pm);

shmctl(hmem, IPC_RMID, NULL); exit(0);

}

Листинг 10.4.1а. Первая программа с разделяемой памятью в Unix

#include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <sys/ipc.h> #include <sys/stat.h> #include <sys/shm.h>

void main() {char *pb, st[20]; int hmem;

int k;

hmem=shmget(18011970, 16000, 0600); if (hmem= =-1)

{printf(“Error Shared Get Mem with\n”); getchar(); exit(0);} pb=shmat(hmem, NULL, SHM_RDONLY);

if (pb= =0)

{printf(“Error Attach Shared Mem\n”); getchar(); exit(0);} for (k=0;k<10;k++ )

{strncpy(st, pb, 20); st[19]=’\0'; printf(“%s\n”, st); sleep(2);} shmdt(pb);

exit(0);

}

Листинг 10.4.1b. Вторая программа с разделяемой памятью в Unix

Вначале следует запускать программу, созданную из исходного текста листинга 10.4.1a, а затем уже программу, созданную из исходного текста по листингу 10.4.1b.

При использовании разделяемой памяти в Linux программисту предоставляется очень удобное системное средство – команда ipcs. Для получения информации о разделяемой памяти эту команду следует вызвать с опцией m, так что весь вызов имеет вид

ipcs -m

Эта команда выводит информацию о присвоенном ключе-идентификаторе, идентификаторе области памяти, владельце, размере и правах доступа к ней.

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

HANDLE CreateFileMapping(HANDLE hFile, //хэндл файла или условное значение

SECURITY_ATTRIBUTES *pFileMappingAttributes, DWORD protect, // protection for mapping object DWORD MaxSizeHigh, // high-order 32 bits of object size DWORD MaxSizeLow, // low-order 32 bits of object size CTSTR *pName);

Для построения области разделяемой памяти в первом аргументе этой функции должна задаваться специальная константа, равная -1 (обычно задается как (HANDLE)0xFFFFFFFF ). Второй аргумент связан с атрибутами защиты и в простейших случаях берется равным нулевому указателю. Четвертый и пятый аргументы в совокупности задают 64-битное значение предельного размера отображаемого объекта, причем в аргументе MaxSizeHigh должны находиться старшие биты этого значения. Для 32-битных версий Windows этот аргумент можно спокойно брать равным нулю. Последний аргумент pName задает имя разделяемой области памяти в нашем текущем рассмотрении или имя области отображения в общем случае. Аргумент protect служит для задания видов доступа к используемой области виртуальной памяти и должен в нашем случае задаваться с помощью константы PAGE_READONLY или PAGE_READWRITE. При невозможности выполнения функция возвращает значение NULL, иначе она предоставляет значение хэндла, через который в дальнейшем возможен доступ к области виртуальной памяти. Заметим, что на этапе, обеспечиваемом рассмотренной функцией, получен только хэндл, но нет информации о базовом адресе виртуальной области памяти, которую с помощью этого хэндла можно использовать. Практически этот этап имеет много общего с действиями фунции shmget из Unix.

Базовый адрес разделяемой памяти получается для использования с помощью функции, имеющий прототип

void* MapViewOfFile(HANDLE hFileMappingObject, DWORD DesiredAccess,

DWORD OffsetHigh, DWORD OffsetLow, DWORD size), которая и возвращает требуемый базовый адрес памяти или NULL при невозможности выполнения.

Здесь первый параметр должен быть взят от предыдущего вызова функции CreateFileMapping, предоставляющего хэндл объекта отображения. Третий и четвертый аргументы этой функции совместно задают 64-битное смещение внутри виртуальной области памяти, созданной вызовом функции CreateFileMapping. В большинстве применений это смещение берется нулевым. Аргумент DesiredAccess может задаваться константой FILE_MAP_WRITE или FILE_MAP_READ. Первая задает доступ как по чтению, так и по записи, вторая – только по чтению. Последний аргумент size задает размер разрешенного для дальнейшего использования диапазона виртуальных адресов с заданным видом доступа. Нулевое значение этого аргумента равносильно указанию на использование всего возможного диапазона адресов исходного объекта, созданного функцией CreateFileMapping.

После завершения использования разделяемой памяти для освобождения уже ненужных ресурсов следует вызывать функцию UnmapViewOfFile, имеющую прототип

BOOL UnmapViewOfFile( void* pBaseAddress),

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

Следующий пример, приведенный в листинге 10.4.2a и 10.4.2b, демонстрирует применение разделяемой памяти для передачи данных между двумя процессами в Windows.

#include <windows.h> #include <stdio.h> #include <stdlib.h> #include <string.h>

void main() {char *pm; HANDLE hmem;

hmem=CreateFileMapping((HANDLE)0xFFFFFFFF,NULL, PAGE_READWRITE, 0, 16000, “SHAREMEM_MYY”);

if (hmem= =0)

{printf(“Error AllocSharedMem with RC=%ld\n”, GetLastError()); getchar(); exit(0);}

pm=MapViewOfFile(hmem, FILE_MAP_WRITE,0,0,0); if (pm= =NULL)

{printf(“Error Mapping SharedMem with RC=%ld\n”, GetLastError());

getchar(); exit(0);}

 

Sleep(4000);

 

strcpy(pm, “Privet Shara !!!”);

printf(“Middle work\n”); Sleep(10000);

printf(“Two step\n”);

 

strcpy(pm+7, “- Good Bye !!!”);

Sleep(10000);

UnmapViewOfFile(pm);

 

CloseHandle(hmem);

 

}

Листинг 10.4.2a. Первая программа с разделяемой памятью для Windows

#include <windows.h> #include <stdio.h> #include <stdlib.h> #include <string.h>

void main() {char *pb, st[20]; HANDLE hmem; int k;

hmem=OpenFileMapping(FILE_MAP_READ, FALSE, “SHAREMEM_MYY”);

if (hmem= =0)

{printf(“Error OpenSharedMem with RC=%ld\n”, GetLastError()); getchar(); exit(0);}

pb=MapViewOfFile(hmem, FILE_MAP_READ,0,0,0); if (pb= =NULL)

{printf(“Error Mapping SharedMem with RC=%ld\n”, GetLastError()); getchar(); exit(0);}

for (k=0;k<10;k++ )

{strncpy(st, pb, 20); st[19]=’\0'; printf(“%s\n”, st); Sleep(2000); } UnmapViewOfFile(pb); CloseHandle(hmem);

}

Листинг 10.4.2b. Вторая программа с разделяемой памятью для Windows

Для демонстрации поведения программ следует вначале запускать программу, созданную из исходного текста в листинге 10.4.2a, а затем уже программу, созданную из исходного текста в листинге 10.4.2b.

10.5. Отображение файлов в оперативную память

Отображение файлов в оперативную память впервые появилось в BSD Unix. Идея такого отображения появилась как результат осмысливания избыточности действий при файловом вводе и выводе в операционной системе, использующей виртуальную память. Действительно, при вводе и выводе происходит пересылка данных между внешней памятью, хранящей файл, и служебными областями ОС (буферами ввода-вывода внутри адресного пространства ОС), и тут же неизбежная пересылка данных между буферами ОС и буферами данных прикладного процесса. Заметим, что использование системой ввода-вывода непосредственно пользовательских буферов оказалось бы нарушением правил защиты памяти с вытекающими отсюда неприятностями для функционирования системы. А двукратная пересылка данных заметно снижает быстродействие системы при работе с файлами.

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

Использование в программах Unix файлов, которые отображаются в память, требует заголовочного файла mman.h, расположенного в подкаталоге sys стандартного каталога для заголовочных файлов, так что его подключение выполняется директивой

#include <sys/mman.h>

Для использования рассматриваемого отображения в Unix служит системная функция с прототипом

void* mmap(void* addr, size_t size, int prot, int flag, int fd, off_t pos),

где аргумент fd задается хэндлом уже открытого файла, который требуется отображать. Пожелание о значении виртуального адреса процесса, с которого предполагается отображение, задается аргументом addr. Если этот аргумент задается равным нулю, то сама система назначает виртуальный адрес начала области отображения, который всегда возвращается как значение функции при ее успешном выполнении. При неудаче возвращается значение -1.

Аргумент size определяет размер отображаемой области файла. Если его значение не кратно размеру страницы, то size байтов будут взяты из файла, а оставшаяся часть страницы заполнена нулями. Аргумент pos задает начальную позицию в файле, с которой начинается отображение, что дает возможность отображать не только весь файл, но и любой его последовательный участок. Параметр pos должен быть кратен размеру страницы.

Аргумент prot задает права доступа к отображаемой памяти и может представляться одной из следующих символьных констант: PROT_READ,

Соседние файлы в предмете Операционные системы