2

My /proc/meminfo shows about 500 MB is allocated as Shmem. I want to get more specific figures. I found an explanation here:

https://lists.kernelnewbies.org/pipermail/kernelnewbies/2013-July/008628.html

It includes tmpfs memory, SysV shared memory (from ipc/shm.c), POSIX shared memory (under /dev/shm [which is a tmpfs]), and shared anonymous mappings (from mmap of /dev/zero with MAP_SHARED: see call to shmem_zero_setup() from drivers/char/mem.c): whatever allocates pages through mm/shmem.c.

2-> as per the developer comments NR_SHMEM included tmpfs and GEM pages. what is GEM pages?

Ah yes, and the Graphics Execution Manager uses shmem for objects shared with the GPU: see use of shmem_read_mapping_page*() in drivers/gpu/drm/.

I have about

  • 50MB in user-visible tmpfs, found with df -h -t tmpfs.
  • 40MB (10,000 pages of 4096 bytes) in sysvipc shared memory, found with ipcs -mu.

I would like to get some more positive accounting, for what uses the 500MB! Is there a way to show total GEM allocations? (Or any other likely contributor).

I expect I have some GEM allocations, since I am running a graphical desktop on intel graphics hardware. My kernel version is 4.18.16-200.fc28.x86_64 (Fedora Workstation 28).

2 Answers 2

2

These appear in process maps as “drm mm object” or “i915”. You can see this in /proc/<pid>/maps; given the PID of a process using GEM/DRM:

awk '/(drm mm object)|i915/ { hypidx = index($1, "-"); from = substr($1, 1, hypidx - 1); to = substr($1, hypidx + 1); sum += strtonum("0x" to) - strtonum("0x" from) } END { print sum }' /proc/${PID}/maps

will show the total size of the allocated GEM buffers. Calculating the total can be done by feeding in all maps which contain at least one occurrence of “drm mm object” or “i915”; as root:

find /proc -maxdepth 2 -name maps |
xargs grep -E -l "(drm mm object)|i915" |
xargs awk '/(drm mm object)|i915/ { hypidx = index($1, "-"); sum += strtonum("0x" substr($1, hypidx + 1)) - strtonum("0x" substr($1, 1, hypidx - 1)) } END { print sum }'

(-maxdepth 2 is necessary to avoid looking at thread maps). Some additional inode-based de-duplication might be necessary.

3
  • Yeah. maps shows device and inode number, so you can de-duplicate as well. I don't see "drm mm object"s, I see some "/i915 (deleted)" though.
    – sourcejedi
    Nov 19, 2018 at 17:09
  • Indeed. There’s also “ttm swap” but that’s something different. Nov 19, 2018 at 17:20
  • This strategy did not suffice. I added an answer to explain why.
    – sourcejedi
    Nov 20, 2018 at 16:35
0

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')

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .