/proc/%u/fd/%u的大小是多少?

4

我需要为/proc/%u/fd/%u分配多少大小?

strace代码中,他们分配了char path[sizeof("/proc/%u/fd/%u") + 2 * sizeof(int)*3];

我不理解计算方式,他们是如何计算出这个大小的?


我的猜测是:MAX_PATH。除非你的空间很紧,否则为什么要冒险呢?我也希望你正在使用 snprintf 而不是 sprintf - tadman
对于所有C类型,“3*sizeof(type) > log10(MAX_TYPE)” - spectras
既然pid将是1和/proc/sys/kernel/pid_max之间的某个值,而文件描述符将是远小于/proc/sys/fs/file-max的某个值,因此您可以通过对这两个值都取floor(log10(x)+1)来计算最小安全缓冲区大小。话虽如此,使用MAX_PATH肯定是更简单的选择。 - Felix G
@tadman:在Linux上,MAX_PATH并没有什么实际意义。这个宏主要是出于历史原因和与其他(较差的?)操作系统的源代码兼容而存在的。但在Linux(和现代BSD)中,轻松创建远远超过MAX_PATH限制的文件系统树是微不足道的。 - datenwolf
@datenwolf,除非他们开始使用2048位PID和FD号码,否则这将是可以的。这不是针对一般路径,而是一个非常特定的情况。 - tadman
@tadman:确实。节省空间但又具备未来性的方法应该是 char proc_fd_path[sizeof("/proc//fd/") + (((sizeof(pid_t) + sizeof(int)) + 1)*10*CHAR_BIT - 2)/33 + 1];;我会让读者自行解决为什么这会始终为任何 /proc/%u/fd/%u 可能抛出来的问题保留足够的内存。提示 log2(10) ≈ 3.3 - datenwolf
4个回答

4

这可能看起来是一种非常奇怪的方法,但它能够正常工作(见下文)。他们假设你需要不超过2+3n个字符来表示进程ID和文件描述符,其中n是一个整数的字节数(2代表%u将被替换,3n代表sizeof部分)。

实际上,这确实可以工作,以下是针对8位charchars/int值、公式结果和最大的无符号整数:

chars/int  sizeof(int) * 3 + 2       max value
---------  -------------------  --------------------
        1                    5                   255 (3/5 needed)
        2                    8                 65535 (5/8 needed)
        4                   14            4294967296 (10/14)
        8                   26  18446744073709551616 (20/26)
       16                   50           3.3 * 10^38 (39/50)

您可以看到所分配的存储空间与所需存储空间保持一致,以至于您可以轻松地使用:

char path[sizeof("/proc/%u/fd/%u") + 2 * sizeof(int) * 3 - 2];
//                                              add this ^^^

此外,即使对于超过八位的char大小也可以使用此方法,因为在值中将位数翻倍最多会将十进制数字的数量翻倍(表格显示所需大小为:3, 5, 10, 20, 39, ...)。再加上int也会翻倍,这意味着总是有足够的空间。


您可能会认为,为了安全和可移植性,您实际上可以从/proc/sys/kernel/pid_max/proc/<pid>/limits(以编程方式)获取最大PID和进程文件描述符,并使用它来动态分配足够的空间来容纳信息:

pax:~> cat /proc/sys/kernel/pid_max
32768
pax:~> cat /proc/self/limits | awk '$1$2$3=="Maxopenfiles"{print $5}'
4096

因此,第一个%u需要5个字符,第二个需要4个字符。
或者,你可以为每个分配20个字符,因为在不久的将来你可能不会看到66位PID或FD :-) 你仍然可以检查procfs文件,如果它们太大就优雅地退出。
但是,说实话,这两种方法都有些过度,因为你可能已经有了一个用于提供文件路径限制的东西,即Linux下的PATH_MAX(参见limits.h)。使用它并将浪费降到最低可能是可以接受的。如果你没有这个值,你可以通过调用pathconf()获取最大长度的值。或者选择一个明智的默认值(比如60个字符),然后只需使用snprintf来检测实际路径是否超过了缓冲区的长度,并优雅地退出。
如果你真的想使用最小存储的最佳方法(即你认为4K PATH_MAX是太多的浪费),那么请尽管使用strace方法,但按照我的早期评论减去两个字符。
(a)如果你要担心这里和那里的4K,那么你可能要全力以赴了 :-)

