• linux下文件描述符的查看及分析


    起因

    近期在调试一个Android播放内核是遇到上层传递的是fd(file descriptor),但是在文件播放结束之后调用lseek却提示返回-1,errno=29(#define ESPIPE 29 /* Illegal seek */)。
    好吧。那就确定下原因。
    在网上搜到有说lseek存在问题,“对于已经到达EOF的stream,使用lseek是不能让stream再次可读的”。具体参考Android NDK之fseek, lseek。随即写了个命令行程序,在android shell下验证了下,经过验证是可以的。那就继续找吧。
    最终发现一个有趣的现象,Android的MediaServer传递的fd只能在调用时使用,之后就被复用了,指针都改变了。具体发现的方法就是本文描述的内容。

    文件操作

    文件操作比较通用的就是C库的FILE(带缓冲的文件流),也就是常用的fopen, fclose, fprintf, fscanf, fseek, fread, fwrite等函数。这里面比较核心的概念是FILE结构,这是C库提供的跨平台的文件操作函数,多数情况下是封装了系统内核提供的文件读写函数,比如在windows下是CreateFile, CloseFile, OpenFile, WriteFile, ReadFile等函数,在linux下是open, close, lseek, read, write等内核API。
    在linux下内核API主要提供了基于文件描述(FD,file descriptor)的文件操作机制,注意FD默认是非负的,通常0-stdin、1-stdout、2-stderr。

    先看看如何实现FILE到fd的转换,函数fileno可以实现这种转换,原型如下:

    int fileno(FILE *stream);
    

    那么fd如何转换为FILE呢? 函数fdopen可以基于FD打开文件,原型如下:

    FILE *fdopen(int fd, const char *mode);
    

    那么如何通过fd拿到文件原始路径呢? 函数readlink提供了这种机制,可以参考下面代码

    #include <iostream>
    #include <cstdio>
    #include <cstdlib>
    #include <climits>
    #include <cstring>
    #include <sys/types.h>
    #include <unistd.h>
    #include <errno.h>
    
    int main()
    {
       FILE * stream = fopen(__FILE__, "rb");
       if (NULL == stream)
       {
            printf("open %s failed
    ", __FILE__);
            return -1;
       }
       
       int fd = fileno(stream);
       char buf[4096] = {0};
       
       // read to file end
       while (read(fd, buf, sizeof(buf)) > 0);
          
       // test whether lseek is ok in EOF
       off_t offset = lseek(fd, 0, SEEK_CUR);  
       printf("lseek ret %d err_no %d
    ", offset, errno);
       
       // read file path from fd
       char path[PATH_MAX] = {0};
       snprintf(path, sizeof(path), "/proc/%d/fd/%d", getpid(), fd);
       
       memset(buf, 0, sizeof(buf));
       int buf_size = readlink(path, buf, sizeof(buf));
       if (buf_size < 0) 
       {
            printf("readlink() ret %d error %d
    ", buf_size, errno);
       } 
       else 
            printf("readlink() returned '%s' for '%s'
    ", buf, path);
       
       getchar();
       
       if (NULL != stream)
            fclose(stream);
       
       return 0;
    }
    

    原理很简单,linux下的fd就是一个链接,可以通过/proc/pid/fd读取到相关信息。
    比如上面那个程序的输出如下:

    /proc/11203/fd$ ll
    总用量 0
    dr-x------ 2 root root  0  4月  1 15:48 ./
    dr-xr-xr-x 9 root root  0  4月  1 15:48 ../
    lrwx------ 1 root root 64  4月  1 15:48 0 -> /dev/pts/22
    lrwx------ 1 root root 64  4月  1 15:48 1 -> /dev/pts/22
    lrwx------ 1 root root 64  4月  1 15:48 2 -> /dev/pts/22
    lr-x------ 1 root root 64  4月  1 15:48 3 -> /home/tocy/project/test.cpp
    

    总结

    了解下系统提供的文件操作接口还是不错的,以后遇到问题最起码知道去哪里跟踪。

    主要参考:

    1. linux用户手册
    2. http://stackoverflow.com/questions/16117610/using-file-descriptors-with-readlink
    3. http://www.cnblogs.com/carekee/articles/1749655.html
  • 相关阅读:
    python之模块与包
    python之模块4
    python之模块3
    python之模块2
    Day10:Linux基础:搭建samba服务
    Day9:Linux基础:程序管理
    Day8: Linux基础片:网络配置
    番外篇:硬盘分区、创建文件系统
    Day7: Linux基础片:系统监控
    Day6: Linux基础片:文件压缩、Vim用法
  • 原文地址:https://www.cnblogs.com/tocy/p/linux-file-descriptor-intro.html
Copyright © 2020-2023  润新知