SIer だけど技術やりたいブログ

Linux procfs 徹底入門

linux

これは Linux Advent Calendar 2019の 15 日目の記事です。procfs について勉強したことをまとめます。

検証環境

CentOS 8 を利用する。

]# cat /etc/redhat-release
CentOS Linux release 8.0.1905 (Core)
]# uname -a
Linux localhost.localdomain 4.18.0-80.el8.x86_64 #1 SMP Tue Jun 4 09:19:46 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux

procfs とは

疑似ファイルシステムのひとつ。 ディスク上に実体は存在せず、メモリから情報を取得する。

カーネルだけが知っている情報 (例えばシステム全体のロードアベレージ/CPU負荷/メモリ利用状況や、プロセスごとの情報)が取得できる。 専用のシステムコールを設けるのではなくファイルシステムにすることで、 システムコールの追加/変更をせずに新たな情報が追加できる。

基本的には読み込み専用のファイルが多いが、一部に書き込み可能なファイルも存在する。

ファイルシステム全般については前回調べた。

Linux ファイルシステム 徹底入門 - SIerだけど技術やりたいブログwww.kimullaa.com

代表的なファイル

Wikipedia や man proc が詳しい。
参考 Wikipedia procfs
参考 man page of proc

よく使いそうなものに内容を絞ってさらっと説明する。

システム全体

/proc/cpuinfo

cpu のコア数や CPU モデルなどの基礎情報が取れる。

]# cat /proc/cpuinfo
processor       : 0
vendor_id       : GenuineIntel
cpu family      : 6
model           : 60
model name      : Intel(R) Core(TM) i5-4690 CPU @ 3.50GHz
stepping        : 3
microcode       : 0xffffffff
cpu MHz         : 3499.998
cache size      : 6144 KB
physical id     : 0
siblings        : 4
core id         : 0
cpu cores       : 4
apicid          : 0
initial apicid  : 0
fpu             : yes
fpu_exception   : yes
cpuid level     : 13
wp              : yes
flags           : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ss ht syscall nx pdpe1gb rdtscp lm constant_tsc rep_good nopl xtopology cpuid pni pclmulqdq ssse3 fma cx16 pcid sse4_1 sse4_2 movbe popcnt aes xsave avx f16c rdrand hypervisor lahf_lm abm invpcid_single pti ssbd ibrs ibpb stibp fsgsbase bmi1 avx2 smep bmi2 erms invpcid xsaveopt flush_l1d arch_capabilities
...
/proc/meminfo

メモリに関する情報を表示できる。

]# cat /proc/meminfo
MemTotal:        7902220 kB
MemFree:          122836 kB
MemAvailable:     558992 kB
Buffers:              96 kB
Cached:           639408 kB
SwapCached:          456 kB
Active:          1020052 kB
Inactive:         694488 kB
Active(anon):     918604 kB
Inactive(anon):   160244 kB
Active(file):     101448 kB
Inactive(file):   534244 kB
Unevictable:           0 kB
...
/proc/cmdline

カーネルの起動引数を表示できる。

]# cat /proc/cmdline
BOOT_IMAGE=(hd0,gpt2)/vmlinuz-4.18.0-80.11.2.el8_0.x86_64 root=/dev/mapper/cl-root ro crashkernel=auto resume=/dev/mapper/cl-swap rd.lvm.lv=cl/root rd.lvm.lv=cl/swap rhgb quiet
/proc/filesystems

サポートされているファイルシステム(カーネルに組み込まれているものかモジュールでロードされているもの)の一覧を表示できる。

]# cat /proc/filesystems
nodev   sysfs
nodev   rootfs
nodev   ramfs
nodev   bdev
nodev   proc
nodev   cpuset
nodev   cgroup
nodev   cgroup2
nodev   tmpfs
nodev   devtmpfs
nodev   configfs
nodev   debugfs
nodev   tracefs
nodev   securityfs
nodev   sockfs
nodev   dax
nodev   bpf
nodev   pipefs
nodev   hugetlbfs
nodev   devpts
nodev   autofs
nodev   pstore
nodev   efivarfs
nodev   mqueue
nodev   selinuxfs
        xfs
        ext3
        ext2
        ext4
        vfat