许多发行版已将 pid_max 设为 131072,因此它们变得更大了。假设某件事情有点冒险,你会在挖掘那些 /proc 文件以寻找答案时消耗超过 ~4KB 的内存。 - tadman
2
读取 /proc/sys/kernel/pid_max/proc/self/limits ..只是为了获取 proc/pid/ 的缓冲区大小会很奇怪;-) 使用 MAX_PATH 或者甚至只是将其硬编码为类似于256的东西,这是一个更好的选择。 - P.P
@P.P:是的,当我编辑答案时,我也很快得出了同样的观点 :-) - paxdiablo
为什么要使用MAX_PATH(4kb),如果我们可以安全地使用更少的空间呢? - MicrosoctCprog
@Andrew,它在Linux上是随时可用的,这也是问题中的操作系统标签。其他遵循POSIX标准的操作系统(如您链接中提到的),我无法发表评论。 - paxdiablo
显示剩余4条评论

2
最好的答案是,将这个计算替换为MAX_PATH(包括<limits.h>),这样可以使你的代码更加清晰和可移植。

1
...和可移植性。首先,它是PATH_MAX而且PATH_MAX不必定义:“在特定的实现中,如果相应的值等于或大于所述最小值,但该值可能因适用的文件而异,则以下列表中一个符号常量的定义将从<limits.h>头文件中省略。对于特定路径名支持的实际值应由pathconf()函数提供。” - Andrew Henle

1

如果你使用Linux,asprintf(3)会为你分配足够的内存来容纳最终字符串,因此你不必去处理可变长度数组或提前计算所需的长度:

char *path = NULL;
if (asprintf(&path, "/proc/%u/fd/%u", pid, fd) < 0) {
    // Handle error
}
// Use path
free(path); // Don't forget to free it when done.


您也可以使用长度为0的缓冲区调用snprintf(3)以获取所需的长度,然后再使用适当大小的缓冲区重新调用它:
int len = snprintf(NULL, 0, "/proc/%u/fd/%u", pid, fd);
if (len < 0) {
    // Handle error
}
char buf[len + 1]; // Need to add one for the trailing nul
if (snprintf(buf, len + 1, "/proc/%u/fd/%u", pid, fd) != len) {
    // Handle error
}

但我更喜欢在堆栈中分配 - MicrosoctCprog
@MicrosoctCprog 请参考编辑中使用 snprintf() 计算所需确切长度的方法。 - Shawn

1
简单的答案是只需使用PATH_MAX,因为在这种情况下它应该没问题。
更复杂的版本是strace所做的。
那个strace代码似乎有正确的想法。对于任何给定的字节(0..255),你需要最多3位小数来表示它。
该计算假定4字节的int始终可以用3倍的十进制位表示,或者不超过12位数字。
实际上只需要10位,因此这里有一个安全保障,并且如果int因某种原因变得更大,则有未来的余地。
更新
这里是strlensizeof问题的快速演示:
#include <stdlib.h>
#include <stdio.h>

int main() {
  // Character array
  char tmpl[] = "/proc/%u/fd/%u";

  printf("sizeof(char[])=%lu\n", sizeof(tmpl));

  // Character pointer
  char* ptr = "/proc/%u/fd/%u";

  printf("sizeof(char*)=%lu\n", sizeof(ptr));

  // String literal without variable
  printf("sizeof(\"...\")=%lu\n", sizeof("/proc/%u/fd/%u"));

  return 0;
}

对于我而言,64位系统会产生以下结果:

sizeof(char[])=15
sizeof(char*)=8
sizeof("...")=15

sizeof("...任何字符串...")始终是相同的,即char指针的大小,换句话说,在64位系统上为8。

tmpl[]定义了一个数组,虽然由于数组到指针的衰减而在很大程度上可以与指针互换,但在其定义所在的范围内编译器确实可以获得其大小信息。


5
sizeof("/proc/%u/fd/%u") 不等于 strlen("/proc/%u/fd/%u"),因此代码是错误的。 - bruno
5
@bruno,实际上,字面字符串的类型是const char [N],所以在这种情况下大小是正确的。使用strlen会使数组成为一个在运行时计算大小而不是编译时计算大小的VLA。 - phuclv
2
@tadman sizeof 在字符串字面量上可以正常工作。试一下就知道了。在你的例子中,你使用它在一个 char* 上,这不是 strace 代码所做的。 - interjay
1
自1989年以来,const一直是C语言的一部分。它只是没有被广泛使用。 - tadman
2
@tadman,我指的是字符串字面值的类型,而不是const关键字本身。为了更加清晰明确:在C语言中,字符串字面值的类型不是const char [N],而是char [N] - P.P
显示剩余10条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接