LinuxForum.Fans
Тема

Ошибка в использовании функции mmap в С

Автор: nadya_shakh 22 сообщений 4554 просмотров
#321099

Доброго времени суток!

У меня вопрос по использованию функции отображения файла в память mmap.
void * mmap(void *start, size_t length, int prot , int flags, int fd, off_t offset);

У меня вызов этой функции приводит к ошибке, т.е. возвращаемый адрес области памяти равен -1. После изучения документации по mmap я думаю, что у меня неверно задан параметр length. Хотя в описании функции никаких ограничений на length не накладывается (длина может быть равна любому количеству байт), я так поняла, Linux читает файлы кусками размером со страницу, а потом их копирует в память. Так? Но я не могу понять, каков размер этой страницы. Должен ли параметр length быть кратен размеру этой страницы?

Я пробовала задавать разные значения параметров (len и stset -- см. ниже), но результат один и тот же. Вот соответствующий кусок из моего кода:

text
fd = open(argv[1], O_RDONLY);
if (fd == -1) {
 fprintf(stderr,"Ошибка чтения файла %s.\n", argv[1]);
 exit(1);
} 
addr = mmap(0, len, PROT_READ, MAP_SHARED, fd, stset);
close(fd);

Откомпилировалась программа успешно, но вот запуск идет с ошибкой. Какую бы проверку придумать, чтобы локализовать ошибку?
Надеюсь на Ваши советы, уважаемые участники форума. Спасибо заранее!

#321105

А какое значение записывается в errno после неудачного вызова mmap()?

#321109

Так, теперь пробую выразить, то что мне дали маны.
Определение функции: void * mmap(void *start, size_t length, int prot , int flags, int fd, off_t offset).
mmap() копирует в память length байт из файла, на который указывает дескриптор fd, начиная со смещения offset. При чем offset должен быть кратен размеру страницы памяти. Т.е. offset перед вызовом mmap() нужно уменьшить до ближайшего кратного размеру страницы памяти значения. Это делается так:

text

//offset - необходимое смещение
//pa_offset - смещение, кратное размеру страницы
pa_offset = offset & ~(sysconf(_SC_PAGE_SIZE) - 1);

Теперь в mmap() надо передавать не offset, а pa_offset. Т.е. в память отобразится кусок файла начиная не с offset, а начиная с pa_offset. Соответственно, параметр length необходимо увеличить на значение, на которое pa_offset сместился по отношению к старому offset. Элементарно:

text

new_length = length + offset - pa_offset;

В итоге получаем такой вызов mmap():

text

addr = mmap(NULL, new_length, PROT_READ, MAP_SHARED, fd, pa_offset);

Единственное НО. В мане написано, что в память будет отображаться length байт. Но у меня только что получилось, что отображается вся страница памяти целиком. Как и на что это повлияет - сейчас лень разбираться.
Да, еще. При работе с полученным адресом, информация, которую мы хотели получить по старому offset теперь будет начинаться не с addr[0], а, соответственно, с addr[offset - pa_offset] .
Вот, вроде, как-то так.

P.S.
Параметров void *start, int prot, int flags намеренно не касался, т.к. вроде бы к вопросу они не относятся.

#321112

Merzley,
большое спасибо за подробный и такой содержательный ответ!
Выходит, что я не могу, скажем, отразить только два байта из файла?
Почему-то в манах в интернете при кратность offset четко не сказано. Вот больше не буду теперь ориентироваться на переводы, надо читать англоязычные исходники :)
Еще раз спасибо, мне теперь все ясно!

#321114
"Merzley⇓" написал:
Единственное НО. В мане написано, что в память будет отображаться length байт.

Только что подробнее разобрал ман и понял, что я ошибся. Там написано, что length задает длину отображения, но само отображение происходит кратными размеру страницы порциями.

"nadya.shakh⇓" написал:
Выходит, что я не могу, скажем, отразить только два байта из файла?

Именно из-за такой реализации не получится прочитать из файла только два байта. mmap() будет читать из файла кусок длиной равной размеру страницы, в котором и будут находится искомые два байта. Или, при размере страницы в 4096 (как в моем случае), и попытке прочитать кусок файла длиной 4097, mmap() прочитает полные две страницы, т.е. 8192 байт.

#321115
Merzley написал:

Только что подробнее разобрал ман и понял, что я ошибся. Там написано, что length задает длину отображения, но само отображение происходит кратными размеру страницы порциями.

Тогда получается, что Length -- это параметр, на который mmap лишь ориентируется в выборе длины отображаемого куска, а кусок все равно будет кратен размеру страницы, так?

Merzley написал:
[quote="nadya.shakh⇓"]Выходит, что я не могу, скажем, отразить только два байта из файла?

