I have a problem considering the usage of mmap. I am trying to map a pci device to a virtual address and read its content. In the future I am planning to write values to it as well.
The problem is that I (seemingly) successfully mapped the device to virtual memory space. However when I read the content of that virtual address all values are zero, despite the file not being empty.
Here is my code:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include "../include/types.h"
#include "../include/pci.h"
#define PRINT_ERROR \
do { \
fprintf(stderr, "Error at line %d, file %s (%d) [%s]\n", \
__LINE__, __FILE__, errno, strerror(errno)); exit(1);\
} while(0)
#define MAP_SIZE 4069
#define MAP_MASK (MAP_SIZE - 1)
int main(int argc, char *argv[])
{
int pci_dev;
int *mmap_base;
int *content;
char file[] = {"/sys/bus/pci/devices/0000:04:00.0/resource"};
int i;
printf("File to be read from: %s\n", file);
pci_dev = open(file, O_RDONLY);
if (pci_dev < 0)
{
PRINT_ERROR;
}
mmap_base = mmap(NULL, MAP_SIZE, PROT_READ, MAP_PRIVATE | MAP_ANON, pci_dev, 0);
if (mmap_base == (void *)-1 || mmap_base == NULL)
{
PRINT_ERROR;
}
printf("Mapped on address %p of size %d Byte\n", mmap_base, (int)MAP_SIZE);
content = (int *)mmap_base;
for(i = 0; i < 1024; i++)
{
printf("%x", content[i]);
}
return 0;
}
Here's the content of the first line from the file "/sys/bus/pci/devices/0000:04:00.0/resource" that I am trying to access:
0x00000000cd000000 0x00000000cd07ffff 0x0000000000040200
However the output I get is:
File to be read from: /sys/bus/pci/devices/0000:04:00.0/resource
Mapped on address 0xb7705000 of size 4096 Byte
0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000...
Am I doing something wrong? Every help is appreciated!
Actually you've got 2 mistakes:
Don't use MAP_ANON when you create a map for a real file on the file system, it's meant for IPC and requiring extra memory from OS e.g. while malloc().
When you remove the flag, the mmap() will likely return ENODEV, because linux sysfs doesn't support mmaping; So you have to use read()'s here.
Related
This question already has answers here:
How to change characters in a text file using C's mmap()?
(2 answers)
Closed 2 years ago.
I am trying to read and write a struct using mmap, however the changes I do after the mmap are not being persisted to the disk or are not being loaded correctly.
Here's my code sample, when run the first time the file is created and the prints show the correct data, on the second time when the file already exists the struct is empty, filled with 0's. So it looks like the changes were not written to the file but I am having trouble figuring out why.
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
typedef struct {
int age;
char name[128];
} person;
int main(int argc, char *argv[argc]){
char filename [] = "data.person";
int fd = open(filename, O_RDWR | O_CREAT , S_IRWXU);
if (fd == -1) {
printf("Failed to create version vector file, error is '%s'", strerror(errno));
exit(1);
}
struct stat st;
fstat(fd, &st);
person *p;
if (st.st_size == 0) {
ftruncate(fd, sizeof(person));
p = (person *) mmap(0, sizeof(person), PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
strcpy(p[0].name, "Hello");
p[0].age = 10;
msync(p, sizeof(person), MS_SYNC);
}else{
p = (person *) mmap(0, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
if( p == MAP_FAILED){
printf("mmap failed with error '%s'\n", strerror(errno));
exit(0);
}
}
printf("%s %d\n", p->name, p->age);
munmap(p, sizeof(person));
close(fd);
}
My OS is manjaro 20 and the gcc version is 10.1
Do not use MAP_PRIVATE because:
Create a private copy-on-write mapping. Updates to the
mapping are not visible to other processes mapping the same
file, and are not carried through to the underlying file. It
is unspecified whether changes made to the file after the
mmap() call are visible in the mapped region.
After a major OS upgrade this C code behaviour has changed:
...
if ((fd = open(argv[1], O_RDWR | O_SYNC)) == -1)
FATAL;
printf("character device %s opened.\n", argv[1]);
fflush(stdout);
/* map one page */
map_base = mmap(0xe0000000, MAP_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (map_base == (void *)-1)
FATAL;
printf("Memory mapped at address %p.\n", map_base);
...
With a binary inherited from an old OS, "old mmap" returns a virtual address 0x7fb20d725000. If I rebuild the same C file on a new OS, it returns 0xe0000000 which seems to be a physical, and subsequent code - which uses this returned address - now fails with a segmentation fault.
How to force mmap to work as before without downgrading the OS or using old binary? Any modern flags for gcc or mmap itself?
Run a code example below with sudo ./test /dev/zero 0x01000000 : (/dev/zero instead of a real device gives the same results)
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <byteswap.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
#include <fcntl.h>
#include <ctype.h>
#include <termios.h>
#include <sys/types.h>
#include <sys/mman.h>
/* ltoh: little to host */
/* htol: little to host */
#if __BYTE_ORDER == __LITTLE_ENDIAN
#define ltohl(x) (x)
#define ltohs(x) (x)
#define htoll(x) (x)
#define htols(x) (x)
#elif __BYTE_ORDER == __BIG_ENDIAN
#define ltohl(x) __bswap_32(x)
#define ltohs(x) __bswap_16(x)
#define htoll(x) __bswap_32(x)
#define htols(x) __bswap_16(x)
#endif
#define FATAL do { fprintf(stderr, "Error at line %d, file %s (%d) [%s]\n", __LINE__, __FILE__, errno, strerror(errno)); exit(1); } while(0)
#define MAP_SIZE (16*1024*1024UL)
#define MAP_MASK (MAP_SIZE - 1)
int main(int argc, char **argv)
{
int fd;
void *map_base, *virt_addr;
uint32_t read_result, writeval;
off_t target;
char *device;
if (argc != 3) {
fprintf(stderr,
"\nUsage:\t%s <device> <address> [[type] data]\n"
"\tdevice : character device to access\n"
"\taddress : memory address to access\n\n",
argv[0]);
exit(1);
}
device = strdup(argv[1]);
target = strtoul(argv[2], 0, 0);
fprintf("argc = %d, device: %s, address: 0x%08x\n", argc, device, (unsigned int)target);
if ((fd = open(argv[1], O_RDWR | O_SYNC)) == -1)
FATAL;
fprintf(stdout, "character device %s opened.\n", argv[1]);
fflush(stdout);
/* map one page */
map_base = mmap(0xe0000000, MAP_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (map_base == (void *)-1)
FATAL;
fprintf(stdout, "Memory mapped at address %p.\n", map_base);
fflush(stdout);
/* calculate the virtual address to be accessed */
virt_addr = map_base + target;
/* read only */
read_result = *((uint32_t *) virt_addr);
/* swap 32-bit endianess if host is not little-endian */
read_result = ltohl(read_result);
printf("Read 32-bit value at address 0x%08x (%p): 0x%08x\n",
(unsigned int)target, virt_addr, (unsigned int)read_result);
if (munmap(map_base, MAP_SIZE) == -1)
FATAL;
close(fd);
return 0;
}
You seem to be confusing virtual and physical addresses. User programs usually only work with virtual addresses. The mmap syscall accepts an hint as first argument: a desired virtual address for the requested mapped area. See man 2 mmap for more information.
What was most likely happening with your previous program was that the call to mmap was probably something like:
map_area = mmap(NULL, /* same arguments here */);
This way, the operating system will choose an appropriate address and return it.
What you are doing in the new program instead, is letting the OS know that you would prefer a specific address (0xe...), and the OS will map memory at that address if possible (very likely). You really shouldn't need this, the program works regardless of the position of the mapped area, but in any case you can keep it.
The reason why you are getting a segmentation fault is because you are mapping an area of 16 * 1024 * 1024 bytes (0x01000000), but then you are accessing memory at an higher offset than the specified size (target >= 0x01000000).
The correct way to do what you are trying to do is to use the offset argument of mmap to request a map that starts at an appropriate offset in the file. Requesting a mapping of two pages starting at that offset will ensure that what you want to read or write will be correctly mapped (assuming the file is big enough, otherwise MAP_FAILED will be returned).
Here's how it should be done:
offset = target & 0xFFFFFFFFFFFFF000; // align target to page size
// Map two pages starting at 0xe... and corresponding to the calculated offset in the file.
map_base = mmap((void *)0xe0000000, 0x1000 * 2, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, offset);
// ...
virt_addr = map_base + (target & 0xfff); // cut target to get offset within the mapped pages
read_result = *((uint32_t *) virt_addr);
read_result = ltohl(read_result);
printf("Read 32-bit value at address 0x%08x (%p): 0x%08x\n",
(unsigned int)target, virt_addr, (unsigned int)read_result);
I am trying to mmap BAR-0 of a PCI device to the virtual address space in order to read (and later modify) its contents. However when I mmap it, I get the following error:
Path to BAR-0: /sys/bus/pci/devices/0000:00:16.0/resource0
Error at line 42, file src/main.c (9) [Bad file descriptor]
I am using Debian 9.1 32-Bit. The code I use is the following:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include "../include/eeprom.h"
#include "../include/pci.h"
#include "../include/i2c.h"
#include "../include/types.h"
#define PRINT_ERROR \
do { \
fprintf(stderr, "Error at line %d, file %s (%d) [%s]\n", \
__LINE__, __FILE__, errno, strerror(errno)); exit(1);\
} while(0)
#define MAP_SIZE 4096
#define MAP_MASK (MAP_SIZE - 1)
int main (int argc, char* argv[]) {
uint32_t pci_dev;
void *mmap_base;
char *file = {"/sys/bus/pci/devices/0000:00:16.0/resource0"};
int i;
printf ("Path to BAR-0: %s\n", file);
//Open Bar-0 of PCI device
pci_dev = open (file, O_RDWR | O_SYNC);
if (pci_dev < 0)
{
PRINT_ERROR;
}
//Map BAR-0 from physical memory to virtual address space
mmap_base = mmap (NULL, MAP_SIZE, PROT_WRITE, MAP_SHARED, pci_dev, 0);
if (mmap_base == (void *)-1 || mmap_base == NULL) //error here
{
PRINT_ERROR;
}
printf ("Mapped on address %p of size %d Byte\n", mmap_base, MAP_SIZE);
return 0;
}
I can already say, that I removed the Kernel Module used by the device, so this isn't the problem.
Can somebody help me? Thanks in advance.
I faced a similar issue while I was learning Linux Kernal programming. Running the program with superuser permission worked for me.
I am trying to run this code, while I am ending with -
value: 1
value: 0.000000
My question is why the both results are different??
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <string.h>
#include <unistd.h>
int main ()
{
int fd;
struct stat mystat;
void *pmap;
int i,integer;
double *values;
int32_t *mapped_baseA;
int32_t fdA;
fd = open("test.txt",O_RDWR); // a text file containing- 1 2 3 4 5
if(fd==-1)
{
perror("open");
exit(1);
}
if(fstat(fd,&mystat)<0)
{
perror("fstat");
close(fd);
exit(1);
}
pmap = mmap(0,mystat.st_size,PROT_READ | PROT_WRITE,MAP_SHARED,fd,0);
if(pmap==MAP_FAILED)
{
perror("mmap failed");
close(fd);
exit(1);
}
//strncpy(pmap,"That is my name",15);
sscanf (pmap, " %d", &integer);
printf("value: %d \n", integer);
//printing the values after scanning from string.
values = (double *) mmap(0,mystat.st_size,PROT_READ | PROT_WRITE,MAP_SHARED,fd,0);
printf("value: %lf \n", values[1]);
//printing the values from pointer
munmap (pmap, mystat.st_size);
close(fd);
return 0;
}
Read carefully (and several times) mmap(2). Notice:
A file is mapped in multiples of the page size.
and in the ERRORS section
EINVAL We don't like addr, length, or offset (e.g., they are too
large, or not aligned on a page boundary).
Consider also using strace(1) on your executable.
Of course, a memory mapping is just giving a view (as raw sequence of bytes) of the mapped file into the process by modifying its virtual address space. It obviously won't do any conversion (you might use sscanf(3) or strtol(3) on parts of that view to make such a conversion from an UTF8 or ASCII string representation of a number into its machine representation).
I have a problem to write struct into a mapped memory file.
I have two file namely mmap.write.c and mmap.read.c, and in these files, I'm writing an integer to a file and reading it from file.
When I want to write struct and read it, I could not think about that since in line 32 of mmap.write.c
sprintf((char*) file_memory, "%d\n", i);
and in line 25 of mmap.read.c
sscanf (file_memory, "%d", &integer);
There is no difference to write and read integer/double/float/char etc. since I can put pattern as second argument "%d" for integer. But what I will write here to indicate struct? That is my main problem.
The struct that I want to write and read:
#define CHANNELS 20
typedef dataholder struct {
int value[CHANNELS];
time_t time;
int hash;
}dataholder;
mmap.read.c
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <unistd.h>
#include "mmap.h"
#define FILE_LENGTH 0x10000
int main (int argc, char* const argv[])
{
int fd;
void* file_memory;
int integer;
/* Open the file. */
fd = open (argv[1], O_RDWR, S_IRUSR | S_IWUSR);
printf("file opened\n");
/* Create the memory mapping. */
file_memory = mmap (0, FILE_LENGTH, PROT_READ | PROT_WRITE,
MAP_SHARED, fd, 0);
printf("memfile opened\n");
close (fd);
printf("file closed\n");
/* Read the integer, print it out, and double it. */
while(1) {
sscanf (file_memory, "%d", &integer);
printf ("value: %d\n", integer);
usleep(100000);
}
//sprintf ((char*) file_memory, "%d\n", 2 * integer);
/* Release the memory (unnecessary because the program exits). */
munmap (file_memory, FILE_LENGTH);
return 0;
}
mmap.write.c
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <time.h>
#include <unistd.h>
#include "mmap.h"
#define FILE_LENGTH 0x10000
/* Return a uniformly random number in the range [low,high]. */
int random_range (unsigned const low, unsigned const high)
{
unsigned const range = high - low + 1;
return low + (int) (((double) range) * rand () / (RAND_MAX + 1.0));
}
int main (int argc, char* const argv[])
{
int fd, i;
void* file_memory;
/* Seed the random number generator. */
srand (time (NULL));
/* Prepare a file large enough to hold an unsigned integer. */
fd = open (argv[1], O_RDWR | O_CREAT | O_APPEND, S_IRUSR | S_IWUSR);
//lseek (fd, FILE_LENGTH+1, SEEK_SET);
write (fd, "", 1);
//lseek (fd, 0, SEEK_SET);
/* Create the memory mapping. */
file_memory = mmap (0, FILE_LENGTH, PROT_WRITE, MAP_SHARED, fd, 0);
close (fd);
/* Write a random integer to memory-mapped area. */
for(i=0; i<10000; i++) {
sprintf((char*) file_memory, "%d\n", i);
//goto a;
usleep(100000);
}
a:
/* Release the memory (unnecessary because the program exits). */
munmap (file_memory, FILE_LENGTH);
return 0;
}
Thanks a lot in advance.
First of all you have to keep track of where in the memory you want to write, second you have to remember that the mapped memory is just like any other pointer to memory. The last bit is important, as this means you can use normal array indexing to access the memory, or use functions such as memcpy to copy into the memory.
To write a structure, you have three choices:
Write the structure as-is, like in a binary file. This will mean you have to memcpy the structure to a specified position.
Write the structure, field-by-field, as text using e.g. sprintf to the correct position.
Treat the memory as one large string, and do e.g. sprintf of each field into a temporary buffer, then strcat to add it to the memory.
The simplest way is to just use a pointer:
dataholder *dh = file_memory;
/* now you can access dh->value, dh->time, dh->hash */
Since this struct doesn't contain any pointers, if you need to copy it in or out, you can just assign it, like:
dataholder dh_other = *dh;
or
*dh = dh_other;