zoukankan      html  css  js  c++  java
  • VC++的文件描述符和内核文件句柄HANDLE

    VC++的文件描述符和内核文件句柄HANDLE

    本文描述VC++中的C语言使用代码文件描述符(file descriptor),和内核文件句柄HANDLE之间关系,以及两者之间的转换函数_get_osfhandle,_open_osfhandle以及使用他们的风险。在Windows代码中代码中间文件描述符号和内核句柄HANDLE千万不要共用。

    在文章的开头,要声明这是我写的bug,但是是被两个小伙子derickhu,Sasukeliu发现了在此再次对它们表示感谢。

    去年在写一些跨平台代码的时候,我们在Windows下尽量模拟一下POSIX(Linux),的函数,结果发现一个问题,如果希望使用到Windows下使用更多的功能,你必须使用内核的文件句柄HANDLE,而不能VC++中C语言函数open等使用的文件描述符(file descriptor),也就是int。到为了方便跨平台,我尽量希望接口是统一的,我们实现的如下:

    //我们的垮平台代码
    ZEN_HANDLE ZEN_OS::open (const char *filename,
                             int open_mode,
                             mode_t perms)

    ZEN_HANDLE在多个WINDOWS平台下被定义为Windows的内核句柄HANDLE,而在LINUX下被定义为LINUX的内核句柄int。

    而VC++默认的确实现了一个符合POSIX标准的open函数,返回值也是标准的文件描述符int

    //WINDOWS下实现的open函数
    int _open(
       const char *filename,
       int oflag [,
       int pmode] 
    );

    那么如果有一个方法能从C语言的文件描述符转换到Windows内核句柄,那么我就可以直接利用_open这个函数了。Google发现了这两个函数_get_osfhandle,_open_osfhandle。当时的理解是_get_osfhandle将文件描述符号转换成HANDLE,_open_osfhandle将HANDLE转换文件描述符。

    //MSDN
    Retrieves the operating-system file handle that is associated with the specified file descriptor.
    intptr_t _get_osfhandle( 
       int fd );
    
    Associates a C run-time file descriptor with an existing operating-system file handle.
    int _open_osfhandle (
       intptr_t osfhandle,
       int flags 
    );

    于是我的代码写成了。

    //在open的时候,调用_get_osfhandle得到内核HANDLE,
    //省略了很多其他代码
    //open函数用_sopen_s打开文件后,用_get_osfhandle转换为句柄提供给其他地方使用
    ZEN_HANDLE ZEN_OS::open (const char *filename,
                             int open_mode,
                             mode_t perms)
    {
        int file_id = -1;
        errno_t open_error =::_sopen_s (&file_id,
                                        filename,
                                        open_mode,
                                        _SH_DENYNO,
                                        nt_perms);
        HANDLE openfile_handle =  (HANDLE)::_get_osfhandle(file_id);
        return openfile_handle;
    }
    
    //关闭的时候,用_open_osfhandle将HANDLE转换为文件描述符,再用_close关闭
    int ZEN_OS::close (ZEN_HANDLE handle)
    {
        int file_des = ::_open_osfhandle((intptr_t)handle, _O_RDONLY);
        if (-1 == file_des )
        {
            return -1;
        }
        return ::_close(file_des);
    }

    代码最开始的实现是利用open函数(我们的代码定义了_CRT_NONSTDC_NO_DEPRECATE,不用在POSIX函数前面加_),一开始的测试也基本OK。但后来发现在部分同事的机器::close函数会出现断言错误,当时有点莫名奇妙,通过将代码open函数换成了sopen_s,close函数替换_close暂时规避了问题(注意_close函数和close函数的确是两个实现)。

    最近重构上线,一个服务程序会不断open,close文件的,在运行3天后会就会崩溃,错误是文件描述符号不够用。两位同事将上面的代码改写提取出来,发现了问题。他们的测试代码大致如下:

    int test_osadapt_file(int  /*argc*/,char * /*argv*/[])
    {
        int file_desc = open("C:\\123.txt",O_CREAT|O_APPEND);
        if (file_desc == 0)
        {
            return 0;
        }
        //fh_1 == fh_2 内核句柄一致
        HANDLE fh_1 = (HANDLE)_get_osfhandle(file_desc);
        HANDLE fh_2 = (HANDLE)_get_osfhandle(file_desc);
        
    
        std::cout << (int) fh_1 << std::endl;
        std::cout << (int) fh_2 << std::endl;
    
        //file_desc==3  filedesc_1==4 filedesc_2==5,3个文件描述符不一样
        int filedesc_1 = _open_osfhandle((intptr_t)fh_1,O_RDONLY);
        int filedesc_2 = _open_osfhandle((intptr_t)fh_1,O_RDONLY);
    
        std::cout << (int) filedesc_1 << std::endl;
        std::cout << (int) filedesc_2 << std::endl;
    
        //fh_1 == fh_2 == fh_3,内核句柄一致
        HANDLE fh_3 = (HANDLE)_get_osfhandle(filedesc_1);
        std::cout << (int) fh_3 << std::endl;
    
        return 0;
    }

    输出的结果在上面的注释都有,结果发现每次调用_open_osfhandle得到的文件描述符号并不是原来最初的文件描述符。

    看来完全错误理解_open_osfhandle函数的意义。_open_osfhandle根本就不是关联原来的文件描述符,而是对于HANDLE重新分配一个相关的C语言描述符,并不是找回这个HANDLE对应的(MSDN这个地方的描述居然用了Associates)。

    image

    从下面这个图可以很清楚的说明为什么会有C函数的文件描述符泄漏。(这很可能也是原来导致断言的原因)

    当然仔细看完MSDN,可以看出MSDN _open_osfhandle函数的remark还是有相关的蛛丝马迹的(E文烂实在影响编程能力)。

    The _open_osfhandle function allocates a C run-time file descriptor and associates it with the operating-system file handle specified by osfhandle.……
    To close a file opened with _open_osfhandle, call _close. The underlying handle is also closed by a call to _close, so it is not necessary to call the Win32 function CloseHandle on the original handle. 

    其实转念想想,写这段代码的时候过于大意了,Windows内部肯定是使用自己的API,内核自己内部的句柄管理可定是HANDLE,C语言运行时库的函数封装肯定只是上层的一套封装。所以C函数库内部保存的映射关系,肯定只是是文件描述符到内核句柄的,不可能存在内核句柄到文件描述符的映射,所以不要指望通过内核句柄得到相关文件描述符。

    混用C运行时库和API不是一个好方法,其实如果没有记错,C语言封装线程的函数(_beginthread,_endthread)和API(CreateThread,CloseHandle)两者之间也有类似的问题。

    总结:

    _open_osfhandle和_get_osfhandle都是要慎重使用的函数,_open_osfhandle是根据HANDLE分配一个文件描述符,这种情况必须用_close关闭文件描述符,_get_osfhandle只是根据文件描述符得到将对应文件句柄HANDLE,你仍然要管理原来的文件描述符。这2个函数试图连接起来C运行时库文件描述符和内核文件句柄HANDLE,但其实会导致很多陷阱,不如不用。

    最好的方案还是不要混用文件描述符和内核句柄HANDLE,该用HANDLE的时候,老老实实使用Windows的API。当年写这段代码的时候看过ACE的实现,发现他的内部实现是老老实实用API,::CreateFile(用::CreateFile实现类似open的接口要写好多代码),我还无比嘲笑,结果发现土鳖的还是自己。

    推荐音乐:黑撒的《西安事变》和《流川枫和苍井空》,derickhu,Sasukeliu他们都是西安毕业的。送给他们。

    【本文作者是雁渡寒潭,本着自由的精神,你可以在无盈利的情况完整转载此文档,转载时请附上BLOG链接:http://www.cnblogs.com/fullsail/ 或者http://blog.csdn.net/fullsail,否则每字一元,每图一百不讲价。对Baidu文库。360doc加价一倍】

  • 相关阅读:
    SpringCloud
    SpringCloud
    一个表的字段更新另一个表的字段
    MYSQL5.7 sql_mode=only_full_group_by
    CentOS7 防火墙操作
    log4j DailyRollingFileAppender, DatePattern 配置
    Fiddler抓包-会话框添加查看get与post请求类型选项
    Fiddler抓包-工具介绍(request和response)
    Fiddler抓包-get与post请求
    Fiddler抓包-只抓APP的请求
  • 原文地址:https://www.cnblogs.com/fullsail/p/2732873.html
Copyright © 2011-2022 走看看