c - linux 线程和 fopen() fclose() fgets()

标签 c linux pthreads

我正在查看一些使用 pthreads 的遗留 Linux 代码。

在一个线程中,通过 fgets() 读取文件。 FILE 变量是所有线程共享的全局变量。 (嘿,这不是我写的……)

在另一个线程中,FILE 不时地关闭并以另一个文件名重新打开。

在发生这种情况后的几秒钟内,线程 fgets() 的行为就好像它正在继续读取它从前一个文件中读取的最后一条记录:几乎就像有错误但 fgets() 没有返回 NULL。然后它自行整理并开始从新文件中读取。

代码看起来有点像这样(为简洁起见,我希望它仍然可以理解):

在一个线程中:

while(gRunState != S_EXIT){
  nanosleep(&timer_delay,0);
  flag = fgets(buff, sizeof(buff), gFile);
  if (flag != NULL){
    // do something with buff...
  }
}

在另一个线程中:

fclose(gFile);
gFile = fopen(newFileName,"r");

没有锁来确保 fgets() 不会与 fclose()/fopen() 同时调用。

关于可能导致 fgets() 失败但不返回 NULL 的失败模式有什么想法吗?

最佳答案

描述的代码是怎么出错的

stdio 库缓冲数据,分配内存来存储缓冲数据。 GNU C 库动态分配文件结构(一些库,特别是在 Solaris 上,使用指向静态分配文件结构的指针,但缓冲区仍然是动态分配的,除非您另外设置缓冲)。

如果您的线程使用指向全局文件指针的指针的副本(因为您将文件指针作为参数传递给函数),那么可以想象代码将继续访问原来的数据结构分配(即使它在关闭时被释放),并且会从已经存在的缓冲区中读取数据。只有当您退出该函数或读取超出缓冲区内容的内容时,事情才会开始出错 - 或者之前分配给文件结构的空间被重新分配用于新用途。

FILE *global_fp;

void somefunc(FILE *fp, ...)
{
    ...
    while (fgets(buffer, sizeof(buffer), fp) != 0)
        ...
}

void another_function(...)
{
    ...
    /* Pass global file pointer by value */
    somefunc(global_fp, ...);
    ...
}

概念验证代码

使用 GCC 4.0.1 在 MacOS X 10.5.8 (Leopard) 上测试:

#include <stdio.h>
#include <stdlib.h>

FILE *global_fp;
const char etc_passwd[] = "/etc/passwd";

static void error(const char *fmt, const char *str)
{
    fprintf(stderr, fmt, str);
    exit(1);
}

static void abuse(FILE *fp, const char *filename)
{
    char buffer1[1024];
    char buffer2[1024];
    if (fgets(buffer1, sizeof(buffer1), fp) == 0)
        error("Failed to read buffer1 from %s\n", filename);
    printf("buffer1: %s", buffer1);

    /* Dangerous!!! */
    fclose(global_fp);
    if ((global_fp = fopen(etc_passwd, "r")) == 0)
        error("Failed to open file %s\n", etc_passwd);

    if (fgets(buffer2, sizeof(buffer2), fp) == 0)
        error("Failed to read buffer2 from %s\n", filename);
    printf("buffer2: %s", buffer2);
}

int main(int argc, char **argv)
{
    if (argc != 2)
        error("Usage: %s file\n", argv[0]);

    if ((global_fp = fopen(argv[1], "r")) == 0)
        error("Failed to open file %s\n", argv[1]);

    abuse(global_fp, argv[1]);

    return(0);
}

在其自己的源代码上运行时,输出为:

Osiris JL: ./xx xx.c
buffer1: #include <stdio.h>
buffer2: ##
Osiris JL:

因此,经验证明在某些系统上,我概述的场景可能会发生。

如何修复代码

在其他答案中很好地讨论了对代码的修复。如果您避免我说明的问题(例如,通过避免全局文件指针),那是最简单的。假设这是不可能的,使用适当的标志进行编译可能就足够了(在许多类 Unix 系统上,编译器标志 '-D_REENTRANT' 完成这项工作),你最终将使用 thread - 基本标准 I/O 功能的安全版本。如果做不到这一点,您可能需要围绕对文件指针的访问制定明确的线程安全管理策略;互斥量或类似的东西(并修改代码以确保线程在使用相应的文件指针之前使用互斥量)。

关于c - linux 线程和 fopen() fclose() fgets(),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/1638709/

相关文章:

c++ - kernel.h 在哪里?

c - 忽略 fscanf 的返回值和段错误

c - 有没有办法在 C 中访问文件的修改日期时间?

java - 与整数表示相比,浮点表示可以支持更大的值

c - 如何在gdb中的工作目录外设置断点

c - 向轮询监听套接字的 epoll 实例以外的 epoll 实例添加新套接字

c - 从C到汇编jge和jg?

c++ - pthreads 的 Makefile

c++ - pthread_sigmask :unusual behavior

c - 编译C程序时出错