/proc/modules

カーネルにロードされているモジュールの一覧を表示できる。

]# cat /proc/modules
nf_tables_set 32768 5 - Live 0xffffffffc0676000
nft_fib_inet 16384 1 - Live 0xffffffffc0671000
nft_fib_ipv4 16384 1 nft_fib_inet, Live 0xffffffffc066c000
nft_fib_ipv6 16384 1 nft_fib_inet, Live 0xffffffffc0667000
nft_fib 16384 3 nft_fib_inet,nft_fib_ipv4,nft_fib_ipv6, Live 0xffffffffc0662000
nft_reject_inet 16384 4 - Live 0xffffffffc065d000
nf_reject_ipv4 16384 1 nft_reject_inet, Live 0xffffffffc0658000
nf_reject_ipv6 16384 1 nft_reject_inet, Live 0xffffffffc0653000
nft_reject 16384 1 nft_reject_inet, Live 0xffffffffc064e000
nft_ct 20480 7 - Live 0xffffffffc0648000
nft_chain_nat_ipv6 16384 6 - Live 0xffffffffc0643000
nf_conntrack_ipv6 20480 8 - Live 0xffffffffc0639000
nf_defrag_ipv6 20480 1 nf_conntrack_ipv6, Live 0xffffffffc062f000
...
/proc/mounts

現在マウントされているマウントポイントの一覧を表示できる。

]# cat /proc/mounts
sysfs /sys sysfs rw,seclabel,nosuid,nodev,noexec,relatime 0 0
proc /proc proc rw,nosuid,nodev,noexec,relatime 0 0
devtmpfs /dev devtmpfs rw,seclabel,nosuid,size=3936592k,nr_inodes=984148,mode=755 0 0
securityfs /sys/kernel/security securityfs rw,nosuid,nodev,noexec,relatime 0 0
tmpfs /dev/shm tmpfs rw,seclabel,nosuid,nodev 0 0
devpts /dev/pts devpts rw,seclabel,nosuid,noexec,relatime,gid=5,mode=620,ptmxmode=000 0 0
tmpfs /run tmpfs rw,seclabel,nosuid,nodev,mode=755 0 0
tmpfs /sys/fs/cgroup tmpfs ro,seclabel,nosuid,nodev,noexec,mode=755 0 0
cgroup /sys/fs/cgroup/systemd cgroup rw,seclabel,nosuid,nodev,noexec,relatime,xattr,release_agent=/usr/lib/systemd/systemd-cgroups-agent,name=systemd 0 0
pstore /sys/fs/pstore pstore rw,seclabel,nosuid,nodev,noexec,relatime 0 0
efivarfs /sys/firmware/efi/efivars efivarfs rw,nosuid,nodev,noexec,relatime 0 0
bpf /sys/fs/bpf bpf rw,nosuid,nodev,noexec,relatime,mode=700 0 0
...
/proc/uptime

システムの起動時間(1こめ)と idle プロセスが消費した時間の合計(2こめ)を表示できる。

]# cat /proc/uptime
2008.01 3391.90
/proc/loadavg

1,5,15 分のロードアベレージを表示できる。 2/168 は、現在スケジュールされているプロセスやスレッドの数/システム全体のプロセスやスレッドの数。 一番最後は直近で生成されたプロセスのid。

]# cat /proc/loadavg
1.06 2.82 4.14 2/168 25833

プロセスごと

/proc/[pid]ではプロセスごとの情報が取れる。/tmp ディレクトリで sleep を実行しておき、このプロセスの情報を確認する。

]# cd /tmp
]# sleep 1000 &
[1] 4048
/proc/[pid]/comm

