4 ms·
On Linux, if you get a SIGBUS from poking a memory map that generally means you'd have certainly gotten -ENOMEM or -EIO during an equivalent sequence of syscal
by jcalvinowens 2y ago
On Linux, if you get a SIGBUS
from poking a memory map that generally means you'd have certainly gotten -ENOMEM or -EIO during an equivalent sequence of syscalls (or been oom-killed, if you overcommit). Those are treated as fatal in the vast majority of programs, so dying to SIGBUS isn't meaningfully different for most usecases.
By your logic, passing a file descriptor to a library is also "unwieldy", because the library might not handle -EIO.
You can use MAP_POPULATE|MAP_LOCKED to ensure you get an error from mmap() instead of getting killed in the ENOMEM case, if you don't overcommit (if you do, you can still be oom-killed). You still get SIGBUS beyond EOF, but that's the behavior you want: it's equivalent to overrunning a buffer.
The behavior when file size isn't a multiple of PAGE_SIZE is legitimately weird (writes to the final page beyond EOF are visible to the entire system in memory but never written back to the file), but it's intuitive if you understand how the page cache works at a high level, and you can avoid it by making the size page aligned.
For more complex usecases where you really do want to handle these sorts of errors, userfaultfd() gives you all the tools you need: https://www.man7.org/linux/man-pages/man2/userfaultfd.2.html https://www.man7.org/linux/man-pages/man2/userfaultfd.2.html
EDIT: Initially described MAP_POPULATE wrong.
- loeg 2y ago> On Linux, if you get a SIGBUS from poking a memory map that generally means you'd have certainly gotten -ENOMEM or -EIO during an equivalent sequence of syscalls (or been oom-killed, if you overcommit). Those are treated as fatal in the vast majority of programs, so dying to SIGBUS isn't meaningfully different for most usecases. No. EIO is not instantly fatal in the same way that SIGBUS is. It allows for printing an error message, associating the error with some context of what failed, and either recovering in a degraded state or exiting cleanly. Doing any of this in a SIGBUS handler ranges from unwieldy to impossible.
- jcalvinowens 2y ago> It allows for printing an error message, associating the error with some context of what failed. Nope: -EIO is not synchronous unless you're using O_SYNC. More commonly, you're going to get -EIO from sync() or close(), not write(). All write() does a lot of the time is memcpy() into the pagecache and mark the page as dirty. Once you've written enough to trigger writeback, write() will return -EIO. But the error is still asynchronous, probably having occurred during the writeback of data you passed to a successful write() call in the past. A SIGBUS handler that inspects /proc/self/maps and tells you what file the failure occurred in before calling abort() is trivial. That's sufficient for 95% of usecases.
- loeg 2y agoFirst, you're free to not use buffered IO. Second, EIO on fsync or close for buffered IO is still adjacent to the relevant file descriptor.
- jcalvinowens 2y ago> adjacent to the relevant file descriptor. So is SIGBUS: you get the address in the handler. You probably have a data structure associating the two things somewhere anyway, and if you don't you can look it up in /proc.
- krilovsky 2y agoYou're assuming here that mmap is only used for writing, where TFA is actually describing a read-only scenario, in which case EIO is synchronous as the read can't be completed. As for the triviality of writing a SIGBUS handler correctly, that is an oversimplification at best. I/O errors aren't always fatal, even in the write case, and handling SIGBUS in the way you describe wouldn't work when e.g. you're also out of file descriptors, or when the cause of SIGBUS isn't due to an I/O error. So what works for 95% of your usecases doesn't necessarily apply to the 95% of other people's usecases.
- jcalvinowens 2y agoThe point is the same for reads: the vast majority of usecases just immediately abort() when a read fails. Writing byzantine fault logic to deal with broken storage media is like trying to recover from SIGSEGV, it's almost never a good idea. > I/O errors aren't always fatal, even in the write case Linux will not return -EIO unless the disk is in an unrecoverable state. Generally the assumption is that userspace will treat -EIO as fatal, so the kernel won't return it unless it's truly hosed. Sometimes the error is specific to a file, but that's the far less common case in practice. > e.g. you're also out of file descriptors, ENFILE is easy to deal with in a fatal path, by closing stdin so fd #0 can be reused (you're about to call abort(), you don't need it anymore). Try again :) > or when the cause of SIGBUS isn't due to an I/O error. It's either -EIO, or it's I/O beyond EOF. The second thing is a bug equivalent to a buffer overrun. That's synchronous, you can handle it just like you handle SIGSEGV if you want to emit more debugging or even write byzantine recovery logic.
- malkia 2y agoPassing file handle is different - a well written library would check and handle errors, but pointer to buffer - and I don't expect a library to "try" and "catch" (even if it's possible) cases. Would strlen do this? No.
- jcalvinowens 2y agoSee below: this is wrong, the IO errors from the syscalls aren't synchronous. You might successfully call write() and then get -EIO from a sync() call with no indication of what actually failed. At least with fsync() you would know which file it was, but you have no way of knowing what portion of the data you wrote was successfully written back. The failure is fatal either way, most of the time. It's very rare you actually want to handle -EIO, you just want to tell the user to replace their broken disk and call abort(). SIGBUS is fine, you can easily write a handler to add debugging context to your failure message.
- malkia 2y agoAre you saying that I can get somehow an error during "strlen" post "read()" - is this what are you saying? Because what I'm saying is that with "mmap" you can get that.
- jcalvinowens 2y agoAbsolutely: if the user page you read into experiences an uncorrectable ECC error in strlen(), you get SIGBUS. Friends don't let friends write code to attempt recovery from hardware failures.
- malkia 2y agoECC errors of a magnitudes lower chance to happen than broken file system
- jcalvinowens 2y agoDepends on who you buy hardware from :) How about swap then: the user page you read() into might get swapped out, and an I/O error might occur when strlen() faults it back in. I can't remember if you get SEGV or BUS in that case... but it's certainly fatal.