forked from mirrors/nixpkgs
a5bc11f9eb
This seems to be the root cause of the random page allocation failures and @wizeman did a very good job on not only finding the root problem but also giving a detailed explanation of it in #10828. Here is an excerpt: The problem here is that the kernel is trying to allocate a contiguous section of 2^7=128 pages, which is 512 KB. This is way too much: kernel pages tend to get fragmented over time and kernel developers often go to great lengths to try allocating at most only 1 contiguous page at a time whenever they can. From the error message, it looks like the culprit is unionfs, but this is misleading: unionfs is the name of the userspace process that was running when the system ran out of memory, but it wasn't unionfs who was allocating the memory: it was the kernel; specifically it was the v9fs_dir_readdir_dotl() function, which is the code for handling the readdir() function in the 9p filesystem (the filesystem that is used to share a directory structure between a qemu host and its VM). If you look at the code, here's what it's doing at the moment it tries to allocate memory: buflen = fid->clnt->msize - P9_IOHDRSZ; rdir = v9fs_alloc_rdir_buf(file, buflen); If you look into v9fs_alloc_rdir_buf(), you will see that it will try to allocate a contiguous buffer of memory (using kzalloc(), which is a wrapper around kmalloc()) of size buflen + 8 bytes or so. So in reality, this code actually allocates a buffer of size proportional to fid->clnt->msize. What is this msize? If you follow the definition of the structures, you will see that it's the negotiated buffer transfer size between 9p client and 9p server. On the client side, it can be controlled with the msize mount option. What this all means is that, the reason for running out of memory is that the code (which we can't easily change) tries to allocate a contiguous buffer of size more or less equal to "negotiated 9p protocol buffer size", which seems to be way too big (in our NixOS tests, at least). After that initial finding, @lethalman tested the gnome3 gdm test without setting the msize parameter at all and it seems to have resolved the problem. The reason why I'm committing this without testing against all of the NixOS VM test is basically that I think we can only go better but not worse than the current state. Signed-off-by: aszlig <aszlig@redmoonstudios.org>
265 lines
6.8 KiB
Nix
265 lines
6.8 KiB
Nix
{ stdenv, writeScript, vmTools, makeInitrd
|
|
, samba, vde2, openssh, socat, netcat, coreutils, gnugrep, gzip
|
|
}:
|
|
|
|
{ sshKey
|
|
, qemuArgs ? []
|
|
, command ? "sync"
|
|
, suspendTo ? null
|
|
, resumeFrom ? null
|
|
, installMode ? false
|
|
}:
|
|
|
|
with stdenv.lib;
|
|
|
|
let
|
|
preInitScript = writeScript "preinit.sh" ''
|
|
#!${vmTools.initrdUtils}/bin/ash -e
|
|
export PATH=${vmTools.initrdUtils}/bin
|
|
mount -t proc none /proc
|
|
mount -t sysfs none /sys
|
|
for arg in $(cat /proc/cmdline); do
|
|
if [ "x''${arg#command=}" != "x$arg" ]; then
|
|
command="''${arg#command=}"
|
|
fi
|
|
done
|
|
|
|
for i in $(cat ${modulesClosure}/insmod-list); do
|
|
insmod $i
|
|
done
|
|
|
|
mkdir -p /dev /fs
|
|
|
|
mount -t tmpfs none /dev
|
|
mknod /dev/null c 1 3
|
|
mknod /dev/zero c 1 5
|
|
mknod /dev/random c 1 8
|
|
mknod /dev/urandom c 1 9
|
|
mknod /dev/tty c 5 0
|
|
|
|
ifconfig lo up
|
|
ifconfig eth0 up 192.168.0.2
|
|
|
|
mount -t tmpfs none /fs
|
|
mkdir -p /fs/nix/store /fs/xchg /fs/dev /fs/sys /fs/proc /fs/etc /fs/tmp
|
|
|
|
mount -o bind /dev /fs/dev
|
|
mount -t sysfs none /fs/sys
|
|
mount -t proc none /fs/proc
|
|
|
|
mount -t 9p \
|
|
-o trans=virtio,version=9p2000.L,cache=loose \
|
|
store /fs/nix/store
|
|
|
|
mount -t 9p \
|
|
-o trans=virtio,version=9p2000.L,cache=loose \
|
|
xchg /fs/xchg
|
|
|
|
echo root:x:0:0::/root:/bin/false > /fs/etc/passwd
|
|
|
|
set +e
|
|
chroot /fs $command $out
|
|
echo $? > /fs/xchg/in-vm-exit
|
|
|
|
poweroff -f
|
|
'';
|
|
|
|
initrd = makeInitrd {
|
|
contents = singleton {
|
|
object = preInitScript;
|
|
symlink = "/init";
|
|
};
|
|
};
|
|
|
|
shellEscape = x: "'${replaceChars ["'"] [("'\\'" + "'")] x}'";
|
|
|
|
loopForever = "while :; do ${coreutils}/bin/sleep 1; done";
|
|
|
|
initScript = writeScript "init.sh" (''
|
|
#!${stdenv.shell}
|
|
${coreutils}/bin/cp -L "${sshKey}" /ssh.key
|
|
${coreutils}/bin/chmod 600 /ssh.key
|
|
'' + (if installMode then ''
|
|
echo -n "Waiting for Windows installation to finish..."
|
|
while ! ${netcat}/bin/netcat -z 192.168.0.1 22; do
|
|
echo -n .
|
|
# Print a dot every 10 seconds only to shorten line length.
|
|
${coreutils}/bin/sleep 10
|
|
done
|
|
${coreutils}/bin/touch /xchg/waiting_done
|
|
echo " success."
|
|
# Loop forever, because this VM is going to be killed.
|
|
${loopForever}
|
|
'' else ''
|
|
${coreutils}/bin/mkdir -p /etc/samba /etc/samba/private \
|
|
/var/lib/samba /var/log /var/run
|
|
${coreutils}/bin/cat > /etc/samba/smb.conf <<CONFIG
|
|
[global]
|
|
security = user
|
|
map to guest = Bad User
|
|
guest account = root
|
|
workgroup = cygwin
|
|
netbios name = controller
|
|
server string = %h
|
|
log level = 1
|
|
max log size = 1000
|
|
log file = /var/log/samba.log
|
|
|
|
[nixstore]
|
|
path = /nix/store
|
|
writable = yes
|
|
guest ok = yes
|
|
|
|
[xchg]
|
|
path = /xchg
|
|
writable = yes
|
|
guest ok = yes
|
|
CONFIG
|
|
|
|
${samba}/sbin/nmbd -D
|
|
${samba}/sbin/smbd -D
|
|
|
|
echo -n "Waiting for Windows VM to become available..."
|
|
while ! ${netcat}/bin/netcat -z 192.168.0.1 22; do
|
|
echo -n .
|
|
${coreutils}/bin/sleep 1
|
|
done
|
|
${coreutils}/bin/touch /xchg/waiting_done
|
|
echo " success."
|
|
|
|
${openssh}/bin/ssh \
|
|
-o UserKnownHostsFile=/dev/null \
|
|
-o StrictHostKeyChecking=no \
|
|
-i /ssh.key \
|
|
-l Administrator \
|
|
192.168.0.1 -- ${shellEscape command}
|
|
'') + optionalString (suspendTo != null) ''
|
|
${coreutils}/bin/touch /xchg/suspend_now
|
|
${loopForever}
|
|
'');
|
|
|
|
kernelAppend = concatStringsSep " " [
|
|
"panic=1"
|
|
"loglevel=4"
|
|
"console=tty1"
|
|
"console=ttyS0"
|
|
"command=${initScript}"
|
|
];
|
|
|
|
controllerQemuArgs = concatStringsSep " " (maybeKvm64 ++ [
|
|
"-pidfile $CTRLVM_PIDFILE"
|
|
"-nographic"
|
|
"-no-reboot"
|
|
"-virtfs local,path=/nix/store,security_model=none,mount_tag=store"
|
|
"-virtfs local,path=$XCHG_DIR,security_model=none,mount_tag=xchg"
|
|
"-kernel ${modulesClosure.kernel}/bzImage"
|
|
"-initrd ${initrd}/initrd"
|
|
"-append \"${kernelAppend}\""
|
|
"-net nic,vlan=0,macaddr=52:54:00:12:01:02,model=virtio"
|
|
"-net vde,vlan=0,sock=$QEMU_VDE_SOCKET"
|
|
]);
|
|
|
|
maybeKvm64 = optional (stdenv.system == "x86_64-linux") "-cpu kvm64";
|
|
|
|
cygwinQemuArgs = concatStringsSep " " (maybeKvm64 ++ [
|
|
"-monitor unix:$MONITOR_SOCKET,server,nowait"
|
|
"-pidfile $WINVM_PIDFILE"
|
|
"-nographic"
|
|
"-net nic,vlan=0,macaddr=52:54:00:12:01:01"
|
|
"-net vde,vlan=0,sock=$QEMU_VDE_SOCKET"
|
|
"-rtc base=2010-01-01,clock=vm"
|
|
] ++ qemuArgs ++ optionals (resumeFrom != null) [
|
|
"-incoming 'exec: ${gzip}/bin/gzip -c -d \"${resumeFrom}\"'"
|
|
]);
|
|
|
|
modulesClosure = overrideDerivation vmTools.modulesClosure (o: {
|
|
rootModules = o.rootModules ++ singleton "virtio_net";
|
|
});
|
|
|
|
preVM = ''
|
|
(set; declare -p) > saved-env
|
|
XCHG_DIR="$(${coreutils}/bin/mktemp -d nix-vm.XXXXXXXXXX --tmpdir)"
|
|
${coreutils}/bin/mv saved-env "$XCHG_DIR/"
|
|
|
|
eval "$preVM"
|
|
|
|
QEMU_VDE_SOCKET="$(pwd)/vde.ctl"
|
|
MONITOR_SOCKET="$(pwd)/monitor"
|
|
WINVM_PIDFILE="$(pwd)/winvm.pid"
|
|
CTRLVM_PIDFILE="$(pwd)/ctrlvm.pid"
|
|
${vde2}/bin/vde_switch -s "$QEMU_VDE_SOCKET" &
|
|
echo 'alive?' | ${socat}/bin/socat - \
|
|
UNIX-CONNECT:$QEMU_VDE_SOCKET/ctl,retry=20
|
|
'';
|
|
|
|
vmExec = ''
|
|
${vmTools.qemuProg} ${controllerQemuArgs} &
|
|
${vmTools.qemuProg} ${cygwinQemuArgs} &
|
|
echo -n "Waiting for VMs to start up..."
|
|
timeout=60
|
|
while ! test -e "$WINVM_PIDFILE" -a -e "$CTRLVM_PIDFILE"; do
|
|
timeout=$(($timeout - 1))
|
|
echo -n .
|
|
if test $timeout -le 0; then
|
|
echo " timed out."
|
|
exit 1
|
|
fi
|
|
${coreutils}/bin/sleep 1
|
|
done
|
|
echo " done."
|
|
'';
|
|
|
|
checkDropOut = ''
|
|
if ! test -e "$XCHG_DIR/waiting_done" &&
|
|
! kill -0 $(< "$WINVM_PIDFILE"); then
|
|
echo "Windows VM has dropped out early, bailing out!" >&2
|
|
exit 1
|
|
fi
|
|
'';
|
|
|
|
toMonitor = "${socat}/bin/socat - UNIX-CONNECT:$MONITOR_SOCKET";
|
|
|
|
postVM = if suspendTo != null then ''
|
|
while ! test -e "$XCHG_DIR/suspend_now"; do
|
|
${checkDropOut}
|
|
${coreutils}/bin/sleep 1
|
|
done
|
|
${toMonitor} <<CMD
|
|
stop
|
|
migrate_set_speed 4095m
|
|
migrate "exec:${gzip}/bin/gzip -c > '${suspendTo}'"
|
|
CMD
|
|
echo -n "Waiting for memory dump to finish..."
|
|
while ! echo info migrate | ${toMonitor} | \
|
|
${gnugrep}/bin/grep -qi '^migration *status: *complete'; do
|
|
${coreutils}/bin/sleep 1
|
|
echo -n .
|
|
done
|
|
echo " done."
|
|
echo quit | ${toMonitor}
|
|
wait $(< "$WINVM_PIDFILE")
|
|
eval "$postVM"
|
|
exit 0
|
|
'' else if installMode then ''
|
|
wait $(< "$WINVM_PIDFILE")
|
|
eval "$postVM"
|
|
exit 0
|
|
'' else ''
|
|
while kill -0 $(< "$CTRLVM_PIDFILE"); do
|
|
${checkDropOut}
|
|
done
|
|
if ! test -e "$XCHG_DIR/in-vm-exit"; then
|
|
echo "Virtual machine didn't produce an exit code."
|
|
exit 1
|
|
fi
|
|
eval "$postVM"
|
|
exit $(< "$XCHG_DIR/in-vm-exit")
|
|
'';
|
|
|
|
in writeScript "run-cygwin-vm.sh" ''
|
|
#!${stdenv.shell} -e
|
|
${preVM}
|
|
${vmExec}
|
|
${postVM}
|
|
''
|