プロセス実行時のコマンドを表示する。

]# cat /proc/4048/comm
sleep
/proc/[pid]/cmdline

プロセス実行時のコマンドと引数を表示する。

# cat /proc/4048/cmdline
sleep1000
/proc/[pid]/cwd

プロセスのワーキングディレクトリのシンボリックリンク。

]# ls -ald /proc/4048/cwd
lrwxrwxrwx. 1 root root 0 12月 14 08:06 /proc/4048/cwd -> /tmp
/proc/[pid]/environ

プロセスの環境変数を表示する。

]# cat /proc/4048/environ
LS_COLORS=rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:mi=01;05;37;41:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arc=01;31:*.arj=01;31:*.taz=01;31:*.lha=01;31:*.lz4=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.tzo=01;31:*.t7z=01;31:*.zip=01;31:*.z=01;31:*.dz=01;31:*.gz=01;31:*.lrz=01;31:*.lz=01;31:*.lzo=01;31:*.xz=01;31:*.zst=01;31:*.tzst=01;31:*.bz2=01;31:*.bz=01;31:*.tbz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.alz=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.cab=01;31:*.wim=01;31:*.swm=01;31:*.dwm=01;31:*.esd=01;31:*.jpg=01;35:*.jpeg=01;35:*.mjpg=01;35:*.mjpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.webm=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=01;36:*.au=01;36:*.flac=01;36:*.m4a=01;36:*.mid=01;36:*.midi=01;36:*.mka=01;36:*.mp3=01;36:*.mpc=01;36:*.ogg=01;36:*.ra=01;36:*.wav=01;36:*.oga=01;36:*.opus=01;36:*.spx=01;36:*.xspf=01;36:SSH_CONNECTION=192.168.11.104 57035 192.168.11.111 22MODULES_RUN_QUARANTINE=LD_LIBRARY_PATHLANG=ja_JP.UTF-8HISTCONTROL=ignoredupsHOSTNAME=localhost.localdomainXDG_SESSION_ID=4MODULES_CMD=/usr/share/Modules/libexec/modulecmd.tclUSER=rootENV=/usr/share/Modules/init/profile.shSELINUX_ROLE_REQUESTED=PWD=/tmpHOME=/rootSSH_CLIENT=192.168.11.104 57035 22SELINUX_LEVEL_REQUESTED=BASH_ENV=/usr/share/Modules/init/bashLOADEDMODULES=SSH_TTY=/dev/pts/1MAIL=/var/spool/mail/rootTERM=xtermSHELL=/bin/bashSELINUX_USE_CURRENT_RANGE=SHLVL=1MODULEPATH=/usr/share/Modules/modulefiles:/etc/modulefiles:/usr/share/modulefilesLOGNAME=rootDBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/0/busXDG_RUNTIME_DIR=/run/user/0MODULEPATH_modshare=/usr/share/modulefiles:1:/etc/modulefiles:1:/usr/share/Modules/modulefiles:1PATH=/usr/share/Modules/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/root/binMODULESHOME=/usr/share/ModulesHISTSIZE=1000LESSOPEN=||/usr/bin/lesspipe.sh %sBASH_FUNC_module%%=() {  _moduleraw "$@" 2>&1
}BASH_FUNC_switchml%%=() {  typeset swfound=1;
 if [ "${MODULES_USE_COMPAT_VERSION:-0}" = '1' ]; then
 typeset swname='main';
 if [ -e /usr/share/Modules/libexec/modulecmd.tcl ]; then
 typeset swfound=0;
 unset MODULES_USE_COMPAT_VERSION;
 fi;
 else
 typeset swname='compatibility';
 ...
/proc/[pid]/exe

プロセスを起動したコマンドのフルパスを表示する。

]# ls -ald /proc/4048/exe
lrwxrwxrwx. 1 root root 0 12月 14 08:06 /proc/4048/exe -> /usr/bin/sleep
/proc/[pid]/fd/