Именно из-за такой реализации не получится прочитать из файла только два байта. mmap() будет читать из файла кусок длиной равной размеру страницы, в котором и будут находится искомые два байта. Или, при размере страницы в 4096 (как в моем случае), и попытке прочитать кусок файла длиной 4097, mmap() прочитает полные две страницы, т.е. 8192 байт.[/quote]
Интересно, а что произойдет, если размер файла меньше размера страницы? Или такого не может быть в принципе и минимальная длина файла равна 1 странице?

#321123

Насколько я помню, под файл отводится не меньше, чем кластер,
а он может быть и больше страницы.

Ещё есть fmemopen.

Пётр.

#321127
"Peter⇓" написал:
Насколько я помню, под файл отводится не меньше, чем кластер,

На сколько я помню, тоже. Т.е. за одну операцию чтения с диска читается минимум один блок. Даже если был запрос на один байт. Но это уже дела файловой системы. mmap() же, на сколько я понял, в первую очередь предназначен для работы с файлами не через файловые операции напрямую, а банально как с массивом байт, который, собственно, mmap() и предоставляет. Отсюда и ограничения на выравнивание по адресам - для оптимизации памяти. К файловой системе эти ограничения не относятся,

"nadya.shakh⇓" написал:
Тогда получается, что Length -- это параметр, на который mmap лишь ориентируется в выборе длины отображаемого куска, а кусок все равно будет кратен размеру страницы, так?

length - это количество байт, которые нам нужны. Но реально будет отображено от length байт до length + (PAGE_SIZE - (length % PAGE_SIZE)) байт. Или по-русски: будет гарантированно отображено length байт плюс сколько-то "лишних" байт, чтобы заполнить до конца последнюю страницу отображаемой памяти.

"nadya.shakh⇓" написал:
Интересно, а что произойдет, если размер файла меньше размера страницы? Или такого не может быть в принципе и минимальная длина файла равна 1 странице?

Если mmap() при отображении доходит до конца файла, то "недостающие" байты в выделенной памяти заполняются нулями. При этом, если попытаться что-то записать в ту область памяти, которая была заполнена нулями, то эта информация в файл не попадет. Т.е., на сколько я понял, эти нули добавляются исключительно для сохранения выравнивания в памяти, но реально в отображении область с нулями уже не участвует.

#321128

Да, еще:

"nadya.shakh⇓" написал:
Или такого не может быть в принципе и минимальная длина файла равна 1 странице?

Страница памяти и блок файловой системы - это разные вещи. Не путать. Ограничения mmap() как раз относятся не к файловой системе, а к памяти. Но да, минимальный размер файла на диске - один блок файловой системы.

#321133

Merzley,
спасибо за подробные разъяснения! Теоретически мне теперь все ясно. Написала небольшую тестовую программу, чтобы убедиться, что я правильно понимаю, как работает mmap. Эта программа сработала, как надо. Однако теперь, когда я беру реальный файл, который надо отразить (это двоичный файл, поток данных с радиотелескопа), то тут пока не все получается. Пытаюсь в цикле пройтись по отраженной области указателем и присвоить определенным переменным содержимое ячеек памяти. Получаю ошибку сегментирования... А какая именно операция к ней приводит, выяснить не получается пока. Буду разбираться. Если не получится самой, то я надеюсь снова получить в этой теме совет или подсказку:) Спасибо!

#321172
"nadya.shakh⇓" написал:
А какая именно операция к ней приводит, выяснить не получается пока.

чтобы определить:
1. собираешь с отладочной инфой (ключик -g для gcc)
2. запускаешь под gdb. (gdb path/to/my_super_program)
3. набираешь run.
4. ?????
5. PROFIT!!!

#321175
"nadya.shakh" написал:
А какая именно операция к ней приводит, выяснить не получается пока.

Ошибка сегментации происходит при записи в запрещенную область памяти. В случае с mmap() может произойти в нескольких случаях:
1. Либо когда происходит выход за границы выделенной mmap() памяти. Т.е. под addr было выделено 4096 байт, и пытаемся что-то записать в addr[4096] или дальше.
2. Файловый дескриптор fd был открыт только для чтения. (Здесь могу ошибаться. Не проверял, но в теории возможно)
3. mmap() была вызвана с параметром PROT_READ. В этом случае память, выделенная под addr помечается только для чтения, и попытка записи туда как раз приводит к ошибке сегментации.

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

#321366

