Московский физико-технический институт
Опубликован: 16.09.2004 | Доступ: свободный | Студентов: 7876 / 1661 | Оценка: 4.44 / 4.28 | Длительность: 15:33:00
Лекция 8:

Организация файловой системы в UNIX. Работа с файлами и директориями. Понятие о memory mapped файлах

Понятие о файлах, отображаемых в память (memory mapped файлах). Системные вызовы mmap(), munmap()

Как уже говорилось, с помощью системного вызова open() операционная система отображает файл из пространства имен в дисковое пространство файловой системы, подготавливая почву для осуществления других операций. С появлением концепции виртуальной памяти, которая рассматривалась в лекции 9, когда физические размеры памяти перестали играть роль сдерживающего фактора в развитии вычислительных систем, стало возможным отображать файлы непосредственно в адресное пространство процессов. Иными словами, появилась возможность работать с файлами как с обычной памятью, заменив выполнение базовых операций над ними с помощью системных вызовов на использование операций обычных языков программирования. Файлы, чье содержимое отображается непосредственно в адресное пространство процессов, получили название файлов, отображаемых в память, или, по-английски, memory mapped файлов (см. лекцию 10). Надо отметить, что такое отображение может быть осуществлено не только для всего файла в целом, но и для его части.

С точки зрения программиста работа с такими файлами выглядит следующим образом:

  • Отображение файла из пространства имен в адресное пространство процесса происходит в два этапа: сначала выполняется отображение в дисковое пространство, а уже затем из дискового пространства в адресное. Поэтому вначале файл необходимо открыть, используя обычный системный вызов open() .
  • Вторым этапом является отображение файла целиком или частично из дискового пространства в адресное пространство процесса. Для этого используется системный вызов mmap() . Файл после этого можно и закрыть, выполнив системный вызов close() , так как необходимую информацию о расположении файла на диске мы уже сохранили в других структурах данных при вызове mmap() .

Системный вызов mmap()

Прототип системного вызова

#include <sys/types.h>
#include <unistd.h>
#include <sys/mman.h> 
void *mmap (void *start, size_t length, 
    int prot, int flags, int fd, 
    off_t offset);

Описание системного вызова

Системный вызов mmap служит для отображения предварительно открытого файла (например, с помощью системного вызова open() ) в адресное пространство вычислительной системы. После его выполнения файл может быть закрыт (например, системным вызовом close() ), что никак не повлияет на дальнейшую работу с отображенным файлом.

Настоящее описание не является полным описанием системного вызова, а предназначено только для использования в рамках данного курса. Для получения полной информации обращайтесь к UNIX Manual.

Параметр fd является файловым дескриптором для файла, который мы хотим отобразить в адресное пространство (т.е. значением, которое вернул системный вызов open() ).

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

В память будет отображаться часть файла, начиная с позиции внутри его, заданной значением параметра offset – смещение от начала файла в байтах, и длиной, равной значению параметра length (естественно, тоже в байтах). Значение параметра length может и превышать реальную длину от позиции offset до конца существующего файла. На поведении системного вызова это никак не отразится, но в дальнейшем при попытке доступа к ячейкам памяти, лежащим вне границ реального файла, возникнет сигнал SIGBUS (реакция на него по умолчанию – прекращение процесса с образованием core файла).

Параметр flags определяет способ отображения файла в адресное пространство. В рамках нашего курса мы будем использовать только два его возможных значения: MAP_SHARED и MAP_PRIVATE. Если в качестве его значения выбрано MAP_SHARED, то полученное отображение файла впоследствии будет использоваться и другими процессами, вызвавшими mmap для этого файла с аналогичными значениями параметров, а все изменения, сделанные в отображенном файле, будут сохранены во вторичной памяти. Если в качестве значения параметра flags указано MAP_PRIVATE, то процесс получает отображение файла в свое монопольное распоряжение, но все изменения в нем не могут быть занесены во вторичную память (т.е., проще говоря, не сохранятся).

Параметр prot определяет разрешенные операции над областью памяти, в которую будет отображен файл. В качестве его значения мы будем использовать значения PROT_READ (разрешено чтение), PROT_WRITE (разрешена запись) или их комбинацию через операцию "побитовое или" – " | ". Необходимо отметить две существенные особенности системного вызова, связанные с этим параметром:

  1. Значение параметра prot не может быть шире, чем операции над файлом, заявленные при его открытии в параметре flags системного вызова open() . Например, нельзя открыть файл только для чтения, а при его отображении в память использовать значение prot = PROT_READ | PROT_WRITE.
  2. В результате ошибки в операционной системе Linux при работе на 486-х и 586-х процессорах попытка записать в отображение файла, открытое только для записи, более 32-х байт одновременно приводит к ошибке (возникает сигнал о нарушении защиты памяти).

Возвращаемое значение

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

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

Системный вызов munmap

Прототип системного вызова

#include <sys/types.h>
#include <unistd.h>
#include <sys/mman.h> 

int munmap (void *start, size_t length);

Описание системного вызова

Системный вызов munmap служит для прекращения отображения memory mapped файла в адресное пространство вычислительной системы. Если при системном вызове mmap() было задано значение параметра flags, равное MAP_SHARED, и в отображении файла была разрешена операция записи (в параметре prot использовалось значение PROT_WRITE ), то munmap синхронизирует содержимое отображения с содержимым файла во вторичной памяти. После его выполнения области памяти, использовавшиеся для отображения файла, становятся недоступны текущему процессу.