プロセスがオープンしているファイルを表示する。 0(stdin), 1(stdout), 2(stderr)。

]# ls -al /proc/4048/fd
合計 0
dr-x------. 2 root root  0 1214 08:06 .
dr-xr-xr-x. 9 root root  0 1214 08:05 ..
lrwx------. 1 root root 64 1214 08:09 0 -> /dev/pts/1
lrwx------. 1 root root 64 1214 08:09 1 -> /dev/pts/1
lrwx------. 1 root root 64 1214 08:09 2 -> /dev/pts/1
/proc/[pid]/maps/

プロセス空間のメモリを表示する。

# cat /proc/4048/maps
55643566b000-556435672000 r-xp 00000000 fd:00 100669977                  /usr/bin/sleep
556435872000-556435873000 r--p 00007000 fd:00 100669977                  /usr/bin/sleep
556435873000-556435874000 rw-p 00008000 fd:00 100669977                  /usr/bin/sleep
556436829000-55643684a000 rw-p 00000000 00:00 0                          [heap]
7fa44d2e7000-7fa44d4a1000 r-xp 00000000 fd:00 1789                       /usr/lib64/libc-2.28.so
7fa44d4a1000-7fa44d6a1000 ---p 001ba000 fd:00 1789                       /usr/lib64/libc-2.28.so
7fa44d6a1000-7fa44d6a5000 r--p 001ba000 fd:00 1789                       /usr/lib64/libc-2.28.so
7fa44d6a5000-7fa44d6a7000 rw-p 001be000 fd:00 1789                       /usr/lib64/libc-2.28.so
7fa44d6a7000-7fa44d6ab000 rw-p 00000000 00:00 0
7fa44d6ab000-7fa44d6d3000 r-xp 00000000 fd:00 1782                       /usr/lib64/ld-2.28.so
...
/proc/[pid]/root

プロセスのルートディレクトリを表示する。chroot したときは別の場所を指す。(dockerとか)

]# ls  /proc/4048/root/
bin   dev  home  lib64  mnt  proc  run   srv  tmp  var
boot  etc  lib   media  opt  root  sbin  sys  usr

試しにコンテナを立ち上げてみる。(CentOS 8 からは podman が使えます)

// コンテナを立ち上げてファイルを作る
]# podman run -it centos bash
Trying to pull registry.redhat.io/centos:latest...Failed
Trying to pull quay.io/centos:latest...Failed
Trying to pull docker.io/centos:latest...Getting image source signatures
Copying blob 729ec3a6ada3: 67.50 MiB / 68.21 MiB [=============================]
Copying blob 729ec3a6ada3: 68.21 MiB / 68.21 MiB [==========================] 8s
Copying config 0f3e07c0138f: 2.13 KiB / 2.13 KiB [==========================] 0s
Writing manifest to image destination
Storing signatures
[root@13f3333aff33 /]# touch hello_from_podman

// 別ターミナル
// ホストのルートには反映されない
]# ls /
bin  boot  dev  etc  home  lib  lib64  media  mnt  opt  proc  root  run  sbin  srv  sys  tmp  usr  var
// ホストでコンテナの pid を確認して ls するとコンテナ内のルートが見える
]# ls /proc/6978/root/
bin  dev  etc  hello_from_podman  home  lib  lib64  lost+found  media  mnt  opt  proc  root  run  sbin  srv  sys  tmp  usr  var
/proc/[pid]/stat

色々取れる。CPU 割り当て時間やメモリ使用率など。

]# cat  /proc/4048/stat
4048 (sleep) S 31244 4048 31244 34817 25721 1077952512 135 0 0 0 0 0 0 0 20 0 1 0 624150 5591040 73 18446744073709551615 93888881012736 93888881040864 140722657125040 0 0 0 0 0 0 1 0 0 17 2 0 0 0 0 0 93888883141456 93888883142784 93888899616768 140722657132307 140722657132318 140722657132318 140722657136617 0
/proc/[pid]/status

プロセスの状態が取れる。 例えばタスクが実行中か寝てるか、親プロセスidは何か、など。

]# cat  /proc/4048/status  | head
Name:   sleep
Umask:  0022
State:  S (sleeping)
Tgid:   4048
Ngid:   0
Pid:    4048
PPid:   31244
TracerPid:      0
Uid:    0       0    0  0
Gid:    0       0    0  0

ユーザ空間ライブラリでの利用

メモリ使用率を題材に、ユーザ空間ライブラリがどのように /proc を利用しているか確認する。メモリについての基本的な情報は次を参考にしてください。

Linux メモリ管理 徹底入門(プロセス編) - SIerだけど技術やりたいブログwww.kimullaa.com

今回調べる対象は free。 他の人もよく題材にしてるようだが、自分の勉強のためなので関係なく進める。
【RHEL】linuxメモリのfreeとmeminfoの関係を図解し利用率の計算方法を説明してみる
参考 free(1)のtotalとかusedなどの各項目をカーネルの方から見てみる
参考 /proc/meminfo の Cached

]# free
              total        used        free      shared  buff/cache   available
Mem:        7902032     7023984      544584        8524      333464      624448
Swap:       6713340       11276     6702064

ソースコードをダウンロードし、コードを読み進める。
参考 Linux rpmパッケージのコードリーディングをしたい

すると free の出力部分は次のようになっていた。

