
After weeks of testing different memory allocators to check memory usage by desktop Linux programs, mainly libtcmalloc.so
, libjemalloc.so
, and libmimalloc.so
, I identified significant improvements. However, loading these libs with LD_PRELOAD
has the side effect that child processes also switch the default memory allocator, which makes it unfeasible for use in some desktop programs.
Besides this library, I count on your help to discover which programs have better memory management using another malloc
. You can help by sending the information as an issue or on our forum at www.biglinux.com.br/forum.
It is also a good idea to inform the project's maintainer about this improvement, because with a few adjustments, the program could use another malloc
by default, and thus we help make the Linux desktop more efficient. The main point to check is not the initial memory usage, but to verify if during prolonged use the programs are freeing memory or are constantly increasing their consumption.
Some programs I tested and that showed great improvement in memory management, which in some situations with prolonged use multiply their initial memory usage several times, and with other allocators maintain memory usage close to the initial usage:
- Nemo File Manager
- Thunar File Manager
- Pamac Manager
- Gnome Shell
- Cinnamon
With libchildenv
, we can use LD_PRELOAD
to load another memory allocator into the process we are going to run and not pass this change to child processes. But this is just one use case; we can do this with any environment variable, including passing environment variables to child processes without activating them in the process we are running.
libchildenv
is a shared library (.so) for Linux that intercepts exec*
family system calls to modify the environment that is passed to new processes. The main objective is to prevent the inheritance of sensitive or configuration environment variables, such as LD_PRELOAD
, by child processes.
The source code only depends on glibc
and libdl
, requiring no other dependencies.
gcc -shared -fPIC -o libchildenv.so libchildenv.c -ldl
To make the library globally available without needing to specify its full path, move it to a standard library directory.
-
Move the library (e.g., to
/usr/local/lib
):sudo mv libchildenv.so /usr/local/lib/
-
Update the dynamic linker's cache:
sudo ldconfig
After these steps, you can refer to the library in LD_PRELOAD
simply as libchildenv.so
.
Operation is configured through two environment variables:
LD_PRELOAD
: Must containlibchildenv.so
, optionally along with other libraries (e.g.,libchildenv.so:libtcmalloc.so
).CHILD_ENV_RULES
: Contains the environment modification rules.
Rules are specified in a single, comma-separated string.
- To remove a variable:
VARIABLE_NAME
- To set/overwrite a variable:
VARIABLE_NAME=VALUE
The easiest way to run a program with an isolated memory allocator is to use the provided libchildenv.sh
script. This script sets up the correct environment for libmimalloc
, libjemalloc
, or libtcmalloc
with libchildenv
so that only the target program uses the allocator, and child processes do not inherit it.
libchildenv.sh tcmalloc nemo
libchildenv.sh jemalloc some_other_program
libchildenv.sh mimalloc some_other_program
libchildenv.sh verify nemo
This prints the loaded allocator and childenv libraries for the newest nemo
process, matching the verification style below.
This example demonstrates how libchildenv
removes the USER
environment variable from a child process.
-
Open terminal.
-
In Terminal, start a bash shell with
libchildenv
loaded and the rule to addMY_TEST
not to main process, only to child:CHILD_ENV_RULES="MY_TEST=biglinux" LD_PRELOAD=libchildenv.so bash
You are now in a new shell where
libchildenv
is active. -
Still in Terminal, verify that the
MY_TEST
variable exists:echo $MY_TEST
Expected output: (a blank line)
-
Now, start a child process (another
bash
shell) from this shell:bash
-
In this new shell (the child process), check the
USER
variable:echo $MY_TEST
Expected output:
biglinux
The variable was successfully removed from the child's environment.
It is common to use LD_PRELOAD
to inject memory allocation libraries like tcmalloc
, jemalloc
, or mimalloc
to improve an application's performance. However, this causes all child processes (terminals, text editors, etc.) to also inherit this configuration, which is often undesirable. libchildenv
solves this.
Objective: Run the nemo
file manager with tcmalloc
, but prevent any program opened from nemo
from inheriting LD_PRELOAD
and tcmalloc
's variables.
# The rules remove LD_PRELOAD and the tcmalloc configuration variable.
CHILD_ENV_RULES="LD_PRELOAD,TCMALLOC_AGGRESSIVE_DECOMMIT" \
LD_PRELOAD="libchildenv.so:libtcmalloc.so" \
TCMALLOC_AGGRESSIVE_DECOMMIT=1 \
nemo
CHILD_ENV_RULES="LD_PRELOAD" \
LD_PRELOAD="libchildenv.so:libjemalloc.so" \
some_other_program
CHILD_ENV_RULES="LD_PRELOAD,MIMALLOC_PURGE_DELAY" \
LD_PRELOAD="libchildenv.so:libmimalloc.so" \
MIMALLOC_PURGE_DELAY=0 \
some_other_program
We can confirm that the nemo
process from the previous example is using the libraries, but its children are not.
-
Get the Process ID (PID) of Nemo:
pgrep nemo
Will return a number, like
12345
-
List the mapped libraries in the parent process (nemo): Use the
lsof
(List Open Files) command to inspect the loaded libraries.# The $(pgrep nemo) command inserts the PID directly. lsof -p $(pgrep nemo) | grep -E 'tcmalloc|childenv|libmimalloc|libjemalloc'
Expected Output (confirms the parent loaded the libraries):
nemo 12345 your_user mem REG 8,1 123456 /usr/local/lib/libchildenv.so nemo 12345 your_user mem REG 8,1 789012 /usr/lib/x86_64-linux-gnu/libtcmalloc.so.4
-
Verify the Child Process (The Final Proof):
- Inside the
nemo
window, open a child process. - In a separate terminal, find the child process's PID (e.g.,
pgrep gedit
) and check its libraries:
# Replace 'gedit' with the name of the process you started lsof -p $(pgrep gedit) | grep -E 'tcmalloc|childenv|libmimalloc|libjemalloc'
Expected Output:
(No output)
The absence of output proves that the child process did not inherit
LD_PRELOAD
and therefore did not loadlibtcmalloc.so
orlibchildenv.so
. The objective was achieved. - Inside the
Currently, libchildenv
intercepts the following glibc
functions:
execve
execvpe
execvp
- Loading: The library is loaded into a target process before any other by setting
LD_PRELOAD=libchildenv.so
. - Hooking:
libchildenv
provides its own implementations of theexec*
family functions (execve
,execvp
). When the target process tries to create a child, thelibchildenv
implementation runs first. - Resolving the Original Function: Inside the hooked function, a pointer to the original
glibc
exec*
function is obtained usingdlsym(RTLD_NEXT, "execve")
. - Processing Rules: The library reads and parses the content of the
CHILD_ENV_RULES
environment variable. - Building the New Environment: A new environment vector (
char*[]
) is allocated in memory. The library iterates over the parent's environment and applies the rules to build the new vector, unsetting or overwriting variables as specified. - Execution: The original
exec*
function is finally called, but with the new, modified environment vector. If the call fails, the allocated memory is freed to prevent memory leaks.
libchildenv
currently intercepts the following glibc
functions:
execve
execvpe
execvp