Edit: there is an interface for kernel debugging purposes only. It is only accessible by root
and is not stable. It might be rewritten, renamed, and/or misleading if you are not a kernel developer. (It might even be buggy, for all I know). But if you have a problem, it might be useful to know it's there.
My i915
driver gives me the information here:
$ sudo sh -c 'cat /sys/kernel/debug/dri/*/i915_gem_objects'
643 objects, 205852672 bytes
75 unbound objects, 7811072 bytes
568 bound objects, 198041600 bytes
16 purgeable objects, 5750784 bytes
16 mapped objects, 606208 bytes
13 huge-paged objects (2M, 4K) 123764736 bytes
13 display objects (globally pinned), 14954496 bytes
4294967296 [0x0000000010000000] gtt total
Supported page sizes: 2M, 4K
[k]contexts: 16 objects, 548864 bytes (0 active, 548864 inactive, 548864 global, 0 shared, 0 unbound)
systemd-logind: 324 objects, 97374208 bytes (0 active, 115798016 inactive, 23941120 global, 5246976 shared, 3858432 unbound)
Xwayland: 24 objects, 6995968 bytes (0 active, 12169216 inactive, 5283840 global, 5246976 shared, 110592 unbound)
gnome-shell: 246 objects, 89739264 bytes (26517504 active, 120852480 inactive, 63016960 global, 5242880 shared, 3629056 unbound)
Xwayland: 25 objects, 17309696 bytes (0 active, 22503424 inactive, 5304320 global, 5242880 shared, 90112 unbound)
Again, exercise caution. I notice mapped objects
only shows 600KB. I guess mapped
here means something different than I was expecting. For comparison, running the python script below to show the i915 objects mapped in user process' address spaces, I see a total of 70MB.
The line for systemd-logind
in my output is representing a second gnome-shell
instance, running on a different virtual console. If I switch over to a virtual console which has a text login running on it instead, then this file shows two systemd-logind
lines and no gnome-shell
lines :-).
Otherwise, the best you can do is find some of the shmem files by looking through all open files, in /proc/*/fd/
and /proc/*/map_files/
(or /proc/*/maps
).
With the right hacks, it appears possible to reliably identify which files belong to the hidden shmem filesystem(s).
Each shared memory object is a file with a name. And the names can be used to identify which kernel subsystem created the file.
The problem is this does not show all DRM / GEM allocations. DRM buffers can exist without being mapped, simply as a numeric handle. These are tied to the open DRM file they were created on. When the program crashes or is killed, the DRM file will be closed, and all its DRM handles will be cleaned up automatically. (Unless some other software keeps a copy of the file descriptor open, like this old bug.)
https://www.systutorials.com/docs/linux/man/7-drm-gem/
You can find open DRM files in /proc/*/fd/
, but they show as a zero-size file with zero blocks allocated.
For example, the output below shows a system where I cannot account for over 50% / 300MB of the Shmem
.
$ grep Shmem: /proc/meminfo
Shmem: 612732 kB
$ df -h -t tmpfs
Filesystem Size Used Avail Use% Mounted on
tmpfs 3.9G 59M 3.8G 2% /dev/shm
tmpfs 3.9G 2.5M 3.9G 1% /run
tmpfs 3.9G 0 3.9G 0% /sys/fs/cgroup
tmpfs 3.9G 9.0M 3.9G 1% /tmp
tmpfs 786M 20K 786M 1% /run/user/42
tmpfs 786M 8.0M 778M 2% /run/user/1000
tmpfs 786M 5.7M 781M 1% /run/user/1001
$ sudo ipcs -mu
------ Shared Memory Status --------
segments allocated 20
pages allocated 4226
pages resident 3990
pages swapped 0
Swap performance: 0 attempts 0 successes
All open files on hidden shmem filesystem(s):
$ sudo python3 ~/shm -s
15960 /SYSV*
79140 /i915
7912 /memfd:gdk-wayland
1164 /memfd:pulseaudio
104176
Here is a "before and after", logging out one of my two logged-in GNOME users. It might be explained if gnome-shell
had over 100MB of unmapped DRM buffers.
$ grep Shmem: /proc/meminfo
Shmem: 478780 kB
$ df -t tmpfs -h
Filesystem Size Used Avail Use% Mounted on
tmpfs 3.9G 4.0K 3.9G 1% /dev/shm
tmpfs 3.9G 2.5M 3.9G 1% /run
tmpfs 3.9G 0 3.9G 0% /sys/fs/cgroup
tmpfs 3.9G 276K 3.9G 1% /tmp
tmpfs 786M 20K 786M 1% /run/user/42
tmpfs 786M 8.0M 778M 2% /run/user/1000
tmpfs 786M 5.7M 781M 1% /run/user/1001
$ sudo ./shm -s
80 /SYSV*
114716 /i915
1692 /memfd:gdk-wayland
1156 /memfd:pulseaudio
117644
$ grep Shmem: /proc/meminfo
Shmem: 313008 kB
$ df -t tmpfs -h
Filesystem Size Used Avail Use% Mounted on
tmpfs 3.9G 4.0K 3.9G 1% /dev/shm
tmpfs 3.9G 2.1M 3.9G 1% /run
tmpfs 3.9G 0 3.9G 0% /sys/fs/cgroup
tmpfs 3.9G 204K 3.9G 1% /tmp
tmpfs 786M 20K 786M 1% /run/user/42
tmpfs 786M 6.8M 780M 1% /run/user/1000
$ sudo ./shm -s
40 /SYSV*
88496 /i915
1692 /memfd:gdk-wayland
624 /memfd:pulseaudio
90852
Python script to generate the above output:
#!/bin/python3
# Reads Linux /proc. No str, all bytes.
import sys
import os
import stat
import glob
import collections
import math
# File.
# 'name' is first name encountered, we don't track hardlinks.
Inode = collections.namedtuple('Inode', ['name', 'bytes', 'pids'])
# inode number -> Inode object
inodes = dict()
# pid -> program name
pids = dict()
# filename -> list() of Inodes
filenames = dict()
def add_file(pid, proclink):
try:
vfs = os.statvfs(proclink)
# The tmpfs which reports 0 blocks is an internal shm mount
# python doesn't admit f_fsid ...
if vfs.f_blocks != 0:
return
filename = os.readlink(proclink)
# ... but all the shm files are deleted (hack :)
if not filename.endswith(b' (deleted)'):
return
filename = filename[:-10]
# I tried a consistency check that all our st_dev are the same
# but actually there can be more than one internal shm mount!
# i915 added a dedicated "gemfs" so they could control mount options.
st = os.stat(proclink)
# hack the second: ignore deleted character devices from devpts
if stat.S_ISCHR(st.st_mode):
return
# Read process name succesfully,
# before we record file owned by process.
if pid not in pids:
pids[pid] = open(b'/proc/' + pid + b'/comm', 'rb').read()[:-1]
if st.st_ino not in inodes:
inode_pids = set()
inode_pids.add(pid)
inode = Inode(name=filename,
bytes=st.st_blocks * 512,
pids=inode_pids)
inodes[st.st_ino] = inode
else:
inode = inodes[st.st_ino]
inode.pids.add(pid)
# Group SYSV shared memory objects.
# There could be many, and the rest of the name is just a numeric ID
if filename.startswith(b'/SYSV'):
filename = b'/SYSV*'
filename_inodes = filenames.setdefault(filename, set())
filename_inodes.add(st.st_ino)
except FileNotFoundError:
# File disappeared (race condition).
# Don't bother to distinguish "file closed" from "process exited".
pass
summary = False
if sys.argv[1:]:
if sys.argv[1:] == ['-s']:
summary = True
else:
print("Usage: {0} [-s]".format(sys.argv[0]))
sys.exit(2)
os.chdir(b'/proc')
for pid in glob.iglob(b'[0-9]*'):
for f in glob.iglob(pid + b'/fd/*'):
add_file(pid, f)
for f in glob.iglob(pid + b'/map_files/*'):
add_file(pid, f)
def pid_name(pid):
return pid + b'/' + pids[pid]
def kB(b):
return str(math.ceil(b / 1024)).encode('US-ASCII')
out = sys.stdout.buffer
total = 0
for (filename, filename_inodes) in sorted(filenames.items(), key=lambda p: p[0]):
filename_bytes = 0
for ino in filename_inodes:
inode = inodes[ino]
filename_bytes += inode.bytes
if not summary:
out.write(kB(inode.bytes))
out.write(b'\t')
#out.write(str(ino).encode('US-ASCII'))
#out.write(b'\t')
out.write(inode.name)
out.write(b'\t')
out.write(b' '.join(map(pid_name, inode.pids)))
out.write(b'\n')
total += filename_bytes
out.write(kB(filename_bytes))
out.write(b'\t')
out.write(filename)
out.write(b'\n')
out.write(kB(total))
out.write(b'\n')