Can I create a circular buffer on Linux? (current code segfaults) - c

Inspired by this example for Windows. In short, they create a file handle (with CreateFileMapping) then create 2 different pointers to the same memory (MapViewOfFileEx or MapViewOfFile3)
So I tried to do the same thing with shm_open, ftruncate and mmap. I used mmap a few times in the past for memory and files but I never mixed it with shm_open or used shm_open.
My code fails on the second mmap with a segfault. I tried doing a syscall directly on both mmaps and it still segfaults :( How do I do this properly? The idea is I can do memcpy(p+len-10, src, 20) and have the first 10bytes of src be at the end of the memory and last 10 written to the start (hence circular)
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <assert.h>
int main()
{
write(2, "Start\n", 6); //prints this
int len = 1024*1024*2;
int fd = shm_open("example", O_RDWR | O_CREAT, 0777);
assert(fd > 0); //ok
int r1 = ftruncate(fd, len);
assert(r1 == 0); //ok
char*p = (char*)mmap(0, len, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
assert((long long)p>0); //ok
//Segfaults on next line
char*p2 = (char*)mmap(p+len, len, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_FIXED, fd, 0); //segfaults
write(2, "Finish\n", 7); //doesn't print this
return 0;
}

Linux usually selects address space for mappings starting from a certain point and goes lower with each reservation. So your 2nd mmap call replaces one of previous file mappings (likely libc.so), which leads to SIGSEGV with SEGV_ACCERR - invalid access permissions. You are overwriting executable section of libc.so (that is being executed right now) with non-executable data.
Use strace to check what is going on inside:
$ strace ./a.out
...
openat(AT_FDCWD, "/dev/shm/example", O_RDWR|O_CREAT|O_NOFOLLOW|O_CLOEXEC, 0777) = 3
ftruncate(3, 2097152) = 0
mmap(NULL, 2097152, PROT_READ|PROT_WRITE, MAP_PRIVATE, 3, 0) = 0x7f134c1bf000
mmap(0x7f134c3bf000, 2097152, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED, 3, 0) = 0x7f134c3bf000
--- SIGSEGV {si_signo=SIGSEGV, si_code=SEGV_ACCERR, si_addr=0x7f134c4ccc37} ---
+++ killed by SIGSEGV +++
Compare addresses you are passing around with /proc/$pid/maps file and you will see what you are overwriting.
Your mistake was to assume MAP_FIXED can be used without reserving memory beforehand. To do this properly you need to:
Reserve memory by calling mmap with len * 2 size, PROT_NONE and MAP_ANONYMOUS | MAP_PRIVATE (and without file)
Use mmap with MAP_FIXED to overwrite portions of that mapping with the content you need
Additionally, you should prefer using memfd_create instead of shm_open on Linux to avoid shared memory files from staying around. Unlinking them with shm_unlink doesn't help if your program crashes. This also gives you a file that is private to your program instance.

You do not need to call mmap again to gen new pointer. (You even must not to do it.) Just increment it.
The pointer p2 will not point to the address just after the memory block allocated.
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <assert.h>
int main()
{
write(2, "Start\n", 6); //prints this
int len = 1024*1024*2;
int fd = shm_open("example", O_RDWR | O_CREAT, 0777);
assert(fd > 0); //ok
int r1 = ftruncate(fd, len);
assert(r1 == 0); //ok
char*p = (char*)mmap(0, len, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
assert((long long)p>0); //ok
char*p2 = p+len;
write(2, "Finish\n", 7); //doesn't print this
return 0;
}

Related

How to map shared memory to the same address in different processes?

I'm trying to share some data between different processes but I would like to use the same address in all of them.
From what I've read, it seems that this should be possible using mmap() with the MAP_FIXED flag (or MAP_FIXED_NOREPLACE).
Some people suggest to create a shared block and then broadcast the address of that memory block to all the other processes. This comment is a good summary of the whole idea.
So I tried to implement that into this example. There are a sender and a receiver. The sender will create a shared block of memory. The receiver tries to allocate that block of memory at the same address. The address is passed around using a shared memory block too.
The issue is that the receiver can't map the memory object to said address.
I get EINVAL error.
Is it possible to do what I'm trying? If so, how or what am I doing wrong in this example?
// header.h
#pragma once
#define SHM_DATA_ADDR "/bitarray-addr"
#define SHM_DATA "/bitarray-data"
#define NUM 3
#define SIZE (NUM * sizeof(int))
#define SIZE_ADDR (sizeof(void *))
// sender.c
#include "header.h"
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#include <stddef.h>
int main() {
// Open the shared memory object for read-only access
// Set permissions
int fd = shm_open(SHM_DATA, O_CREAT | O_EXCL | O_RDWR, 0600);
int fd_addr = shm_open(SHM_DATA_ADDR, O_CREAT | O_EXCL | O_RDWR, 0600);
ftruncate(fd, SIZE);
ftruncate(fd_addr, SIZE_ADDR);
// Establish mapping between address space and memory object
int RDWR = PROT_READ | PROT_WRITE;
int *data = (int *)mmap(0, SIZE, RDWR, MAP_SHARED, fd, 0);
void **data_addr = (void **)mmap(0, SIZE_ADDR, RDWR, MAP_SHARED, fd_addr, 0);
*data_addr = data;
printf("%p\n", data);
printf("%p\n", data_addr);
printf("%p\n", *data_addr);
// Unmap address space
munmap(data_addr, SIZE);
munmap(data, SIZE);
// Close file descriptors
close(fd_addr);
close(fd);
return EXIT_SUCCESS;
}
// receiver.c
#include "header.h"
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#include <stddef.h>
#include <errno.h>
int main() {
// Open the shared memory object for read-only access
// Set permissions
int fd = shm_open(SHM_DATA, O_RDONLY, 0600);
int fd_addr = shm_open(SHM_DATA_ADDR, O_RDONLY, 0600);
ftruncate(fd, SIZE);
ftruncate(fd_addr, SIZE_ADDR);
// Establish mapping between address space and memory object
void **data_addr = (void **)mmap(0, SIZE_ADDR, PROT_READ, MAP_SHARED, fd_addr, 0);
int *data = (int *)mmap(*data_addr, SIZE, PROT_READ, MAP_FIXED, fd, 0);
printf("%p\n", data_addr);
printf("%p\n", *data_addr);
printf("%p\n", data);
// Unmap address space
munmap(data_addr, SIZE_ADDR);
munmap(data, SIZE);
// Close file descriptors
close(fd_addr);
close(fd);
// Destroy shared memory object
shm_unlink(SHM_DATA_ADDR);
shm_unlink(SHM_DATA);
return EXIT_SUCCESS;
}
From the mmap(2) man page, emphasis mine:
The flags argument determines whether updates to the mapping are
visible to other processes mapping the same region, and whether
updates are carried through to the underlying file. This behavior is
determined by including exactly one of the following values in flags: [MAP_SHARED, MAP_SHARED_VALIDATE, MAP_PRIVATE]
In addition, zero or more of the following values can be ORed in
flags: [MAP_ANONYMOUS, MAP_FIXED, etc]
In your receiver code, you have:
int *data = (int *)mmap(*data_addr, SIZE, PROT_READ, MAP_FIXED, fd, 0);
which does not include any of MAP_SHARED, MAP_SHARED_VALIDATE, MAP_PRIVATE, hence the error. It's even stated explicitly in the Errors section:
EINVAL: flags contained none of MAP_PRIVATE, MAP_SHARED or MAP_SHARED_VALIDATE.
Change it to MAP_SHARED | MAP_FIXED and it runs successfully for me.

map shared memory size using mmap more than set size done by ftruncate

i have few question based on below source:
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <stdio.h>
int g;
int main(void) {
int fd = shm_open("/myregion", O_CREAT | O_RDWR, S_IRUSR | S_IWUSR);
ftruncate(fd, sizeof(int)); // set size by sizeof(int)
int *p1 = mmap(NULL, 10*sizeof(int), PROT_READ | PROT_WRITE, MAP_SHARED, fd,0); //now map 10*sizeof(int).
if (p1== MAP_FAILED) {
printf("*****************error");
}
*p1 = mmap(NULL, 8*sizeof(int), PROT_READ | PROT_WRITE, MAP_SHARED, fd,0);
if (p1== MAP_FAILED) {
printf("*****************error");
}
*p1=89;
return g;
}
Question 1 :
why i don't see any error while i set size as size_of(int) and then map 10*size_of(int)
Question 2:
how many instace of shared mem is created here? i mean is there only one shared mem created or two as i did mmap twice?
Thanks
Given the code
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <stdio.h>
int g;
int main(void) {
int fd = shm_open("/myregion", O_CREAT | O_RDWR, S_IRUSR | S_IWUSR);
ftruncate(fd, sizeof(int)); // set size by sizeof(int)
int *p1 = mmap(NULL, 10*sizeof(int), PROT_READ | PROT_WRITE, MAP_SHARED, fd,0); //now map 10*sizeof(int).
*p1 = mmap(NULL, 8*sizeof(int), PROT_READ | PROT_WRITE, MAP_SHARED, fd,0);
if (!p1){
printf("*****************error");
}
*p1 = g;
*p1=89;
return g;
}
Question 1 : why i don't see any error while i set size as size_of(int) and then map 10*size_of(int)
Because you don't check the return value from mmap(), you don't know if an error happened or not. By immediately calling mmap() again with
*p1 = mmap(NULL, 8*sizeof(int), PROT_READ | PROT_WRITE, MAP_SHARED, fd,0);
you mask any potential errors from the first mmap() call, and you also leak any memory that was successfully allocated.
Question 2: how many instace of shared mem is created here? i mean is there only one shared mem created or two as i did mmap twice?
If the first call succeeded, you mapped two memory segments. If it failed, you only mapped one if the second once succeeded.
If the first call did succeed, you leaked the memory.
Note that if you tried to write to the mmap()'d segment past the end of the size of the file you set with ftruncate(), you will not cause the file to grow. Per the POSIX mmap() documentation:
Note that references beyond the end of the object do not extend the object as the new end cannot be determined precisely by most virtual memory hardware. Instead, the size can be directly manipulated by ftruncate().
On Linux, trying to access mmap()'d data beyond the end of the mapped file will likely result in your process receiving a SIGBUS signal.

Implementing copy-on-write buffer with mmap on Mac OS X

I've been playing around with copy-on-write buffers on Linux and the following example seems to work as intended:
#include <stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#define SIZE 4096
#define SHM_NAME "foobar"
int main(void)
{
int fd = shm_open(SHM_NAME, O_RDWR | O_CREAT, 0666);
int r = ftruncate(fd, SIZE);
char *buf1 = mmap(NULL, SIZE, PROT_READ | PROT_WRITE,
MAP_SHARED, fd, 0);
strcpy(buf1, "Original buffer");
char *buf2 = mmap(NULL, SIZE, PROT_READ | PROT_WRITE,
MAP_PRIVATE, fd, 0);
// At this point buf2 is aliased to buf1
// Now modifying buf2 should trigger copy-on-write)...
strcpy(buf2, "Modified buffer");
// buf1 and buf2 are now two separate buffers
strcpy(buf1, "Modified original buffer");
// clean up
r = munmap(buf2, SIZE);
printf("munmap(buf2): %i\n", r);
r = munmap(buf1, SIZE);
printf("munmap(buf1): %i\n", r);
r = shm_unlink(SHM_NAME);
printf("shm_unlink: %i\n", r);
return EXIT_SUCCESS;
}
However under OS X (10.10) the second mmap call returns MAP_FAILED, with errno = 22 (EINVAL). The OS X man page for mmap seems to suggest that this should work (it even mentions copy-on-write in the description of the MAP_PRIVATE flag), and I've experimented with various different flags for the calls to mmap, but nothing seems to work. Any ideas ?
It appears that using shm_open with MAP_SHARED and MAP_PRIVATE does something undesirable with the file descriptor. Using open is a possible workaround:
int fd = open(SHM_NAME, O_RDWR | O_CREAT, 0666);
...
Result:
munmap(buf2): 0
munmap(buf1): 0
shm_unlink: -1
Using shm_open with MAP_SHARED and MAP_PRIVATE results in an Invalid file descriptor, although using it with MAP_SHARED and MAP_SHARED for example does not. It's unclear to me whether this is a bug, or by design - the behavior does not seem correct though.

Segmentation fault with Posix-C program using mmap and mapfile

Well I have this program and I get a segmentation fault: 11 (core dumped). After lots of checks I get this when the for loop gets to i=1024 and it tries to mapfile[i]=0. The program is about making a server and a client program that communicates by read/writing in a common file made in the server program. This is the server program and it prints the value inside before and after the change. I would like to see what's going on, if it's a problem with the mapping or just problem with memory of the *mapfile. Thanks!
#include <sys/shm.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <errno.h>
#include <math.h>
int main()
{
int ret, i;
int *mapfile;
system("dd if=/dev/zero of=/tmp/c4 bs=4 count=500");
ret = open("/tmp/c4", O_RDWR | (mode_t)0600);
if (ret == -1)
{
perror("File");
return 0;
}
mapfile = mmap(NULL, 2000, PROT_READ | PROT_WRITE, MAP_SHARED, ret, 0);
for (i=1; i<=2000; i++)
{
mapfile[i] = 0;
}
while(mapfile[0] != 555)
{
mapfile = mmap(NULL, 2000, PROT_READ | PROT_WRITE, MAP_SHARED, ret, 0);
if (mapfile[0] != 0)
{
printf("Readed from file /tmp/c4 (before): %d\n", mapfile[0]);
mapfile[0]=mapfile[0]+5;
printf("Readed from file /tmp/c4 (after) : %d\n", mapfile[0]);
mapfile[0] = 0;
}
sleep(1);
}
ret = munmap(mapfile, 2000);
if (ret == -1)
{
perror("munmap");
return 0;
}
close(ret);
return 0;
}
mapfile = mmap(NULL, 2000, PROT_READ | PROT_WRITE, MAP_SHARED, ret, 0);
for (i=1; i<=2000; i++)
{
mapfile[i] = 0;
}
In this code here, you see that you are requesting 2000 units of memory. In this case mmap is taking in a size_t type meaning that its looking for a size, and not an amount of things for memory. As #Mat mentioned, you will need t use the sizeof(int) operator in order to feed mmap the proper size it requires.
The other issue that should be noted about this code that may cause a problem for you down the road, is beginning your loop index at i=1 rather than i=0. Starting your index at 0 wil ensure that you are going from the indices 0 - 1999, which corresponds to the memory you are trying to allocate.
Overall here, it looks like what your trying to do is initialize the values of your memory to 0. perhaps you could do this easier by relying on a builtin function called memset:
void *memset(void *str, int c, size_t n)
your code then becomes:
mapfile = mmap(NULL, 2000*sizeof(int), PROT_READ | PROT_WRITE, MAP_SHARED, ret, 0);
void *returnedPointer = memset(mapfile, 0, 2000*sizeof(int));
docs for memset can be found here:
http://www.tutorialspoint.com/c_standard_library/c_function_memset.htm
You're requesting 2000 bytes from mmap, but treating the returned value as an array of 2000 ints. That can't work, an int is usually 4 or 8 bytes these days. You'll be writing past the end of the reserved memory in your loop.
Change the mmap calls to use 2000*sizeof(int). And while you're at it, give that 2000 constant a name (e.g. const int num_elems = 2000; near the top) and don't repeat the magic constant all over the place. And once that's done change it to 1024 or 2048 so that the resulting size is a multiple of the page size (if you're not sure of your page size, getconf PAGE_SIZE on the command line).
And also change your dd command to create a large-enough file. It is currently creating a 2000 byte file, you'll need to increase that as well.
And validate the return value of mmap - it can fail, and you should detect that.
Finally, don't continuously remap, you're using MAP_SHARED modifications through other shared mappings of the same file and offset will be visible to your process. (Must really be the same file, if the other process also does a dd, that might not work. Only one process should have the responsibility of creating that file.)
If you do want to remap, you must also unmap each time. Otherwise you're leaking mappings.

Linux mapping virtual memory range to existing virtual memory range?

In Linux, is there a way (in user space) to map a virtual address range to the physical pages that back an existing virtual address range? The mmap() function only allows one to map files or "new" physical pages. I need to be able to do something like this:
int* addr1 = malloc(SIZE);
int* addr2 = 0x60000; // Assume nothing is allocated here
fancy_map_function(addr1, addr2, SIZE);
assert(*addr1 == *addr2); // Should succeed
assert(addr1 != addr2); // Should succeed
I was curious so I tested the shared memory idea suggested in question comments, and it seems to work:
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <assert.h>
#define SIZE 256
int main (int argc, char ** argv) {
int fd;
int *addr1, *addr2;
fd = shm_open("/example_shm", O_RDWR | O_CREAT, 0777);
ftruncate( fd, SIZE);
addr1 = mmap(0, SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
addr2 = mmap(0, SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
printf("addr1 = %p addr2 = %p\n", addr1, addr2);
*addr1 = 0x12345678;
assert(*addr1 == *addr2); // Should succeed
assert(addr1 != addr2); // Should succeed
return 0;
}
(Obviously real code will want to check the return value of the syscalls for errors and clean up after itself)
If you have the fd for the file mapped at addr1, you can simply mmap it again at addr2.
Otherwise, the Linux-specific remap_file_pages can modify the virtual address ⇆ file offset translation within a single VMA, with page-sized granularity, including mapping the same file offset to multiple addresses.
Open /proc/self/mem and mmap the range of virtual addresses you need from it.

Resources