如何在 Linux 上通过 C API 判断给定的 fd 的类型?
最近接到一个任务,需要判断传过来的 fd 是不是属于 eventfd/signalfd 这一类特定的 fd。因为这一类 fd 不支持某些操作,如果调用时不加判断,会报 Invalid Argument 错误。按理说,如果能把 fd 类型作为一个额外的参数传进来,就能轻松解决问题了。不过因为一些限制,拿到手时只有 fd 这一个整数。好在需要过滤的地方不在重要的路径上,所以 hack 一点的办法应该也能被接受。
既然 Linux 能够根据一个整数 fd 调用特定的操作,那么内核层面上一定会有一个 fd 到实际文件类型的映射。所以最好的打算,就是 Linux 把这个映射通过 syscall 的方式暴露出来了,然后我只要调用它,就能完成工作了。可惜找了一段时间,依然没有找到对应的 syscall。看来是不存在这样的好事了。
补充一个前提,我正在编写的是 C 代码。如果写的是脚本的话,至少有两种方法可以获取特定 fd 的类型。
一种是调用 lsof -p $pid -a -d $fd
:
¥ lsof -p 16166 -a -d 15 COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME xxxxx 16166 lzx 15u a_inode 0,13 0 10094 [eventfd]
我们可以看到 进程 16166 的第 15 号 fd 是一个 eventfd。
另一种是调用 cat /proc/$pid/fdinfo/$fd
¥ cat /proc/16166/fdinfo/16 pos: 0 flags: 02004002 mnt_id: 13 sigmask: 0000000000010000
该操作并不会直接显示 fd 的类型。但是因为不同类型的 fdinfo 格式不一样,你可以根据 man procfs
里面的说明,解析出具体 fd 的类型。比如 进程 16166 的第 16 号 fd 是一个 signalfd。
毫无疑问,对于 C 程序,上面两种方法都不太合适。虽说并不是无法实现,但是你让一个 C 程序每次判断 fd 类型时都要读完整个 fdinfo 文件,然后再通过一个 parser 解析出具体的 fd 类型,无论从性能上还是代码量上都不可接受。
也许存在某个 API,可以直接获取 fd 类型?为什么不看看神奇的 lsof 的源码呢?
抱着源码里可能隐藏着谜底的想法,我下载了 lsof 的代码。打开底下一个名为 linux 的文件夹,看了里面的文件几眼,我感觉被欺骗了。
没想到 lsof 你这个浓眉大眼的,居然也是用读 /proc
的方式来获取 fd 信息的!
看来这条路也断了。难道真的非得去解析 fdinfo 不可吗……
仔细看多几眼,lsof 貌似只是从 /proc/$pid/fd
里面读取了基本的信息。有些信息,比如我想要的 NAME,不是直接从那里面获取的。看来还是有希望的。我试着找找看,看看有什么关键的操作。
以下略去一些曲折的碰壁和反复的试错…… 反正我费了若干个小时,走了些弯路,终于明白汲汲以求的 NAME 是怎么得到的。其实就是 readlink /proc/$pid/fd/$fd
!
对于 regular file,readlink /proc/$pid/fd/$fd
返回的是文件路径。而对于 eventfd/signalfd 这一类不可能有真正的 inode 的文件,返回的是 anon_inode:[eventfd]
。于是我需要做的事情相对简单了:
先拼接 readlink 的文件名参数,然后调用 readlink,解析返回来的文件名。由于我只需要知道某个整数 fd 在本进程内的类型,可以用 /proc/self
代替 /proc/$pid
。这么一来,我就只需要拼接后面的 $fd
部分。因为 fd 是一个整数,整个文件名 buffer 大小是有上限的,而且这个上限不大。所以可以把这个 buffer 弄成静态分配的,不用操心内存管理。接下来是 readlink 这个系统调用,原来最好的打算里面,也是逃不开一次系统调用的,所以这部分不算额外的开销。最后是对返回文件名的解析,凭借该文件名有着固定前缀的特性,我们可以先 strncmp
判断是否是 anon_inode:[
,接着一路解析出方括号内具体的 fd 类型。也不是很难就是了。
后来看了相关的内核代码,估计应该不会有更好的方法了。
下面是 eventfd 创建代码:
/* fs/eventfd.c */ struct file *file; ... file = anon_inode_getfile("[eventfd]", &eventfd_fops, ctx, O_RDWR | (flags & EFD_SHARED_FCNTL_FLAGS));
你可以这么认为,anon_inode_get_file
返回了 file 的一个实例,而这个实例组合了 eventfd_fops
这个 eventfd 功能的具体实现。eventfd 之所以跟其他 fd 不同,全靠 eventfd_fops
里面定义的方法。如果内核要想提供一个 API,返回某个 fd 的类型,最好是每个 fd 都实现返回自己的类型方法。但是 eventfd_fops
这个结构体里面,没法返回 eventfd 标识的方法。有一个 show_fdinfo
的方法,不过这个方法是提供给 /proc/$pid/fdinfo
展示用的。另外 eventfd_fops
这个结构体是 static
的,所以也排除存在 if (file_operation == &eventfd_fops) return enum_eventfd;
这样的代码的可能性。
signalfd 的情况也差不多。
最后我没有采用 readlink 的方式,而是改由创建特殊 fd 的代码顺便把 fd 编号记录下来,接收到 fd 之后,我再去查找,看看是否是特殊的 fd。