Fat-Zer, Merzley,
Спасибо за советы! С mmap я разобралась с помощью ваших подсказок, все работает! НО. Теперь запуск программы на исполнение приводит к ошибке сегментации, которая мне совсем уж непонятна. Точнее, понятно, что происходит обращение к какой-то области памяти, куда не следовало бы, но словить эту ошибку не получается даже с помощью GDB. Далее я приведу набор команд (вывод на экран) и их результат. Буду благодарна за совет!!
Пояснение: push_02_2012306010000_1MiB.rdf -- это входной файл данных, обрабатываемый моей программой test_separate_streams.c (передается программе через аргумент командной строки).
------------------------------------------------------------------------------------------------------------------------------------------------------------

nadya@rainbow:~/precorr/develop/rdf1.1> make CFLAGS=-g test_separate_streams
cc -g -c -o rdf1.o rdf1.c
gcc -o test_separate_streams test_separate_streams.c rdf1.o
nadya@rainbow:~/precorr/develop/rdf1.1> gdb ./test_separate_streams
GNU gdb (GDB) SUSE (7.3-0.6.1)
Copyright (C) 2011 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law. Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-suse-linux".
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>...
Reading symbols from /home/nadya/precorr/develop/rdf1.1/test_separate_streams...done.
(gdb) run push_02_2012306010000_1MiB.rdf
Starting program: /home/nadya/precorr/develop/rdf1.1/test_separate_streams push_02_2012306010000_1MiB.rdf
Missing separate debuginfo for /lib64/ld-linux-x86-64.so.2
Try: zypper install -C "debuginfo(build-id)=3d68b93f8701971da6133437486f3909223534f8"
Missing separate debuginfo for /lib64/libc.so.6
Try: zypper install -C "debuginfo(build-id)=1a1e5cfce80fa9a99ba91775481b16897510abc2"
Значения, содержащиеся в 1-м и 2-м байтах данных (в восьмеричном формате):
x1 = 336
x2 = 166

Program received signal SIGSEGV, Segmentation fault.
0x0000000000407763 in ?? ()
(gdb) up
#1 0x00007ffff7dec844 in _dl_fini () from /lib64/ld-linux-x86-64.so.2
(gdb) up
#2 0x00007ffff7a9f7f5 in __run_exit_handlers () from /lib64/libc.so.6
(gdb) up
#3 0x00007ffff7a9f845 in exit () from /lib64/libc.so.6
(gdb)

#321367

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

#321378

А можно код посмотреть?

#321449

Да, код я вставила далее: два файла -- один с описанием функций, другой их вызывает и печатает результат. Заголовочный файл не стала помещать сюда.
Спасибо Вам большое за то, что помогаете мне найти ошибку!

#321451

Файл rdf1.c

text
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/mman.h> 
#include <unistd.h>
#include "rdf1.h"
#define TWO_TO_16 65536    // 2^16

typedef union { struct { unsigned char one; unsigned char two; } __attribute__ ((packed)) byte ; uint16_t uint16; } twobytes_t;

// Таблица для быстрого разделения потоков static twobytes_t stream_separate_table[TWO_TO_16]; /* Если рассматривать байты со смещением 2*i и 2*i+1 в rdf данных как 16-битовое целое без знака j, то stream_separate_table[j].byte.one и stream_separate_table[j].byte.two содержат, соответственно, байты со смещением i из первого и второго потоков */

int // Инициализация обработки rdf1_open(char* path, time_t* start_time, char* channels /* 'I', 'Q', 'A' или 'R' */ , rdr_mode_t* rdr_mode){

// Инициализация чтения rdf-файла (с помощью mmap) int fp, rval, j, length; rdr_mode_t rdr_mode_v; time_t start_time_v; rdf1_header_t rdf_data; struct tm time_date; void *addr, *ptr_rdf; rdf1_header_t rdf_header; unsigned char *ptr_in, *ptr_out;

/*****************************************************************************/ fp = open(path, O_RDONLY); // Открытие файла данных (режим чтения) length = TWO_TO_16*2 + sizeof(rdf_data); // Количество байт, отражаемых в память // (сумма размера таблицы и размера заголовка) addr = mmap(0, length, PROT_READ, MAP_SHARED, fp, 0); // Отражение файла в память close(fp); // Закрытие файла

