9

I am in a situation where I need to read a binary search tree (BST) inside a signal handler (SIGSEGV signal handler, which according to my knowledge is per thread base). The BST can be modified by the other threads in the application.

Now since a signal handler can't use semaphores, mutexes etc. and therefore can't access shared data, How do I solve this problem? Note that my application is multithreaded and running on a multicore system.

MetallicPriest
  • 29,191
  • 52
  • 200
  • 356
  • 5
    I would try really, really hard, to think of a way to not read shared data in that signal handler. – dbeer Dec 13 '11 at 16:12
  • 1
    To emphasize the point of @dbeer, in a signal handler you should generally not do anything that will block or raise other signals, or any lengthy operations. A signal handler should be small, quick and short. – Some programmer dude Dec 13 '11 at 16:18
  • 1
    Maybe I'm missing something, but if only the threads of your program access the shared memory (no other interrupts and exceptions), why could you not use a semaphore (whether it's good style is a different question)? If a thread accesses the critical region, blocks it, is put to sleep by another thread, the semaphore is still locked for the other thread, and eventually your initial thread will be scheduled to access it again. Performance reasons aside, I see neither a danger of data corruption nor stalling the system. – gnometorule Dec 13 '11 at 16:23
  • @gnometorule: You mean I can use the semaphore in both the signal handler and threads? – MetallicPriest Dec 13 '11 at 16:41
  • 2
    On theoretical grounds, it seems to me that's ok. The kernel itself, say, protects critical regions accessed by only exceptions using sometimes only semaphores - in UP and MP architectures. However, I'm not terribly practically experienced with these issues in user space, and might overlook something. As I outlined above, I just don't see how in your case it could lead to issues. – gnometorule Dec 13 '11 at 16:47
  • 1
    The danger of accessing shared data in a signal handler is easy to show: thread A locks shared data X, and while it holds the lock it handles a signal, calling the signal handler, which tries to lock data X. Deadlock! – dbeer Dec 13 '11 at 21:11
  • @dbeer How about a recursive mutex then? :) – mlvljr Jan 24 '17 at 21:29
  • @mlvjr switch the example to thread B locks data X, thread A is interrupted for the signal handler, and then it blocks because the lock is being held by B, while B never advances due to the signal handler being active. – dbeer Jan 24 '17 at 23:32

4 Answers4

4

I can see two quite clean solutions:

  1. Linux-specific: Create a dedicated thread handling signals. Catch signals using signalfd(). This way you will handle signals in a regular thread, not any limited handler.
  2. Portable: Also use a dedicated thread that sleeps until signal is received. You may use a pipe to create a pair of file descriptors. The thread may read(2) from the first descriptor and in a signal handler you may write(2) to the second descriptor. Using write() in a signal handler is legal according to POSIX. When the thread reads something from the pipe it knows it must perform some action.
Daper
  • 41
  • 1
4

You shouldn't access shared data from signal handler. You can find out more information about signals in following articles:

Linux Signals for the Application Programmer

The Linux Signals Handling Model

All about Linux signals

Looks like the safest way to deal with signals in linux so far is signalfd.

ILYA Khlopotov
  • 705
  • 3
  • 5
3

Assuming the SH can't access the shared data directly, then maybe you could do it indirectly:

  1. Have some global variable that only signal handlers can write to, but can be read from elsewhere (even if only within the same thread).
  2. SH sets the flag when it is invoked
  3. Threads poll this flag when they are not in the middle of modifying the BST; when the find it set, they do the processing that is required by the original signal (using whatever synchronizations are necessary), and then raise a different signal (like SIGUSR1) to indicate that the processing is done
  4. The SH for THAT signal resets the flag

If you're worried about overlapping SIGSEGVs, add a counter to the mix to keep track. (Hey! You just built your own semaphore!)

The weak link here is obviously the polling, but its a start.

Scott Hunter
  • 48,888
  • 12
  • 60
  • 101
  • I was thinking exactly the same thing :-p! But what will happen when a thread is modifying the BST when the signal occurs. That point is a little worrisome. – MetallicPriest Dec 13 '11 at 16:23
  • I think that wouldn't be a problem: the signal will get caught, set the flag & return; the thread will continue modifying the BST, and won't try to "handle" the signal until it is done w/ its modification. Assuming that such modifications are to be atomic, that seems like a reasonable outcome. – Scott Hunter Dec 13 '11 at 18:47
1

You might consider mmap-ing a fuse file system (in user space).

Actually, you'll be more happy on Gnu Hurd which has support for external pagers

And perhaps your hack of reading a binary search tree in your signal handler could often work in practice, non-portably and in a kernel version dependent way. Perhaps serializing access with low-level non portable tricks (e.g. futexes and atomic gcc builtins) might work. Reading the (machine specific) source code of NPTL i.e. current Linux pthread routines should help.

It could probably be the case that pthread_mutex_lock etc are in fact usable from inside a Linux signal handler... (because it probably does only futex and atomic instructions).

Basile Starynkevitch
  • 223,805
  • 18
  • 296
  • 547