【问题标题】:where is file descriptor stored in process memory?文件描述符存储在进程内存中的什么位置?
【发布时间】:2017-10-30 12:14:02
【问题描述】:

当一个函数A从一个执行点被调用时,内部是一个JMP语句到指向函数A的地址。所以当前的执行点被保存到堆栈中,PC加载被调用函数的地址A 并继续。

要在函数调用后返回执行点,函数块应该具有相等的推送和弹出堆栈。通常在 C 中退出函数时,定义的堆栈变量被破坏(我认为这意味着从堆栈中弹出),但我决定在我的函数中定义一个文件描述符变量。代码如下:

void main() {
    printf("In the beginning there was main()\n");
    func_call();
    printf("func_call complete\n");
    while(1);
}

void func_call() {
    int fp;
    //Opening a file to get handle to it.
    fp = open("stack_flush.c", O_RDONLY);
    if (fp < 0 ) {
        perror("fp could not open stack_flush.c");
        return;
    }
}

在运行这个程序并检查 lsof 时,我可以看到 fd 在退出函数 func_call() 时仍然打开。

stack_flu 3791 vvdnlt260    0u   CHR  136,1      0t0        4 /dev/pts/1
stack_flu 3791 vvdnlt260    1u   CHR  136,1      0t0        4 /dev/pts/1
stack_flu 3791 vvdnlt260    2u   CHR  136,1      0t0        4 /dev/pts/1
stack_flu 3791 vvdnlt260    3r   REG    8,3      526 24660187 /home/vvdnlt260/Nishanth/test_space/stack_flush.c

我检查了 wikipedia 条目中的文件描述符,发现:

为了执行输入或输出,进程通过系统调用将文件描述符传递给内核,内核将代表进程访问文件。该进程没有直接访问文件或 inode 表的权限。

从上面的语句很明显文件描述符整数值存储在进程内存中,但是虽然它是在函数中定义的,但文件描述符不是函数本地的,因为它没有在函数退出时被删除。

所以我的问题是 2 倍:

1) 如果文件描述符是 func_call() 堆栈的一部分,那么代码虽然没有被弹出,但如何返回到它的函数调用前执行点?同样在这种情况下,为什么它在函数调用存在后仍然存在?

2) 如果不是 func_call() 堆栈的一部分,文件描述符在进程内存中的位置是什么?

【问题讨论】:

  • 相关,我认为close()函数可能值得一读。
  • 函数返回后,本地(自动)变量fp 将超出范围。带有元数据的实际“文件”存储在内核内部结构的表中。 “文件描述符”基本上只是该表的索引。
  • 由内核存储。

标签: c linux memory-management file-descriptor


【解决方案1】:

文件描述符是特殊的。如您所知,它们只是整数。但是它们“包含”大量关于正在读取的文件的信息(文件在磁盘上的位置、读/写指针在 fie 中的位置等),那么这些信息存储在哪里?答案是它存储在操作系统内核的某个地方。它存储在操作系统内核中,因为它是内核为您管理文件 I/O 的工作。当我们说指向打开文件的 int 是“文件描述符”时,我们的意思是 int 指的是存储在其他地方的信息,有点像指针。 “描述符”这个词很重要。有时用于这种情况的另一个词是“处理”。

如您所知,局部变量的内存通常存储在堆栈中。当你从一个函数返回时,为函数的局部变量释放内存非常简单——它们基本上会随着函数的堆栈帧一起消失。当它们消失时,它们确实消失了:(在 C 中)没有办法与它们的消失相关联的一些动作。特别是,对于碰巧是文件描述符的变量,没有办法产生调用 close() 的效果。

(如果您想在变量消失时进行清理操作,一种方法是使用 C++,并使用类变量,并定义显式 析构函数。)

当您致电malloc 时,也会出现类似情况。在这个函数中:

void f()
{
    char *p = malloc(10);
}

我们调用malloc分配10字节的内存并将返回的指针存储在一个局部指针变量p中,当函数f返回时该变量消失。所以我们丢失了指向分配内存的指针,但是没有调用free(),所以内存仍然是分配的。 (这是一个内存泄漏的例子。)

【讨论】:

    【解决方案2】:

    变量int fd; 仅在函数func_call() 中可见,并且在此函数执行完成后,它将从堆栈中弹出,并且可能会在输入新函数时覆盖内存。您销毁一些指向文件的int 值这一事实并不意味着您关闭了所述文件。如果你做了类似的事情:

    int global_fd;
    void foo() {
        int local_fd = open("bar.txt", O_RDONLY);
        global_fd = local_fd;
    }
    

    然后叫foo()?您是否希望在foo 退出后无法再使用global_fd

    在这种情况下,将文件描述符视为指针会很有帮助,您要求内核将文件提供给您,它会为您提供一个值,您可以将其用作此特定文件的标记,这令牌是您用来让内核知道readlseek 之类的函数应该在哪个文件上起作用的东西。当令牌被传递或销毁时,文件保持打开状态,就像销毁指针不会释放分配的内存一样。

    【讨论】:

      【解决方案3】:

      当你打开一个文件时,内核中有一个存储文件描述符的表。因此,当您打开文件时,您在该表中创建了一个条目。如果您不关闭文件(及其描述符),则永远不会删除该条目(这并不意味着您无法再次打开该文件)。

      如果文件描述符是 func_call() 堆栈的一部分,那么代码虽然没有被弹出,但如何返回到它的函数调用前执行点?同样在这种情况下,为什么它在函数调用存在后仍然存在?

      据我所知,每个进程只有一个堆栈,而不是每个函数。所以fp变量存储在进程的堆栈中,当函数结束时它会从那里删除。

      【讨论】:

      • 所以当我使用 lsof -p pid 时,列出的是进程的内核表条目。由于我没有关闭文件描述符,因此该条目仍然存在。这与我可以在 /proc/pid/fd 中看到 fd 的原因相同,因为 procfs 是由内核创建的抽象。这解决了条目在 lsof 命令中可见的原因。总而言之,当 func_call() 退出时,所有写在进程堆栈上的 func_call() 相关的局部变量(包括 fp)都会被弹出并超出范围。不关闭文件指针也会导致内核内存泄漏。对吗?
      • 这就是通常所说的“资源泄漏”,但是是的。但是请注意,内核知道给定进程打开了哪些资源,因此当进程终止时,它的所有相关资源(内存、打开的文件、套接字等)无论如何都会被释放。
      • 你是对的。而且,正如@MatteoItalia 所说,这就是所谓的“资源泄漏”:)。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-10-17
      • 2010-11-20
      • 2021-01-17
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多