views:

224

answers:

6

I want to allocate and initialise a fairly large chunk of contiguous memory (~1GB), then mark it as read-only and fork multiple (say several dozen) child processes which will use it, without making their own copies of the memory (the machine won't have enough memory for this).

Am I right in thinking that if I malloc the memory as usual, then mark it as read-only with mprotect(addr, size, PROT_READ) and then fork, this will allow the child processes to safely use the memory without causing it to be copied? (Providing I ensure nothing tries to write to the allocated memory after the mprotect call).

edit: Thanks for all the answers.

A followup question - I was planning on using shmget, but I thought it used mm and thus would be limited to smaller allocations (see the Restrictions section of this page). eg /proc/sys/kernel/shmmax is 32MB on the server I'm using this one. But I want 1GB of contiguous memory. Am I wrong about this limitation?

A: 

My understanding is yes, since Linux uses a copy-on-write mechanism for memory pages passed to a child process.

therefromhere
+6  A: 

man mprotect

The implementation will require that addr be a multiple of the page size as returned by sysconf().

The behaviour of this function is unspecified if the mapping was not established by a call to mmap().

  1. mprotect only works on pages, not arbitrary byte ranges, so in general malloc is inappropriate. posix_memalign may help, but...
  2. While it may work on your system at the present time, you shoud not mprotect anything that you didn't mmap yourself. Use mmap(0, pages*sysconf(_SC_PAGESIZE), PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0) instead.
ephemient
Ah thanks, I obviously didn't read the mprotect man page closely enough.
therefromhere
@therefromhere `MAP_SHARED|MAP_ANONYMOUS` here works for sharing from a pre-forked parent to any children. If you want to share among arbitrary processes, take out the `MAP_ANONYMOUS` and give it a real FD instead of `-1`; this FD may come from `open` a file on disk or from `shm_open`.
ephemient
The linux man page for mprotect does note "On Linux it is always legal to call mprotect() on any address in a processes address space..."http://linux.die.net/man/2/mprotect
Andrew Edgecombe
+3  A: 

You are not right by the reason than any of child processes can call mprotect() to remove protection and start writing there. If the pages have not been copied, it would violate the principles of fork().

Even if it works that way that copy-on-write is used for forked processes, I don't reckon any place in standards that says so (POSIX doesn't say it's copy-on-write, for instance).

Instead of using non-standard behavior, you may use standard measures to share memory. For example, the POSIX shared memory with shm_open and consequent mmap (as was pointed out in comment and explained in his post by ephemient). The file descriptor will be preserved through forking.

Pavel Shved
Thanks. as per the edit I was planning on using `shmget` but I thought it was limited in the size of allocation it could do.
therefromhere
I'd say `shm_open` is safer: it gives you a file handle to `mmap`, as opposed to `shmget` which gives you some arbitrary identifier to `shmat`, and I already covered why you should use `mmap` in my answer. (The two APIs, despite their similar names, come from different heritages and operate very differently.)
ephemient
@ephemient, yeah, thank you for pointing this out.
Pavel Shved
@ephemient Thanks, I think shm_open + mmap is the way to go for me then. Accepting your answer but I've voted up both.
therefromhere
+1  A: 

You are making an assumption that the kernel would do copy-on-write optimization and not copy the mprotect-ed pages. I would'n count on it though. malloc-ed memory has all sorts of metadata floating around it - guard pages, etc. etc. and only Ulrich Drepper knows what's going on inside libc :)

It would probably be easier and safer to prepare data in a disk file and them mmap it into all the processes, or just go normal POSIX shm_open route.

Nikolai N Fetissov
Thanks. as per the edit I was planning on using `shmget` but I thought it was limited in the size of allocation it could do.
therefromhere
A: 

You could do it that way.

An alternative is to use mmap().

Another alternative is to use the POSIX shared memory (shm_open()); the other main alternative is System V shared memory (shmget(), shmat()). One advantage of the formal shared memory systems is that your parent process can create the memory and then unrelated process could connect to it - if that was beneficial.

Jonathan Leffler
Thanks. as per the edit I was planning on using `shmget` but I thought it was limited in the size of allocation it could do.
therefromhere
@therefromhere: you may need to adjust the kernel configuration (shmmax or related sizes), but IBM Informix Dynamic Server uses shmget() to allocate multi-gigabyte chunks of shared memory if you tell it to do so.
Jonathan Leffler
+2  A: 

There is no need to mark it read-only, just get your child processes to leave it alone.

If neither the parent nor child writes to it, it should remain shared. If you don't ever want to change it, that's fine.

If you want to write to it, you'll want to use mmap with MAP_SHARED.

MarkR