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)。
从下面这个图可以很清楚的说明为什么会有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加价一倍】