if(addr == MAP_FAILED) { // Проверка корректности отражения printf("Ошибка использования mmap\n"); return (MMAP_ERR); // MMAP_ERR = -1 exit(1); };

ptr_rdf = memcpy(&rdf_data, addr, sizeof(rdf_data)); // Копирование отраженной области, //соответствующей заголовку исходного RDF-файла в переменную rdf_data

if (strcmp(rdf_data.file_type,"RDF1") != 0) { // Проверка идентификатора формата файла return(RDF1_BAD_ID_STRUCT); exit(1); }

if (strcmp(rdf_data.mode,"DEC") == 0){ // Идентификация характеристики видеоконвертора rdr_mode_v = rdr_mode_dec; rdr_mode = &rdr_mode_v; } else if (strcmp(rdf_data.mode,"DAS") == 0){ rdr_mode_v = rdr_mode_das; rdr_mode = &rdr_mode_v; } else { rdr_mode_v = rdr_mode_undef; rdr_mode = &rdr_mode_v; return(RDF1_BAD_RDR_MODE); exit(1); }

// Считывание информации из заголовка strptime(rdf_data.utc,"%Y %j-%T",&time_date); // Преобразование времени из строки в // структурный тип tm start_time_v = mktime(&time_date); // Преобразование времени в POSIX-формат start_time = &start_time_v; channels = &rdf_data.channels; // Мода RDF-файла

/**********************************************************************************/ // Заполнение stream_separate_table (с помощью rdf1_separate_stream)

ptr_in = (unsigned char*)addr + sizeof(rdf_data); // Смещение указателя на начало // блока радио-данных for (j = 0; j <= TWO_TO_16; j++) { rdf1_separate_streams(ptr_in, ptr_out); // Разделение потоков stream_separate_table[j].byte.one = *ptr_out; // Заполнение ячеек таблицы stream_separate_table[j].byte.two = *(ptr_out+1); ptr_in = ptr_in + 2; }

//Распечатка первых двух элементов таблицы (для отладки, потом будет удалено) printf("Значения, содержащиеся в 1-м и 2-м байтах данных (в восьмеричном формате):\n"); printf("x1 = %o\n", stream_separate_table[0].byte.one); printf("x2 = %o\n", stream_separate_table[0].byte.two);

return(RDF1_OK); }; /***************************************************************************/ // ВСПОМОГАТЕЛЬНЫЕ ПОДПРОГРАММЫ

void // Разделение потоков данных rdf1_separate_streams(unsigned char* rdf, unsigned char* streams){ unsigned char str_in_1, str_in_2; // Значения, содержащиеся в 1-м и 2-м байтах // входного потока rdf-данных до разделения потоков unsigned char str_out_1, str_out_2; // Значения, содержащиеся в 1-м и 2-м байтах // после разделения потоков unsigned char s1_0, s2_0, s1_1, s2_1, s1_2, s2_2, s1_3, s2_3; // Промежуточные переменные, // соответствующие парам битов rdf-данных: s1_0 <--> S1L(0) и S1U(0) и т.д.

str_in_1 = *rdf; str_in_2 = *(rdf+1);

// Выделение пар битов, соответствующих двум круговым поляризациям S1 и S2 // в моменты времени 0, 1, 2, 3: s1_0 = str_in_1 & 3; s2_0 = (str_in_1 >> 2) & 3; s1_1 = (str_in_1 >> 4) & 3; s2_1 = (str_in_1 >> 6) & 3; s1_2 = str_in_2 & 3; s2_2 = (str_in_2 >> 2) & 3; s1_3 = (str_in_2 >> 4) & 3; s2_3 = (str_in_2 >> 6) & 3;

// Разделение потоков ("сборка" новых чисел, соответствующим первому и второму потокам) str_out_1 = (s1_0 << 6) | (s1_1 << 4) | (s1_2 << 2) | s1_3; str_out_2 = (s2_0 << 6) | (s2_1 << 4) | (s2_2 << 2) | s2_3;

*streams = str_out_1; *(streams + 1) = str_out_2;

return; }

#321452

Файл test_separate_streams.c (вызывает функцию из rdf1.c и печатает результаты ее работы).

text
#include <stdlib.h>
#include <stdio.h>
#include "rdf1.h"
#include <fcntl.h>

int main(int argc, char *argv[]){ // argv[1] -- путь файла данных

int fd, flag; time_t start_time; char channels; rdr_mode_t rdr_mode;

if (argc > 2) { fprintf(stderr,"Неверное число аргументов в командной строке.\n"); exit(1); }

fd = open(argv[1], O_RDONLY); if (fd == -1) { fprintf(stderr,"Ошибка чтения файла %s.\n", argv[1]); exit(1); }

// Инициализация заголовка rdf-файла flag = rdf1_open(argv[1], &start_time, &channels, &rdr_mode); if (flag != 0) { fprintf(stderr,"Ошибочный формат заголовка входного файла данных %s.\n",argv[1]); fprintf(stderr,"Код возврата = %d.\n", flag); exit(1); }

fprintf(stdout,"Время первого отсчета в формате POSIX = %d", start_time); fprintf(stdout,"Мода RDF-файла = %c", channels); fprintf(stdout,"Характеристика видеоконвертора = %d", rdr_mode); fprintf(stdout,"(Возможные значения: 0 - неопределена; 1 - DAS (Земля); 2 - DEC (ВИРК))"); return 0; }

Пользуйтесь тегом [codе]. Fat-Zer

Войдите или зарегистрируйтесь, чтобы ответить.