요약·해설과 원문, 전문 번역을 서로 분리했습니다. API 이름, symbol, source path는 원문 표기를 사용합니다.
1. 요약·해설
원문의 핵심 논리와 kernel programming 관점의 보충 설명입니다. 아래의 전문 번역과는 별도로 작성했습니다.
2. 영어 원문 전체
번역 기준이 된 Linux v6.18.37 원문입니다. 줄 번호는 이 버전의 파일 좌표입니다.
원문 전체 펼치기
.. SPDX-License-Identifier: GPL-2.0
#########
UML HowTo
#########
.. contents:: :local:
************
Introduction
************
Welcome to User Mode Linux
User Mode Linux is the first Open Source virtualization platform (first
release date 1991) and second virtualization platform for an x86 PC.
How is UML Different from a VM using Virtualization package X?
==============================================================
We have come to assume that virtualization also means some level of
hardware emulation. In fact, it does not. As long as a virtualization
package provides the OS with devices which the OS can recognize and
has a driver for, the devices do not need to emulate real hardware.
Most OSes today have built-in support for a number of "fake"
devices used only under virtualization.
User Mode Linux takes this concept to the ultimate extreme - there
is not a single real device in sight. It is 100% artificial or if
we use the correct term 100% paravirtual. All UML devices are abstract
concepts which map onto something provided by the host - files, sockets,
pipes, etc.
The other major difference between UML and various virtualization
packages is that there is a distinct difference between the way the UML
kernel and the UML programs operate.
The UML kernel is just a process running on Linux - same as any other
program. It can be run by an unprivileged user and it does not require
anything in terms of special CPU features.
The UML userspace, however, is a bit different. The Linux kernel on the
host machine assists UML in intercepting everything the program running
on a UML instance is trying to do and making the UML kernel handle all
of its requests.
This is different from other virtualization packages which do not make any
difference between the guest kernel and guest programs. This difference
results in a number of advantages and disadvantages of UML over let's say
QEMU which we will cover later in this document.
Why Would I Want User Mode Linux?
=================================
* If User Mode Linux kernel crashes, your host kernel is still fine. It
is not accelerated in any way (vhost, kvm, etc) and it is not trying to
access any devices directly. It is, in fact, a process like any other.
* You can run a usermode kernel as a non-root user (you may need to
arrange appropriate permissions for some devices).
* You can run a very small VM with a minimal footprint for a specific
task (for example 32M or less).
* You can get extremely high performance for anything which is a "kernel
specific task" such as forwarding, firewalling, etc while still being
isolated from the host kernel.
* You can play with kernel concepts without breaking things.
* You are not bound by "emulating" hardware, so you can try weird and
wonderful concepts which are very difficult to support when emulating
real hardware such as time travel and making your system clock
dependent on what UML does (very useful for things like tests).
* It's fun.
Why not to run UML
==================
* The syscall interception technique used by UML makes it inherently
slower for any userspace applications. While it can do kernel tasks
on par with most other virtualization packages, its userspace is
**slow**. The root cause is that UML has a very high cost of creating
new processes and threads (something most Unix/Linux applications
take for granted).
* UML is strictly uniprocessor at present. If you want to run an
application which needs many CPUs to function, it is clearly the
wrong choice.
***********************
Building a UML instance
***********************
There is no UML installer in any distribution. While you can use off
the shelf install media to install into a blank VM using a virtualization
package, there is no UML equivalent. You have to use appropriate tools on
your host to build a viable filesystem image.
This is extremely easy on Debian - you can do it using debootstrap. It is
also easy on OpenWRT - the build process can build UML images. All other
distros - YMMV.
Creating an image
=================
Create a sparse raw disk image::
# dd if=/dev/zero of=disk_image_name bs=1 count=1 seek=16G
This will create a 16G disk image. The OS will initially allocate only one
block and will allocate more as they are written by UML. As of kernel
version 4.19 UML fully supports TRIM (as usually used by flash drives).
Using TRIM inside the UML image by specifying discard as a mount option
or by running ``tune2fs -o discard /dev/ubdXX`` will request UML to
return any unused blocks to the OS.
Create a filesystem on the disk image and mount it::
# mkfs.ext4 ./disk_image_name && mount ./disk_image_name /mnt
This example uses ext4, any other filesystem such as ext3, btrfs, xfs,
jfs, etc will work too.
Create a minimal OS installation on the mounted filesystem::
# debootstrap buster /mnt http://deb.debian.org/debian
debootstrap does not set up the root password, fstab, hostname or
anything related to networking. It is up to the user to do that.
Set the root password - the easiest way to do that is to chroot into the
mounted image::
# chroot /mnt
# passwd
# exit
Edit key system files
=====================
UML block devices are called ubds. The fstab created by debootstrap
will be empty and it needs an entry for the root file system::
/dev/ubd0 ext4 discard,errors=remount-ro 0 1
The image hostname will be set to the same as the host on which you
are creating its image. It is a good idea to change that to avoid
"Oh, bummer, I rebooted the wrong machine".
UML supports vector I/O high performance network devices which have
support for some standard virtual network encapsulations like
Ethernet over GRE and Ethernet over L2TPv3. These are called vecX.
When vector network devices are in use, ``/etc/network/interfaces``
will need entries like::
# vector UML network devices
auto vec0
iface vec0 inet dhcp
We now have a UML image which is nearly ready to run, all we need is a
UML kernel and modules for it.
Most distributions have a UML package. Even if you intend to use your own
kernel, testing the image with a stock one is always a good start. These
packages come with a set of modules which should be copied to the target
filesystem. The location is distribution dependent. For Debian these
reside under /usr/lib/uml/modules. Copy recursively the content of this
directory to the mounted UML filesystem::
# cp -rax /usr/lib/uml/modules /mnt/lib/modules
If you have compiled your own kernel, you need to use the usual "install
modules to a location" procedure by running::
# make INSTALL_MOD_PATH=/mnt/lib/modules modules_install
This will install modules into /mnt/lib/modules/$(KERNELRELEASE).
To specify the full module installation path, use::
# make MODLIB=/mnt/lib/modules modules_install
At this point the image is ready to be brought up.
*************************
Setting Up UML Networking
*************************
UML networking is designed to emulate an Ethernet connection. This
connection may be either point-to-point (similar to a connection
between machines using a back-to-back cable) or a connection to a
switch. UML supports a wide variety of means to build these
connections to all of: local machine, remote machine(s), local and
remote UML and other VM instances.
+-----------+--------+------------------------------------+------------+
| Transport | Type | Capabilities | Throughput |
+===========+========+====================================+============+
| tap | vector | checksum, tso | > 8Gbit |
+-----------+--------+------------------------------------+------------+
| hybrid | vector | checksum, tso, multipacket rx | > 6GBit |
+-----------+--------+------------------------------------+------------+
| raw | vector | checksum, tso, multipacket rx, tx" | > 6GBit |
+-----------+--------+------------------------------------+------------+
| EoGRE | vector | multipacket rx, tx | > 3Gbit |
+-----------+--------+------------------------------------+------------+
| Eol2tpv3 | vector | multipacket rx, tx | > 3Gbit |
+-----------+--------+------------------------------------+------------+
| bess | vector | multipacket rx, tx | > 3Gbit |
+-----------+--------+------------------------------------+------------+
| fd | vector | dependent on fd type | varies |
+-----------+--------+------------------------------------+------------+
| vde | vector | dep. on VDE VPN: Virt.Net Locator | varies |
+-----------+--------+------------------------------------+------------+
* All transports which have tso and checksum offloads can deliver speeds
approaching 10G on TCP streams.
* All transports which have multi-packet rx and/or tx can deliver pps
rates of up to 1Mps or more.
* GRE and L2TPv3 allow connections to all of: local machine, remote
machines, remote network devices and remote UML instances.
Network configuration privileges
================================
The majority of the supported networking modes need ``root`` privileges.
For example, for vector transports, ``root`` privilege is required to fire
an ioctl to setup the tun interface and/or use raw sockets where needed.
This can be achieved by granting the user a particular capability instead
of running UML as root. In case of vector transport, a user can add the
capability ``CAP_NET_ADMIN`` or ``CAP_NET_RAW`` to the uml binary.
Thenceforth, UML can be run with normal user privilges, along with
full networking.
For example::
# sudo setcap cap_net_raw,cap_net_admin+ep linux
Configuring vector transports
===============================
All vector transports support a similar syntax:
If X is the interface number as in vec0, vec1, vec2, etc, the general
syntax for options is::
vecX:transport="Transport Name",option=value,option=value,...,option=value
Common options
--------------
These options are common for all transports:
* ``depth=int`` - sets the queue depth for vector IO. This is the
amount of packets UML will attempt to read or write in a single
system call. The default number is 64 and is generally sufficient
for most applications that need throughput in the 2-4 Gbit range.
Higher speeds may require larger values.
* ``mac=XX:XX:XX:XX:XX`` - sets the interface MAC address value.
* ``gro=[0,1]`` - sets GRO off or on. Enables receive/transmit offloads.
The effect of this option depends on the host side support in the transport
which is being configured. In most cases it will enable TCP segmentation and
RX/TX checksumming offloads. The setting must be identical on the host side
and the UML side. The UML kernel will produce warnings if it is not.
For example, GRO is enabled by default on local machine interfaces
(e.g. veth pairs, bridge, etc), so it should be enabled in UML in the
corresponding UML transports (raw, tap, hybrid) in order for networking to
operate correctly.
* ``mtu=int`` - sets the interface MTU
* ``headroom=int`` - adjusts the default headroom (32 bytes) reserved
if a packet will need to be re-encapsulated into for instance VXLAN.
* ``vec=0`` - disable multipacket IO and fall back to packet at a
time mode
Shared Options
--------------
* ``ifname=str`` Transports which bind to a local network interface
have a shared option - the name of the interface to bind to.
* ``src, dst, src_port, dst_port`` - all transports which use sockets
which have the notion of source and destination and/or source port
and destination port use these to specify them.
* ``v6=[0,1]`` to specify if a v6 connection is desired for all
transports which operate over IP. Additionally, for transports that
have some differences in the way they operate over v4 and v6 (for example
EoL2TPv3), sets the correct mode of operation. In the absence of this
option, the socket type is determined based on what do the src and dst
arguments resolve/parse to.
tap transport
-------------
Example::
vecX:transport=tap,ifname=tap0,depth=128,gro=1
This will connect vec0 to tap0 on the host. Tap0 must already exist (for example
created using tunctl) and UP.
tap0 can be configured as a point-to-point interface and given an IP
address so that UML can talk to the host. Alternatively, it is possible
to connect UML to a tap interface which is connected to a bridge.
While tap relies on the vector infrastructure, it is not a true vector
transport at this point, because Linux does not support multi-packet
IO on tap file descriptors for normal userspace apps like UML. This
is a privilege which is offered only to something which can hook up
to it at kernel level via specialized interfaces like vhost-net. A
vhost-net like helper for UML is planned at some point in the future.
Privileges required: tap transport requires either:
* tap interface to exist and be created persistent and owned by the
UML user using tunctl. Example ``tunctl -u uml-user -t tap0``
* binary to have ``CAP_NET_ADMIN`` privilege
hybrid transport
----------------
Example::
vecX:transport=hybrid,ifname=tap0,depth=128,gro=1
This is an experimental/demo transport which couples tap for transmit
and a raw socket for receive. The raw socket allows multi-packet
receive resulting in significantly higher packet rates than normal tap.
Privileges required: hybrid requires ``CAP_NET_RAW`` capability by
the UML user as well as the requirements for the tap transport.
raw socket transport
--------------------
Example::
vecX:transport=raw,ifname=p-veth0,depth=128,gro=1
This transport uses vector IO on raw sockets. While you can bind to any
interface including a physical one, the most common use it to bind to
the "peer" side of a veth pair with the other side configured on the
host.
Example host configuration for Debian:
**/etc/network/interfaces**::
auto veth0
iface veth0 inet static
address 192.168.4.1
netmask 255.255.255.252
broadcast 192.168.4.3
pre-up ip link add veth0 type veth peer name p-veth0 && \
ifconfig p-veth0 up
UML can now bind to p-veth0 like this::
vec0:transport=raw,ifname=p-veth0,depth=128,gro=1
If the UML guest is configured with 192.168.4.2 and netmask 255.255.255.0
it can talk to the host on 192.168.4.1
The raw transport also provides some support for offloading some of the
filtering to the host. The two options to control it are:
* ``bpffile=str`` filename of raw bpf code to be loaded as a socket filter
* ``bpfflash=int`` 0/1 allow loading of bpf from inside User Mode Linux.
This option allows the use of the ethtool load firmware command to
load bpf code.
In either case the bpf code is loaded into the host kernel. While this is
presently limited to legacy bpf syntax (not ebpf), it is still a security
risk. It is not recommended to allow this unless the User Mode Linux
instance is considered trusted.
Privileges required: raw socket transport requires `CAP_NET_RAW`
capability.
GRE socket transport
--------------------
Example::
vecX:transport=gre,src=$src_host,dst=$dst_host
This will configure an Ethernet over ``GRE`` (aka ``GRETAP`` or
``GREIRB``) tunnel which will connect the UML instance to a ``GRE``
endpoint at host dst_host. ``GRE`` supports the following additional
options:
* ``rx_key=int`` - GRE 32-bit integer key for rx packets, if set,
``txkey`` must be set too
* ``tx_key=int`` - GRE 32-bit integer key for tx packets, if set
``rx_key`` must be set too
* ``sequence=[0,1]`` - enable GRE sequence
* ``pin_sequence=[0,1]`` - pretend that the sequence is always reset
on each packet (needed to interoperate with some really broken
implementations)
* ``v6=[0,1]`` - force IPv4 or IPv6 sockets respectively
* GRE checksum is not presently supported
GRE has a number of caveats:
* You can use only one GRE connection per IP address. There is no way to
multiplex connections as each GRE tunnel is terminated directly on
the UML instance.
* The key is not really a security feature. While it was intended as such
its "security" is laughable. It is, however, a useful feature to
ensure that the tunnel is not misconfigured.
An example configuration for a Linux host with a local address of
192.168.128.1 to connect to a UML instance at 192.168.129.1
**/etc/network/interfaces**::
auto gt0
iface gt0 inet static
address 10.0.0.1
netmask 255.255.255.0
broadcast 10.0.0.255
mtu 1500
pre-up ip link add gt0 type gretap local 192.168.128.1 \
remote 192.168.129.1 || true
down ip link del gt0 || true
Additionally, GRE has been tested versus a variety of network equipment.
Privileges required: GRE requires ``CAP_NET_RAW``
l2tpv3 socket transport
-----------------------
_Warning_. L2TPv3 has a "bug". It is the "bug" known as "has more
options than GNU ls". While it has some advantages, there are usually
easier (and less verbose) ways to connect a UML instance to something.
For example, most devices which support L2TPv3 also support GRE.
Example::
vec0:transport=l2tpv3,udp=1,src=$src_host,dst=$dst_host,srcport=$src_port,dstport=$dst_port,depth=128,rx_session=0xffffffff,tx_session=0xffff
This will configure an Ethernet over L2TPv3 fixed tunnel which will
connect the UML instance to a L2TPv3 endpoint at host $dst_host using
the L2TPv3 UDP flavour and UDP destination port $dst_port.
L2TPv3 always requires the following additional options:
* ``rx_session=int`` - l2tpv3 32-bit integer session for rx packets
* ``tx_session=int`` - l2tpv3 32-bit integer session for tx packets
As the tunnel is fixed these are not negotiated and they are
preconfigured on both ends.
Additionally, L2TPv3 supports the following optional parameters.
* ``rx_cookie=int`` - l2tpv3 32-bit integer cookie for rx packets - same
functionality as GRE key, more to prevent misconfiguration than provide
actual security
* ``tx_cookie=int`` - l2tpv3 32-bit integer cookie for tx packets
* ``cookie64=[0,1]`` - use 64-bit cookies instead of 32-bit.
* ``counter=[0,1]`` - enable l2tpv3 counter
* ``pin_counter=[0,1]`` - pretend that the counter is always reset on
each packet (needed to interoperate with some really broken
implementations)
* ``v6=[0,1]`` - force v6 sockets
* ``udp=[0,1]`` - use raw sockets (0) or UDP (1) version of the protocol
L2TPv3 has a number of caveats:
* you can use only one connection per IP address in raw mode. There is
no way to multiplex connections as each L2TPv3 tunnel is terminated
directly on the UML instance. UDP mode can use different ports for
this purpose.
Here is an example of how to configure a Linux host to connect to UML
via L2TPv3:
**/etc/network/interfaces**::
auto l2tp1
iface l2tp1 inet static
address 192.168.126.1
netmask 255.255.255.0
broadcast 192.168.126.255
mtu 1500
pre-up ip l2tp add tunnel remote 127.0.0.1 \
local 127.0.0.1 encap udp tunnel_id 2 \
peer_tunnel_id 2 udp_sport 1706 udp_dport 1707 && \
ip l2tp add session name l2tp1 tunnel_id 2 \
session_id 0xffffffff peer_session_id 0xffffffff
down ip l2tp del session tunnel_id 2 session_id 0xffffffff && \
ip l2tp del tunnel tunnel_id 2
Privileges required: L2TPv3 requires ``CAP_NET_RAW`` for raw IP mode and
no special privileges for the UDP mode.
BESS socket transport
---------------------
BESS is a high performance modular network switch.
https://github.com/NetSys/bess
It has support for a simple sequential packet socket mode which in the
more recent versions is using vector IO for high performance.
Example::
vecX:transport=bess,src=$unix_src,dst=$unix_dst
This will configure a BESS transport using the unix_src Unix domain
socket address as source and unix_dst socket address as destination.
For BESS configuration and how to allocate a BESS Unix domain socket port
please see the BESS documentation.
https://github.com/NetSys/bess/wiki/Built-In-Modules-and-Ports
BESS transport does not require any special privileges.
VDE vector transport
--------------------
Virtual Distributed Ethernet (VDE) is a project whose main goal is to provide a
highly flexible support for virtual networking.
http://wiki.virtualsquare.org/#/tutorials/vdebasics
Common usages of VDE include fast prototyping and teaching.
Examples:
``vecX:transport=vde,vnl=tap://tap0``
use tap0
``vecX:transport=vde,vnl=slirp://``
use slirp
``vec0:transport=vde,vnl=vde:///tmp/switch``
connect to a vde switch
``vecX:transport=\"vde,vnl=cmd://ssh remote.host //tmp/sshlirp\"``
connect to a remote slirp (instant VPN: convert ssh to VPN, it uses sshlirp)
https://github.com/virtualsquare/sshlirp
``vec0:transport=vde,vnl=vxvde://234.0.0.1``
connect to a local area cloud (all the UML nodes using the same
multicast address running on hosts in the same multicast domain (LAN)
will be automagically connected together to a virtual LAN.
***********
Running UML
***********
This section assumes that either the user-mode-linux package from the
distribution or a custom built kernel has been installed on the host.
These add an executable called linux to the system. This is the UML
kernel. It can be run just like any other executable.
It will take most normal linux kernel arguments as command line
arguments. Additionally, it will need some UML-specific arguments
in order to do something useful.
Arguments
=========
Mandatory Arguments:
--------------------
* ``mem=int[K,M,G]`` - amount of memory. By default in bytes. It will
also accept K, M or G qualifiers.
* ``ubdX[s,d,c,t]=`` virtual disk specification. This is not really
mandatory, but it is likely to be needed in nearly all cases so we can
specify a root file system.
The simplest possible image specification is the name of the image
file for the filesystem (created using one of the methods described
in `Creating an image`_).
* UBD devices support copy on write (COW). The changes are kept in
a separate file which can be discarded allowing a rollback to the
original pristine image. If COW is desired, the UBD image is
specified as: ``cow_file,master_image``.
Example:``ubd0=Filesystem.cow,Filesystem.img``
* UBD devices can be set to use synchronous IO. Any writes are
immediately flushed to disk. This is done by adding ``s`` after
the ``ubdX`` specification.
* UBD performs some heuristics on devices specified as a single
filename to make sure that a COW file has not been specified as
the image. To turn them off, use the ``d`` flag after ``ubdX``.
* UBD supports TRIM - asking the Host OS to reclaim any unused
blocks in the image. To turn it off, specify the ``t`` flag after
``ubdX``.
* ``root=`` root device - most likely ``/dev/ubd0`` (this is a Linux
filesystem image)
Important Optional Arguments
----------------------------
If UML is run as "linux" with no extra arguments, it will try to start an
xterm for every console configured inside the image (up to 6 in most
Linux distributions). Each console is started inside an
xterm. This makes it nice and easy to use UML on a host with a GUI. It is,
however, the wrong approach if UML is to be used as a testing harness or run
in a text-only environment.
In order to change this behaviour we need to specify an alternative console
and wire it to one of the supported "line" channels. For this we need to map a
console to use something different from the default xterm.
Example which will divert console number 1 to stdin/stdout::
con1=fd:0,fd:1
UML supports a wide variety of serial line channels which are specified using
the following syntax
conX=channel_type:options[,channel_type:options]
If the channel specification contains two parts separated by comma, the first
one is input, the second one output.
* The null channel - Discard all input or output. Example ``con=null`` will set
all consoles to null by default.
* The fd channel - use file descriptor numbers for input/output. Example:
``con1=fd:0,fd:1.``
* The port channel - start a telnet server on TCP port number. Example:
``con1=port:4321``. The host must have /usr/sbin/in.telnetd (usually part of
a telnetd package) and the port-helper from the UML utilities (see the
information for the xterm channel below). UML will not boot until a client
connects.
* The pty and pts channels - use system pty/pts.
* The tty channel - bind to an existing system tty. Example: ``con1=/dev/tty8``
will make UML use the host 8th console (usually unused).
* The xterm channel - this is the default - bring up an xterm on this channel
and direct IO to it. Note that in order for xterm to work, the host must
have the UML distribution package installed. This usually contains the
port-helper and other utilities needed for UML to communicate with the xterm.
Alternatively, these need to be complied and installed from source. All
options applicable to consoles also apply to UML serial lines which are
presented as ttyS inside UML.
Starting UML
============
We can now run UML.
::
# linux mem=2048M umid=TEST \
ubd0=Filesystem.img \
vec0:transport=tap,ifname=tap0,depth=128,gro=1 \
root=/dev/ubda con=null con0=null,fd:2 con1=fd:0,fd:1
This will run an instance with ``2048M RAM`` and try to use the image file
called ``Filesystem.img`` as root. It will connect to the host using tap0.
All consoles except ``con1`` will be disabled and console 1 will
use standard input/output making it appear in the same terminal it was started.
Logging in
============
If you have not set up a password when generating the image, you will have to
shut down the UML instance, mount the image, chroot into it and set it - as
described in the Generating an Image section. If the password is already set,
you can just log in.
The UML Management Console
============================
In addition to managing the image from "the inside" using normal sysadmin tools,
it is possible to perform a number of low-level operations using the UML
management console. The UML management console is a low-level interface to the
kernel on a running UML instance, somewhat like the i386 SysRq interface. Since
there is a full-blown operating system under UML, there is much greater
flexibility possible than with the SysRq mechanism.
There are a number of things you can do with the mconsole interface:
* get the kernel version
* add and remove devices
* halt or reboot the machine
* Send SysRq commands
* Pause and resume the UML
* Inspect processes running inside UML
* Inspect UML internal /proc state
You need the mconsole client (uml\_mconsole) which is a part of the UML
tools package available in most Linux distritions.
You also need ``CONFIG_MCONSOLE`` (under 'General Setup') enabled in the UML
kernel. When you boot UML, you'll see a line like::
mconsole initialized on /home/jdike/.uml/umlNJ32yL/mconsole
If you specify a unique machine id on the UML command line, i.e.
``umid=debian``, you'll see this::
mconsole initialized on /home/jdike/.uml/debian/mconsole
That file is the socket that uml_mconsole will use to communicate with
UML. Run it with either the umid or the full path as its argument::
# uml_mconsole debian
or
# uml_mconsole /home/jdike/.uml/debian/mconsole
You'll get a prompt, at which you can run one of these commands:
* version
* help
* halt
* reboot
* config
* remove
* sysrq
* help
* cad
* stop
* go
* proc
* stack
version
-------
This command takes no arguments. It prints the UML version::
(mconsole) version
OK Linux OpenWrt 4.14.106 #0 Tue Mar 19 08:19:41 2019 x86_64
There are a couple actual uses for this. It's a simple no-op which
can be used to check that a UML is running. It's also a way of
sending a device interrupt to the UML. UML mconsole is treated internally as
a UML device.
help
----
This command takes no arguments. It prints a short help screen with the
supported mconsole commands.
halt and reboot
---------------
These commands take no arguments. They shut the machine down immediately, with
no syncing of disks and no clean shutdown of userspace. So, they are
pretty close to crashing the machine::
(mconsole) halt
OK
config
------
"config" adds a new device to the virtual machine. This is supported
by most UML device drivers. It takes one argument, which is the
device to add, with the same syntax as the kernel command line::
(mconsole) config ubd3=/home/jdike/incoming/roots/root_fs_debian22
remove
------
"remove" deletes a device from the system. Its argument is just the
name of the device to be removed. The device must be idle in whatever
sense the driver considers necessary. In the case of the ubd driver,
the removed block device must not be mounted, swapped on, or otherwise
open, and in the case of the network driver, the device must be down::
(mconsole) remove ubd3
sysrq
-----
This command takes one argument, which is a single letter. It calls the
generic kernel's SysRq driver, which does whatever is called for by
that argument. See the SysRq documentation in
Documentation/admin-guide/sysrq.rst in your favorite kernel tree to
see what letters are valid and what they do.
cad
---
This invokes the ``Ctl-Alt-Del`` action in the running image. What exactly
this ends up doing is up to init, systemd, etc. Normally, it reboots the
machine.
stop
----
This puts the UML in a loop reading mconsole requests until a 'go'
mconsole command is received. This is very useful as a
debugging/snapshotting tool.
go
--
This resumes a UML after being paused by a 'stop' command. Note that
when the UML has resumed, TCP connections may have timed out and if
the UML is paused for a long period of time, crond might go a little
crazy, running all the jobs it didn't do earlier.
proc
----
This takes one argument - the name of a file in /proc which is printed
to the mconsole standard output
stack
-----
This takes one argument - the pid number of a process. Its stack is
printed to a standard output.
*******************
Advanced UML Topics
*******************
Sharing Filesystems between Virtual Machines
============================================
Don't attempt to share filesystems simply by booting two UMLs from the
same file. That's the same thing as booting two physical machines
from a shared disk. It will result in filesystem corruption.
Using layered block devices
---------------------------
The way to share a filesystem between two virtual machines is to use
the copy-on-write (COW) layering capability of the ubd block driver.
Any changed blocks are stored in the private COW file, while reads come
from either device - the private one if the requested block is valid in
it, the shared one if not. Using this scheme, the majority of data
which is unchanged is shared between an arbitrary number of virtual
machines, each of which has a much smaller file containing the changes
that it has made. With a large number of UMLs booting from a large root
filesystem, this leads to a huge disk space saving.
Sharing file system data will also help performance, since the host will
be able to cache the shared data using a much smaller amount of memory,
so UML disk requests will be served from the host's memory rather than
its disks. There is a major caveat in doing this on multisocket NUMA
machines. On such hardware, running many UML instances with a shared
master image and COW changes may cause issues like NMIs from excess of
inter-socket traffic.
If you are running UML on high-end hardware like this, make sure to
bind UML to a set of logical CPUs residing on the same socket using the
``taskset`` command or have a look at the "tuning" section.
To add a copy-on-write layer to an existing block device file, simply
add the name of the COW file to the appropriate ubd switch::
ubd0=root_fs_cow,root_fs_debian_22
where ``root_fs_cow`` is the private COW file and ``root_fs_debian_22`` is
the existing shared filesystem. The COW file need not exist. If it
doesn't, the driver will create and initialize it.
Disk Usage
----------
UML has TRIM support which will release any unused space in its disk
image files to the underlying OS. It is important to use either ls -ls
or du to verify the actual file size.
COW validity.
-------------
Any changes to the master image will invalidate all COW files. If this
happens, UML will *NOT* automatically delete any of the COW files and
will refuse to boot. In this case the only solution is to either
restore the old image (including its last modified timestamp) or remove
all COW files which will result in their recreation. Any changes in
the COW files will be lost.
Cows can moo - uml_moo : Merging a COW file with its backing file
-----------------------------------------------------------------
Depending on how you use UML and COW devices, it may be advisable to
merge the changes in the COW file into the backing file every once in
a while.
The utility that does this is uml_moo. Its usage is::
uml_moo COW_file new_backing_file
There's no need to specify the backing file since that information is
already in the COW file header. If you're paranoid, boot the new
merged file, and if you're happy with it, move it over the old backing
file.
``uml_moo`` creates a new backing file by default as a safety measure.
It also has a destructive merge option which will merge the COW file
directly into its current backing file. This is really only usable
when the backing file only has one COW file associated with it. If
there are multiple COWs associated with a backing file, a -d merge of
one of them will invalidate all of the others. However, it is
convenient if you're short of disk space, and it should also be
noticeably faster than a non-destructive merge.
``uml_moo`` is installed with the UML distribution packages and is
available as a part of UML utilities.
Host file access
==================
If you want to access files on the host machine from inside UML, you
can treat it as a separate machine and either nfs mount directories
from the host or copy files into the virtual machine with scp.
However, since UML is running on the host, it can access those
files just like any other process and make them available inside the
virtual machine without the need to use the network.
This is possible with the hostfs virtual filesystem. With it, you
can mount a host directory into the UML filesystem and access the
files contained in it just as you would on the host.
*SECURITY WARNING*
Hostfs without any parameters to the UML Image will allow the image
to mount any part of the host filesystem and write to it. Always
confine hostfs to a specific "harmless" directory (for example ``/var/tmp``)
if running UML. This is especially important if UML is being run as root.
Using hostfs
------------
To begin with, make sure that hostfs is available inside the virtual
machine with::
# cat /proc/filesystems
``hostfs`` should be listed. If it's not, either rebuild the kernel
with hostfs configured into it or make sure that hostfs is built as a
module and available inside the virtual machine, and insmod it.
Now all you need to do is run mount::
# mount none /mnt/host -t hostfs
will mount the host's ``/`` on the virtual machine's ``/mnt/host``.
If you don't want to mount the host root directory, then you can
specify a subdirectory to mount with the -o switch to mount::
# mount none /mnt/home -t hostfs -o /home
will mount the host's /home on the virtual machine's /mnt/home.
hostfs as the root filesystem
-----------------------------
It's possible to boot from a directory hierarchy on the host using
hostfs rather than using the standard filesystem in a file.
To start, you need that hierarchy. The easiest way is to loop mount
an existing root_fs file::
# mount root_fs uml_root_dir -o loop
You need to change the filesystem type of ``/`` in ``etc/fstab`` to be
'hostfs', so that line looks like this::
/dev/ubd/0 / hostfs defaults 1 1
Then you need to chown to yourself all the files in that directory
that are owned by root. This worked for me::
# find . -uid 0 -exec chown jdike {} \;
Next, make sure that your UML kernel has hostfs compiled in, not as a
module. Then run UML with the boot device pointing at that directory::
ubd0=/path/to/uml/root/directory
UML should then boot as it does normally.
Hostfs Caveats
--------------
Hostfs does not support keeping track of host filesystem changes on the
host (outside UML). As a result, if a file is changed without UML's
knowledge, UML will not know about it and its own in-memory cache of
the file may be corrupt. While it is possible to fix this, it is not
something which is being worked on at present.
Tuning UML
============
UML at present is strictly uniprocessor. It will, however spin up a
number of threads to handle various functions.
The UBD driver, SIGIO and the MMU emulation do that. If the system is
idle, these threads will be migrated to other processors on a SMP host.
This, unfortunately, will usually result in LOWER performance because of
all of the cache/memory synchronization traffic between cores. As a
result, UML will usually benefit from being pinned on a single CPU,
especially on a large system. This can result in performance differences
of 5 times or higher on some benchmarks.
Similarly, on large multi-node NUMA systems UML will benefit if all of
its memory is allocated from the same NUMA node it will run on. The
OS will *NOT* do that by default. In order to do that, the sysadmin
needs to create a suitable tmpfs ramdisk bound to a particular node
and use that as the source for UML RAM allocation by specifying it
in the TMP or TEMP environment variables. UML will look at the values
of ``TMPDIR``, ``TMP`` or ``TEMP`` for that. If that fails, it will
look for shmfs mounted under ``/dev/shm``. If everything else fails use
``/tmp/`` regardless of the filesystem type used for it::
mount -t tmpfs -ompol=bind:X none /mnt/tmpfs-nodeX
TEMP=/mnt/tmpfs-nodeX taskset -cX linux options options options..
*******************************************
Contributing to UML and Developing with UML
*******************************************
UML is an excellent platform to develop new Linux kernel concepts -
filesystems, devices, virtualization, etc. It provides unrivalled
opportunities to create and test them without being constrained to
emulating specific hardware.
Example - want to try how Linux will work with 4096 "proper" network
devices?
Not an issue with UML. At the same time, this is something which
is difficult with other virtualization packages - they are
constrained by the number of devices allowed on the hardware bus
they are trying to emulate (for example 16 on a PCI bus in qemu).
If you have something to contribute such as a patch, a bugfix, a
new feature, please send it to ``linux-um@lists.infradead.org``.
Please follow all standard Linux patch guidelines such as cc-ing
relevant maintainers and run ``./scripts/checkpatch.pl`` on your patch.
For more details see ``Documentation/process/submitting-patches.rst``
Note - the list does not accept HTML or attachments, all emails must
be formatted as plain text.
Developing always goes hand in hand with debugging. First of all,
you can always run UML under gdb and there will be a whole section
later on on how to do that. That, however, is not the only way to
debug a Linux kernel. Quite often adding tracing statements and/or
using UML specific approaches such as ptracing the UML kernel process
are significantly more informative.
Tracing UML
=============
When running, UML consists of a main kernel thread and a number of
helper threads. The ones of interest for tracing are NOT the ones
that are already ptraced by UML as a part of its MMU emulation.
These are usually the first three threads visible in a ps display.
The one with the lowest PID number and using most CPU is usually the
kernel thread. The other threads are the disk
(ubd) device helper thread and the SIGIO helper thread.
Running ptrace on this thread usually results in the following picture::
host$ strace -p 16566
--- SIGIO {si_signo=SIGIO, si_code=POLL_IN, si_band=65} ---
epoll_wait(4, [{EPOLLIN, {u32=3721159424, u64=3721159424}}], 64, 0) = 1
epoll_wait(4, [], 64, 0) = 0
rt_sigreturn({mask=[PIPE]}) = 16967
ptrace(PTRACE_GETREGS, 16967, NULL, 0xd5f34f38) = 0
ptrace(PTRACE_GETREGSET, 16967, NT_X86_XSTATE, [{iov_base=0xd5f35010, iov_len=832}]) = 0
ptrace(PTRACE_GETSIGINFO, 16967, NULL, {si_signo=SIGTRAP, si_code=0x85, si_pid=16967, si_uid=0}) = 0
ptrace(PTRACE_SETREGS, 16967, NULL, 0xd5f34f38) = 0
ptrace(PTRACE_SETREGSET, 16967, NT_X86_XSTATE, [{iov_base=0xd5f35010, iov_len=2696}]) = 0
ptrace(PTRACE_SYSEMU, 16967, NULL, 0) = 0
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_TRAPPED, si_pid=16967, si_uid=0, si_status=SIGTRAP, si_utime=65, si_stime=89} ---
wait4(16967, [{WIFSTOPPED(s) && WSTOPSIG(s) == SIGTRAP | 0x80}], WSTOPPED|__WALL, NULL) = 16967
ptrace(PTRACE_GETREGS, 16967, NULL, 0xd5f34f38) = 0
ptrace(PTRACE_GETREGSET, 16967, NT_X86_XSTATE, [{iov_base=0xd5f35010, iov_len=832}]) = 0
ptrace(PTRACE_GETSIGINFO, 16967, NULL, {si_signo=SIGTRAP, si_code=0x85, si_pid=16967, si_uid=0}) = 0
timer_settime(0, 0, {it_interval={tv_sec=0, tv_nsec=0}, it_value={tv_sec=0, tv_nsec=2830912}}, NULL) = 0
getpid() = 16566
clock_nanosleep(CLOCK_MONOTONIC, 0, {tv_sec=1, tv_nsec=0}, NULL) = ? ERESTART_RESTARTBLOCK (Interrupted by signal)
--- SIGALRM {si_signo=SIGALRM, si_code=SI_TIMER, si_timerid=0, si_overrun=0, si_value={int=1631716592, ptr=0x614204f0}} ---
rt_sigreturn({mask=[PIPE]}) = -1 EINTR (Interrupted system call)
This is a typical picture from a mostly idle UML instance.
* UML interrupt controller uses epoll - this is UML waiting for IO
interrupts:
epoll_wait(4, [{EPOLLIN, {u32=3721159424, u64=3721159424}}], 64, 0) = 1
* The sequence of ptrace calls is part of MMU emulation and running the
UML userspace.
* ``timer_settime`` is part of the UML high res timer subsystem mapping
timer requests from inside UML onto the host high resolution timers.
* ``clock_nanosleep`` is UML going into idle (similar to the way a PC
will execute an ACPI idle).
As you can see UML will generate quite a bit of output even in idle. The output
can be very informative when observing IO. It shows the actual IO calls, their
arguments and returns values.
Kernel debugging
================
You can run UML under gdb now, though it will not necessarily agree to
be started under it. If you are trying to track a runtime bug, it is
much better to attach gdb to a running UML instance and let UML run.
Assuming the same PID number as in the previous example, this would be::
# gdb -p 16566
This will STOP the UML instance, so you must enter `cont` at the GDB
command line to request it to continue. It may be a good idea to make
this into a gdb script and pass it to gdb as an argument.
Developing Device Drivers
=========================
Nearly all UML drivers are monolithic. While it is possible to build a
UML driver as a kernel module, that limits the possible functionality
to in-kernel only and non-UML specific. The reason for this is that
in order to really leverage UML, one needs to write a piece of
userspace code which maps driver concepts onto actual userspace host
calls.
This forms the so-called "user" portion of the driver. While it can
reuse a lot of kernel concepts, it is generally just another piece of
userspace code. This portion needs some matching "kernel" code which
resides inside the UML image and which implements the Linux kernel part.
*Note: There are very few limitations in the way "kernel" and "user" interact*.
UML does not have a strictly defined kernel-to-host API. It does not
try to emulate a specific architecture or bus. UML's "kernel" and
"user" can share memory, code and interact as needed to implement
whatever design the software developer has in mind. The only
limitations are purely technical. Due to a lot of functions and
variables having the same names, the developer should be careful
which includes and libraries they are trying to refer to.
As a result a lot of userspace code consists of simple wrappers.
E.g. ``os_close_file()`` is just a wrapper around ``close()``
which ensures that the userspace function close does not clash
with similarly named function(s) in the kernel part.
Using UML as a Test Platform
============================
UML is an excellent test platform for device driver development. As
with most things UML, "some user assembly may be required". It is
up to the user to build their emulation environment. UML at present
provides only the kernel infrastructure.
Part of this infrastructure is the ability to load and parse fdt
device tree blobs as used in Arm or Open Firmware platforms. These
are supplied as an optional extra argument to the kernel command
line::
dtb=filename
The device tree is loaded and parsed at boottime and is accessible by
drivers which query it. At this moment in time this facility is
intended solely for development purposes. UML's own devices do not
query the device tree.
Security Considerations
-----------------------
Drivers or any new functionality should default to not
accepting arbitrary filename, bpf code or other parameters
which can affect the host from inside the UML instance.
For example, specifying the socket used for IPC communication
between a driver and the host at the UML command line is OK
security-wise. Allowing it as a loadable module parameter
isn't.
If such functionality is desirable for a particular application
(e.g. loading BPF "firmware" for raw socket network transports),
it should be off by default and should be explicitly turned on
as a command line parameter at startup.
Even with this in mind, the level of isolation between UML
and the host is relatively weak. If the UML userspace is
allowed to load arbitrary kernel drivers, an attacker can
use this to break out of UML. Thus, if UML is used in
a production application, it is recommended that all modules
are loaded at boot and kernel module loading is disabled
afterwards.
3. 한국어 전문 번역
영어 원문의 문단 순서와 의미를 유지한 전체 번역입니다. 코드, 함수명, symbol과 URL은 원문 표기를 유지합니다.
UML HowTo 소개
1-17이 문서는 User Mode Linux(UML)의 사용법을 설명하는 HowTo입니다. UML은 최초 공개가 1991년인 첫 번째 Open Source virtualization platform이며, x86 PC용으로 등장한 두 번째 virtualization platform이라고 소개됩니다.
이후 절에서는 UML이 일반적인 hardware emulation 기반 VM과 어떻게 다른지, UML instance와 filesystem image를 어떻게 만들고 networking을 구성하는지 단계별로 설명합니다.
첫 문단에서 밝히는 UML의 정체성과 범위입니다.
.. SPDX-License-Identifier: GPL-2.0
#########
UML HowTo
#########
.. contents:: :local:
************
Introduction
************
Welcome to User Mode Linux
User Mode Linux is the first Open Source virtualization platform (first
release date 1991) and second virtualization platform for an x86 PC.
일반 가상화 패키지와 UML의 차이
18-48가상화가 반드시 어느 정도의 hardware emulation을 뜻하는 것은 아닙니다. 가상화 패키지가 운영체제가 인식할 수 있고 driver를 가진 device를 제공한다면, 그 device가 실제 hardware를 모사할 필요는 없습니다.
오늘날 대부분의 운영체제에는 가상화 환경에서만 쓰는 여러 'fake' device 지원이 내장돼 있습니다. UML은 이 개념을 극단까지 밀어붙여 실제 device를 하나도 노출하지 않고, 올바른 용어로는 100% paravirtual인 환경을 구성합니다.
모든 UML device는 host가 제공하는 file, socket, pipe 같은 자원에 대응하는 추상 개념입니다. 따라서 guest가 보는 device contract와 host에서 실제로 이용하는 객체를 분리합니다.
또 하나의 큰 차이는 UML kernel과 UML program의 실행 방식이 명확히 다르다는 점입니다. UML kernel은 다른 program과 마찬가지로 Linux host에서 실행되는 하나의 process이며, unprivileged user로 실행할 수 있고 특별한 CPU feature도 요구하지 않습니다.
반면 UML userspace가 수행하려는 모든 동작은 host Linux kernel의 도움으로 가로채지고 UML kernel이 그 요청을 처리합니다. Guest kernel과 guest program을 같은 방식으로 취급하는 QEMU 같은 다른 가상화 패키지와 달라, 뒤에서 설명할 UML 고유의 장점과 단점이 생깁니다.
실제 hardware emulation 여부와 kernel/userspace 처리 차이를 비교합니다.
Guest program의 요청이 UML kernel로 전달되는 경로입니다.
How is UML Different from a VM using Virtualization package X?
==============================================================
We have come to assume that virtualization also means some level of
hardware emulation. In fact, it does not. As long as a virtualization
package provides the OS with devices which the OS can recognize and
has a driver for, the devices do not need to emulate real hardware.
Most OSes today have built-in support for a number of "fake"
devices used only under virtualization.
User Mode Linux takes this concept to the ultimate extreme - there
is not a single real device in sight. It is 100% artificial or if
we use the correct term 100% paravirtual. All UML devices are abstract
concepts which map onto something provided by the host - files, sockets,
pipes, etc.
The other major difference between UML and various virtualization
packages is that there is a distinct difference between the way the UML
kernel and the UML programs operate.
The UML kernel is just a process running on Linux - same as any other
program. It can be run by an unprivileged user and it does not require
anything in terms of special CPU features.
The UML userspace, however, is a bit different. The Linux kernel on the
host machine assists UML in intercepting everything the program running
on a UML instance is trying to do and making the UML kernel handle all
of its requests.
This is different from other virtualization packages which do not make any
difference between the guest kernel and guest programs. This difference
results in a number of advantages and disadvantages of UML over let's say
QEMU which we will cover later in this document.
User Mode Linux를 선택할 이유
49-75UML kernel이 crash해도 host kernel은 안전합니다. UML은 `vhost`, `kvm` 같은 방식으로 가속되지 않고 device에 직접 접근하지 않으며, 실제로 다른 application과 같은 하나의 process이기 때문입니다.
일부 device에 적절한 permission을 마련해야 할 수 있지만, usermode kernel 자체는 non-root user로 실행할 수 있습니다.
특정 작업만 수행하는 footprint가 매우 작은 VM을 만들 수 있습니다. 원문은 32M 이하의 memory로도 실행할 수 있는 예를 듭니다.
Forwarding이나 firewalling처럼 kernel specific task는 host kernel과 격리된 상태에서도 매우 높은 성능을 낼 수 있습니다.
Host를 망가뜨리지 않고 kernel concept를 실험할 수 있습니다. 실제 hardware emulation에 묶이지 않으므로 time travel이나 UML 동작에 따라 system clock이 달라지는 구성처럼, 실제 hardware를 흉내 내는 VM에서는 구현하기 어려운 아이디어도 시험할 수 있습니다.
UML 동작에 종속된 clock은 재현 가능한 test 같은 용도에 특히 유용합니다.
마지막 장점으로 원문은 간결하게 '재미있다'고 덧붙입니다.
안전성·권한·규모·성능·실험성 관점으로 정리했습니다.
원문이 제시하는 선택 기준을 작업 유형으로 묶었습니다.
Why Would I Want User Mode Linux?
=================================
* If User Mode Linux kernel crashes, your host kernel is still fine. It
is not accelerated in any way (vhost, kvm, etc) and it is not trying to
access any devices directly. It is, in fact, a process like any other.
* You can run a usermode kernel as a non-root user (you may need to
arrange appropriate permissions for some devices).
* You can run a very small VM with a minimal footprint for a specific
task (for example 32M or less).
* You can get extremely high performance for anything which is a "kernel
specific task" such as forwarding, firewalling, etc while still being
isolated from the host kernel.
* You can play with kernel concepts without breaking things.
* You are not bound by "emulating" hardware, so you can try weird and
wonderful concepts which are very difficult to support when emulating
real hardware such as time travel and making your system clock
dependent on what UML does (very useful for things like tests).
* It's fun.
UML을 선택하지 말아야 할 경우
76-88UML의 syscall interception 기법은 userspace application을 본질적으로 느리게 만듭니다. Kernel task 성능은 다른 가상화 패키지와 비슷할 수 있지만, 새 process와 thread를 만드는 비용이 매우 높아 일반 Unix/Linux application이 당연하게 사용하는 동작에서 userspace가 느립니다.
현재 UML은 엄격한 uniprocessor 환경입니다. 정상 동작에 여러 CPU가 필요한 application이라면 UML은 분명히 적합하지 않습니다.
작업 부하 특성에 따라 UML 사용을 피해야 하는 이유입니다.
Why not to run UML
==================
* The syscall interception technique used by UML makes it inherently
slower for any userspace applications. While it can do kernel tasks
on par with most other virtualization packages, its userspace is
**slow**. The root cause is that UML has a very high cost of creating
new processes and threads (something most Unix/Linux applications
take for granted).
* UML is strictly uniprocessor at present. If you want to run an
application which needs many CPUs to function, it is clearly the
wrong choice.
UML instance 구축 준비
89-102어떤 distribution에도 UML installer는 없습니다. 일반 가상화 패키지처럼 기성 installation media로 빈 VM에 OS를 설치하는 동등한 절차가 없으므로, host의 적절한 도구를 사용해 실행 가능한 filesystem image를 직접 만들어야 합니다.
Debian에서는 `debootstrap`으로 매우 쉽게 만들 수 있고, OpenWRT는 build process가 UML image를 생성할 수 있습니다. 다른 distribution의 난이도는 환경마다 다를 수 있다는 의미로 원문은 YMMV라고 표현합니다.
문서가 언급한 구축 경로입니다.
***********************
Building a UML instance
***********************
There is no UML installer in any distribution. While you can use off
the shelf install media to install into a blank VM using a virtualization
package, there is no UML equivalent. You have to use appropriate tools on
your host to build a viable filesystem image.
This is extremely easy on Debian - you can do it using debootstrap. It is
also easy on OpenWRT - the build process can build UML images. All other
distros - YMMV.
Sparse disk image와 최소 OS 설치
103-137먼저 `dd if=/dev/zero of=disk_image_name bs=1 count=1 seek=16G`로 sparse raw disk image를 만듭니다. 논리 크기는 16G지만 OS는 처음에 block 하나만 할당하고, UML이 실제로 기록할 때 추가 block을 할당합니다.
Kernel 4.19부터 UML은 flash drive에서 흔히 쓰는 TRIM을 완전히 지원합니다. UML image 안에서 mount option에 `discard`를 지정하거나 `tune2fs -o discard /dev/ubdXX`를 실행하면, 사용하지 않는 block을 host OS에 돌려주도록 UML에 요청합니다.
`mkfs.ext4 ./disk_image_name && mount ./disk_image_name /mnt`로 image에 ext4 filesystem을 만들고 `/mnt`에 mount합니다. 예시는 ext4지만 ext3, btrfs, xfs, jfs 같은 다른 filesystem도 사용할 수 있습니다.
Mount한 filesystem에 `debootstrap buster /mnt http://deb.debian.org/debian`을 실행해 최소 Debian OS를 설치합니다.
`debootstrap`은 root password, `fstab`, hostname, network 설정을 만들지 않으므로 사용자가 직접 구성해야 합니다.
Root password는 mount한 image에 `chroot /mnt`로 들어가 `passwd`를 실행한 뒤 `exit`하는 방법이 가장 간단합니다.
명령과 생성되는 상태를 순서대로 정리합니다.
빈 image에서 로그인 가능한 최소 OS까지의 흐름입니다.
Creating an image
=================
Create a sparse raw disk image::
# dd if=/dev/zero of=disk_image_name bs=1 count=1 seek=16G
This will create a 16G disk image. The OS will initially allocate only one
block and will allocate more as they are written by UML. As of kernel
version 4.19 UML fully supports TRIM (as usually used by flash drives).
Using TRIM inside the UML image by specifying discard as a mount option
or by running ``tune2fs -o discard /dev/ubdXX`` will request UML to
return any unused blocks to the OS.
Create a filesystem on the disk image and mount it::
# mkfs.ext4 ./disk_image_name && mount ./disk_image_name /mnt
This example uses ext4, any other filesystem such as ext3, btrfs, xfs,
jfs, etc will work too.
Create a minimal OS installation on the mounted filesystem::
# debootstrap buster /mnt http://deb.debian.org/debian
debootstrap does not set up the root password, fstab, hostname or
anything related to networking. It is up to the user to do that.
Set the root password - the easiest way to do that is to chroot into the
mounted image::
# chroot /mnt
# passwd
# exit
핵심 system file과 kernel module 구성
138-184UML block device 이름은 `ubd` 계열입니다. `debootstrap`이 만든 빈 `fstab`에는 root filesystem 항목으로 `/dev/ubd0 ext4 discard,errors=remount-ro 0 1`을 추가합니다.
Image hostname은 기본적으로 image를 만든 host와 같으므로, 잘못된 machine을 reboot하는 사고를 피하려면 다른 이름으로 바꾸는 것이 좋습니다.
UML은 Ethernet over GRE와 Ethernet over L2TPv3 같은 표준 virtual network encapsulation을 지원하는 고성능 vector I/O network device를 제공합니다. Interface 이름은 `vecX` 형식입니다.
Vector network device를 사용한다면 `/etc/network/interfaces`에 `auto vec0`과 `iface vec0 inet dhcp` 같은 항목을 추가합니다.
이 단계가 끝나면 image를 실행하기 위해 UML kernel과 module만 더 필요합니다. 대부분의 distribution은 UML package를 제공하며, 자체 kernel을 쓸 예정이어도 먼저 stock kernel로 image를 시험하는 것이 좋습니다.
Distribution package의 module은 target filesystem으로 복사해야 합니다. Debian에서는 `/usr/lib/uml/modules` 아래 내용을 `cp -rax /usr/lib/uml/modules /mnt/lib/modules`로 재귀 복사합니다.
직접 compile한 kernel은 `make INSTALL_MOD_PATH=/mnt/lib/modules modules_install`로 설치하며 결과는 `/mnt/lib/modules/$(KERNELRELEASE)`에 놓입니다. 전체 module 설치 경로를 직접 지정하려면 `make MODLIB=/mnt/lib/modules modules_install`을 사용합니다. 이제 image를 기동할 준비가 끝났습니다.
Boot와 network, module에 필요한 file과 명령입니다.
Filesystem 편집 이후 kernel과 module을 갖추는 순서입니다.
Edit key system files
=====================
UML block devices are called ubds. The fstab created by debootstrap
will be empty and it needs an entry for the root file system::
/dev/ubd0 ext4 discard,errors=remount-ro 0 1
The image hostname will be set to the same as the host on which you
are creating its image. It is a good idea to change that to avoid
"Oh, bummer, I rebooted the wrong machine".
UML supports vector I/O high performance network devices which have
support for some standard virtual network encapsulations like
Ethernet over GRE and Ethernet over L2TPv3. These are called vecX.
When vector network devices are in use, ``/etc/network/interfaces``
will need entries like::
# vector UML network devices
auto vec0
iface vec0 inet dhcp
We now have a UML image which is nearly ready to run, all we need is a
UML kernel and modules for it.
Most distributions have a UML package. Even if you intend to use your own
kernel, testing the image with a stock one is always a good start. These
packages come with a set of modules which should be copied to the target
filesystem. The location is distribution dependent. For Debian these
reside under /usr/lib/uml/modules. Copy recursively the content of this
directory to the mounted UML filesystem::
# cp -rax /usr/lib/uml/modules /mnt/lib/modules
If you have compiled your own kernel, you need to use the usual "install
modules to a location" procedure by running::
# make INSTALL_MOD_PATH=/mnt/lib/modules modules_install
This will install modules into /mnt/lib/modules/$(KERNELRELEASE).
To specify the full module installation path, use::
# make MODLIB=/mnt/lib/modules modules_install
At this point the image is ready to be brought up.
UML networking 모델과 transport
185-226UML networking은 Ethernet connection을 모사하도록 설계되었습니다. Back-to-back cable로 두 machine을 연결한 것과 비슷한 point-to-point 방식 또는 switch 연결 방식을 사용할 수 있습니다.
연결 대상은 local machine, remote machine, local·remote UML instance, 다른 VM instance까지 폭넓게 지원합니다.
`tap` vector transport는 checksum과 TSO를 지원하며 8Gbit를 넘는 throughput을 제시합니다. `hybrid`는 checksum, TSO, multipacket RX로 6Gbit를 넘고, `raw`는 checksum, TSO, multipacket RX/TX로 6Gbit를 넘습니다. 원문 표의 `raw` capability 끝 따옴표는 그대로 보존합니다.
EoGRE, Eol2tpv3, `bess`는 multipacket RX/TX를 지원하며 각각 3Gbit를 넘는 throughput을 제시합니다. `fd` 성능은 file descriptor 종류에 따라, `vde` 성능은 VDE VPN 또는 Virtual Network Locator에 따라 달라집니다.
TSO와 checksum offload를 모두 갖춘 transport는 TCP stream에서 10G에 가까운 속도를 낼 수 있습니다. Multipacket RX 또는 TX를 지원하면 초당 1M packet 이상 처리할 수 있습니다. GRE와 L2TPv3는 local·remote machine, remote network device, remote UML instance를 모두 연결할 수 있습니다.
원문 ASCII 표를 동일한 항목의 구조화 표로 다시 그렸습니다.
Transport가 연결할 수 있는 endpoint를 구조화했습니다.
*************************
Setting Up UML Networking
*************************
UML networking is designed to emulate an Ethernet connection. This
connection may be either point-to-point (similar to a connection
between machines using a back-to-back cable) or a connection to a
switch. UML supports a wide variety of means to build these
connections to all of: local machine, remote machine(s), local and
remote UML and other VM instances.
+-----------+--------+------------------------------------+------------+
| Transport | Type | Capabilities | Throughput |
+===========+========+====================================+============+
| tap | vector | checksum, tso | > 8Gbit |
+-----------+--------+------------------------------------+------------+
| hybrid | vector | checksum, tso, multipacket rx | > 6GBit |
+-----------+--------+------------------------------------+------------+
| raw | vector | checksum, tso, multipacket rx, tx" | > 6GBit |
+-----------+--------+------------------------------------+------------+
| EoGRE | vector | multipacket rx, tx | > 3Gbit |
+-----------+--------+------------------------------------+------------+
| Eol2tpv3 | vector | multipacket rx, tx | > 3Gbit |
+-----------+--------+------------------------------------+------------+
| bess | vector | multipacket rx, tx | > 3Gbit |
+-----------+--------+------------------------------------+------------+
| fd | vector | dependent on fd type | varies |
+-----------+--------+------------------------------------+------------+
| vde | vector | dep. on VDE VPN: Virt.Net Locator | varies |
+-----------+--------+------------------------------------+------------+
* All transports which have tso and checksum offloads can deliver speeds
approaching 10G on TCP streams.
* All transports which have multi-packet rx and/or tx can deliver pps
rates of up to 1Mps or more.
* GRE and L2TPv3 allow connections to all of: local machine, remote
machines, remote network devices and remote UML instances.
Network 구성 권한
227-243지원되는 networking mode 대부분은 `root` 권한이 필요합니다. 예를 들어 vector transport는 TUN interface를 설정하는 ioctl을 실행하거나 필요한 raw socket을 사용하기 위해 높은 권한이 필요합니다.
UML 전체를 root로 실행하는 대신 특정 capability만 UML binary에 부여할 수 있습니다. Vector transport에서는 `CAP_NET_ADMIN` 또는 `CAP_NET_RAW`를 추가하면 일반 user privilege로 UML을 실행하면서 전체 networking 기능을 사용할 수 있습니다.
예제 명령은 `sudo setcap cap_net_raw,cap_net_admin+ep linux`이며, `linux` UML binary에 두 capability의 effective·permitted bit를 설정합니다.
필요 작업과 최소 capability를 구분합니다.
권한 범위를 networking에 필요한 기능으로 제한합니다.
Network configuration privileges
================================
The majority of the supported networking modes need ``root`` privileges.
For example, for vector transports, ``root`` privilege is required to fire
an ioctl to setup the tun interface and/or use raw sockets where needed.
This can be achieved by granting the user a particular capability instead
of running UML as root. In case of vector transport, a user can add the
capability ``CAP_NET_ADMIN`` or ``CAP_NET_RAW`` to the uml binary.
Thenceforth, UML can be run with normal user privilges, along with
full networking.
For example::
# sudo setcap cap_net_raw,cap_net_admin+ep linux
Vector transport 설정 문법
244-253모든 vector transport는 비슷한 option 문법을 사용합니다. Interface 번호 X는 `vec0`, `vec1`, `vec2`처럼 `vecX` 이름에 들어갑니다.
일반 형식은 `vecX:transport="Transport Name",option=value,...`입니다. 먼저 transport 이름을 지정하고 쉼표로 `option=value` 쌍을 이어 붙입니다.
Interface, transport, option의 위치를 설명합니다.
Configuring vector transports
===============================
All vector transports support a similar syntax:
If X is the interface number as in vec0, vec1, vec2, etc, the general
syntax for options is::
vecX:transport="Transport Name",option=value,option=value,...,option=value
Vector transport 공통 option
254-284`depth=int`는 vector I/O queue depth, 즉 UML이 한 system call에서 읽거나 쓰려는 packet 수를 정합니다. 기본값 64는 대체로 2–4Gbit throughput이 필요한 application에 충분하며, 더 높은 속도에는 더 큰 값이 필요할 수 있습니다.
`mac=XX:XX:XX:XX:XX`는 interface MAC address를 설정합니다.
`gro=[0,1]`은 GRO를 끄거나 켜 receive/transmit offload를 제어합니다. 실제 효과는 transport의 host-side 지원에 따라 달라지지만, 대체로 TCP segmentation과 RX/TX checksum offload를 활성화합니다.
GRO 설정은 host와 UML 쪽이 같아야 하며 다르면 UML kernel이 warning을 출력합니다. Local interface, 예를 들어 veth pair나 bridge에서는 GRO가 기본 활성화되므로 대응하는 UML `raw`, `tap`, `hybrid` transport에서도 올바른 networking을 위해 GRO를 켜야 합니다.
`mtu=int`는 interface MTU를 설정합니다. `headroom=int`는 packet을 VXLAN 같은 형식으로 다시 encapsulate할 때 쓸 공간으로 예약되는 기본 32 bytes headroom을 조정합니다.
`vec=0`은 multipacket I/O를 비활성화하고 한 번에 packet 하나를 처리하는 mode로 되돌립니다.
줄 254–284의 option 이름, 기본값, 제약을 보존했습니다.
성능과 host 호환성을 함께 맞추는 순서입니다.
Common options
--------------
These options are common for all transports:
* ``depth=int`` - sets the queue depth for vector IO. This is the
amount of packets UML will attempt to read or write in a single
system call. The default number is 64 and is generally sufficient
for most applications that need throughput in the 2-4 Gbit range.
Higher speeds may require larger values.
* ``mac=XX:XX:XX:XX:XX`` - sets the interface MAC address value.
* ``gro=[0,1]`` - sets GRO off or on. Enables receive/transmit offloads.
The effect of this option depends on the host side support in the transport
which is being configured. In most cases it will enable TCP segmentation and
RX/TX checksumming offloads. The setting must be identical on the host side
and the UML side. The UML kernel will produce warnings if it is not.
For example, GRO is enabled by default on local machine interfaces
(e.g. veth pairs, bridge, etc), so it should be enabled in UML in the
corresponding UML transports (raw, tap, hybrid) in order for networking to
operate correctly.
* ``mtu=int`` - sets the interface MTU
* ``headroom=int`` - adjusts the default headroom (32 bytes) reserved
if a packet will need to be re-encapsulated into for instance VXLAN.
* ``vec=0`` - disable multipacket IO and fall back to packet at a
time mode
tap transport
302-329예제 `vecX:transport=tap,ifname=tap0,depth=128,gro=1`은 UML의 vector interface를 host `tap0`에 연결합니다. 원문의 설명은 `vec0` 연결이라고 표현하며, `tap0`은 `tunctl` 등으로 미리 만들어져 있고 UP 상태여야 합니다.
`tap0`에 IP address를 부여해 point-to-point interface로 구성하면 UML과 host가 직접 통신할 수 있습니다. 또는 bridge에 연결된 tap interface에 UML을 붙여 switch 형태의 network를 만들 수 있습니다.
Tap은 vector infrastructure를 사용하지만 현재 시점에는 진정한 vector transport가 아닙니다. Linux가 UML 같은 일반 userspace application에 tap file descriptor의 multipacket I/O를 허용하지 않기 때문입니다.
이 권한은 `vhost-net`처럼 kernel-level specialized interface에 연결할 수 있는 구성에만 제공됩니다. 문서는 향후 UML용 vhost-net 유사 helper를 계획하고 있다고 밝힙니다.
권한은 두 방법 중 하나로 충족합니다. `tunctl -u uml-user -t tap0`처럼 UML user가 소유한 persistent tap interface를 미리 만들거나, UML binary에 `CAP_NET_ADMIN`을 부여합니다.
Interface 준비, 연결 방식, 권한 요구를 분리했습니다.
Host tap 준비부터 UML 연결까지의 순서입니다.
tap transport
-------------
Example::
vecX:transport=tap,ifname=tap0,depth=128,gro=1
This will connect vec0 to tap0 on the host. Tap0 must already exist (for example
created using tunctl) and UP.
tap0 can be configured as a point-to-point interface and given an IP
address so that UML can talk to the host. Alternatively, it is possible
to connect UML to a tap interface which is connected to a bridge.
While tap relies on the vector infrastructure, it is not a true vector
transport at this point, because Linux does not support multi-packet
IO on tap file descriptors for normal userspace apps like UML. This
is a privilege which is offered only to something which can hook up
to it at kernel level via specialized interfaces like vhost-net. A
vhost-net like helper for UML is planned at some point in the future.
Privileges required: tap transport requires either:
* tap interface to exist and be created persistent and owned by the
UML user using tunctl. Example ``tunctl -u uml-user -t tap0``
* binary to have ``CAP_NET_ADMIN`` privilege
hybrid transport
330-343예제 `vecX:transport=hybrid,ifname=tap0,depth=128,gro=1`은 hybrid transport를 `tap0`에 연결합니다.
Hybrid는 송신에 tap을, 수신에 raw socket을 결합한 experimental/demo transport입니다. Raw socket이 multipacket receive를 허용하므로 일반 tap보다 packet rate가 크게 높아집니다.
Hybrid를 쓰려면 UML user가 `CAP_NET_RAW` capability를 가져야 하며, 여기에 persistent tap 또는 `CAP_NET_ADMIN` 같은 tap transport의 요구사항도 모두 충족해야 합니다.
송신과 수신이 서로 다른 backend를 사용합니다.
방향별 backend 선택을 나타냅니다.
hybrid transport
----------------
Example::
vecX:transport=hybrid,ifname=tap0,depth=128,gro=1
This is an experimental/demo transport which couples tap for transmit
and a raw socket for receive. The raw socket allows multi-packet
receive resulting in significantly higher packet rates than normal tap.
Privileges required: hybrid requires ``CAP_NET_RAW`` capability by
the UML user as well as the requirements for the tap transport.
raw socket transport
344-393예제 `vecX:transport=raw,ifname=p-veth0,depth=128,gro=1`은 raw socket에서 vector I/O를 사용하고 `p-veth0`에 bind합니다. Physical interface에도 bind할 수 있지만, 일반적으로 veth pair의 peer 쪽에 bind하고 다른 쪽을 host에서 설정합니다.
Debian 예제의 `/etc/network/interfaces`는 `veth0`을 static으로 올려 `192.168.4.1`, netmask `255.255.255.252`, broadcast `192.168.4.3`을 설정합니다. `pre-up`에서는 `ip link add veth0 type veth peer name p-veth0`로 pair를 만들고 `p-veth0`을 UP 상태로 전환합니다.
UML은 `vec0:transport=raw,ifname=p-veth0,depth=128,gro=1`로 peer interface에 bind합니다. Guest 주소를 `192.168.4.2`, netmask를 원문 그대로 `255.255.255.0`으로 구성하면 host `192.168.4.1`과 통신할 수 있습니다.
Raw transport는 filtering 일부를 host로 offload할 수 있습니다. `bpffile=str`은 socket filter로 load할 raw BPF code file을 지정합니다.
`bpfflash=int`의 0/1 값은 User Mode Linux 안에서 BPF를 load할 수 있는지 정합니다. 활성화하면 `ethtool load firmware` command로 BPF code를 load할 수 있습니다.
두 방식 모두 BPF code가 host kernel에 load됩니다. 현재 legacy BPF syntax만 지원하고 eBPF는 지원하지 않지만 여전히 security risk가 있으므로, UML instance를 trusted로 간주할 수 있을 때만 허용해야 합니다.
Raw socket transport에는 `CAP_NET_RAW` capability가 필요합니다.
Veth 연결과 BPF option, 권한을 정리합니다.
Host veth pair와 UML endpoint를 연결합니다.
raw socket transport
--------------------
Example::
vecX:transport=raw,ifname=p-veth0,depth=128,gro=1
This transport uses vector IO on raw sockets. While you can bind to any
interface including a physical one, the most common use it to bind to
the "peer" side of a veth pair with the other side configured on the
host.
Example host configuration for Debian:
**/etc/network/interfaces**::
auto veth0
iface veth0 inet static
address 192.168.4.1
netmask 255.255.255.252
broadcast 192.168.4.3
pre-up ip link add veth0 type veth peer name p-veth0 && \
ifconfig p-veth0 up
UML can now bind to p-veth0 like this::
vec0:transport=raw,ifname=p-veth0,depth=128,gro=1
If the UML guest is configured with 192.168.4.2 and netmask 255.255.255.0
it can talk to the host on 192.168.4.1
The raw transport also provides some support for offloading some of the
filtering to the host. The two options to control it are:
* ``bpffile=str`` filename of raw bpf code to be loaded as a socket filter
* ``bpfflash=int`` 0/1 allow loading of bpf from inside User Mode Linux.
This option allows the use of the ethtool load firmware command to
load bpf code.
In either case the bpf code is loaded into the host kernel. While this is
presently limited to legacy bpf syntax (not ebpf), it is still a security
risk. It is not recommended to allow this unless the User Mode Linux
instance is considered trusted.
Privileges required: raw socket transport requires `CAP_NET_RAW`
capability.
GRE socket transport
394-451예제 `vecX:transport=gre,src=$src_host,dst=$dst_host`는 Ethernet over GRE tunnel을 구성해 UML instance를 `dst_host`의 GRE endpoint에 연결합니다. 이 방식은 `GRETAP` 또는 `GREIRB`라고도 합니다.
`rx_key=int`와 `tx_key=int`는 수신·송신 packet용 32-bit GRE key입니다. 한쪽 key를 설정하면 반대쪽 key도 반드시 설정해야 합니다.
`sequence=[0,1]`은 GRE sequence를 활성화합니다. `pin_sequence=[0,1]`은 매 packet마다 sequence가 reset된 것처럼 처리해, 동작이 잘못된 일부 구현과 상호 운용할 때 사용합니다.
`v6=[0,1]`은 각각 IPv4 또는 IPv6 socket을 강제합니다. 현재 GRE checksum은 지원하지 않습니다.
GRE는 IP address 하나당 connection 하나만 사용할 수 있습니다. 각 tunnel이 UML instance에서 직접 끝나기 때문에 여러 connection을 multiplex할 방법이 없습니다.
GRE key는 실제 security 기능으로 신뢰할 수 없지만 tunnel misconfiguration을 발견하거나 방지하는 식별값으로는 유용합니다.
Host `192.168.128.1`과 UML `192.168.129.1`을 연결하는 예제는 `gt0`에 `10.0.0.1/24`, broadcast `10.0.0.255`, MTU 1500을 설정하고 `ip link add ... type gretap local ... remote ...`로 tunnel을 만듭니다. GRE는 여러 network equipment와의 상호 운용 시험을 거쳤으며, `CAP_NET_RAW`이 필요합니다.
Key, sequence, address family 지원을 보존했습니다.
연결 수, key 의미, 권한 제약입니다.
예제 주소로 tunnel을 구성하는 흐름입니다.
GRE socket transport
--------------------
Example::
vecX:transport=gre,src=$src_host,dst=$dst_host
This will configure an Ethernet over ``GRE`` (aka ``GRETAP`` or
``GREIRB``) tunnel which will connect the UML instance to a ``GRE``
endpoint at host dst_host. ``GRE`` supports the following additional
options:
* ``rx_key=int`` - GRE 32-bit integer key for rx packets, if set,
``txkey`` must be set too
* ``tx_key=int`` - GRE 32-bit integer key for tx packets, if set
``rx_key`` must be set too
* ``sequence=[0,1]`` - enable GRE sequence
* ``pin_sequence=[0,1]`` - pretend that the sequence is always reset
on each packet (needed to interoperate with some really broken
implementations)
* ``v6=[0,1]`` - force IPv4 or IPv6 sockets respectively
* GRE checksum is not presently supported
GRE has a number of caveats:
* You can use only one GRE connection per IP address. There is no way to
multiplex connections as each GRE tunnel is terminated directly on
the UML instance.
* The key is not really a security feature. While it was intended as such
its "security" is laughable. It is, however, a useful feature to
ensure that the tunnel is not misconfigured.
An example configuration for a Linux host with a local address of
192.168.128.1 to connect to a UML instance at 192.168.129.1
**/etc/network/interfaces**::
auto gt0
iface gt0 inet static
address 10.0.0.1
netmask 255.255.255.0
broadcast 10.0.0.255
mtu 1500
pre-up ip link add gt0 type gretap local 192.168.128.1 \
remote 192.168.129.1 || true
down ip link del gt0 || true
Additionally, GRE has been tested versus a variety of network equipment.
Privileges required: GRE requires ``CAP_NET_RAW``
L2TPv3 socket transport
452-526원문은 L2TPv3가 'GNU ls보다 option이 많다'는 농담으로 복잡성을 경고합니다. 장점은 있지만 UML instance를 연결하는 더 쉽고 간결한 방법이 흔하며, L2TPv3를 지원하는 device 대부분은 GRE도 지원합니다.
예제는 UDP mode, source·destination host와 port, depth 128, RX session `0xffffffff`, TX session `0xffff`를 지정합니다. 이렇게 하면 UML instance와 `$dst_host`의 L2TPv3 endpoint 사이에 Ethernet over L2TPv3 fixed tunnel을 만들고 UDP destination port `$dst_port`를 사용합니다.
L2TPv3는 항상 `rx_session=int`와 `tx_session=int` 32-bit session ID를 요구합니다. Fixed tunnel이므로 이 값들은 협상되지 않고 양 endpoint에 미리 같은 관계로 구성해야 합니다.
`rx_cookie=int`와 `tx_cookie=int`는 각 방향의 32-bit cookie입니다. GRE key와 마찬가지로 실제 보안보다는 misconfiguration 방지에 가깝습니다. `cookie64=[0,1]`을 켜면 64-bit cookie를 사용합니다.
`counter=[0,1]`은 L2TPv3 counter를 켭니다. `pin_counter=[0,1]`은 매 packet에서 counter가 reset된 것처럼 처리해 잘못된 일부 구현과 상호 운용할 때 씁니다.
`v6=[0,1]`은 IPv6 socket을 강제하고, `udp=[0,1]`은 raw socket mode 0과 UDP mode 1 사이를 선택합니다.
Raw mode에서는 IP address당 connection 하나만 사용할 수 있고 tunnel이 UML instance에서 직접 끝나므로 multiplex할 수 없습니다. UDP mode는 서로 다른 port를 사용해 여러 connection을 구분할 수 있습니다.
Linux host 예제는 loopback `127.0.0.1` 사이에 UDP tunnel ID 2를 만들고 source port 1706, destination port 1707을 사용합니다. 이어서 `l2tp1` session ID와 peer session ID를 모두 `0xffffffff`로 만들고, interface에는 `192.168.126.1/24`, broadcast `192.168.126.255`, MTU 1500을 설정합니다.
Raw IP mode에는 `CAP_NET_RAW`이 필요하지만 UDP mode에는 특별한 privilege가 필요하지 않습니다.
필수 session과 선택 cookie·counter·transport mode입니다.
Raw와 UDP mode의 multiplex·권한 차이입니다.
원문의 Linux host 예제를 단계로 정리했습니다.
l2tpv3 socket transport
-----------------------
_Warning_. L2TPv3 has a "bug". It is the "bug" known as "has more
options than GNU ls". While it has some advantages, there are usually
easier (and less verbose) ways to connect a UML instance to something.
For example, most devices which support L2TPv3 also support GRE.
Example::
vec0:transport=l2tpv3,udp=1,src=$src_host,dst=$dst_host,srcport=$src_port,dstport=$dst_port,depth=128,rx_session=0xffffffff,tx_session=0xffff
This will configure an Ethernet over L2TPv3 fixed tunnel which will
connect the UML instance to a L2TPv3 endpoint at host $dst_host using
the L2TPv3 UDP flavour and UDP destination port $dst_port.
L2TPv3 always requires the following additional options:
* ``rx_session=int`` - l2tpv3 32-bit integer session for rx packets
* ``tx_session=int`` - l2tpv3 32-bit integer session for tx packets
As the tunnel is fixed these are not negotiated and they are
preconfigured on both ends.
Additionally, L2TPv3 supports the following optional parameters.
* ``rx_cookie=int`` - l2tpv3 32-bit integer cookie for rx packets - same
functionality as GRE key, more to prevent misconfiguration than provide
actual security
* ``tx_cookie=int`` - l2tpv3 32-bit integer cookie for tx packets
* ``cookie64=[0,1]`` - use 64-bit cookies instead of 32-bit.
* ``counter=[0,1]`` - enable l2tpv3 counter
* ``pin_counter=[0,1]`` - pretend that the counter is always reset on
each packet (needed to interoperate with some really broken
implementations)
* ``v6=[0,1]`` - force v6 sockets
* ``udp=[0,1]`` - use raw sockets (0) or UDP (1) version of the protocol
L2TPv3 has a number of caveats:
* you can use only one connection per IP address in raw mode. There is
no way to multiplex connections as each L2TPv3 tunnel is terminated
directly on the UML instance. UDP mode can use different ports for
this purpose.
Here is an example of how to configure a Linux host to connect to UML
via L2TPv3:
**/etc/network/interfaces**::
auto l2tp1
iface l2tp1 inet static
address 192.168.126.1
netmask 255.255.255.0
broadcast 192.168.126.255
mtu 1500
pre-up ip l2tp add tunnel remote 127.0.0.1 \
local 127.0.0.1 encap udp tunnel_id 2 \
peer_tunnel_id 2 udp_sport 1706 udp_dport 1707 && \
ip l2tp add session name l2tp1 tunnel_id 2 \
session_id 0xffffffff peer_session_id 0xffffffff
down ip l2tp del session tunnel_id 2 session_id 0xffffffff && \
ip l2tp del tunnel tunnel_id 2
Privileges required: L2TPv3 requires ``CAP_NET_RAW`` for raw IP mode and
no special privileges for the UDP mode.
BESS socket transport
527-550BESS는 high-performance modular network switch입니다. 프로젝트와 구성 방법은 원문에 제시된 `https://github.com/NetSys/bess` 및 BESS built-in module·port 문서를 참고합니다.
BESS는 단순한 sequential packet socket mode를 지원하며, 최신 version에서는 고성능을 위해 vector I/O를 사용합니다.
예제 `vecX:transport=bess,src=$unix_src,dst=$unix_dst`는 source로 `unix_src`, destination으로 `unix_dst` Unix domain socket address를 사용하는 BESS transport를 구성합니다.
BESS 설정과 Unix domain socket port 할당 방법은 BESS documentation을 따라야 하며, 이 transport에는 특별한 privilege가 필요하지 않습니다.
Endpoint, packet mode, 권한 특성입니다.
BESS port 할당과 UML endpoint 연결 순서입니다.
BESS socket transport
---------------------
BESS is a high performance modular network switch.
https://github.com/NetSys/bess
It has support for a simple sequential packet socket mode which in the
more recent versions is using vector IO for high performance.
Example::
vecX:transport=bess,src=$unix_src,dst=$unix_dst
This will configure a BESS transport using the unix_src Unix domain
socket address as source and unix_dst socket address as destination.
For BESS configuration and how to allocate a BESS Unix domain socket port
please see the BESS documentation.
https://github.com/NetSys/bess/wiki/Built-In-Modules-and-Ports
BESS transport does not require any special privileges.
VDE vector transport
551-585Virtual Distributed Ethernet(VDE)은 매우 유연한 virtual networking 지원을 목표로 하는 프로젝트입니다. Fast prototyping과 교육이 흔한 활용 사례입니다.
`vecX:transport=vde,vnl=tap://tap0`은 `tap0`을 사용하고, `vecX:transport=vde,vnl=slirp://`은 slirp를 사용합니다.
`vec0:transport=vde,vnl=vde:///tmp/switch`는 `/tmp/switch`의 VDE switch에 연결합니다.
`vecX:transport="vde,vnl=cmd://ssh remote.host //tmp/sshlirp"`는 remote slirp에 연결합니다. `sshlirp`를 사용해 SSH connection을 즉석 VPN으로 바꾸는 방식이며 원문은 `https://github.com/virtualsquare/sshlirp`를 참조합니다.
`vec0:transport=vde,vnl=vxvde://234.0.0.1`은 local area cloud에 연결합니다.
같은 multicast domain, 즉 LAN의 host에서 같은 multicast address를 사용하는 모든 UML node는 자동으로 하나의 virtual LAN에 연결됩니다.
VNL scheme에 따라 연결 대상이 달라집니다.
원하는 network 범위에 맞는 locator를 고릅니다.
VDE vector transport
--------------------
Virtual Distributed Ethernet (VDE) is a project whose main goal is to provide a
highly flexible support for virtual networking.
http://wiki.virtualsquare.org/#/tutorials/vdebasics
Common usages of VDE include fast prototyping and teaching.
Examples:
``vecX:transport=vde,vnl=tap://tap0``
use tap0
``vecX:transport=vde,vnl=slirp://``
use slirp
``vec0:transport=vde,vnl=vde:///tmp/switch``
connect to a vde switch
``vecX:transport=\"vde,vnl=cmd://ssh remote.host //tmp/sshlirp\"``
connect to a remote slirp (instant VPN: convert ssh to VPN, it uses sshlirp)
https://github.com/virtualsquare/sshlirp
``vec0:transport=vde,vnl=vxvde://234.0.0.1``
connect to a local area cloud (all the UML nodes using the same
multicast address running on hosts in the same multicast domain (LAN)
will be automagically connected together to a virtual LAN.
UML kernel 실행 준비
586-598이 절은 distribution의 `user-mode-linux` package 또는 직접 build한 UML kernel이 host에 설치돼 있다고 가정합니다.
설치 결과 생기는 `linux` executable이 UML kernel입니다. 일반 executable처럼 실행하며 대부분의 일반 Linux kernel argument를 command line으로 받을 수 있지만, 유용한 instance를 만들려면 UML-specific argument도 지정해야 합니다.
Host에 준비해야 할 executable과 argument 종류입니다.
***********
Running UML
***********
This section assumes that either the user-mode-linux package from the
distribution or a custom built kernel has been installed on the host.
These add an executable called linux to the system. This is the UML
kernel. It can be run just like any other executable.
It will take most normal linux kernel arguments as command line
arguments. Additionally, it will need some UML-specific arguments
in order to do something useful.
Memory·UBD·root 필수 인자
599-635`mem=int[K,M,G]`는 UML instance에 줄 memory 양을 지정합니다. 단위가 없으면 byte이며 K, M, G suffix를 사용할 수 있습니다.
`ubdX[s,d,c,t]=`는 virtual disk를 정의합니다. 문법상 완전한 필수 인자는 아니지만 root filesystem을 지정해야 하는 거의 모든 경우에 필요합니다. 가장 단순한 값은 앞 절에서 만든 filesystem image file 이름입니다.
UBD는 copy on write(COW)를 지원합니다. 변경분을 버릴 수 있는 별도 file에 보관해 pristine master image로 rollback할 수 있으며, `cow_file,master_image` 형식을 사용합니다. 예제는 `ubd0=Filesystem.cow,Filesystem.img`입니다.
`ubdX` 뒤의 `s` flag는 synchronous I/O를 켜 모든 write를 즉시 disk로 flush합니다.
Single filename이 실제 image가 아니라 COW file인지 점검하는 UBD heuristic을 끄려면 `d` flag를 사용합니다. 문법 표제의 `c`도 원문에 포함되지만 이 구간에는 별도 설명이 없습니다.
UBD는 image의 unused block을 host OS가 회수하도록 요청하는 TRIM을 기본 지원합니다. 이를 끄려면 `t` flag를 붙입니다. Root device는 일반적으로 Linux filesystem image가 연결된 `/dev/ubd0`을 `root=`에 지정합니다.
Memory, disk image, UBD flag, root device를 정리합니다.
Master image를 instance root로 연결하는 순서입니다.
Arguments
=========
Mandatory Arguments:
--------------------
* ``mem=int[K,M,G]`` - amount of memory. By default in bytes. It will
also accept K, M or G qualifiers.
* ``ubdX[s,d,c,t]=`` virtual disk specification. This is not really
mandatory, but it is likely to be needed in nearly all cases so we can
specify a root file system.
The simplest possible image specification is the name of the image
file for the filesystem (created using one of the methods described
in `Creating an image`_).
* UBD devices support copy on write (COW). The changes are kept in
a separate file which can be discarded allowing a rollback to the
original pristine image. If COW is desired, the UBD image is
specified as: ``cow_file,master_image``.
Example:``ubd0=Filesystem.cow,Filesystem.img``
* UBD devices can be set to use synchronous IO. Any writes are
immediately flushed to disk. This is done by adding ``s`` after
the ``ubdX`` specification.
* UBD performs some heuristics on devices specified as a single
filename to make sure that a COW file has not been specified as
the image. To turn them off, use the ``d`` flag after ``ubdX``.
* UBD supports TRIM - asking the Host OS to reclaim any unused
blocks in the image. To turn it off, specify the ``t`` flag after
``ubdX``.
* ``root=`` root device - most likely ``/dev/ubd0`` (this is a Linux
filesystem image)
Console과 serial line channel
636-687`linux`를 추가 인자 없이 실행하면 image 안에 구성된 console마다 xterm을 시작하려고 합니다. 대부분의 Linux distribution에서는 최대 6개이며, GUI host에서는 편리하지만 test harness나 text-only 환경에는 적합하지 않습니다.
동작을 바꾸려면 console을 기본 xterm이 아닌 지원 line channel에 연결합니다. `con1=fd:0,fd:1`은 console 1의 input을 stdin file descriptor 0, output을 stdout file descriptor 1로 보냅니다.
일반 문법은 `conX=channel_type:options[,channel_type:options]`입니다. 쉼표로 두 부분을 쓰면 첫 번째가 input, 두 번째가 output channel입니다.
`null` channel은 input 또는 output을 모두 버립니다. `con=null`은 모든 console의 기본 channel을 null로 만듭니다.
`fd` channel은 input/output에 file descriptor 번호를 사용합니다. 예제는 `con1=fd:0,fd:1`입니다.
`port` channel은 지정한 TCP port에서 telnet server를 시작합니다. `con1=port:4321`을 쓰려면 host에 `/usr/sbin/in.telnetd`와 UML utility의 `port-helper`가 있어야 하며, client가 연결할 때까지 UML은 boot하지 않습니다.
`pty`와 `pts` channel은 system pty/pts를 사용합니다. `tty` channel은 기존 system tty에 bind하며 `con1=/dev/tty8`은 보통 비어 있는 host 8번째 console을 사용합니다.
기본값인 `xterm` channel은 xterm을 띄워 I/O를 연결합니다. Host에 `port-helper` 등 utility가 든 UML distribution package가 설치돼 있거나 source에서 직접 compile·install돼 있어야 합니다. Console option은 UML 안에서 `ttyS`로 보이는 serial line에도 그대로 적용됩니다.
Console input/output backend와 사전 조건입니다.
GUI와 자동화 환경에 맞춰 channel을 고릅니다.
Important Optional Arguments
----------------------------
If UML is run as "linux" with no extra arguments, it will try to start an
xterm for every console configured inside the image (up to 6 in most
Linux distributions). Each console is started inside an
xterm. This makes it nice and easy to use UML on a host with a GUI. It is,
however, the wrong approach if UML is to be used as a testing harness or run
in a text-only environment.
In order to change this behaviour we need to specify an alternative console
and wire it to one of the supported "line" channels. For this we need to map a
console to use something different from the default xterm.
Example which will divert console number 1 to stdin/stdout::
con1=fd:0,fd:1
UML supports a wide variety of serial line channels which are specified using
the following syntax
conX=channel_type:options[,channel_type:options]
If the channel specification contains two parts separated by comma, the first
one is input, the second one output.
* The null channel - Discard all input or output. Example ``con=null`` will set
all consoles to null by default.
* The fd channel - use file descriptor numbers for input/output. Example:
``con1=fd:0,fd:1.``
* The port channel - start a telnet server on TCP port number. Example:
``con1=port:4321``. The host must have /usr/sbin/in.telnetd (usually part of
a telnetd package) and the port-helper from the UML utilities (see the
information for the xterm channel below). UML will not boot until a client
connects.
* The pty and pts channels - use system pty/pts.
* The tty channel - bind to an existing system tty. Example: ``con1=/dev/tty8``
will make UML use the host 8th console (usually unused).
* The xterm channel - this is the default - bring up an xterm on this channel
and direct IO to it. Note that in order for xterm to work, the host must
have the UML distribution package installed. This usually contains the
port-helper and other utilities needed for UML to communicate with the xterm.
Alternatively, these need to be complied and installed from source. All
options applicable to consoles also apply to UML serial lines which are
presented as ttyS inside UML.
UML instance 시작 예제
688-703예제 command는 `mem=2048M`, `umid=TEST`, `ubd0=Filesystem.img`, tap vector transport, `root=/dev/ubda`와 console mapping을 한 번에 지정합니다.
Instance에는 2048M RAM이 주어지고 `Filesystem.img`가 root image로 연결됩니다. Networking은 host `tap0`에 연결된 `vec0`을 사용하며 queue depth 128과 GRO를 활성화합니다.
`con=null con0=null,fd:2 con1=fd:0,fd:1`은 console 1을 제외한 console을 비활성화하고, console 1을 시작한 terminal의 standard input/output에 연결합니다. `con0`은 input을 null로 두고 output을 file descriptor 2로 보냅니다.
Command line의 각 설정과 결과입니다.
Image·network·console을 조합해 instance를 시작합니다.
Starting UML
============
We can now run UML.
::
# linux mem=2048M umid=TEST \
ubd0=Filesystem.img \
vec0:transport=tap,ifname=tap0,depth=128,gro=1 \
root=/dev/ubda con=null con0=null,fd:2 con1=fd:0,fd:1
This will run an instance with ``2048M RAM`` and try to use the image file
called ``Filesystem.img`` as root. It will connect to the host using tap0.
All consoles except ``con1`` will be disabled and console 1 will
use standard input/output making it appear in the same terminal it was started.
UML 로그인
704-711Image 생성 단계에서 password를 설정하지 않았다면 UML instance를 shutdown하고 image를 mount한 뒤 chroot로 들어가 password를 설정해야 합니다.
이미 password가 설정돼 있다면 연결한 console에서 바로 로그인할 수 있습니다.
Password 유무에 따른 조치입니다.
Logging in
============
If you have not set up a password when generating the image, you will have to
shut down the UML instance, mount the image, chroot into it and set it - as
described in the Generating an Image section. If the password is already set,
you can just log in.
UML management console
712-771Image 내부에서는 일반 sysadmin tool을 쓰지만, UML management console은 실행 중인 UML kernel에 low-level operation을 수행하는 별도 interface입니다.
i386 SysRq와 비슷하지만 UML 아래에는 완전한 operating system이 있으므로 SysRq보다 훨씬 유연합니다.
`mconsole`로 kernel version 조회, device 추가·제거, halt·reboot, SysRq command 전송, UML pause·resume, 내부 process 검사, UML 내부 `/proc` state 검사를 할 수 있습니다.
Client는 대부분의 Linux distribution UML tools package에 포함된 `uml_mconsole`입니다. UML kernel에는 General Setup 아래의 `CONFIG_MCONSOLE`을 활성화해야 합니다.
UML을 boot하면 `~/.uml/<자동-id>/mconsole` socket path가 출력됩니다. Command line에 `umid=debian`처럼 고유 machine ID를 주면 path는 `/home/jdike/.uml/debian/mconsole`처럼 예측 가능해집니다.
이 file은 `uml_mconsole`이 UML과 통신하는 socket입니다. Client에는 `uml_mconsole debian`처럼 umid를 주거나 `uml_mconsole /home/jdike/.uml/debian/mconsole`처럼 전체 path를 전달합니다.
연결 후 prompt에서 `version`, `help`, `halt`, `reboot`, `config`, `remove`, `sysrq`, `cad`, `stop`, `go`, `proc`, `stack` command를 실행할 수 있습니다. 원문 목록에는 `help`가 두 번 나오며 원문 자체는 그대로 보존합니다.
Management console은 running UML의 device로 내부 처리되므로 command request 자체가 UML에 device interrupt를 전달할 수 있습니다.
Low-level 관리 범위를 command group별로 정리합니다.
Kernel option부터 command prompt까지의 흐름입니다.
The UML Management Console
============================
In addition to managing the image from "the inside" using normal sysadmin tools,
it is possible to perform a number of low-level operations using the UML
management console. The UML management console is a low-level interface to the
kernel on a running UML instance, somewhat like the i386 SysRq interface. Since
there is a full-blown operating system under UML, there is much greater
flexibility possible than with the SysRq mechanism.
There are a number of things you can do with the mconsole interface:
* get the kernel version
* add and remove devices
* halt or reboot the machine
* Send SysRq commands
* Pause and resume the UML
* Inspect processes running inside UML
* Inspect UML internal /proc state
You need the mconsole client (uml\_mconsole) which is a part of the UML
tools package available in most Linux distritions.
You also need ``CONFIG_MCONSOLE`` (under 'General Setup') enabled in the UML
kernel. When you boot UML, you'll see a line like::
mconsole initialized on /home/jdike/.uml/umlNJ32yL/mconsole
If you specify a unique machine id on the UML command line, i.e.
``umid=debian``, you'll see this::
mconsole initialized on /home/jdike/.uml/debian/mconsole
That file is the socket that uml_mconsole will use to communicate with
UML. Run it with either the umid or the full path as its argument::
# uml_mconsole debian
or
# uml_mconsole /home/jdike/.uml/debian/mconsole
You'll get a prompt, at which you can run one of these commands:
* version
* help
* halt
* reboot
* config
* remove
* sysrq
* help
* cad
* stop
* go
* proc
* stack
version command
772-785`version` command는 인자를 받지 않고 UML version string을 출력합니다. 예제 결과는 OpenWrt Linux 4.14.106 x86_64 build 정보를 보여 줍니다.
이 command는 side effect가 거의 없는 no-op으로 running UML이 응답하는지 점검하는 health check에 사용할 수 있습니다.
또한 mconsole 자체가 UML device로 취급되므로 `version` request는 UML에 device interrupt를 보내는 간단한 방법이기도 합니다.
조회 이상의 두 가지 진단 용도입니다.
version
-------
This command takes no arguments. It prints the UML version::
(mconsole) version
OK Linux OpenWrt 4.14.106 #0 Tue Mar 19 08:19:41 2019 x86_64
There are a couple actual uses for this. It's a simple no-op which
can be used to check that a UML is running. It's also a way of
sending a device interrupt to the UML. UML mconsole is treated internally as
a UML device.
help command
786-792`help` command는 인자를 받지 않습니다.
현재 UML management console이 지원하는 command의 짧은 help screen을 출력합니다.
입력과 출력을 간단히 정리합니다.
help
----
This command takes no arguments. It prints a short help screen with the
supported mconsole commands.
halt·reboot command
793-802`halt`와 `reboot`는 인자를 받지 않고 machine을 즉시 종료하거나 재부팅합니다.
Disk sync와 userspace clean shutdown을 수행하지 않으므로 정상 shutdown이라기보다 machine crash에 가깝습니다.
예제 `(mconsole) halt`는 `OK`를 반환하지만, 중요한 data가 있다면 내부 userspace에서 먼저 정상 shutdown 절차를 수행해야 합니다.
즉시 동작이 생략하는 절차입니다.
halt and reboot
---------------
These commands take no arguments. They shut the machine down immediately, with
no syncing of disks and no clean shutdown of userspace. So, they are
pretty close to crashing the machine::
(mconsole) halt
OK
config command
803-811`config`는 virtual machine에 새 device를 추가하며 대부분의 UML device driver가 지원합니다.
인자는 kernel command line과 같은 device specification 하나입니다. 예제는 `config ubd3=/home/jdike/incoming/roots/root_fs_debian22`로 새 UBD block device를 runtime에 연결합니다.
Command line device 문법을 runtime에 재사용합니다.
config
------
"config" adds a new device to the virtual machine. This is supported
by most UML device drivers. It takes one argument, which is the
device to add, with the same syntax as the kernel command line::
(mconsole) config ubd3=/home/jdike/incoming/roots/root_fs_debian22
remove command
812-822`remove`는 system에서 device를 삭제하며 인자로 제거할 device name만 받습니다.
Device는 driver가 요구하는 의미에서 idle이어야 합니다. UBD block device는 mount, swap, open 상태가 아니어야 합니다.
Network device는 DOWN 상태여야 합니다. 예제 `(mconsole) remove ubd3`는 조건을 충족한 `ubd3`를 분리합니다.
Driver별 idle 의미를 구분합니다.
remove
------
"remove" deletes a device from the system. Its argument is just the
name of the device to be removed. The device must be idle in whatever
sense the driver considers necessary. In the case of the ubd driver,
the removed block device must not be mounted, swapped on, or otherwise
open, and in the case of the network driver, the device must be down::
(mconsole) remove ubd3
sysrq command
823-831`sysrq`는 한 글자 인자 하나를 받습니다.
Generic kernel SysRq driver를 호출하고 글자에 대응하는 operation을 수행합니다. 유효한 글자와 동작은 `Documentation/admin-guide/sysrq.rst`를 따라야 합니다.
mconsole 입력이 generic SysRq로 이어집니다.
sysrq
-----
This command takes one argument, which is a single letter. It calls the
generic kernel's SysRq driver, which does whatever is called for by
that argument. See the SysRq documentation in
Documentation/admin-guide/sysrq.rst in your favorite kernel tree to
see what letters are valid and what they do.
cad command
832-838`cad`는 running image에서 `Ctl-Alt-Del` action을 호출합니다.
실제 결과는 init, systemd 등 userspace 설정에 달려 있으며 보통 machine을 reboot합니다.
Key sequence와 userspace policy의 관계입니다.
cad
---
This invokes the ``Ctl-Alt-Del`` action in the running image. What exactly
this ends up doing is up to init, systemd, etc. Normally, it reboots the
machine.
stop·go command
839-853`stop`은 UML을 mconsole request만 읽는 loop에 넣고 `go` command가 올 때까지 일반 실행을 멈춥니다. Debugging과 snapshotting에 매우 유용합니다.
`go`는 `stop`으로 pause한 UML을 다시 실행합니다.
Pause 중에는 guest time과 외부 network 시간이 계속 어긋날 수 있어 resume 후 TCP connection이 timeout됐을 수 있습니다.
오랫동안 pause하면 `crond`가 그동안 실행하지 못한 job을 한꺼번에 처리할 수 있으므로 재개 직후 workload를 주의해야 합니다.
정지 중 유지되는 관리 경로와 재개 위험입니다.
정지와 재개 전후의 운영 점검입니다.
stop
----
This puts the UML in a loop reading mconsole requests until a 'go'
mconsole command is received. This is very useful as a
debugging/snapshotting tool.
go
--
This resumes a UML after being paused by a 'stop' command. Note that
when the UML has resumed, TCP connections may have timed out and if
the UML is paused for a long period of time, crond might go a little
crazy, running all the jobs it didn't do earlier.
proc command
854-859`proc`는 `/proc` 아래에서 읽을 file name 하나를 인자로 받습니다.
해당 UML 내부 proc file 내용을 mconsole standard output에 출력합니다.
UML 내부 proc state를 외부 console로 가져옵니다.
proc
----
This takes one argument - the name of a file in /proc which is printed
to the mconsole standard output
stack command
860-865`stack`은 process ID, 즉 PID 하나를 인자로 받습니다.
선택한 UML process의 stack을 standard output에 출력해 hang이나 실행 위치를 진단할 수 있게 합니다.
Process 선택과 출력입니다.
stack
-----
This takes one argument - the pid number of a process. Its stack is
printed to a standard output.
Advanced UML과 filesystem 공유 경고
866-876Advanced UML Topics의 첫 주제는 virtual machine 사이의 filesystem 공유입니다.
같은 image file에서 UML 두 개를 단순히 boot해 filesystem을 공유해서는 안 됩니다. 이는 물리 machine 두 대를 하나의 shared disk에서 동시에 boot하는 것과 같아 filesystem corruption을 일으킵니다.
공유가 필요하다면 master image를 직접 write하는 대신 각 instance가 별도 변경 계층을 갖는 UBD copy-on-write 방식을 사용해야 합니다.
동시 write 방식의 안전성을 비교합니다.
공통 데이터와 instance 변경분을 분리합니다.
*******************
Advanced UML Topics
*******************
Sharing Filesystems between Virtual Machines
============================================
Don't attempt to share filesystems simply by booting two UMLs from the
same file. That's the same thing as booting two physical machines
from a shared disk. It will result in filesystem corruption.
UBD layered block device
877-910두 virtual machine이 filesystem 데이터를 공유하는 올바른 방법은 UBD block driver의 COW layering 기능을 사용하는 것입니다.
변경된 block은 private COW file에 저장합니다. Read할 block이 COW에 유효하면 private file에서, 아니면 shared master에서 읽습니다.
변하지 않은 대부분의 데이터는 임의 개수의 VM이 공유하고, 각 VM은 자신이 만든 변경분만 담은 훨씬 작은 file을 갖습니다. 큰 root filesystem에서 많은 UML을 boot할 때 disk 공간을 크게 절약합니다.
Shared data는 host page cache가 같은 block을 적은 memory로 재사용하게 하므로 disk request가 host disk 대신 memory에서 처리될 가능성이 높아져 성능에도 도움이 됩니다.
다만 multisocket NUMA machine에서는 shared master와 여러 COW 변경분을 쓰는 UML을 많이 실행할 때 inter-socket traffic이 과도해져 NMI 같은 문제가 생길 수 있습니다.
고성능 NUMA hardware에서는 `taskset`으로 UML을 같은 socket의 logical CPU 집합에 bind하거나 뒤의 tuning 절을 적용해야 합니다.
기존 block image에 COW layer를 붙이는 형식은 `ubd0=root_fs_cow,root_fs_debian_22`입니다. `root_fs_cow`가 private file, `root_fs_debian_22`가 shared filesystem이며 COW file이 없으면 driver가 생성하고 초기화합니다.
Block 위치와 NUMA 운영 조건을 정리합니다.
요청 block의 유효 위치를 선택합니다.
Using layered block devices
---------------------------
The way to share a filesystem between two virtual machines is to use
the copy-on-write (COW) layering capability of the ubd block driver.
Any changed blocks are stored in the private COW file, while reads come
from either device - the private one if the requested block is valid in
it, the shared one if not. Using this scheme, the majority of data
which is unchanged is shared between an arbitrary number of virtual
machines, each of which has a much smaller file containing the changes
that it has made. With a large number of UMLs booting from a large root
filesystem, this leads to a huge disk space saving.
Sharing file system data will also help performance, since the host will
be able to cache the shared data using a much smaller amount of memory,
so UML disk requests will be served from the host's memory rather than
its disks. There is a major caveat in doing this on multisocket NUMA
machines. On such hardware, running many UML instances with a shared
master image and COW changes may cause issues like NMIs from excess of
inter-socket traffic.
If you are running UML on high-end hardware like this, make sure to
bind UML to a set of logical CPUs residing on the same socket using the
``taskset`` command or have a look at the "tuning" section.
To add a copy-on-write layer to an existing block device file, simply
add the name of the COW file to the appropriate ubd switch::
ubd0=root_fs_cow,root_fs_debian_22
where ``root_fs_cow`` is the private COW file and ``root_fs_debian_22`` is
the existing shared filesystem. The COW file need not exist. If it
doesn't, the driver will create and initialize it.
COW disk 사용량 확인
911-917UML은 disk image의 unused space를 underlying OS에 돌려주는 TRIM을 지원합니다.
Sparse image의 논리 크기와 실제 할당량은 다르므로 실제 file size는 `ls -ls` 또는 `du`로 확인하는 것이 중요합니다.
Sparse·TRIM image에서 적절한 측정 방법입니다.
Disk Usage
----------
UML has TRIM support which will release any unused space in its disk
image files to the underlying OS. It is important to use either ls -ls
or du to verify the actual file size.
Master image 변경과 COW validity
918-927Master image를 변경하면 연결된 모든 COW file이 invalid해집니다.
UML은 invalid COW file을 자동 삭제하지 않고 boot를 거부합니다. 잘못된 master와 기존 change map을 섞어 data corruption을 만드는 것을 막기 위한 동작입니다.
해결책은 old master image를 last-modified timestamp까지 포함해 복원하거나, 모든 COW file을 제거해 새로 생성하게 하는 두 가지뿐입니다.
COW file을 제거하면 그 안의 모든 변경분을 잃으므로 master 수정과 COW 정리는 명시적인 backup 계획 아래 수행해야 합니다.
Master 변경 후 가능한 선택과 손실을 비교합니다.
Validity를 회복하는 두 경로입니다.
COW validity.
-------------
Any changes to the master image will invalidate all COW files. If this
happens, UML will *NOT* automatically delete any of the COW files and
will refuse to boot. In this case the only solution is to either
restore the old image (including its last modified timestamp) or remove
all COW files which will result in their recreation. Any changes in
the COW files will be lost.
uml_moo로 COW merge
928-956UML과 COW device의 사용 패턴에 따라 COW 변경분을 backing file에 주기적으로 merge하는 것이 유용할 수 있습니다.
Utility는 `uml_moo`이며 기본 문법은 `uml_moo COW_file new_backing_file`입니다.
현재 backing file 정보는 COW header에 들어 있으므로 command에 따로 지정할 필요가 없습니다. 안전을 중시한다면 새 merged file을 먼저 boot해 검증한 뒤 old backing file을 교체합니다.
기본 동작은 안전을 위해 새 backing file을 만듭니다.
Destructive merge option은 COW를 현재 backing file에 직접 합칩니다. Disk 공간이 부족할 때 편리하고 non-destructive merge보다 빠르지만 backing file에 COW가 하나만 연결된 경우에만 안전합니다.
여러 COW가 같은 backing file을 쓰는 상태에서 하나를 `-d` merge하면 다른 COW가 모두 invalid해집니다. `uml_moo`는 UML distribution package의 UML utilities에 포함됩니다.
새 file 생성과 destructive merge의 trade-off입니다.
새 backing file을 검증한 뒤 교체합니다.
Cows can moo - uml_moo : Merging a COW file with its backing file
-----------------------------------------------------------------
Depending on how you use UML and COW devices, it may be advisable to
merge the changes in the COW file into the backing file every once in
a while.
The utility that does this is uml_moo. Its usage is::
uml_moo COW_file new_backing_file
There's no need to specify the backing file since that information is
already in the COW file header. If you're paranoid, boot the new
merged file, and if you're happy with it, move it over the old backing
file.
``uml_moo`` creates a new backing file by default as a safety measure.
It also has a destructive merge option which will merge the COW file
directly into its current backing file. This is really only usable
when the backing file only has one COW file associated with it. If
there are multiple COWs associated with a backing file, a -d merge of
one of them will invalidate all of the others. However, it is
convenient if you're short of disk space, and it should also be
noticeably faster than a non-destructive merge.
``uml_moo`` is installed with the UML distribution packages and is
available as a part of UML utilities.
Host file access와 보안 경고
957-976UML 안에서 host file에 접근하려면 별도 machine처럼 NFS로 directory를 mount하거나 `scp`로 file을 복사할 수 있습니다.
그러나 UML 자체가 host process이므로 network 없이도 host file에 접근할 수 있습니다. `hostfs` virtual filesystem은 host directory를 UML filesystem에 mount해 host에서처럼 file을 사용하게 합니다.
중요한 security warning이 있습니다. UML image에 parameter 제한 없이 hostfs를 허용하면 guest가 host filesystem의 어느 부분이든 mount하고 write할 수 있습니다.
UML을 실행할 때 hostfs는 `/var/tmp` 같은 특정 harmless directory에 항상 제한해야 합니다.
특히 UML을 root로 실행하면 hostfs write가 host 전체에 미치는 영향이 커지므로 제한이 필수입니다.
Network 방식과 hostfs의 편의·위험을 비교합니다.
Guest에 필요한 host directory만 제공합니다.
Host file access
==================
If you want to access files on the host machine from inside UML, you
can treat it as a separate machine and either nfs mount directories
from the host or copy files into the virtual machine with scp.
However, since UML is running on the host, it can access those
files just like any other process and make them available inside the
virtual machine without the need to use the network.
This is possible with the hostfs virtual filesystem. With it, you
can mount a host directory into the UML filesystem and access the
files contained in it just as you would on the host.
*SECURITY WARNING*
Hostfs without any parameters to the UML Image will allow the image
to mount any part of the host filesystem and write to it. Always
confine hostfs to a specific "harmless" directory (for example ``/var/tmp``)
if running UML. This is especially important if UML is being run as root.
hostfs 사용
977-1001먼저 UML 안에서 `cat /proc/filesystems`를 실행해 `hostfs`가 사용 가능한지 확인합니다.
목록에 없다면 hostfs를 built-in으로 포함해 kernel을 다시 build하거나, module로 build된 hostfs가 VM 안에 존재하는지 확인하고 `insmod`로 load합니다.
`mount none /mnt/host -t hostfs`는 host의 `/`를 UML의 `/mnt/host`에 mount합니다.
Host root 전체가 필요하지 않다면 `-o` option으로 subdirectory를 지정합니다.
예제 `mount none /mnt/home -t hostfs -o /home`은 host `/home`을 UML `/mnt/home`에 mount합니다.
Availability 확인과 mount 범위를 정리합니다.
전체 root 대신 필요한 path만 연결합니다.
Using hostfs
------------
To begin with, make sure that hostfs is available inside the virtual
machine with::
# cat /proc/filesystems
``hostfs`` should be listed. If it's not, either rebuild the kernel
with hostfs configured into it or make sure that hostfs is built as a
module and available inside the virtual machine, and insmod it.
Now all you need to do is run mount::
# mount none /mnt/host -t hostfs
will mount the host's ``/`` on the virtual machine's ``/mnt/host``.
If you don't want to mount the host root directory, then you can
specify a subdirectory to mount with the -o switch to mount::
# mount none /mnt/home -t hostfs -o /home
will mount the host's /home on the virtual machine's /mnt/home.
hostfs를 root filesystem으로 사용
1002-1029Standard image file 대신 host directory hierarchy를 hostfs root로 사용해 boot할 수 있습니다.
먼저 root hierarchy가 필요합니다. 가장 쉬운 방법은 `mount root_fs uml_root_dir -o loop`로 기존 `root_fs` image를 loop mount하는 것입니다.
Hierarchy의 `etc/fstab`에서 `/` filesystem type을 `hostfs`로 바꾸고 `/dev/ubd/0 / hostfs defaults 1 1` 형태로 설정합니다.
그 directory에서 root 소유 file을 UML 실행 user 소유로 바꿔야 합니다. 예제는 `find . -uid 0 -exec chown jdike {} ;`입니다.
UML kernel에는 hostfs가 module이 아니라 built-in으로 compile돼 있어야 합니다. `ubd0=/path/to/uml/root/directory`로 boot device를 directory에 지정하면 일반 image처럼 UML이 boot합니다.
Directory hierarchy에서 boot하기 위한 조건입니다.
Image 내용을 host directory로 노출해 UML root로 사용합니다.
hostfs as the root filesystem
-----------------------------
It's possible to boot from a directory hierarchy on the host using
hostfs rather than using the standard filesystem in a file.
To start, you need that hierarchy. The easiest way is to loop mount
an existing root_fs file::
# mount root_fs uml_root_dir -o loop
You need to change the filesystem type of ``/`` in ``etc/fstab`` to be
'hostfs', so that line looks like this::
/dev/ubd/0 / hostfs defaults 1 1
Then you need to chown to yourself all the files in that directory
that are owned by root. This worked for me::
# find . -uid 0 -exec chown jdike {} \;
Next, make sure that your UML kernel has hostfs compiled in, not as a
module. Then run UML with the boot device pointing at that directory::
ubd0=/path/to/uml/root/directory
UML should then boot as it does normally.
hostfs cache caveat
1030-1038hostfs는 UML 밖의 host에서 발생한 filesystem 변경을 추적하지 못합니다.
UML이 모르는 상태에서 host file이 바뀌면 UML의 in-memory file cache가 stale하거나 corrupt한 상태가 될 수 있습니다.
기술적으로 고칠 수는 있지만 현재 이 개선은 진행 중인 작업이 아니므로, mounted hostfs path를 host와 UML이 동시에 수정하지 않도록 운영해야 합니다.
외부 변경이 UML cache에 미치는 영향입니다.
Hostfs Caveats
--------------
Hostfs does not support keeping track of host filesystem changes on the
host (outside UML). As a result, if a file is changed without UML's
knowledge, UML will not know about it and its own in-memory cache of
the file may be corrupt. While it is possible to fix this, it is not
something which is being worked on at present.
CPU·NUMA locality tuning
1039-1065현재 UML은 strict uniprocessor이지만 UBD driver, SIGIO, MMU emulation 등 여러 기능을 위한 helper thread를 만듭니다.
SMP host가 idle하면 OS가 이 thread들을 다른 processor로 migrate할 수 있습니다. 그 결과 core 사이 cache와 memory synchronization traffic이 늘어 오히려 성능이 낮아지는 경우가 많습니다.
특히 큰 system에서는 UML thread 전체를 CPU 하나에 pin하는 것이 유리하며 일부 benchmark에서는 5배 이상의 성능 차이가 날 수 있습니다.
큰 multi-node NUMA system에서는 UML이 실행될 node와 같은 NUMA node에서 모든 memory를 할당해야 유리하지만 OS는 기본적으로 그렇게 하지 않습니다.
관리자는 특정 node에 bind한 tmpfs ramdisk를 만들고 `TMPDIR`, `TMP`, `TEMP` 중 하나로 지정해 UML RAM allocation source로 사용해야 합니다. UML은 이 순서의 environment value를 확인하고 실패하면 `/dev/shm`의 shmfs, 마지막에는 filesystem type과 관계없이 `/tmp/`를 사용합니다.
예제는 `mount -t tmpfs -ompol=bind:X none /mnt/tmpfs-nodeX`로 node X tmpfs를 만들고 `TEMP=/mnt/tmpfs-nodeX taskset -cX linux ...`로 memory와 CPU를 같은 node에 맞춥니다.
Thread migration과 memory allocation 경로입니다.
CPU와 backing memory를 같은 node에 둡니다.
Tuning UML
============
UML at present is strictly uniprocessor. It will, however spin up a
number of threads to handle various functions.
The UBD driver, SIGIO and the MMU emulation do that. If the system is
idle, these threads will be migrated to other processors on a SMP host.
This, unfortunately, will usually result in LOWER performance because of
all of the cache/memory synchronization traffic between cores. As a
result, UML will usually benefit from being pinned on a single CPU,
especially on a large system. This can result in performance differences
of 5 times or higher on some benchmarks.
Similarly, on large multi-node NUMA systems UML will benefit if all of
its memory is allocated from the same NUMA node it will run on. The
OS will *NOT* do that by default. In order to do that, the sysadmin
needs to create a suitable tmpfs ramdisk bound to a particular node
and use that as the source for UML RAM allocation by specifying it
in the TMP or TEMP environment variables. UML will look at the values
of ``TMPDIR``, ``TMP`` or ``TEMP`` for that. If that fails, it will
look for shmfs mounted under ``/dev/shm``. If everything else fails use
``/tmp/`` regardless of the filesystem type used for it::
mount -t tmpfs -ompol=bind:X none /mnt/tmpfs-nodeX
TEMP=/mnt/tmpfs-nodeX taskset -cX linux options options options..
UML 개발과 기여
1066-1099UML은 filesystem, device, virtualization 같은 새로운 Linux kernel concept를 개발하기 좋은 platform입니다. 특정 hardware emulation에 묶이지 않고 구현하고 시험할 수 있습니다.
예를 들어 4,096개의 'proper' network device를 가진 Linux를 시험할 수 있습니다. QEMU의 PCI bus당 16개 같은 emulated hardware bus limit 때문에 다른 virtualization package에서는 어려운 실험입니다.
Patch, bugfix, new feature는 `linux-um@lists.infradead.org` mailing list로 보냅니다.
관련 maintainer를 CC하고 `./scripts/checkpatch.pl`을 실행하는 등 표준 Linux patch guideline을 따라야 하며 자세한 내용은 `Documentation/process/submitting-patches.rst`를 참조합니다.
Mailing list는 HTML이나 attachment를 받지 않으므로 모든 email을 plain text로 작성해야 합니다.
개발에는 debugging이 따릅니다. UML을 GDB 아래에서 실행할 수 있지만 tracing statement를 추가하거나 UML kernel process를 ptrace하는 UML-specific 방식이 더 많은 정보를 주는 경우가 흔합니다.
실험 장점과 patch 제출 요건입니다.
표준 kernel contribution 절차를 UML list에 적용합니다.
*******************************************
Contributing to UML and Developing with UML
*******************************************
UML is an excellent platform to develop new Linux kernel concepts -
filesystems, devices, virtualization, etc. It provides unrivalled
opportunities to create and test them without being constrained to
emulating specific hardware.
Example - want to try how Linux will work with 4096 "proper" network
devices?
Not an issue with UML. At the same time, this is something which
is difficult with other virtualization packages - they are
constrained by the number of devices allowed on the hardware bus
they are trying to emulate (for example 16 on a PCI bus in qemu).
If you have something to contribute such as a patch, a bugfix, a
new feature, please send it to ``linux-um@lists.infradead.org``.
Please follow all standard Linux patch guidelines such as cc-ing
relevant maintainers and run ``./scripts/checkpatch.pl`` on your patch.
For more details see ``Documentation/process/submitting-patches.rst``
Note - the list does not accept HTML or attachments, all emails must
be formatted as plain text.
Developing always goes hand in hand with debugging. First of all,
you can always run UML under gdb and there will be a whole section
later on on how to do that. That, however, is not the only way to
debug a Linux kernel. Quite often adding tracing statements and/or
using UML specific approaches such as ptracing the UML kernel process
are significantly more informative.
Running UML tracing
1100-1152실행 중인 UML은 main kernel thread와 여러 helper thread로 구성됩니다. Tracing 대상은 MMU emulation 때문에 UML이 이미 ptrace 중인 thread가 아닙니다.
`ps`에 처음 보이는 세 thread가 보통 주요 대상입니다. PID가 가장 낮고 CPU를 가장 많이 쓰는 것이 kernel thread이며, 나머지는 disk(UBD) helper와 SIGIO helper입니다.
`strace -p 16566` 예제는 idle UML에서도 SIGIO, `epoll_wait`, 여러 `ptrace` request, `timer_settime`, `clock_nanosleep`, SIGALRM이 반복되는 모습을 보여 줍니다. 원문 syscall trace 전체는 해당 줄 좌표에 그대로 보존됩니다.
UML interrupt controller는 `epoll`을 사용하므로 `epoll_wait`는 I/O interrupt를 기다리는 동작입니다.
연속된 `ptrace` call은 MMU emulation과 UML userspace 실행의 일부입니다. `timer_settime`은 UML 내부 high-resolution timer 요청을 host high-resolution timer에 매핑합니다.
`clock_nanosleep`은 physical PC의 ACPI idle과 비슷하게 UML이 idle 상태로 들어가는 동작입니다.
따라서 idle에서도 trace output이 많지만 I/O를 관찰할 때 실제 syscall, argument, return value를 모두 보여 주어 매우 유용합니다.
대표 syscall과 UML subsystem의 대응입니다.
Main kernel thread를 찾아 syscall을 해석합니다.
Tracing UML
=============
When running, UML consists of a main kernel thread and a number of
helper threads. The ones of interest for tracing are NOT the ones
that are already ptraced by UML as a part of its MMU emulation.
These are usually the first three threads visible in a ps display.
The one with the lowest PID number and using most CPU is usually the
kernel thread. The other threads are the disk
(ubd) device helper thread and the SIGIO helper thread.
Running ptrace on this thread usually results in the following picture::
host$ strace -p 16566
--- SIGIO {si_signo=SIGIO, si_code=POLL_IN, si_band=65} ---
epoll_wait(4, [{EPOLLIN, {u32=3721159424, u64=3721159424}}], 64, 0) = 1
epoll_wait(4, [], 64, 0) = 0
rt_sigreturn({mask=[PIPE]}) = 16967
ptrace(PTRACE_GETREGS, 16967, NULL, 0xd5f34f38) = 0
ptrace(PTRACE_GETREGSET, 16967, NT_X86_XSTATE, [{iov_base=0xd5f35010, iov_len=832}]) = 0
ptrace(PTRACE_GETSIGINFO, 16967, NULL, {si_signo=SIGTRAP, si_code=0x85, si_pid=16967, si_uid=0}) = 0
ptrace(PTRACE_SETREGS, 16967, NULL, 0xd5f34f38) = 0
ptrace(PTRACE_SETREGSET, 16967, NT_X86_XSTATE, [{iov_base=0xd5f35010, iov_len=2696}]) = 0
ptrace(PTRACE_SYSEMU, 16967, NULL, 0) = 0
--- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_TRAPPED, si_pid=16967, si_uid=0, si_status=SIGTRAP, si_utime=65, si_stime=89} ---
wait4(16967, [{WIFSTOPPED(s) && WSTOPSIG(s) == SIGTRAP | 0x80}], WSTOPPED|__WALL, NULL) = 16967
ptrace(PTRACE_GETREGS, 16967, NULL, 0xd5f34f38) = 0
ptrace(PTRACE_GETREGSET, 16967, NT_X86_XSTATE, [{iov_base=0xd5f35010, iov_len=832}]) = 0
ptrace(PTRACE_GETSIGINFO, 16967, NULL, {si_signo=SIGTRAP, si_code=0x85, si_pid=16967, si_uid=0}) = 0
timer_settime(0, 0, {it_interval={tv_sec=0, tv_nsec=0}, it_value={tv_sec=0, tv_nsec=2830912}}, NULL) = 0
getpid() = 16566
clock_nanosleep(CLOCK_MONOTONIC, 0, {tv_sec=1, tv_nsec=0}, NULL) = ? ERESTART_RESTARTBLOCK (Interrupted by signal)
--- SIGALRM {si_signo=SIGALRM, si_code=SI_TIMER, si_timerid=0, si_overrun=0, si_value={int=1631716592, ptr=0x614204f0}} ---
rt_sigreturn({mask=[PIPE]}) = -1 EINTR (Interrupted system call)
This is a typical picture from a mostly idle UML instance.
* UML interrupt controller uses epoll - this is UML waiting for IO
interrupts:
epoll_wait(4, [{EPOLLIN, {u32=3721159424, u64=3721159424}}], 64, 0) = 1
* The sequence of ptrace calls is part of MMU emulation and running the
UML userspace.
* ``timer_settime`` is part of the UML high res timer subsystem mapping
timer requests from inside UML onto the host high resolution timers.
* ``clock_nanosleep`` is UML going into idle (similar to the way a PC
will execute an ACPI idle).
As you can see UML will generate quite a bit of output even in idle. The output
can be very informative when observing IO. It shows the actual IO calls, their
arguments and returns values.
GDB kernel debugging
1153-1167UML을 GDB 아래에서 시작할 수 있지만 항상 debugger 시작을 순순히 허용하지는 않습니다.
Runtime bug를 추적할 때는 실행 중인 UML에 GDB를 attach한 뒤 계속 실행하게 하는 편이 좋습니다.
앞 trace와 같은 PID라면 `gdb -p 16566`으로 attach합니다.
Attach 즉시 UML instance가 STOP되므로 GDB prompt에서 `cont`를 입력해야 합니다. 이 동작을 GDB script로 만들고 argument로 전달하면 반복 debugging을 자동화할 수 있습니다.
Runtime UML을 멈추고 재개하는 절차입니다.
실행 중 instance에 안전하게 attach합니다.
Kernel debugging
================
You can run UML under gdb now, though it will not necessarily agree to
be started under it. If you are trying to track a runtime bug, it is
much better to attach gdb to a running UML instance and let UML run.
Assuming the same PID number as in the previous example, this would be::
# gdb -p 16566
This will STOP the UML instance, so you must enter `cont` at the GDB
command line to request it to continue. It may be a good idea to make
this into a gdb script and pass it to gdb as an argument.
UML device driver 개발
1168-1197거의 모든 UML driver는 monolithic입니다. Kernel module로 만들 수는 있지만 in-kernel 기능과 non-UML-specific 기능으로 범위가 제한됩니다.
UML의 강점을 제대로 활용하려면 driver concept를 실제 host userspace call에 매핑하는 userspace code를 작성해야 하기 때문입니다.
이 부분을 driver의 `user` portion이라고 하며 kernel concept을 많이 재사용할 수 있어도 본질적으로 userspace code입니다.
이에 대응하는 `kernel` code는 UML image 안에 있고 Linux kernel 측 기능을 구현합니다.
`kernel`과 `user`가 상호 작용하는 방식에는 제한이 거의 없습니다. UML은 엄격한 kernel-to-host API를 정의하지 않고 특정 architecture나 bus를 emulation하지도 않습니다.
두 부분은 developer 설계에 따라 memory와 code를 공유하고 필요한 방식으로 상호 작용할 수 있습니다. 다만 같은 이름의 function과 variable이 많아 include와 library가 어느 쪽 symbol을 뜻하는지 주의해야 합니다.
그래서 userspace code에는 단순 wrapper가 많습니다. `os_close_file()`은 userspace `close()`가 kernel 쪽의 같은 이름 function과 충돌하지 않도록 감싼 예입니다.
User portion과 kernel portion의 책임입니다.
Kernel interface와 host 구현을 연결합니다.
Developing Device Drivers
=========================
Nearly all UML drivers are monolithic. While it is possible to build a
UML driver as a kernel module, that limits the possible functionality
to in-kernel only and non-UML specific. The reason for this is that
in order to really leverage UML, one needs to write a piece of
userspace code which maps driver concepts onto actual userspace host
calls.
This forms the so-called "user" portion of the driver. While it can
reuse a lot of kernel concepts, it is generally just another piece of
userspace code. This portion needs some matching "kernel" code which
resides inside the UML image and which implements the Linux kernel part.
*Note: There are very few limitations in the way "kernel" and "user" interact*.
UML does not have a strictly defined kernel-to-host API. It does not
try to emulate a specific architecture or bus. UML's "kernel" and
"user" can share memory, code and interact as needed to implement
whatever design the software developer has in mind. The only
limitations are purely technical. Due to a lot of functions and
variables having the same names, the developer should be careful
which includes and libraries they are trying to refer to.
As a result a lot of userspace code consists of simple wrappers.
E.g. ``os_close_file()`` is just a wrapper around ``close()``
which ensures that the userspace function close does not clash
with similarly named function(s) in the kernel part.
Device driver test platform
1198-1217UML은 device driver 개발을 위한 훌륭한 test platform입니다.
다만 원문 표현대로 'some user assembly may be required'하므로 사용자가 자신의 emulation environment를 구축해야 합니다. 현재 UML은 kernel infrastructure만 제공합니다.
Infrastructure에는 Arm이나 Open Firmware platform에서 쓰는 FDT device tree blob을 load하고 parse하는 기능이 포함됩니다.
DTB는 kernel command line의 optional argument `dtb=filename`으로 전달합니다.
Device tree는 boot time에 load·parse되고 이를 query하는 driver에서 접근할 수 있습니다. 현재는 development 전용이며 UML 자체 device는 device tree를 query하지 않습니다.
DTB 제공과 사용 범위를 정리합니다.
Virtual hardware description을 UML에 전달합니다.
Using UML as a Test Platform
============================
UML is an excellent test platform for device driver development. As
with most things UML, "some user assembly may be required". It is
up to the user to build their emulation environment. UML at present
provides only the kernel infrastructure.
Part of this infrastructure is the ability to load and parse fdt
device tree blobs as used in Arm or Open Firmware platforms. These
are supplied as an optional extra argument to the kernel command
line::
dtb=filename
The device tree is loaded and parsed at boottime and is accessible by
drivers which query it. At this moment in time this facility is
intended solely for development purposes. UML's own devices do not
query the device tree.
UML security considerations
1218-1240Driver와 새 기능은 UML instance 안에서 host에 영향을 줄 수 있는 arbitrary filename, BPF code, 기타 parameter를 기본적으로 받지 않도록 설계해야 합니다.
Driver와 host의 IPC socket을 UML startup command line에서 지정하는 것은 security 측면에서 허용할 수 있습니다. Host 관리자가 시작 시점에 경계를 정하기 때문입니다.
같은 socket path를 loadable module parameter로 guest가 runtime에 지정하도록 허용하는 것은 안전하지 않습니다.
Raw socket network transport의 BPF 'firmware'처럼 필요한 기능이라면 기본값을 off로 두고 startup command-line parameter로 명시적으로 활성화해야 합니다.
이 원칙을 적용해도 UML과 host 사이 isolation level은 상대적으로 약합니다. UML userspace가 arbitrary kernel driver를 load할 수 있으면 attacker가 이를 이용해 UML 밖으로 탈출할 수 있습니다.
Production에서는 필요한 module을 boot 때 모두 load하고 이후 kernel module loading을 disable하는 것이 권장됩니다.
Host 영향 parameter와 module loading 정책입니다.
Host가 startup 경계를 고정하고 runtime 확장을 닫습니다.
Security Considerations
-----------------------
Drivers or any new functionality should default to not
accepting arbitrary filename, bpf code or other parameters
which can affect the host from inside the UML instance.
For example, specifying the socket used for IPC communication
between a driver and the host at the UML command line is OK
security-wise. Allowing it as a loadable module parameter
isn't.
If such functionality is desirable for a particular application
(e.g. loading BPF "firmware" for raw socket network transports),
it should be off by default and should be explicitly turned on
as a command line parameter at startup.
Even with this in mind, the level of isolation between UML
and the host is relatively weak. If the UML userspace is
allowed to load arbitrary kernel drivers, an attacker can
use this to break out of UML. Thus, if UML is used in
a production application, it is recommended that all modules
are loaded at boot and kernel module loading is disabled
afterwards.
요약·해설
user_mode_linux_howto_v2.rst:1-1240UML kernel은 host의 일반 process로 실행되며 실제 hardware emulation 대신 file·socket·pipe에 대응하는 paravirtual device를 사용합니다. Image 생성, UBD root와 COW, vector network transport, console과 mconsole 운영 절차를 명령 단위로 정리했습니다.
후반부는 hostfs 노출 위험, CPU·NUMA locality, strace·GDB debugging, user/kernel driver 분할과 DTB test를 다룹니다. Production에서는 host 영향 parameter를 startup에서 고정하고 boot 후 module loading을 막아야 한다는 보안 경계를 강조합니다.