我一直在使用linux上的proc文件系统,并且遇到了一些我想弄清楚的行为。
/proc
中的每个进程都有一个指向其可执行文件/proc/{pid}/exe
的符号链接。如果在删除可执行文件后进程继续运行,则读取此符号链接将返回到可执行文件的路径,并在末尾附加(deleted)
。
运行此命令,您甚至可能在系统上看到一些:
grep '(deleted)' <(for dir in $(ls /proc | grep -E '^[0-9]+'); do echo "$dir $(readlink /proc/$dir/exe)"; done)
我尝试使用一些简单的bash命令重新创建此行为:
>>> echo "temporary file" >> tmpfile.test
>>> ln -s tmpfile.test tmpfile.link
>>> rm tmpfile.test
>>> readlink tmpfile.link
tmpfile.test
名称后没有(deleted)
!尝试cat tmpfile.link
确认链接已断开(cat: tmpfile.link: No such file or directory
)。
但是,同一天的同一天,确实导致(deleted)
被附加到readlink的输出中。有什么用?
这是我想知道的:
(deleted)
附加到名称后?/proc/{pid}/exe
对于已删除的可执行文件显示(deleted)
?/proc/{pid}/exe
获得可执行文件的名称没有任何附加的(deleted)
,并保证原始可执行文件不只是命名为some_executable (deleted)
?不是readlink
,但是Linux将符号链接更改为指向<filename> (deleted)
,即(deleted)
被附加到链接的目标。
FWIW,特殊的<filename> (deleted)
行为是在以下Linux内核函数d_path()
中实现的:https://elixir.bootlin.com/linux/v4.1.13/source/fs/dcache.c#L3080。
源注释(在下面的代码段中建议特殊行为仅适用于为某些“合成文件系统”(例如procfs
)和“伪索引节点”即时生成的名称(路径)。
/**
* d_path - return the path of a dentry
* @path: path to report
* @buf: buffer to return value in
* @buflen: buffer length
*
* Convert a dentry into an ASCII path name. If the entry has been deleted
* the string " (deleted)" is appended. Note that this is ambiguous.
*
* Returns a pointer into the buffer or an error code if the path was
* too long. Note: Callers should use the returned pointer, not the passed
* in buffer, to use the name! The implementation often starts at an offset
* into the buffer, and may leave 0 bytes at the start.
*
* "buflen" should be positive.
*/
char *d_path(const struct path *path, char *buf, int buflen)
{
char *res = buf + buflen;
struct path root;
int error;
/*
* We have various synthetic filesystems that never get mounted. On
* these filesystems dentries are never used for lookup purposes, and
* thus don't need to be hashed. They also don't need a name until a
* user wants to identify the object in /proc/pid/fd/. The little hack
* below allows us to generate a name for these objects on demand:
*
* Some pseudo inodes are mountable. When they are mounted
* path->dentry == path->mnt->mnt_root. In that case don't call d_dname
* and instead have d_path return the mounted path.
*/
if (path->dentry->d_op && path->dentry->d_op->d_dname &&
(!IS_ROOT(path->dentry) || path->dentry != path->mnt->mnt_root))
return path->dentry->d_op->d_dname(path->dentry, buf, buflen);
rcu_read_lock();
get_fs_root_rcu(current->fs, &root);
error = path_with_deleted(path, &root, &res, &buflen);
rcu_read_unlock();
if (error < 0)
res = ERR_PTR(error);
return res;
}