I want to share memory using a file descriptor with another process created via fork.
The problem is that I get different address regions from mmap.
I want that mmap returns the same address value. Only in such case I can be sure that I really share the memory.
Probably it is possible to use MAP_FIXED flag to mmap, but how to get memory address from shm_open?
Is it possible to share memory via shm_open at all?
Maybe shmget must be used instead?
This is the minimal working example:
#include <stdio.h>
#include <sys/mman.h>
#include <unistd.h>
#include <fcntl.h>
int main(void)
{
/* Create a new memory object */
int fd = shm_open( "/dummy", O_RDWR | O_CREAT, 0777 );
if(fd == -1) {
fprintf(stderr, "Open failed:%m\n");
return 1;
}
/* Set the memory object's size */
size_t size = 4096; /* minimal */
if (ftruncate(fd, size) == -1) {
fprintf(stderr, "ftruncate: %m\n");
return 1;
}
void *ptr = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (ptr == MAP_FAILED) return 1;
void *ptr2 = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (ptr2 == MAP_FAILED) return 1;
printf("%p\n%p\n", ptr, ptr2);
return 0;
}
Compile it with gcc test.c -lrt.
This is the output:
0x7f3247a78000
0x7f3247a70000
EDIT
If I try to use method described in comment, child does not see changes in memory made by parent. Why? This is how I do it:
In parent:
shm_data = mmap(NULL, shm_size, PROT_WRITE, MAP_ANONYMOUS | MAP_SHARED, -1, 0);
...
snprintf(shared_memory, 20, "%p", shm_data);
execl("/path/to/prog", "prog", shared_memory, (char *) NULL);
In child:
...
void *base_addr;
sscanf(argv[1], "%p", (void **)&base_addr);
shm_data = mmap(base_addr, shm_size, PROT_READ, MAP_ANONYMOUS | MAP_SHARED | MAP_FIXED, -1, 0);
...
EDIT2
See also this question: How to get memory address from memfd_create?
Related
Using Ubuntu 16.04, kernel 4.17.4
I am trying the following code where I have defined a shared memory and mapped current process's address space to the shared memory. When I use MAP_FIXED in mmap() it shows:
mmap: Invalid argument
The code is as follows:
#include <stdio.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#define STORAGE_ID "/SHM_TEST"
#define STORAGE_SIZE 4096
int main(int argc, char *argv[])
{
int res, temp;
int fd;
int len;
pid_t pid;
void *addr;
char data[STORAGE_SIZE];
fd = shm_open(STORAGE_ID, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR);
if (fd == -1)
{
perror("open");
return 10;
}
res = ftruncate(fd, STORAGE_SIZE);
if (res == -1)
{
perror("ftruncate");
return 20;
}
addr = mmap(NULL, STORAGE_SIZE, PROT_WRITE, MAP_FIXED, fd, 0);
if (addr == MAP_FAILED)
{
perror("mmap");
return 30;
}
res = munmap(addr, STORAGE_SIZE);
if (res == -1)
{
perror("munmap");
return 40;
}
// shm_open cleanup
fd = shm_unlink(STORAGE_ID);
if (fd == -1)
{
perror("unlink");
return 100;
}
return 0;
}
The reason I am using MAP_FIXED is I will manually put an address instead of NULL (tried this and got same error). What do I need to do to use MAP_FIXED with mmap()?
I'm reading the source code in https://wayland-book.com/surfaces/shared-memory.html .
The author create a shared memory using shm_open(), and shm_unlink() it immediately, then ftruncate() the fd to a specific size, mmap() the fd and fill the region with pixels.
I'm so confused why the fd still available after shm_unlink().
according to the man page:
The operation of shm_unlink() is analogous to unlink(2): it removes a shared memory object name, and, once all processes have unmapped the object, de-allocates and destroys the contents of the associated memory region. After a successful shm_unlink(), attempts to shm_open() an object with the same name will fail (unless O_CREAT was specified, in which case a new, distinct object is created).
so shm_unlink() will cause the memory destroyed because there is no process mmap
the region. But how fd still avaliable?
here is the code:
static int
create_shm_file(void)
{
int retries = 100;
do {
char name[] = "/wl_shm-XXXXXX";
randname(name + sizeof(name) - 7);
--retries;
int fd = shm_open(name, O_RDWR | O_CREAT | O_EXCL, S_IRUSR | S_IWUSR);
if (fd >= 0) {
shm_unlink(name); // unlink immediately
return fd;
}
} while (retries > 0 && errno == EEXIST);
return -1;
}
static int
allocate_shm_file(size_t size)
{
int fd = create_shm_file();
if (fd < 0)
return -1;
int ret;
do {
ret = ftruncate(fd, size); //why the fd still available?
} while (ret < 0 && errno == EINTR);
if (ret < 0) {
close(fd);
return -1;
}
return fd;
}
//after above, there was mmap
uint32_t *data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
The code snippet works fine on my machine(Linux/x86-64)
int main()
{
char* addr;
int rc;
int fd;
const size_t PAGE_SIZE = 4096; // assuming the page size is 4096
char buf[PAGE_SIZE];
memset(buf, 'x', sizeof(buf));
// error checking is ignored, for demonstration purpose
fd = open("abc", O_RDWR | O_CREAT, S_IWUSR | S_IRUSR);
ftruncate(fd, 0);
write(fd, buf, 4090);
// the file size is less than one page, but we allocate 2 page address space
addr = mmap(NULL, PAGE_SIZE * 2, PROT_WRITE, MAP_SHARED, fd, 0);
// it would crash if we read/write from addr[4096]
// extend the size after mmap
ftruncate(fd, PAGE_SIZE * 2);
// now we can access(read/write) addr[4096]...addr[4096*2 -1]
munmap(addr, PAGE_SIZE * 2);
close(fd);
exit(EXIT_SUCCESS);
}
But POSIX says:
If the size of the mapped file changes after the call to mmap() as a result of some other operation on the mapped file, the effect of references to portions of the mapped region that correspond to added or removed portions of the file is unspecified.
So I guess this is not a portable way. But is it guaranteed to work on Linux?
From the man page,
MAP_ANONYMOUS
The mapping is not backed by any file; its contents are initialized to zero. The fd and offset arguments are ignored; however, some implementations require
fd to be -1 if MAP_ANONYMOUS (or MAP_ANON) is specified, and portable applications should ensure this. The use of MAP_ANONYMOUS in conjunction with
MAP_SHARED is only supported on Linux since kernel 2.4.
What is the purpose of using MAP_ANONYMOUS? Any example would be good. Also From where the memory will be mapped?
It is written on man page that The use of MAP_ANONYMOUS in conjunction with MAP_SHARED is only supported on Linux since kernel 2.4.
How can i share the memory mapped with MAP_ANONYMOUS with other process?
Anonymous mappings can be pictured as a zeroized virtual file.
Anonymous mappings are simply large, zero-filled blocks of memory ready for use.
These mappings reside outside of the heap, thus do not contribute to data segment fragmentation.
MAP_ANONYMOUS + MAP_PRIVATE:
every call creates a distinct mapping
children inherit parent's mappings
childrens' writes on the inherited mapping are catered in copy-on-write manner
the main purpose of using this kind of mapping is to allocate a new zeroized memory
malloc employs anonymous private mappings to serve memory allocation requests larger than MMAP_THRESHOLD bytes.
typically, MMAP_THRESHOLD is 128kB.
MAP_ANONYMOUS + MAP_SHARED:
each call creates a distinct mapping that doesn't share pages with any other mapping
children inherit parent's mappings
no copy-on-write when someone else sharing the mapping writes on the shared mapping
shared anonymous mappings allow IPC in a manner similar to System V memory segments, but only between related processes
On Linux, there are two ways to create anonymous mappings:
specify MAP_ANONYMOUS flag and pass -1 for fd
addr = mmap(NULL, length, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
if (addr == MAP_FAILED)
exit(EXIT_FAILURE);
open /dev/zero and pass this opened fd
fd = open("/dev/zero", O_RDWR);
addr = mmap(NULL, length, PROT_READ | PROT_WRITE, MAP_PRIVATE, fd, 0);
(this method is typically used on systems like BSD, that do not have MAP_ANONYMOUS flag)
Advantages of anonymous mappings:
- no virtual address space fragmentation; after unmapping, the memory is immediately returned to the system
- they are modifiable in terms of allocation size, permissions and they can also receive advice just like normal mappings
- each allocation is a distinct mapping, separate from global heap
Disadvantages of anonymous mappings:
- size of each mapping is an integer multiple of system's page size, thus it can lead to wastage of address space
- creating and returning mappings incur more overhead than that of from the pre-allocated heap
if a program containing such mapping, forks a process, the child inherits the mapping.
The following program demonstrates this kinda inheritance:
#ifdef USE_MAP_ANON
#define _BSD_SOURCE
#endif
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/wait.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
/*Pointer to shared memory region*/
int *addr;
#ifdef USE_MAP_ANON /*Use MAP_ANONYMOUS*/
addr = mmap(NULL, sizeof(int), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0);
if (addr == MAP_FAILED) {
fprintf(stderr, "mmap() failed\n");
exit(EXIT_FAILURE);
}
#else /*Map /dev/zero*/
int fd;
fd = open("/dev/zero", O_RDWR);
if (fd == -1) {
fprintf(stderr, "open() failed\n");
exit(EXIT_FAILURE);
}
addr = mmap(NULL, sizeof(int), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (addr == MAP_FAILED) {
fprintf(stderr, "mmap() failed\n");
exit(EXIT_FAILURE);
}
if (close(fd) == -1) { /*No longer needed*/
fprintf(stderr, "close() failed\n");
exit(EXIT_FAILURE);
}
#endif
*addr = 1; /*Initialize integer in mapped region*/
switch(fork()) { /*Parent and child share mapping*/
case -1:
fprintf(stderr, "fork() failed\n");
exit(EXIT_FAILURE);
case 0: /*Child: increment shared integer and exit*/
printf("Child started, value = %d\n", *addr);
(*addr)++;
if (munmap(addr, sizeof(int)) == -1) {
fprintf(stderr, "munmap()() failed\n");
exit(EXIT_FAILURE);
}
exit(EXIT_SUCCESS);
default: /*Parent: wait for child to terminate*/
if (wait(NULL) == -1) {
fprintf(stderr, "wait() failed\n");
exit(EXIT_FAILURE);
}
printf("In parent, value = %d\n", *addr);
if (munmap(addr, sizeof(int)) == -1) {
fprintf(stderr, "munmap()() failed\n");
exit(EXIT_FAILURE);
}
exit(EXIT_SUCCESS);
}
Sources:
The Linux Programming Interface
Chapter 49: Memory Mappings,
Author: Michael Kerrisk
Linux System Programming (3rd edition)
Chapter 8: Memory Management,
Author: Robert Love
I'm trying to simulate simple shared memory between two processeses on ubuntu. here's the code:
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
int main() {
pid_t pid = fork();
if (pid == 0) { //Child
int shm_fd = shm_open("JSHM", O_CREAT | O_RDWR, 0666);
ftruncate(shm_fd, 4096);
void *ptr = mmap(0, 4096, PROT_WRITE, MAP_SHARED, shm_fd, 0);
sprintf(ptr, "Jani");
printf("%i descriptor from child\n", shm_fd);
printf("%p ptr from child\n\n", ptr);
}
if (pid > 0) { //Parent
sleep(5);
int shm_fd = shm_open("JSHM", O_RDONLY, 0666);
printf("%i descriptor from parent\n", shm_fd);
void *ptr = mmap(0, 4096, PROT_WRITE, MAP_SHARED, shm_fd, 0);
//printf("%s", (char *)ptr);
printf("%p ptr from parent\n", ptr);
}
}
And the output:
3 descriptor from child
0xb7750000 ptr from child
3 descriptor from parent
0xffffffff ptr from parent
why doesn't second mmap (from parent) work?
Thanks.
You have a slight error in your requested access permissions. In the parent section, you're passing O_RDONLY to shm_open() but then you're trying to mmap() it with PROT_WRITE which is not allowed.