3

I am working on a project that deals with multiple processes and threads effecting the same data. I have a line of code which can result into a segmentation fault because data can be updated from anywhere. For that particular line, if it causes segmentation fault, I somehow want to handle it instead of letting the program crash. Like I can simply update the memory location if the previous one was causing a segmentation fault. Is there any possible way to do that?

UPDATE(A short summary of my case):

I want extremely speedy access to a file. For that purpose, I am calling mmap(2) to map that file into all processes accessing it. The data I am writing to the file is in form of a particular data structure and it consumes lots of memory. So if a point comes that the size I mapped is not enough, I need to increase file size and mmap(2) that file again with the new size. For increasing the size I call ftruncate(2). ftruncate(2) may get called from any process so it may end up shrinking the file instead. So I need to check if the memory I am accessing doesn’t lead to seg faults. I am working on macOS.

Mihir Luthra
  • 6,059
  • 3
  • 14
  • 39
  • 1
    You can do it: https://stackoverflow.com/questions/2663456/how-to-write-a-signal-handler-to-catch-sigsegv but that would be an extremely bad design. – Eugene Sh. May 21 '19 at 15:14
  • 1
    This approach is wrong. You need to avoid the segfault. Once you get it, it's usually too late. Segfault means roughly: there is a bug somewhere in your program and things got messed up. – Jabberwocky May 21 '19 at 15:15
  • I would suggest use database for the same, like `sqlite` – Mayur May 21 '19 at 15:16
  • If your "data can be updated from anywhere", then you need to implement a proper synchronization mechanism. – Eugene Sh. May 21 '19 at 15:17
  • @EugeneSh. I am not sure if I understood that correctly. Kindly tell me if I am wrong . Does it mean that after signal handler is called, the line of code that caused that signal to occur gets executed again and the next time if it doesn’t cause set fault, the program won’t crash? – Mihir Luthra May 21 '19 at 15:21
  • Also in my case I really need this kind of functionality. I will update my question to explain it correctly. – Mihir Luthra May 21 '19 at 15:21
  • I have updated the question. Is there any better way to achieve the same? – Mihir Luthra May 21 '19 at 15:35
  • *I want extremely speedy access to a file.* Then why use a **file**? That's unnecessary overhead. If you want "extremely fast access" you can just use something like SysV shared memory and remove the overhead associated with a filesystem. If the data is too big to fit into memory, your `mmap()` solution is going to have serious performance issues as pages will have to be paged in and out of memory anyway. – Andrew Henle May 21 '19 at 16:18
  • 1
    @AndrewHenle , its not available on macOS. Sorry for not mentioning It in my question. I will update it right away. – Mihir Luthra May 21 '19 at 16:21
  • 1
    The other problem with SysV shared memory is that it does not support resizing the shared memory object. Most non-SysV systems (OSX included, I believe), emulate SysV shared memory with mmaped files anyways. Using mmap on a tmpfs (memory) filesystem is pretty much as fast as it can get. – Chris Dodd May 21 '19 at 17:15

2 Answers2

2

This can be made to work, but by bringing signal handlers into the picture you make your inter-process and inter-thread locking problems much more complicated. I would like to suggest an alternative approach: Reserve a field in the first page of the mmapped file to indicate the expected size of the data structure. Use fcntl file locks to mediate access to this field.

When any process wants to update the size, it takes a write lock, reads the current value, increases it, msyncs the page (using MS_ASYNC|MS_INVALIDATE should be enough), then uses ftruncate to enlarge the file, then enlarges its mapping of the file, and only then releases the write lock. If, after taking the write lock, you find that the file is already larger than the size you wanted, just enlarge your mapping and drop the lock, don't call ftruncate or change the field.

This ensures cooperating processes will never make the file smaller, and the region of memory each process has mapped is always backed by allocated storage, so you shouldn't ever get any SIGBUSes. Note that the size of the file on disk will only increase when you actually write to newly allocated space, thanks to the magic of sparse files.

zwol
  • 135,547
  • 38
  • 252
  • 361
1

Yes, you can make this work with a signal handler that catches the SIGSEGV or SIGBUS, adjusts the mmap and returns. When a signal handler returns it will resume where the signal occurred, which means for a synchronous signal like SIGSEGV or SIGBUS, it will rerun the faulting instruction.

You can see this at work in my shared memory malloc implementation -- search for shm_segv in malloc.c to see the signal handler; it's pretty simple. I've never tried this code on MacOS, but I would think it would work on OSX, as it works on all the other BSD-derived UNIXes I've tried it on. There's a an issue that, according to the POSIX spec, mmap is not async safe, so cannot be called from a signal handler, but on all systems that actually support real memory mapping (rather than emulating it with malloc+read) it should be fine.

Chris Dodd
  • 119,907
  • 13
  • 134
  • 226