3

这个问题在某种程度上类似于坏文件描述符,但它根本不一样。我知道这是“糟糕的问题”(也许是“过于本地化”),但我无法弄清楚,我现在没有任何想法。


介绍

我有一个管理器线程,它启动了 75 个其他线程。这些线程中的每一个都做了很多事情,所以我将只描述相关的。

请注意:如果我只启动几个线程 - 例如 3 或 5 或 10,则不会出现此错误!这让我觉得,这是一些多线程问题,但似乎并非如此。您将在下一节中看到原因。

因此,在以下 2 种情况下,有时我会收到此错误Bad file descriptor


情况1

错误出现在TinyXML

有一个 xml 文件,所有线程都需要它。所有这些线程都TinyXML用于解析文件。所有这些线程都以只读方式使用此文件!(我知道这可以优化,但无论如何)。

所以,导致Bad file descriptor错误的代码是这样的:

// ...
// NOTE: this is LOCAL, other threads do NOT have access to it
TiXmlDocument   doc;
doc.LoadFile( filename );

// and here's the LoadFile:
bool TiXmlDocument::LoadFile( const char* _filename, TiXmlEncoding encoding )
{
    //...
    FILE* file = fopen( value.c_str (), "rb" ); 
    if ( file )
    {
        // this IS executed, so file is NOT NULL for sure
        bool result = LoadFile( file, encoding );
        //...
    }
    //...
}

bool TiXmlDocument::LoadFile( FILE* file, TiXmlEncoding encoding )
{
    // ...
    long length = 0;
    fseek( file, 0, SEEK_END );
    // from the code above, we are SURE that file is NOT NULL, it's valid, but
    length = ftell( file ); // RETURNS -1 with errno: 9 (BAD FILE DESCRIPTOR)
    // how is this possible, as "file" is not NULL and it appears to be valid?
    // ...
}

案例2

这有点复杂。我已经删除了对返回值的检查,但我在我的真实代码中有它们,所以这不是问题

int hFileR = open( sAlarmFileName.c_str(), O_CREAT | O_RDONLY, 
                   S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH );
// hFileR is > 0 for sure, so success 

flock( hFileR, LOCK_EX ) /* the result is > 0 for sure, so success*/ 

// read the file into a string
while( (nRes = read(hFileR, BUFF, MAX_RW_BUFF_SIZE)) > 0 ) // ...

//Write new data to file: reopen/create file - write and truncate mode
int hFileW = open( sAlarmFileName.c_str(), 
                   O_CREAT | O_WRONLY | O_TRUNC, S_IRUSR | 
                   S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH );
// hFileW is > 0 for sure, so success

do
{
    int nWrtRes = write( hFileW, 
                        szText + nBytesWritten, nSize - nBytesWritten ); 
    // nWrtRes is always >= 0, so success
    nBytesWritten +=  nWrtRes;
}
while( nSize > nBytesWritten );

close( hFileW );    // this one is successful too

if( flock(hFileR, LOCK_UN) == -1 )
{
    // THIS FAILS and executes _Exit( FAILURE );
}

if( close( hFileR ) < 0 )
{
    // if the previous one do not fail, this one is successful too
}

对不起,很长的问题。有任何想法吗?

4

3 回答 3

6

要寻找的一件事是关闭同一文件描述符两次的代码。

在单线程程序中,这是一个无害的编程错误,因为第二个程序close()除了 return 什么都不做EBADF,而且很多代码也懒得检查close()返回值。然而,在多线程程序中,已关闭描述符的描述符编号可以在两次调用之间分配到另一个线程中close(),因此第二个close()将关闭来自另一个线程的不相关套接字。进一步读取和写入其他线程的描述符将导致“坏文件描述符”错误。

于 2012-12-07T19:21:15.747 回答
3

关于文件描述符理解的一些话:

文件是全局资源。为了处理这种情况,使用(进程)全局索引:整数值,称为文件描述符。如果一个线程打开一个文件,这个打开的文件由索引引用。该索引对于进程(而不是线程)是唯一的。如果文件被关闭,文件描述符(整数索引)将不再使用,并且可以被进程(及其任何线程)重用。

例子:

进程中的任何线程第一次调用open()可能返回 3,第二次调用可能返回 4。

如果然后 3 关闭,则第 3 次调用open()可能会再次返回 3。

如果第一次调用由线程 1 完成,第 2 次由线程 2 完成,第 3 次由线程 3 完成,则很容易理解线程 1 不应再次关闭其文件描述符,因为 3 的值可能已经被回收并在由线程 3 使用,它将尝试访问无效的文件描述符,因为它可能已被close()线程 1 的第二次(错误)调用关闭。好吗?;-)

尝试设置一些示例代码,并检查/记录调用返回open()并分配为文件描述符的整数值,以了解其工作原理。

注意

This also might refer to stdin, stdout and stderr, the "predefined" file descriptors 0, 1 and 2. Under recent Linux closing stdin followed by a call to int fd = open("myfoofile.bar", ...) might very well return 0 as file descriptor fd. Anyhow, either the Kernel or the glibc is not able to handle such a 0 as expected. Obscure errors might occur using lseek(fd, ...) for example. Try it! ;->>

于 2012-12-08T14:52:00.473 回答
2

如果应用程序是多线程的,如果某个线程关闭了文件,而另一个线程仍在尝试访问它,则可能会发生这种情况。

(因为文件描述符,如地址空间,对进程的所有线程是全局的和通用的)

您可以strace用来了解系统调用的完成情况。

于 2012-12-07T18:26:12.503 回答