int main(int argc, char **argv)
{
    ...
  
    do {

        meminfo();
        
        /* Translation Hint: You can use 9 character words in
         * the header, and the words need to be right align to
         * beginning of a number. */
        if (flags & FREE_WIDE) {
            printf(_("              total        used        free      shared     buffers       cache   available"));
        } else {
            printf(_("              total        used        free      shared  buff/cache   available"));
        }
        printf("\n");
        printf("%-7s", _("Mem:"));
        printf(" %11s", scale_size(kb_main_total, flags, args));
        printf(" %11s", scale_size(kb_main_used, flags, args));
        printf(" %11s", scale_size(kb_main_free, flags, args));
        printf(" %11s", scale_size(kb_main_shared, flags, args));
        if (flags & FREE_WIDE) {
            printf(" %11s", scale_size(kb_main_buffers, flags, args));
            printf(" %11s", scale_size(kb_main_cached, flags, args));
        } else {
            printf(" %11s", scale_size(kb_main_buffers+kb_main_cached, flags, args));
        }
        printf(" %11s", scale_size(kb_main_available, flags, args));

では kb_main_total などの変数がどこで定義されているかというと、 次の部分で /proc/meminfo を読み込んでいた。

#define MEMINFO_FILE "/proc/meminfo"
static int meminfo_fd = -1;
...

void meminfo(void){
  ...
  static const mem_table_struct mem_table[] = {
  {"Active",       &kb_active},       // important
  {"Active(file)", &kb_active_file},
  {"AnonPages",    &kb_anon_pages},
  {"Bounce",       &kb_bounce},
  {"Buffers",      &kb_main_buffers}, // important
  {"Cached",       &kb_page_cache},  // important
  {"CommitLimit",  &kb_commit_limit},
  {"Committed_AS", &kb_committed_as},
  {"Dirty",        &kb_dirty},        // kB version of vmstat nr_dirty
  {"HighFree",     &kb_high_free},
  {"HighTotal",    &kb_high_total},
  {"Inact_clean",  &kb_inact_clean},
  {"Inact_dirty",  &kb_inact_dirty},
  {"Inact_laundry",&kb_inact_laundry},
  {"Inact_target", &kb_inact_target},
  {"Inactive",     &kb_inactive},     // important
  {"Inactive(file)",&kb_inactive_file},
  {"LowFree",      &kb_low_free},
  {"LowTotal",     &kb_low_total},
  {"Mapped",       &kb_mapped},       // kB version of vmstat nr_mapped
  {"MemAvailable", &kb_main_available}, // important
  {"MemFree",      &kb_main_free},    // important
  {"MemTotal",     &kb_main_total},   // important
  {"NFS_Unstable", &kb_nfs_unstable},
  {"PageTables",   &kb_pagetables},   // kB version of vmstat nr_page_table_pages
  {"ReverseMaps",  &nr_reversemaps},  // same as vmstat nr_page_table_pages
  {"SReclaimable", &kb_slab_reclaimable}, // "slab reclaimable" (dentry and inode structures)
  {"SUnreclaim",   &kb_slab_unreclaimable},
  {"Shmem",        &kb_main_shared},  // kernel 2.6.32 and later
  {"Slab",         &kb_slab},         // kB version of vmstat nr_slab
  {"SwapCached",   &kb_swap_cached},
  {"SwapFree",     &kb_swap_free},    // important
  {"SwapTotal",    &kb_swap_total},   // important
  {"VmallocChunk", &kb_vmalloc_chunk},
  {"VmallocTotal", &kb_vmalloc_total},
  {"VmallocUsed",  &kb_vmalloc_used},
  {"Writeback",    &kb_writeback},    // kB version of vmstat nr_writeback
  }; 
  ...
  // /proc/meminfo をバッファに読み込み、 mem_table の key と一致する項目に値を設定する
  FILE_TO_BUF(MEMINFO_FILE,meminfo_fd);
  ...
  head = buf;
  for(;;){
    tail = strchr(head, ':');
    if(!tail) break;
    *tail = '\0';
    if(strlen(head) >= sizeof(namebuf)){
      head = tail+1;
      goto nextline;
    }
    strcpy(namebuf,head);
    found = bsearch(&findme, mem_table, mem_table_count,
        sizeof(mem_table_struct), compare_mem_table_structs
    );
    head = tail+1;
    if(!found) goto nextline;
    *(found->slot) = (unsigned long)strtoull(head,&tail,10);
nextline:
    tail = strchr(head, '\n');
    if(!tail) break;
    head = tail+1;
  }
  ...

また一部の変数(kb_main_used など)は /proc/meminfoに存在しないが、 そういう項目は計算して定義していた。

  ...
  mem_used = kb_main_total - kb_main_free - kb_main_cached - kb_main_buffers;
  if (mem_used < 0)
    mem_used = kb_main_total - kb_main_free;
  kb_main_used = (unsigned long)mem_used;

まあソースコードなんて見なくても man に書いてるんですけどね…

total  Total installed memory (MemTotal and SwapTotal in /proc/meminfo)

used   Used memory (calculated as total - free - buffers - cache)

free   Unused memory (MemFree and SwapFree in /proc/meminfo)

shared Memory used (mostly) by tmpfs (Shmem in /proc/meminfo)

buffers
       Memory used by kernel buffers (Buffers in /proc/meminfo)

cache  Memory used by the page cache and slabs (Cached and SReclaimable in /proc/meminfo)

buff/cache
       Sum of buffers and cache

available
       Estimation of how much memory is available for starting new applications, without swapping. Unlike the data provided by the cache  or  free
       fields, this field takes into account page cache and also that not all reclaimable memory slabs will be reclaimed due to items being in use
       (MemAvailable in /proc/meminfo, available on kernels 3.14, emulated on kernels 2.6.27+, otherwise the same as free)

vmstat の cpu 使用率やロードアベレージなどについても、 上記と似た流れで /proc 配下のファイルを読み込んでデータを適宜加工して表示している。 これについてはソースコードまでは説明せず、 strace で ファイルが open されていることで代用する。

]# strace vmstat 2>&1 | grep "/proc/stat"
openat(AT_FDCWD, "/proc/stat", O_RDONLY) = 4

]# strace uptime 2>&1 | grep "/proc/uptime"
openat(AT_FDCWD, "/proc/uptime", O_RDONLY) = 3

ということで、リソース情報を表示するユーザ空間のライブラリは、 大体が /proc を利用していることが分かった。 つまり /proc とその使われ方を理解すれば、各種コマンドの意味や妥当性を判断できるようになる。

procfs を見てみよう

そうと分かれば procfs を読んでみたくなるのが心情。 先ほどと同様の方法でカーネルソースをダウンロードする。
参考 Linux rpmパッケージのコードリーディングをしたい

procfs はfs/proc/にある。

]# ls fs/proc
Kconfig   base.c      cpuinfo.c  fd.h       internal.h    kmsg.c     namespaces.c  proc_net.c     root.c      stat.c        thread_self.c  version.c
Makefile  cmdline.c   devices.c  generic.c  interrupts.c  loadavg.c  nommu.c       proc_sysctl.c  self.c      task_mmu.c    uptime.c       vmcore.c
array.c   consoles.c  fd.c       inode.c    kcore.c       meminfo.c  page.c        proc_tty.c     softirqs.c  task_nommu.c  util.c