Параметр start является адресом начала области памяти, выделенной для отображения файла, т.е. значением, которое вернул системный вызов mmap() .

Параметр length определяет ее длину, и его значение должно совпадать со значением соответствующего параметра в системном вызове mmap() .

Возвращаемое значение

При нормальном завершении системный вызов возвращает значение 0, при возникновении ошибки – значение -1.

Анализ, компиляция и прогон программы для создания memory mapped файла и записи его содержимого

Для закрепления материала, изложенного в предыдущем разделе, рассмотрим пример программы.

/* Программа 11-1.с для иллюстрации работы с 
memory mapped файлом */
int main(void)
{
    int fd; /* Файловый дескриптор для файла, в 
котором будет храниться наша информация*/
    size_t length; /* Длина отображаемой части файла */
    int i; 
    /* Ниже следует описание типа структуры, которым мы забьем
    файл, и двух указателей на подобный тип. Указатель ptr
    будет использоваться в качестве начального адреса 
    выделенной области памяти, а указатель tmpptr – для 
    перемещения внутри этой области. */
    struct A {
        double f;
        double f2;
    } *ptr, tmpptr;
    /* Открываем файл или сначала создаем его (если 
такого файла не было). Права доступа к файлу при создании 
определяем как read-write для всех категорий пользователей 
(0666). Из-за ошибки в Linux мы будем вынуждены ниже в 
системном вызове mmap() разрешить в отображении файла и 
чтение, и запись, хотя реально нам нужна только запись. 
Поэтому и при открытии файла мы вынуждены задавать O_RDWR. */
    fd = open("mapped.dat", O_RDWR | O_CREAT, 0666);
    if( fd == -1){
        /* Если файл открыть не удалось, выдаем 
сообщение об ошибке и завершаем работу */
        printf("File open failed!\n");
        exit(1);
    }
    /* Вычисляем будущую длину файла (мы собираемся записать
    в него 100000 структур) */
    length = 100000*sizeof(struct A);
    /* Вновь созданный файл имеет длину 0. Если мы его 
    отобразим в память с такой длиной, то любая попытка 
    записи в выделенную память приведет к ошибке. Увеличиваем
    длину файла с помощью вызова ftruncate(). */
    ftruncate(fd,length);
    /* Отображаем файл в память. Разрешенные операции над
    отображением указываем как PROT_WRITE | PROT_READ по 
    уже названным причинам. Значение флагов ставим в 
    MAP_SHARED, так как мы хотим с охранить информацию, 
    которую занесем в отображение, на диске. Файл 
    отображаем с его начала (offset = 0) и до конца 
    (length = длине файла). */
    ptr = (struct A )mmap(NULL, length, PROT_WRITE | 
        PROT_READ, MAP_SHARED, fd, 0);
    /* Файловый дескриптор нам более не нужен, и мы его
    закрываем */
    close(fd);
    if( ptr == MAP_FAILED ){
        /* Если отобразить файл не удалось, сообщаем об
        ошибке и завершаем работу */
        printf("Mapping failed!\n");
        exit(2);
    }
    /* В цикле заполняем образ файла числами от 1 до 100000
    и их квадратами. Для перемещения по области памяти 
    используем указатель tmpptr, так как указатель ptr на 
    начало образа файла нам понадобится для прекращения 
    иотображения вызовом munmap(). */
    tmpptr = ptr;
    for(i = 1; i <=100000; i++){
        tmpptr->f = i;
        tmpptr->f2 = tmpptr->f*tmpptr->f;
        tmpptr++;
    }
    /* Прекращаем отображать файл в память, записываем 
    содержимое отображения на диск и освобождаем память. */
    munmap((void *)ptr, length);
    return 0;
}
Листинг 11.1. Программа 11-1.с для иллюстрации работы с memory mapped файлом.

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

Обратите внимание на необходимость увеличения размера файла перед его отображением. Созданный файл имеет нулевой размер, и если его с этим размером отобразить в память, то мы сможем записать в него или прочитать из него не более 0 байт, т.е. ничего. Для увеличения размера файла использован системный вызов ftruncate() , хотя это можно было бы сделать и любым другим способом.

При отображении файла мы вынуждены разрешить в нем и запись, и чтение, хотя реально совершаем только запись. Это сделано для того, чтобы избежать ошибки в операционной системе Linux, связанной с использованием 486-х и 586-х процессоров. Такой список разрешенных операций однозначно требует, чтобы при открытии файла системным вызовом open() файл открывался и на запись, и на чтение. Поскольку информацию мы желаем сохранить на диске, при отображении использовано значение флагов MAP_SHARED. Откомпилируйте эту программу и запустите ее.

Изменение предыдущей программы для чтения из файла, используя его отображение в память

Модифицируйте программу из предыдущего раздела так, чтобы она отображала файл, записанный программой из раздела "Анализ, компиляция и прогон программы для создания memory mapped файла и записи его содержимого", в память и считала сумму квадратов чисел от 1 до 100000, которые уже находятся в этом файле.

Задача повышенной сложности: напишите две программы, использующие memory mapped файл для обмена информацией при одновременной работе, подобно тому, как они могли бы использовать разделяемую память

лия логовина
лия логовина

организовать двустороннюю поочередную связь процесса-родителя и процесса-ребенка через pipe, используя для синхронизации сигналы sigusr1 и sigusr2.

Макар Оганесов
Макар Оганесов