mirror of https://github.com/Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
332 lines
13 KiB
332 lines
13 KiB
unshare system call |
|
=================== |
|
|
|
This document describes the new system call, unshare(). The document |
|
provides an overview of the feature, why it is needed, how it can |
|
be used, its interface specification, design, implementation and |
|
how it can be tested. |
|
|
|
Change Log |
|
---------- |
|
version 0.1 Initial document, Janak Desai ([email protected]), Jan 11, 2006 |
|
|
|
Contents |
|
-------- |
|
1) Overview |
|
2) Benefits |
|
3) Cost |
|
4) Requirements |
|
5) Functional Specification |
|
6) High Level Design |
|
7) Low Level Design |
|
8) Test Specification |
|
9) Future Work |
|
|
|
1) Overview |
|
----------- |
|
|
|
Most legacy operating system kernels support an abstraction of threads |
|
as multiple execution contexts within a process. These kernels provide |
|
special resources and mechanisms to maintain these "threads". The Linux |
|
kernel, in a clever and simple manner, does not make distinction |
|
between processes and "threads". The kernel allows processes to share |
|
resources and thus they can achieve legacy "threads" behavior without |
|
requiring additional data structures and mechanisms in the kernel. The |
|
power of implementing threads in this manner comes not only from |
|
its simplicity but also from allowing application programmers to work |
|
outside the confinement of all-or-nothing shared resources of legacy |
|
threads. On Linux, at the time of thread creation using the clone system |
|
call, applications can selectively choose which resources to share |
|
between threads. |
|
|
|
unshare() system call adds a primitive to the Linux thread model that |
|
allows threads to selectively 'unshare' any resources that were being |
|
shared at the time of their creation. unshare() was conceptualized by |
|
Al Viro in the August of 2000, on the Linux-Kernel mailing list, as part |
|
of the discussion on POSIX threads on Linux. unshare() augments the |
|
usefulness of Linux threads for applications that would like to control |
|
shared resources without creating a new process. unshare() is a natural |
|
addition to the set of available primitives on Linux that implement |
|
the concept of process/thread as a virtual machine. |
|
|
|
2) Benefits |
|
----------- |
|
|
|
unshare() would be useful to large application frameworks such as PAM |
|
where creating a new process to control sharing/unsharing of process |
|
resources is not possible. Since namespaces are shared by default |
|
when creating a new process using fork or clone, unshare() can benefit |
|
even non-threaded applications if they have a need to disassociate |
|
from default shared namespace. The following lists two use-cases |
|
where unshare() can be used. |
|
|
|
2.1 Per-security context namespaces |
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
|
|
|
unshare() can be used to implement polyinstantiated directories using |
|
the kernel's per-process namespace mechanism. Polyinstantiated directories, |
|
such as per-user and/or per-security context instance of /tmp, /var/tmp or |
|
per-security context instance of a user's home directory, isolate user |
|
processes when working with these directories. Using unshare(), a PAM |
|
module can easily setup a private namespace for a user at login. |
|
Polyinstantiated directories are required for Common Criteria certification |
|
with Labeled System Protection Profile, however, with the availability |
|
of shared-tree feature in the Linux kernel, even regular Linux systems |
|
can benefit from setting up private namespaces at login and |
|
polyinstantiating /tmp, /var/tmp and other directories deemed |
|
appropriate by system administrators. |
|
|
|
2.2 unsharing of virtual memory and/or open files |
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
|
|
|
Consider a client/server application where the server is processing |
|
client requests by creating processes that share resources such as |
|
virtual memory and open files. Without unshare(), the server has to |
|
decide what needs to be shared at the time of creating the process |
|
which services the request. unshare() allows the server an ability to |
|
disassociate parts of the context during the servicing of the |
|
request. For large and complex middleware application frameworks, this |
|
ability to unshare() after the process was created can be very |
|
useful. |
|
|
|
3) Cost |
|
------- |
|
|
|
In order to not duplicate code and to handle the fact that unshare() |
|
works on an active task (as opposed to clone/fork working on a newly |
|
allocated inactive task) unshare() had to make minor reorganizational |
|
changes to copy_* functions utilized by clone/fork system call. |
|
There is a cost associated with altering existing, well tested and |
|
stable code to implement a new feature that may not get exercised |
|
extensively in the beginning. However, with proper design and code |
|
review of the changes and creation of an unshare() test for the LTP |
|
the benefits of this new feature can exceed its cost. |
|
|
|
4) Requirements |
|
--------------- |
|
|
|
unshare() reverses sharing that was done using clone(2) system call, |
|
so unshare() should have a similar interface as clone(2). That is, |
|
since flags in clone(int flags, void \*stack) specifies what should |
|
be shared, similar flags in unshare(int flags) should specify |
|
what should be unshared. Unfortunately, this may appear to invert |
|
the meaning of the flags from the way they are used in clone(2). |
|
However, there was no easy solution that was less confusing and that |
|
allowed incremental context unsharing in future without an ABI change. |
|
|
|
unshare() interface should accommodate possible future addition of |
|
new context flags without requiring a rebuild of old applications. |
|
If and when new context flags are added, unshare() design should allow |
|
incremental unsharing of those resources on an as needed basis. |
|
|
|
5) Functional Specification |
|
--------------------------- |
|
|
|
NAME |
|
unshare - disassociate parts of the process execution context |
|
|
|
SYNOPSIS |
|
#include <sched.h> |
|
|
|
int unshare(int flags); |
|
|
|
DESCRIPTION |
|
unshare() allows a process to disassociate parts of its execution |
|
context that are currently being shared with other processes. Part |
|
of execution context, such as the namespace, is shared by default |
|
when a new process is created using fork(2), while other parts, |
|
such as the virtual memory, open file descriptors, etc, may be |
|
shared by explicit request to share them when creating a process |
|
using clone(2). |
|
|
|
The main use of unshare() is to allow a process to control its |
|
shared execution context without creating a new process. |
|
|
|
The flags argument specifies one or bitwise-or'ed of several of |
|
the following constants. |
|
|
|
CLONE_FS |
|
If CLONE_FS is set, file system information of the caller |
|
is disassociated from the shared file system information. |
|
|
|
CLONE_FILES |
|
If CLONE_FILES is set, the file descriptor table of the |
|
caller is disassociated from the shared file descriptor |
|
table. |
|
|
|
CLONE_NEWNS |
|
If CLONE_NEWNS is set, the namespace of the caller is |
|
disassociated from the shared namespace. |
|
|
|
CLONE_VM |
|
If CLONE_VM is set, the virtual memory of the caller is |
|
disassociated from the shared virtual memory. |
|
|
|
RETURN VALUE |
|
On success, zero returned. On failure, -1 is returned and errno is |
|
|
|
ERRORS |
|
EPERM CLONE_NEWNS was specified by a non-root process (process |
|
without CAP_SYS_ADMIN). |
|
|
|
ENOMEM Cannot allocate sufficient memory to copy parts of caller's |
|
context that need to be unshared. |
|
|
|
EINVAL Invalid flag was specified as an argument. |
|
|
|
CONFORMING TO |
|
The unshare() call is Linux-specific and should not be used |
|
in programs intended to be portable. |
|
|
|
SEE ALSO |
|
clone(2), fork(2) |
|
|
|
6) High Level Design |
|
-------------------- |
|
|
|
Depending on the flags argument, the unshare() system call allocates |
|
appropriate process context structures, populates it with values from |
|
the current shared version, associates newly duplicated structures |
|
with the current task structure and releases corresponding shared |
|
versions. Helper functions of clone (copy_*) could not be used |
|
directly by unshare() because of the following two reasons. |
|
|
|
1) clone operates on a newly allocated not-yet-active task |
|
structure, where as unshare() operates on the current active |
|
task. Therefore unshare() has to take appropriate task_lock() |
|
before associating newly duplicated context structures |
|
|
|
2) unshare() has to allocate and duplicate all context structures |
|
that are being unshared, before associating them with the |
|
current task and releasing older shared structures. Failure |
|
do so will create race conditions and/or oops when trying |
|
to backout due to an error. Consider the case of unsharing |
|
both virtual memory and namespace. After successfully unsharing |
|
vm, if the system call encounters an error while allocating |
|
new namespace structure, the error return code will have to |
|
reverse the unsharing of vm. As part of the reversal the |
|
system call will have to go back to older, shared, vm |
|
structure, which may not exist anymore. |
|
|
|
Therefore code from copy_* functions that allocated and duplicated |
|
current context structure was moved into new dup_* functions. Now, |
|
copy_* functions call dup_* functions to allocate and duplicate |
|
appropriate context structures and then associate them with the |
|
task structure that is being constructed. unshare() system call on |
|
the other hand performs the following: |
|
|
|
1) Check flags to force missing, but implied, flags |
|
|
|
2) For each context structure, call the corresponding unshare() |
|
helper function to allocate and duplicate a new context |
|
structure, if the appropriate bit is set in the flags argument. |
|
|
|
3) If there is no error in allocation and duplication and there |
|
are new context structures then lock the current task structure, |
|
associate new context structures with the current task structure, |
|
and release the lock on the current task structure. |
|
|
|
4) Appropriately release older, shared, context structures. |
|
|
|
7) Low Level Design |
|
------------------- |
|
|
|
Implementation of unshare() can be grouped in the following 4 different |
|
items: |
|
|
|
a) Reorganization of existing copy_* functions |
|
|
|
b) unshare() system call service function |
|
|
|
c) unshare() helper functions for each different process context |
|
|
|
d) Registration of system call number for different architectures |
|
|
|
7.1) Reorganization of copy_* functions |
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
|
|
|
Each copy function such as copy_mm, copy_namespace, copy_files, |
|
etc, had roughly two components. The first component allocated |
|
and duplicated the appropriate structure and the second component |
|
linked it to the task structure passed in as an argument to the copy |
|
function. The first component was split into its own function. |
|
These dup_* functions allocated and duplicated the appropriate |
|
context structure. The reorganized copy_* functions invoked |
|
their corresponding dup_* functions and then linked the newly |
|
duplicated structures to the task structure with which the |
|
copy function was called. |
|
|
|
7.2) unshare() system call service function |
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
|
|
|
* Check flags |
|
Force implied flags. If CLONE_THREAD is set force CLONE_VM. |
|
If CLONE_VM is set, force CLONE_SIGHAND. If CLONE_SIGHAND is |
|
set and signals are also being shared, force CLONE_THREAD. If |
|
CLONE_NEWNS is set, force CLONE_FS. |
|
|
|
* For each context flag, invoke the corresponding unshare_* |
|
helper routine with flags passed into the system call and a |
|
reference to pointer pointing the new unshared structure |
|
|
|
* If any new structures are created by unshare_* helper |
|
functions, take the task_lock() on the current task, |
|
modify appropriate context pointers, and release the |
|
task lock. |
|
|
|
* For all newly unshared structures, release the corresponding |
|
older, shared, structures. |
|
|
|
7.3) unshare_* helper functions |
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |
|
|
|
For unshare_* helpers corresponding to CLONE_SYSVSEM, CLONE_SIGHAND, |
|
and CLONE_THREAD, return -EINVAL since they are not implemented yet. |
|
For others, check the flag value to see if the unsharing is |
|
required for that structure. If it is, invoke the corresponding |
|
dup_* function to allocate and duplicate the structure and return |
|
a pointer to it. |
|
|
|
7.4) Finally |
|
~~~~~~~~~~~~ |
|
|
|
Appropriately modify architecture specific code to register the |
|
new system call. |
|
|
|
8) Test Specification |
|
--------------------- |
|
|
|
The test for unshare() should test the following: |
|
|
|
1) Valid flags: Test to check that clone flags for signal and |
|
signal handlers, for which unsharing is not implemented |
|
yet, return -EINVAL. |
|
|
|
2) Missing/implied flags: Test to make sure that if unsharing |
|
namespace without specifying unsharing of filesystem, correctly |
|
unshares both namespace and filesystem information. |
|
|
|
3) For each of the four (namespace, filesystem, files and vm) |
|
supported unsharing, verify that the system call correctly |
|
unshares the appropriate structure. Verify that unsharing |
|
them individually as well as in combination with each |
|
other works as expected. |
|
|
|
4) Concurrent execution: Use shared memory segments and futex on |
|
an address in the shm segment to synchronize execution of |
|
about 10 threads. Have a couple of threads execute execve, |
|
a couple _exit and the rest unshare with different combination |
|
of flags. Verify that unsharing is performed as expected and |
|
that there are no oops or hangs. |
|
|
|
9) Future Work |
|
-------------- |
|
|
|
The current implementation of unshare() does not allow unsharing of |
|
signals and signal handlers. Signals are complex to begin with and |
|
to unshare signals and/or signal handlers of a currently running |
|
process is even more complex. If in the future there is a specific |
|
need to allow unsharing of signals and/or signal handlers, it can |
|
be incrementally added to unshare() without affecting legacy |
|
applications using unshare(). |
|
|
|
|