実装は他のファイルシステムと同様だが、簡単に作成できるように、 大部分の処理がfs/proc/generic.cにまとめられているみたい。(f_ops の設定とかもろもろ)

最終的には open 時に次の処理が実行される。

static int meminfo_proc_show(struct seq_file *m, void *v)
{
        struct sysinfo i;
        unsigned long committed;
        long cached;
        long available;
        unsigned long pages[NR_LRU_LISTS];
        int lru;

        si_meminfo(&i);
        si_swapinfo(&i);
        committed = percpu_counter_read_positive(&vm_committed_as);

        cached = global_node_page_state(NR_FILE_PAGES) -
                        total_swapcache_pages() - i.bufferram;
        if (cached < 0)
                cached = 0;

        for (lru = LRU_BASE; lru < NR_LRU_LISTS; lru++)
                pages[lru] = global_node_page_state(NR_LRU_BASE + lru);

        available = si_mem_available();

        show_val_kb(m, "MemTotal:       ", i.totalram);
        show_val_kb(m, "MemFree:        ", i.freeram);
        show_val_kb(m, "MemAvailable:   ", available);
        show_val_kb(m, "Buffers:        ", i.bufferram);
        show_val_kb(m, "Cached:         ", cached);
        show_val_kb(m, "SwapCached:     ", total_swapcache_pages());
        show_val_kb(m, "Active:         ", pages[LRU_ACTIVE_ANON] +
                                           pages[LRU_ACTIVE_FILE]);
        show_val_kb(m, "Inactive:       ", pages[LRU_INACTIVE_ANON] +
    ...

これを読んでいくと、ページキャッシュ(cached) は file-backed なページ(global_node_page_state(NR_FILE_PAGES)) から スワップしてるページ(total_swapcache_pages() anon のページはスワップ時にファイルに書き出すので、そのタイミングで NR_FILE_PAGES に変わるため) とブロックデバイス向けのキャッシュ(i.bufferram バッファキャッシュは実装上ブロックデバイスに対するページキャッシュになってるため) を引いたものだとわかる。…わかるのか…?

まあソースコードなんて見なくても proc の man に書いてるんですけどね…

/proc/meminfo
       This file reports statistics about memory usage on the system.  It is used by free(1) to report the amount of free and  used  memory  (both
       physical  and swap) on the system as well as the shared memory and buffers used by the kernel.  Each line of the file consists of a parame‐
       ter name, followed by a colon, the value of the parameter, and an option unit of measurement (e.g., "kB").  The list  below  describes  the
       parameter  names  and  the  format  specifier required to read the field value.  Except as noted below, all of the fields have been present
       since at least Linux 2.6.0.  Some fields are displayed only if the kernel was configured with various options; those dependencies are noted
       in the list.

       MemTotal %lu
              Total usable RAM (i.e., physical RAM minus a few reserved bits and the kernel binary code).

       MemFree %lu
              The sum of LowFree+HighFree.

       MemAvailable %lu (since Linux 3.14)
              An estimate of how much memory is available for starting new applications, without swapping.

       Buffers %lu
              Relatively temporary storage for raw disk blocks that shouldn't get tremendously large (20MB or so).

       Cached %lu
              In-memory cache for files read from the disk (the page cache).  Doesn't include SwapCached.

仮想ファイルを作ってみよう

procfs の実装も追えたので、そうなれば、自分で実装してみたくなるのが心情。 次のサイトを参考に独自にカーネルをビルドし、仮想ファイルを作成する。
CentOS 7: カーネルを再ビルドする

meminfo.cを参考にして

  • fs_initcall();で初期化用関数を呼び、
  • その中でproc_dir_entryを登録する関数proc_create_single(generic.c にある)を呼び出す。
  • このときの引数に渡したハンドラが open 時に実行される。

実際に作成したパッチがこちら。

]# diff -u ~/rpmbuild/SPECS/kernel.spec.org ~/rpmbuild/SPECS/kernel.spec
--- /root/rpmbuild/SPECS/kernel.spec.org        2019-12-14 02:10:44.617212554 -0500
+++ /root/rpmbuild/SPECS/kernel.spec    2019-12-14 10:43:13.286154014 -0500
@@ -30,13 +30,13 @@
 %global zipsed -e 's/\.ko$/\.ko.xz/'
 %endif

-# define buildid .local
+%define buildid .local

 %define rpmversion 4.18.0
 %define pkgrelease 80.11.2.el8_0

 # allow pkg_release to have configurable %%{?dist} tag
-%define specrelease 80.11.2%{?dist}
+%define specrelease 80.11.9999%{?dist}

 %define pkg_release %{specrelease}%{?buildid}

@@ -418,6 +418,8 @@
 Patch1001: debrand-rh_taint.patch
 Patch1002: debrand-rh-i686-cpu.patch

+Patch99999: custom.patch
+
 # empty final patch to facilitate testing of kernel patches
 Patch999999: linux-kernel-test.patch

@@ -880,6 +882,7 @@
 ApplyOptionalPatch debrand-single-cpu.patch
 ApplyOptionalPatch debrand-rh_taint.patch
 ApplyOptionalPatch debrand-rh-i686-cpu.patch
+ApplyOptionalPatch custom.patch

 # END OF PATCH APPLICATIONS
]# cat ~/rpmbuild/SOURCES/custom.patch
diff -uprN fs/proc.org/Makefile fs/proc/Makefile
--- a/fs/proc/Makefile  2019-12-14 10:39:54.596541412 -0500
+++ b/fs/proc/Makefile  2019-12-14 10:40:23.841042831 -0500
@@ -19,6 +19,7 @@ proc-y        += devices.o
 proc-y += interrupts.o
 proc-y += loadavg.o
 proc-y += meminfo.o
+proc-y += custom.o
 proc-y += stat.o
 proc-y += uptime.o
 proc-y += util.o
diff -uprN fs/proc.org/custom.c fs/proc/custom.c
--- a/fs/proc/custom.c  1969-12-31 19:00:00.000000000 -0500
+++ b/fs/proc/custom.c  2019-12-14 10:38:06.816378923 -0500
@@ -0,0 +1,22 @@
+// SPDX-License-Identifier: GPL-2.0
+#include <linux/fs.h>
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/proc_fs.h>
+#include <linux/quicklist.h>
+#include <linux/seq_file.h>
+#include <linux/atomic.h>
+#include "internal.h"
+
+static int greeting(struct seq_file *m, void *v)
+{
+       seq_write(m, "ho-ho-ho\n", 9);
+       return 0;
+}
+static int __init proc_custom_init(void)
+{
+       proc_create_single("christmas", 0, NULL, greeting);
+       return 0;
+}
+
+fs_initcall(proc_custom_init);

実行結果はこちら。あ、もしかしてあなたは…

]# uname -r
4.18.0-80.11.9999.el8.local.x86_64

]# cat /proc/christmas
ho-ho-ho

サンタさーん!

それでは、よいクリスマスを。