4 ms·
You can tell (without a syscall!) if between two points there has been a fork or thread created by maintaining two counters: One in a MAP_SHARED and one in MAP_
by geocar 11y ago
You can tell (without a syscall!) if between two points there has been a fork or thread created by maintaining two counters: One in a MAP_SHARED and one in MAP_PRIVATE.
Every check, increment both counters, then compare them. If they disagree, then one of those pages has been copied (the MAP_PRIVATE) and one has not (MAP_SHARED).
- geofft 11y agoI don't think that algorithm works, but I'm probably misunderstanding: char *shared, *private; bool check_fork() { return ++*shared != ++*private; } shared = mmap(MAP_SHARED...); private = mmap(MAP_PRIVATE...); *shared = 0; *private = 0; check_fork(); if (fork() == 0) { printf("%d\n", check_fork()); } else { wait(); } The only way it could work is if you were guaranteed to also call check_fork synchronously in the parent. But if you had that degree of control over forks, then you just know when your forks are and you don't need these tricks. The use case here is in an external library (e.g., OpenSSL) where it might be called post-fork by a semi-naive user of the library, or where the library might explicitly want to be usable post-fork (e.g., Apache prefork serving) and need to adjust some state. If you expect the caller to reliably inform the library of forks, then you don't need any trickery. Another use case that I'm interested in is signal handlers, which could be delivered immediately post-fork. If your handler does something like write to a file descriptor (which is very common), those file descriptors mean something different in the parent and the child, so if the post-fork code in the child was in the middle of switching out file descriptors before exec, you might accidentally write to the wrong thing. So you want to be sure that you're still in the same process before writing to a particular file descriptor. UNIX doesn't offer a way to reset signal handlers on fork, unfortunately.
- agwa 11y agoThe algorithm works, though it doesn't distinguish between the parent and child - the first process to call check_fork() after the fork will return false, and the second process to call it will return true. (Note that upon returning true, it should remap and reset the state.) This is sufficient for the use case of reseeding a PRNG, though not for your signal handler/file descriptor use case. The problem with the algorithm (which I'm discussing on another subthread) is that the increment of the shared counter needs to be atomic, and the integers need to be 64 bits to avoid wraparound, and I'm not sure that all architectures support atomic 64 bit increments. Edit: I just realized that check_fork() would spuriously return true in the first process after the second process calls it, which isn't so nice. Maybe this can be prevented by comparing before you increment, but then I think there might be a race condition? It would be helpful if geocar could provide some pseudocode so we're not left guessing how the algorithm works.
- geofft 11y agoAh, gotcha, it works for a different use case from mine (but a valid one). And yes, I'd like to see code using well-defined atomic operations and orderings / barriers.
- adamtj 11y agoWhat if I use a library that forks or spawns threads? It would know nothing about these counters and wouldn't increment them, so I'd never know. That's the neat thing about the original article: you can know if a fork happened outside of your own code.
- geofft 11y agoAs agwa pointed out, it does work, but with a different definition of work (i.e., a different use case). The use case is for, say, a callback that might be called from different sides of fork, but shouldn't be. If you run this algorithm at the top of your callback, one side of fork will arbitrarily be okay, but the other side of fork will report a discrepancy between the two values. For some applications, that's all you need to know. If the forking library never calls into your code from two different sides of fork, then that's fine (for this use case), and your algorithm will never detect a fork: as long as your code is only called from one side of every fork, the two numbers will stay in sync. The use case is something like a random seed, where you don't want to branch the same seed into two timelines. If you follow only one timeline, that's acceptable, even if there are other timelines. (This algorithm doesn't handle multiple threads either, I think.)
- geocar 11y agoMultiple threads will share the random state, and won't cause a problem there.