Linux 系统的设计中,继承了 Unix "一切皆文件" (Everything is a file) 的思想,系统中的众多对象,都可以表示为文件,可以对它们执行文件操作,如 read()
、write()
、mmap()
、ioctl()
、close()
和 poll()
等。Linux 系统支持通过 Unix 域 socket 等机制跨进程传递打开的文件描述符。跨进程传递打开的文件描述符,常常用于各种各样的目的,如:
- 跨进程传递打开的 eventfd,用来做进程间的事件通知。
- 跨进程传递打开的设备文件的文件描述符。
- 跨进程传递 memfd,以实现高效的跨进程数据传递。
- bluez 和系统音频服务器之间,跨进程传递打开的蓝牙 socket。
- 跨进程传递打开的普通文件。
跨进程传递打开的文件描述符时,不同进程间的文件描述符的数值不一定完全一样,这常常给相关的调试带来障碍。
笔者利用 /proc 虚拟文件系统中导出的 fdinfo
信息,开发了一个分析跨进程打开文件传递的工具 duplicated_fd_in_system.py。对于进程打开的文件描述符,在 /proc/[pid]/fdinfo/ 目录下,有一个对应的文件描述它的基本信息,不同类型的文件,这些信息有一些差异。一些典型的 fdinfo
信息如下:
fdinfo_path:/proc/1960/fdinfo/0, fdinfo:
pos: 0
flags: 0100000
mnt_id: 26
ino: 5
fdinfo_path:/proc/1960/fdinfo/1, fdinfo:
pos: 0
flags: 02
mnt_id: 9
ino: 28250
scm_fds: 0
fdinfo_path:/proc/1960/fdinfo/2, fdinfo:
pos: 0
flags: 02
mnt_id: 9
ino: 28250
scm_fds: 0
fdinfo_path:/proc/1960/fdinfo/4, fdinfo:
pos: 0
flags: 02000002
mnt_id: 16
ino: 2086
tfd: 5 events: 19 data: 564036e84f00 pos:0 ino:826 sdev:f
tfd: 7 events: 19 data: 564036e84f90 pos:0 ino:826 sdev:f
tfd: 53 events: 19 data: 564036ed0ec0 pos:0 ino:16efd sdev:8
tfd: 16 events: 19 data: 564036ea0290 pos:0 ino:826 sdev:f
tfd: 22 events: 19 data: 564036f993f0 pos:0 ino:4e38 sdev:8
tfd: 17 events: 19 data: 564036ea11a0 pos:0 ino:826 sdev:f
tfd: 12 events: 19 data: 564036e917d0 pos:0 ino:826 sdev:f
tfd: 8 events: 19 data: 564036e85000 pos:0 ino:826 sdev:f
tfd: 54 events: 19 data: 564036ea0190 pos:0 ino:1bd65 sdev:8
tfd: 49 events: 19 data: 564037065e20 pos:0 ino:17812 sdev:8
tfd: 31 events: 19 data: 564036f70f90 pos:0 ino:739a sdev:8
tfd: 3 events: 19 data: 564036ea0220 pos:0 ino:8d78 sdev:8
tfd: 32 events: 19 data: 564036f0cc20 pos:0 ino:5f44 sdev:8
tfd: 30 events: 19 data: 564036fe2cd0 pos:0 ino:6782 sdev:8
tfd: 50 events: 19 data: 7fea7381b0a8 pos:0 ino:274 sdev:5
tfd: 29 events: 19 data: 564036ed1880 pos:0 ino:7301 sdev:8
tfd: 18 events: 19 data: 564036ea8a40 pos:0 ino:826 sdev:f
tfd: 20 events: 18 data: 564036eaade0 pos:0 ino:6e6f sdev:8
tfd: 21 events: 19 data: 564036eaae80 pos:0 ino:6e6f sdev:8
tfd: 35 events: 19 data: 564036eccf70 pos:0 ino:6e7a sdev:8
tfd: 34 events: 19 data: 564036ecc6e0 pos:0 ino:6e79 sdev:8
tfd: 52 events: 19 data: 5640370af920 pos:0 ino:12bc3 sdev:8
tfd: 33 events: 19 data: 564036f0ab60 pos:0 ino:77e6 sdev:8
fdinfo_path:/proc/1960/fdinfo/5, fdinfo:
pos: 0
flags: 02004002
mnt_id: 16
ino: 2086
eventfd-count: 0
eventfd-id: 175
eventfd-semaphore: 0
fdinfo_path:/proc/1960/fdinfo/6, fdinfo:
pos: 0
flags: 02000002
mnt_id: 16
ino: 2086
eventfd-count: 0
eventfd-id: 177
eventfd-semaphore: 1
fdinfo_path:/proc/1960/fdinfo/7, fdinfo:
pos: 0
flags: 02004002
mnt_id: 16
ino: 2086
sigmask: 0000000000000002
fdinfo_path:/proc/1960/fdinfo/8, fdinfo:
pos: 0
flags: 02004002
mnt_id: 16
ino: 2086
sigmask: 0000000000004000
fdinfo_path:/proc/1960/fdinfo/9, fdinfo:
pos: 0
flags: 02000002
mnt_id: 16
ino: 2086
tfd: 10 events: 19 data: 564036e916a0 pos:0 ino:826 sdev:f
tfd: 26 events: 19 data: 564036eb7dd8 pos:0 ino:826 sdev:f
tfd: 23 events: 19 data: 564036eb6648 pos:0 ino:826 sdev:f
fdinfo_path:/proc/1960/fdinfo/15, fdinfo:
pos: 0
flags: 02100000
mnt_id: 2601
ino: 79
lock: 1: FLOCK ADVISORY WRITE 1960 00:45:79 0 EOF
fdinfo_path:/proc/1960/fdinfo/17, fdinfo:
pos: 0
flags: 02004002
mnt_id: 16
ino: 2086
clockid: 1
ticks: 0
settime flags: 00
it_value: (0, 0)
it_interval: (0, 0)
各个文件描述符信息,都包含pos 、flags 、mnt_id 和 ino 四个字段。在整个 Linux 系统中,对于一般的文件,可以通过挂载点和 inode 号唯一地确定一个文件,在文件描述符信息中,它们分别用 mnt_id 和 ino 字段描述。更特殊的文件描述符,包含一些其它字段,eventfd 的包含 eventfd-id 等字段,且它们的 mnt_id 和 ino 都是 16 和 2086 ,不同的 eventfd 用 eventfd-id 来区分;UNIX 域 socket 的包含 scm_fds 等字段;timerfd 的包含 clockid 和 ticks 等字段;signalfd 的包含 sigmask 等字段;epollfd 的包含 tfd 等字段;inotifyfd 的包含 inotify wd 等字段。
/proc/self/mountinfo 文件中,包含 Linux 系统的文件系统挂载信息,这个文件的内容类似于下面这样:
$ cat /proc/self/mountinfo
24 29 0:22 / /sys rw,nosuid,nodev,noexec,relatime shared:7 - sysfs sysfs rw
25 29 0:23 / /proc rw,nosuid,nodev,noexec,relatime shared:13 - proc proc rw
26 29 0:5 / /dev rw,nosuid,relatime shared:2 - devtmpfs udev rw,size=20329208k,nr_inodes=5082302,mode=755,inode64
27 26 0:24 / /dev/pts rw,nosuid,noexec,relatime shared:3 - devpts devpts rw,gid=5,mode=620,ptmxmode=000
28 29 0:25 / /run rw,nosuid,nodev,noexec,relatime shared:5 - tmpfs tmpfs rw,size=4073528k,mode=755,inode64
29 1 259:5 / / rw,relatime shared:1 - ext4 /dev/nvme0n1p5 rw,errors=remount-ro
30 24 0:6 / /sys/kernel/security rw,nosuid,nodev,noexec,relatime shared:8 - securityfs securityfs rw
31 26 0:26 / /dev/shm rw,nosuid,nodev shared:4 - tmpfs tmpfs rw,inode64
32 28 0:27 / /run/lock rw,nosuid,nodev,noexec,relatime shared:6 - tmpfs tmpfs rw,size=5120k,inode64
33 24 0:28 / /sys/fs/cgroup rw,nosuid,nodev,noexec,relatime shared:9 - cgroup2 cgroup2 rw,nsdelegate,memory_recursiveprot
34 24 0:29 / /sys/fs/pstore rw,nosuid,nodev,noexec,relatime shared:10 - pstore pstore rw
35 24 0:30 / /sys/firmware/efi/efivars rw,nosuid,nodev,noexec,relatime shared:11 - efivarfs efivarfs rw
36 24 0:31 / /sys/fs/bpf rw,nosuid,nodev,noexec,relatime shared:12 - bpf bpf rw,mode=700
37 25 0:32 / /proc/sys/fs/binfmt_misc rw,relatime shared:14 - autofs systemd-1 rw,fd=29,pgrp=1,timeout=0,minproto=5,maxproto=5,direct,pipe_ino=32794
38 26 0:33 / /dev/hugepages rw,relatime shared:15 - hugetlbfs hugetlbfs rw,pagesize=2M
39 26 0:20 / /dev/mqueue rw,nosuid,nodev,noexec,relatime shared:16 - mqueue mqueue rw
40 24 0:7 / /sys/kernel/debug rw,nosuid,nodev,noexec,relatime shared:17 - debugfs debugfs rw
41 24 0:12 / /sys/kernel/tracing rw,nosuid,nodev,noexec,relatime shared:18 - tracefs tracefs rw
42 24 0:34 / /sys/fs/fuse/connections rw,nosuid,nodev,noexec,relatime shared:19 - fusectl fusectl rw
43 24 0:21 / /sys/kernel/config rw,nosuid,nodev,noexec,relatime shared:20 - configfs configfs rw
66 28 0:35 / /run/credentials/systemd-sysusers.service ro,nosuid,nodev,noexec,relatime shared:21 - ramfs ramfs rw,mode=700
44 28 0:36 / /run/qemu rw,nosuid,nodev,relatime shared:22 - tmpfs tmpfs rw,mode=755,inode64
45 29 7:0 / /snap/bare/5 ro,nodev,relatime shared:23 - squashfs /dev/loop0 ro,errors=continue,threads=single
46 29 7:1 / /snap/core18/2846 ro,nodev,relatime shared:24 - squashfs /dev/loop1 ro,errors=continue,threads=single
47 29 7:2 / /snap/core18/2855 ro,nodev,relatime shared:25 - squashfs /dev/loop2 ro,errors=continue,threads=single
48 29 7:4 / /snap/core20/2501 ro,nodev,relatime shared:26 - squashfs /dev/loop4 ro,errors=continue,threads=single
49 29 7:3 / /snap/core20/2496 ro,nodev,relatime shared:27 - squashfs /dev/loop3 ro,errors=continue,threads=single
51 29 7:6 / /snap/core22/1908 ro,nodev,relatime shared:29 - squashfs /dev/loop6 ro,errors=continue,threads=single
53 29 7:8 / /snap/firefox/6042 ro,nodev,relatime shared:31 - squashfs /dev/loop8 ro,errors=continue,threads=single
54 29 7:9 / /snap/gnome-3-38-2004/112 ro,nodev,relatime shared:32 - squashfs /dev/loop9 ro,errors=continue,threads=single
55 29 7:10 / /snap/gnome-3-38-2004/143 ro,nodev,relatime shared:33 - squashfs /dev/loop10 ro,errors=continue,threads=single
56 29 7:12 / /snap/gnome-42-2204/202 ro,nodev,relatime shared:34 - squashfs /dev/loop12 ro,errors=continue,threads=single
57 29 7:11 / /snap/gnome-42-2204/176 ro,nodev,relatime shared:35 - squashfs /dev/loop11 ro,errors=continue,threads=single
58 29 7:13 / /snap/gtk-common-themes/1535 ro,nodev,relatime shared:36 - squashfs /dev/loop13 ro,errors=continue,threads=single
59 29 7:15 / /snap/pycharm-community/465 ro,nodev,relatime shared:37 - squashfs /dev/loop15 ro,errors=continue,threads=single
61 29 7:17 / /snap/snap-store/1216 ro,nodev,relatime shared:39 - squashfs /dev/loop17 ro,errors=continue,threads=single
62 29 7:16 / /snap/snap-store/1113 ro,nodev,relatime shared:40 - squashfs /dev/loop16 ro,errors=continue,threads=single
64 29 7:19 / /snap/snapd/23771 ro,nodev,relatime shared:42 - squashfs /dev/loop19 ro,errors=continue,threads=single
65 29 7:20 / /snap/snapd-desktop-integration/247 ro,nodev,relatime shared:43 - squashfs /dev/loop20 ro,errors=continue,threads=single
67 29 259:5 /usr/share/hunspell /var/snap/firefox/common/host-hunspell ro,noexec,noatime shared:1 - ext4 /dev/nvme0n1p5 rw,errors=remount-ro
159 29 7:21 / /snap/snapd-desktop-integration/253 ro,nodev,relatime shared:44 - squashfs /dev/loop21 ro,errors=continue,threads=single
163 29 259:13 / /media/data rw,relatime shared:55 - ext4 /dev/nvme1n1p2 rw
166 29 259:6 / /home rw,relatime shared:94 - ext4 /dev/nvme0n1p6 rw
169 29 259:10 / /media/data2 rw,relatime shared:96 - ext4 /dev/nvme0n1p10 rw
172 29 259:2 / /boot/efi rw,relatime shared:98 - vfat /dev/nvme0n1p2 rw,fmask=0077,dmask=0077,codepage=437,iocharset=iso8859-1,shortname=mixed,errors=remount-ro
175 37 0:37 / /proc/sys/fs/binfmt_misc rw,nosuid,nodev,noexec,relatime shared:100 - binfmt_misc binfmt_misc rw
1982 28 0:25 /snapd/ns /run/snapd/ns rw,nosuid,nodev,noexec,relatime - tmpfs tmpfs rw,size=4073528k,mode=755,inode64
2601 28 0:69 / /run/user/1000 rw,nosuid,nodev,relatime shared:1105 - tmpfs tmpfs rw,size=4073524k,nr_inodes=1018381,mode=700,uid=1000,gid=1000,inode64
2640 2601 0:70 / /run/user/1000/gvfs rw,nosuid,nodev,relatime shared:1167 - fuse.gvfsd-fuse gvfsd-fuse rw,user_id=1000,group_id=1000
2679 2601 0:71 / /run/user/1000/doc rw,nosuid,nodev,relatime shared:1187 - fuse.portal portal rw,user_id=1000,group_id=1000
3359 1982 0:4 mnt:[4026532459] /run/snapd/ns/snap-store.mnt rw - nsfs nsfs rw
1528 29 7:22 / /snap/firefox/6073 ro,nodev,relatime shared:718 - squashfs /dev/loop22 ro,errors=continue,threads=single
4011 1982 0:4 mnt:[4026532936] /run/snapd/ns/firefox.mnt rw - nsfs nsfs rw
52 29 259:8 / /media/hanpfei/649E63CF9E6397F6 rw,nosuid,nodev,relatime shared:30 - ntfs3 /dev/nvme0n1p8 rw,uid=1000,gid=1000,windows_names,iocharset=utf8
4836 29 7:7 / /snap/pycharm-community/471 ro,nodev,relatime shared:917 - squashfs /dev/loop7 ro,errors=continue,threads=single
60 29 7:14 / /snap/snapd/24505 ro,nodev,relatime shared:38 - squashfs /dev/loop14 ro,errors=continue,threads=single
63 29 7:18 / /snap/core22/1963 ro,nodev,relatime shared:41 - squashfs /dev/loop18 ro,errors=continue,threads=single
/proc/self/mountinfo 文件的格式如下:
<mount_id> <parent_id> <major:minor> <root> <mount_point> <options> <optional_fields> <filesystem_type> <source> <super_options>
/proc/self/mountinfo 文件中的 shared 字段可以简单理解为 mount_id 的别名。根据 fdinfo
信息中的 mnt_id 和 /proc/self/mountinfo 文件中的信息,可以找到文件的挂载点。
duplicated_fd_in_system.py 工具的设计思路大概为:
- 加载各个进程打开文件的
fdinfo
信息,fdinfo
信息用字典保存,不同类型的文件,其fdinfo
信息的字典字段不完全一样。各个打开文件的fdinfo
信息,用一个以文件描述符为 key 的字典保存。所有打开文件的fdinfo
信息和进程的其它信息,如进程名,用一个字调保存。 - 为这些打开的文件描述符的
fdinfo
信息建立索引,其中包含文件的类型、mount_id 、ino 及不同文件类型特有的一些信息,如 eventfd 的 eventfd-id 等,创建一个以该索引为键,数组为值的字典,数组的元素为进程 ID 和文件描述符组成的元组。 - 检查
fdinfo
信息索引,输出进程间共享的打开文件描述符。
duplicated_fd_in_system.py 工具对于分析 eventfd 的跨进程传递比较有效,其它一些文件类型,可能很少有跨进程传递的需要,如 epollfd 等,只能提供一些提示性信息。
duplicated_fd_in_system.py 工具的更多详细信息,可以参考其源码。
欢迎各位朋友帮忙给 Gitee 的 Repo star,感谢感谢。
Done.