[HN Gopher] Patching until the COWs come home
___________________________________________________________________
Patching until the COWs come home
Author : alex_hirner
Score : 74 points
Date : 2021-06-26 08:08 UTC (14 hours ago)
(HTM) web link (lwn.net)
(TXT) w3m dump (lwn.net)
| AlexanderDhoore wrote:
| Solution: don't use copy-on-write?
| rurban wrote:
| Always look at the hammer first. The solution is not use
| mmunmap() of course. But there are still security cases left
| viraptor wrote:
| That would kill performance of many machines. Some wouldn't
| even notice (for example hosts dedicated to a Go webserver),
| some would get absolutely trashed (PHP pre-fork would likely be
| an extreme case). Additionally your baseline memory usage for
| each application is a full in-memory copy of every linked
| library. Say hello to 10+MB hello-world. (edit: actually just
| 3MB on my machine) Also all stacks would have to get reserved
| and cleared ahead of time, so thread spawning time grows ~5x.
|
| Basically unless you want to run single-process unikernels, our
| systems and software really isn't designed or ready for non-cow
| world.
| jcranmer wrote:
| It's not that bad. The real problem is fork(), which is just
| a bad syscall in general (note how the single most common
| desktop OS doesn't have any equivalent to fork and seems to
| get along just fine). You can still allow mmap in a CoW-less
| world (you'll have to prevent concurrent file modification,
| which--again--a certain common operating system does!).
| Memory reservation is _completely orthogonal_ to CoW: lazy
| page table initialization isn 't the same thing as copy-on-
| write.
| jabl wrote:
| This paper argues that fork+exec is the wrong primitive for
| launching new processes: https://www.microsoft.com/en-
| us/research/publication/a-fork-...
|
| posix_spawn can take care of many common cases today.
| zxzax wrote:
| Unfortunately, on Linux there is no posix_spawn syscall.
| It's implemented in glibc as a wrapper around clone +
| exec.
| geofft wrote:
| Yes, but it's a wrapper around clone(CLONE_VFORK |
| CLONE_VM) (aka vfork) + exec, which makes a big
| difference.
|
| CLONE_VFORK means that the parent process is suspended
| until the child either exits or execs, and CLONE_VM means
| that no copy-on-write happens - the child has full
| read/write access to the _original_ memory space until it
| execs (or exits). In the child, posix_spawn is careful to
| work within temporary memory so it doesn 't overwrite
| anything that the parent process might care about,
| including global variables etc. Then once exec succeeds
| (or fails, and the process exits), posix_spawn in the
| parent cleans up that temporary memory.
|
| This means that posix_spawn doesn't require any copy-on-
| write support from the kernel. Or in other words, there's
| no posix_spawn syscall and there shouldn't be because
| it's a library function, but there _is_ a vfork syscall.
| zxzax wrote:
| I generally agree with the comments in the research paper
| about clone + vfork, most applications seem to not use it
| at all (either directly or indirectly through
| posix_spawn) for those reasons.
| j16sdiz wrote:
| Windows have PAGE_WRITECOPY and PAGE_EXECUTE_WRITECOPY.
|
| Those are essential for efficient shared library loading.
| eyberg wrote:
| You are correct that there is an awful lot of software that
| came out of the 90s that was explicitly written for
| forking/multiple processes (we're talking 25-30 years now)
| but that only reflects the type of commodity computers we had
| then - pre SMP, pre commercialized virtualization, pre-cloud,
| etc. Threads back then weren't in a useable state either.
|
| Fast forward to today and we're finally starting to see the
| tide turn. I can go spin up a 384 thread instance on gcp
| right now. We have very popular languages like go and rust.
| In the case of Go they've made multi-threading easily
| accessible to many developers to the point that many people
| don't even think about it.
|
| Yes, there's a lot of cruft out there but we can build for
| the future.
| toast0 wrote:
| If you don't actually need shared anything but config
| (memory / fds / task pooling), pre-fork is still better
| than threading. 384 threads sitting on one fd table is
| going to bottleneck on open (and maybe close) and anything
| else that is a lock per process.
| ekimekim wrote:
| The second half of the article is here:
| https://lwn.net/Articles/849876/
| giovannibonetti wrote:
| This reminds me of the Haskell language, which goes to great
| lengths to be "lazy", meaning it tries to avoid doing
| computations until the last moment.
|
| Sometimes it is convenient (e.g. to work with infinite/very large
| lists), but I've heard that oftentimes it makes the performance
| very unpredictable, as a CPU spike arrives all of a sudden in the
| last moment.
|
| Anyway, take this with a grain of salt, as I'm still beginning my
| journey with the language.
| jonny383 wrote:
| Still beginning and already shilling the language. What is it
| about Haskell that induces this fanboyism?
| Volundr wrote:
| This doesn't read as anything close to "shilling" to me. It
| compares lazy evaluation to copy on write, then brings up a
| drawback of lazy evaluation with absolutely no claim that the
| benefits outweigh the downsides. It certainly does not appear
| to be pushing anyone else to use the language.
| dundarious wrote:
| I didn't even read GP's comment as an endorsement. Mere
| mention of a language, one of its occasional conveniences,
| and its potential serious downsides, does not constitute
| shilling or "fanboyism".
| whateveracct wrote:
| Haskell if anything has as many or more vigorous anti-
| fanboys that viscerally dislike it.
| chriswarbo wrote:
| > I've heard that oftentimes it makes the performance very
| unpredictable, as a CPU spike arrives all of a sudden in the
| last moment.
|
| In a sense, _all_ of the computation occurs in the "last
| moment", since that's when we're 'forced' to make a decision
| (more precisely, computation occurs when (a) it's needed by the
| value defined as `main`; and (b) a computation is 'needed' when
| we've hit a branch point that depends on its result).
|
| Even more confusing, once a computation has been 'forced', it
| runs _backwards_ : it's the _result_ which gets forced, in
| order to choose a branch; if the definition of that result
| involves branching, that will force _other_ values so we can
| figure out which branch to take; if those involve branching
| then more values may get forced; and so on until we reach some
| branchless 'input'.
___________________________________________________________________
(page generated 2021-06-26 23:02 UTC)