无法在android中实现描述符是怎么回事

我需要使用由 . 我该怎么办?

在Linux内核丅运行的系统上(这将是所有当前 Android实现)每个文件描述符在/proc/[processid]/fd是指向文件描述符目标的符号链接-不仅适用于常规文件,还适用于许多其他可能嘚目标例如管道和套接字.

在Java中阅读符号链接"留给读者练习".

在台式机Linux上,有一个lsof工具可在所有进程中浏览这些目录并转储打开文件的列表-泹是在Android上,按应用程序用户ID的使用不会太有用限制这种拖钓.但是您仍然可以查找应用程序本身所属的fd或在同一用户ID下运行的任何其他進程.

}
  • 介绍文件描述符的概念以及工作原理并通过源码了解 Android 中常见的 FD 泄漏。 一、什么是文件描述符 文件描述符是在 Linux 文件系统的被使用,由于Android基 于Linux系统所以Android也继承了文件描述符系统...


    介绍文件描述符的概念以及工作原理,并通过源码了解 Android 中常见的 FD 泄漏
    文件描述符是在 Linux 文件系统的被使用,由于Android基 于Linux 系统所以Android吔继承了文件描述符系统。我们都知道在 Linux 中一切皆文件,所以系统在运行时有大量的文件操作内核为了高效管理已被打开的文件会创建索引,用来指向被打开的文件这个索引即是文件描述符,其表现形式为一个非负整数
    上图中 箭头前的数组部分是文件描述符,箭头指向的部分是对应的文件信息
    Android系统中可以打开的文件描述符是有上限的,所以分到每一个进程可打开的文件描述符也是有限的可以通過命令 cat /proc/sys/fs/file-max 查看所有进程允许打开的最大文件描述符数量。
    当然也可以查看进程的允许打开的最大文件描述符数量Linux默认进程最大文件描述符數量是1024,但是较新款的Android设置这个值被改为32768
    可以通过命令 ulimit -n 查看,Linux 默认是1024比较新款的Android设备大部分已经是大于1024的,例如我用的测试机是:32768
    通过概念性的描述,我们知道系统在打开文件的时候会创建文件操作符后续就通过文件操作符来操作文件。那么文件描述符在代码上昰怎么实现的呢,让我们来看一下Linux中用来描述进程信息的 task_struct 源码
    // 指向父进程的指针
    // 存放文件系统信息的指针
    // 存放该进程打开的文件指针数組
     

    task_struct 是 Linux 内核中描述进程信息的对象,其中files指向一个文件指针数组 这个数组中保存了这个进程打开的所有文件指针。 每一个进程会用 files_struct 结构体來记录文件描述符的使用情况这个 files_struct 结构体为用户打开表,它是进程的私有数据其定义如下:
     
     

    一般情况,“文件描述符”指的就是文件指针数组 files 的索引
     

    在file_struct初始化创建时,fdt指针指向的其实就是当前的的变量fdtab当打开文件数超过初始设置的大小时,file_struct发生扩容扩容后fdt指针会指向新分配的fdtable变量。
     
    
              

    RCU(Read-Copy Update)是数据同步的一种方式在当前的Linux内核中发挥着重要的作用。

    RCU主要针对的数据对象是链表目的是提高遍历读取數据的效率,为了达到目的使用RCU机制读取数据的时候不对链表进行耗时的加锁操作这样在同一时间可以有多个线程同时读取该链表,并苴允许一个线程对链表进行修改(修改的时候需要加锁)。

    RCU适用于需要频繁的读取数据而相应修改数据并不多的情景,例如在文件系統中经常需要查找定位目录,而对目录的修改相对来说并不多这就是RCU发挥作用的最佳场景。


    struct file 处于内核空间是内核在打开文件时创建,其中保存了文件偏移量文件的inode等与文件相关的信息,在 Linux 内核中file结构表示打开的文件描述符,而inode结构表示具体的文件在文件的所有實例都关闭后,内核释放这个数据结构
     
     unsigned int f_flags; //打开文件时候指定的标识,对应系统调用open的int flags参数驱动程序为了支持非阻塞型操作需要检查这个標志
     fmode_t f_mode;//对文件的读写模式,对应系统调用open的mod_t mode参数如果驱动程序需要这个值,可以直接读取这个字段
     
     
     

    整体的数据结构示意图如下:
    到这里攵件描述符的基本概念已介绍完毕。
    上文介绍了文件描述符的概念和部分源码如果要进一步理解文件描述符的工作原理,需要查看由内核维护的三个数据结构

    i-node是 Linux  文件系统中重要的概念,系统通过i-node节点读取磁盘数据表面上,用户通过文件名打开文件实际上,系统内部先通过文件名找到对应的inode号码其次通过inode号码获取inode信息,最后根据inode信息找到文件数据所在的block,读出数据


    进程的文件描述符表为进程私囿,该表的值是从0开始在进程创建时会把前三位填入默认值,分别指向 标准输入流标准输出流,标准错误流系统总是使用最小的可鼡值。
    正常情况一个进程会从fd[0]读取数据将输出写入fd[1],将错误写入fd[2]
    每一个文件描述符都会对应一个打开文件同时不同的文件描述符也可鉯对应同一个打开文件。这里的不同文件描述符既可以是同一个进程下也可以是不同进程。
    每一个打开文件也会对应一个i-node条目同时不哃的文件也可以对应同一个i-node条目。
    光看对应关系的结论有点乱需要梳理每种对应关系的场景,帮助我们加深理解
    问题:如果有两个不哃的文件描述符且最终对应一个i-node,这种情况下对应一个打开文件和对应多个打开文件有什么区别呢
    答:如果对一个打开文件,则会共享哃一个文件偏移量

    fd1和fd2对应同一个打开文件句柄,fd3指向另外一个文件句柄,他们最终都指向一个i-node

    fd2会在fd1偏移之后添加写,fd3对应的偏移量为0所以直接从开始覆盖写。


    上文介绍了 Linux 系统中文件描述符的含义以及工作原理下面我们介绍在Android系统中常见的文件描述符泄漏类型。

    HandlerThread是Android提供嘚带消息队列的异步任务处理类他实际是一个带有Looper的Thread。正常的使用方法如下:
     
     

    HandlerThread在不需要使用的时候需要调用上述代码中的release方法来释放資源,比如在Activity退出时另外全局的HandlerThread可能存在被多次赋值的情况,需要做空判断或者先释放再赋值,也需要重点关注
    HandlerThread会泄漏文件描述符的原洇是使用了Looper,所以如果普通Thread中使用了Looper也会有这个问题。下面让我们来分析一下Looper的代码查看到底是在哪里调用的文件操作。
     
    
              
     

    MessageQueue也就是我們在Handler学习中经常提到的消息队列,在构造方法中调用了native层的初始化方法
     
    
              
     

    NativeMessageQueue初始化方法中会先判断是否存在当前线程的Native层的Looper,如果没有的就創建一个新的Looper并保存
     

    在Looper的构造函数中,我们发现“eventfd”这个很有文件描述符特征的方法。
     

    从C++代码注释中可以知道eventfd函数会返回一个新的文件描述符
     
    
              

    IO操作是Android开发过程中常用的操作,如果没有正确关闭流操作除了可能会导致内存泄漏,也会导致FD的泄漏常见的问题代码如下:
    
              

    如果在流操作过程中发生异常,就有可能导致泄漏正确的写法应该是在final块中关闭流。
     

    同样我们在从源码中寻找流操作是如何创建文件描述符的。首先查看 FileOutputStream 的构造方法 ,可以发现会初始化一个名为fd的 FileDescriptor 变量,这个 FileDescriptor 对象是Java层对native文件描述符的封装其中只包含一个int类型的成员變量,这个变量的值就是native层创建的文件描述符的值
     
    
              
     
     

    接下来,让我们进入方法查看对应实现
     

    在fileOpen方法中,通过handleOpen生成native层的文件描述符(fd),这個fd就是这个所谓对面的文件描述符
     
     
     

    回到开始,FileOutputStream构造方法中初始化了Java层的文件描述符类 FileDescriptor目前这个对象中的文件描述符的值还是初始的-1,所以目前它还是一个无效的文件描述符native层完成fd创建后,还需要把fd的值传到 Java层
    我们再来看SET_FD这个宏的定义,在这个宏定义中通过反射的方式给Java层对象的成员变量赋值。由于上文内容可知open0是对象的jni方法,所以宏中的this就是初始创建的FileOutputStream在Java层的对象实例。
    
              

    而fid则会在native代码中提前初始化好
     

    收,到这里FileOutputStream的初始化跟进就完成了我们已经找到了底层fd初始化的路径。Android的IO操作还有其他的流操作类大致流程基本类似,这裏不再细述

    并不是不关闭就一定会导致文件描述符泄漏,在流对象的析构方法中会调用close方法所以这个对象被回收时,理论上也是会释放文件描述符但是最好还是通过代码控制释放逻辑。

    
              

    在日常开发中如果使用数据库SQLite管理本地数据在数据库查询的cursor使用完成后,亦需要調用close方法释放资源否则也有可能导致内存和文件描述符的泄漏。
     

    按照理解query操作应该会导致文件描述符泄漏那我们就从query方法的实现开始汾析。
    然而在query方法中并没有发现文件描述符相关的代码。
    经过测试发现moveToNext 调用后才会导致文件描述符增长。通过query方法可以获取cursor的实现类SQLiteCursor
     
    
              
     

    getCount 方法中对成员变量mCount做判断,如果还是初始值则会调用fillWindow方法。
     
    
              
     
    
              
     
    
              
     
    
              
     

    ashmem_create_region 方法最终会调用到open函数打开文件并返回系统创建的文件描述符这部分代碼不在赘述,有兴趣的可以自行查看
    native完成初始化会把fd信息保存在CursorWindow中并会返回一个指针地址到Java层,Java层可以通过这个指针操作c++层对象从而也能获取对应的文件描述符

    通过WindowManager反复添加view也会导致文件描述符增长,可以通过调用removeView释放之前创建的FD
     
    
              
     
    
              
     
    
              
     
    
              
     
    
              
     
    
              
     //创建一对匿名的已经连接的套接字
     

    WindowManager 的汾析涉及WMS,WMS内容比较多本文重点关注文件描述符相关的内容。简单的理解就是进程间通讯会创建socket,所以也会创建文件描述符而且会茬服务端进程和客户端进程各创建一个。另外如果系统进程文件描述符过多,理论上会造成系统崩溃
    如果你的应用收到如下这些崩溃堆栈,恭喜你你的应用存在文件描述符泄漏。

文件描述符导致的崩溃往往无法通过堆栈直接分析道理很简单: 出问题的代码在消耗文件描述符同时,正常的代码逻辑可能也同样在创建文件描述符所以崩溃可能是被正常代码触发了。

遇到这类问题可以先尝试本体复现通過命令 ‘ls -la /proc/$pid/fd’ 查看当前进程文件描述符的消耗情况。一般android应用的文件描述符可以分为几类通过对比哪一类文件描述符数量过高,来缩小问題范围

如果是本地无法复现问题,可以尝试添加线上监控代码定时轮询当前进程使用的FD数量,在达到阈值时读取当前FD的信息,并传箌后台分析获取FD对应文件信息的代码如下。


  

4.4 排查循环打印的日志

除了直接对 FD相关的信息进行分析还需要关注logcat中是否有频繁打印的信息,例如:socket创建失败

  • 文件描述符得到文件路径

    
          
    
          
    
        
  • Android Binder传递文件描述符原理分析前言问题描述问题原因 前言 Binder是Android中最常用,最重要的进程间通信机淛我们知道,Binder不仅可以传递普通的数据还可以传递文件描述符。本文尝试分析Binder传递文件...

    
          

    Binder是Android中最常用最重要的进程间通信机制。我们知道Binder不仅可以传递普通的数据,还可以传递文件描述符本文尝试分析Binder传递文件描述符的原理,切入点是工作中遇到一个和文件描述符楿关的问题本文讲解分析和解决该问题的思路,在分析该问题的过程中Binder传递文件描述符的原理也会呈现出来。我们都知道Binder机制是由內核支持的,所以在这个过程中也会深入到内核中去分析。

    一个native进程需要读取sdcard上的一个大文件但是没有读sdcard权限。所以在app中打开该文件并通过binder机制将该文件的fd传递到native进程中。但是在native进程中接收到fd后无法通过fd读取文件。
          

    native进程在读取到fd后会在C++层的Parcel对象析构时,关闭fd相當于app端传递过来的fd,只在Parcel对象析构之前有效下面看代码:
          
    
          
          
    
          
          
    
          
          
    
          
          

    所以,当我们从Parcel中通过调用readFileDescriptor读到文件描述符(fd)后如果这时Parcel对象被析构了,那么峩们读取的文件描述符就是无效的因为底层的文件已经关闭了,我们读取的这个fd就只是一个普通的整数已经和底层的文件没有关联了。
    
          
          
     
     
    
          

    这个注释的意思是如果你通过readFileDescriptor读取一个文件描述符,那么你读取的是一个原生(raw)的文件描述符这个描述符并不属于你,也就是说这个攵件描述符还是归Parcel所有所以Parcel可以在析构时关闭文件。如果我们要使用这个文件描述符那么就需要调用dup,复制这个文件描述符然后我們使用复制的这个文件描述符,就不怕Parcel关闭文件了
    分析到这里,我们已经知道如何解决这个问题了但是我们并不满足。还有一些问题昰我们没搞明白的比如:
    1 文件描述符到底是怎么通过Binder传递的?
    2 dup的原理是什么
    3 即使我们使用dup复制了文件描述符,但是Parcel里的close还是调用了為什么文件没有被关闭?
    我们接下来深入分析这些问题。

    我们先看客户端是怎么发起Binder请求的在C++层中,客户端发起Binder调用是在BpBindertransact函数中:
          
    
          
          
    
          
          
    
          
          

    ioctl是┅个系统调用,通过ioctl就陷入到了内核中下面我们继续跟踪内核中的ioctl是如何处理Binder请求的。
          
    
          
          
    
          
          
    
          
          
    
          
          

    上面的代码是Binder驱动处理传递的fd的过程
    首先调用fget,传递的参数fp->handle是客户端进程要传递的文件fdfget根据fd获取到相关的struct file,一个struct file代表被进程打开的一个文件当一个进程打开一个文件的时候,内核會创建一个struct file然后将一个fd关联到该struct file,然后将fd返回到用户空间
    在目标进程中找到一个可用的fd后,然后调用task_fd_install将上一步中找到的struct file关联到目标进程中的fd
    程序执行到这里,相当于Binder驱动在内核空间中帮目标进程打开了这个文件然后将fd返回目标进程的用户空间,用户空间的程序就可鉯通过这个fd访问到相关文件
          

    首先我们看一下dup的文档:
          

    dup的文档上说dup调用复制一个文件描述符,新的描述符和旧的描述符它们指向同一个咑开的文件(在内核中就是同一个struct file)。
          

    文件状态f_flags和文件偏移f_pos都定义在struct file中所以和struct file关联的fd会共享文件状态和文件偏移量。并且struct file中有一个使用计数f_count当一个fd被关闭后,f_count减1只有减到0后,struct file才会被释放才算是真正关闭文件。
    所以关闭旧的fd后dup出的新的fd还是有效的。这也是为什么Parcel析构時关闭了文件描述符后,我们自己dup出的文件描述符还是可用的因为底层的文件(struct file)还有fd引用它,所以不会关闭通过新的fd还是可以访问的。
          
          

    1 通过Binder传递文件描述符相当Binder驱动为服务端进程打开同一个文件,这时客户端和服务端进程在内核中共享同一个struct file
    2 当服务端进程将fd传递到用戶空间的Parcel中时,Parcel在析构时会关闭这个fd这是因为考虑到如果服务端进程不使用这个fd,这个fd就永远无法关闭会造成fd泄露。
    3 如果我们要使用這个fd需要dup一个新的fd来使用,即使Parcel析构时关闭了原来的fd新的fd不受影响。
          

    Java层不需要显示调用dup

          

    关于Binder的使用相对于C++接口,我们更常用的是Java接ロ我们使用Java接口读取其他进程传递过来的文件描述符时,并没有显示的调用dup为什么我们读到的文件描述符是可用的呢?
    其实不是不需偠dup而是我们在Java层中调用Parcel.java的readFileDescriptor的时候,jni层会自动帮我们dup出一个文件描述符然后返回这个dup的文件描述符,而不是直接返回原生的文件描述符
          
    
          
          
    
          
          
    
          
          
    
          
          
    
          
          
    
          
          
    
          
          

    可以看到,该方法首先从一个C++的Parcel对象中读取到fd然后通过dup复制一个新的fd。
        
  • Android进程间文件描述符传递原理-初1.进程表2.进程控制块状态存储文件3.攵件描述符4.进程间共享文件描述符a.binder驱动将调用进程传入的文件描述符使用fget内核函数后去file对象b.binder驱动在被调用进程中创建新的...


    操作系统为了了┅张表格即进程表,每个进程占用一个表格项
    每个进程在进程表中占有的表格项目被称为进程控制块,进程控制块中的内容用于辅助管理进程的状态存储以及文件,用于保存以及快速恢复进程的状态

    状态即进程的寄存器状态,程序计数器(CS)状态进程状态,堆栈指針优先级,进程ID等信息

    存储堆栈段,数据段以及正文段的指针

    前面说到,进程控制块中保存有打开的文件状态这个文件状态在内核中是由file对象管理保存,file对象对应一个文件描述符是一个整数,有当前打开文件的进程分配并且保存在进程控制块中
    内核函数fget可以通過文件描述符返回对应的file对象。

    a.binder驱动将调用进程传入的文件描述符使用fget内核函数后去file对象

    
          

    b.binder驱动在被调用进程中创建新的文件描述符与该file对潒关联

    
          

    c.binder修改调用进程传入的文件描述符改为该被调用进程中创建的新的文件描述符

    
        
  • android binder传递文件描述符在linux中,进程打开一个文件返回一个整数的文件描述符,然后就可以在这个文件描述符上对该文件进行操作那么文件描述符和文件到底是什么关系?进程使用的是虚拟地址不同进程间是...

  • 在平时的 Android 开发中,你与文件描述符打过交道吗一些知识点会涉及到文件描述符,比如:mmap 函数的文件描述符参数epoll 机制对文件描述符的限制问题...这时如果让你说说对文件描述符的了解,你能回答上来吗...

  • }

    创建了一个或多或少类似的应用程序:

    不幸的是,有时应用程序在不同的活动中崩溃并出现以下:

    但我不知道这个是什么意思,因为我不使用.任何的想法

    或多或少相同.不幸嘚是,这些问题没有得到令人满意的(并且足够普遍)的答案,所以无论如何我都要尝试.

    描述符在Android中的多个位置使用:

    >字(是的,开放的网络连接也是“”);
    > Pipes-Linux在任何地方都使用它们,例如,导致异常的尝试打开管道,可能需要在IME()进程和您的应用程序之间发送输入事件.

    所有描述符均以为准;当超过该時,您的应用程序开始遇到严重问题.在这种情况下,使进程死亡是最佳方案,因为否则内核将耗尽内存(描述符存储在内核内存中).

    您可能会遇到没囿描述符(,网络连接)的问题.你必须尽快它们.您可能还遇到内存泄漏问题 – 对象在应该进行收集时(而某些泄漏的对象可能会依次保留在描述符仩).

    您自己的不必是有罪的,您使用的库甚至一些系统组件可能有,导致内存泄漏和描述符泄漏.我建议你使用-一个简单易用的库来检测泄漏(好吧,臸少是内存泄漏,这是最常见的).

    以上是为你收集整理的全部内容,希望文章能够帮你解决所遇到的程序开发问题

    如果觉得网站内容还不错,欢迎将推荐给程序员好友

    本图文内容来源于网友网络收集整理提供,作为学习参考使用版权属于原作者。

    喜欢与人分享编程技术与笁作经验欢迎加入编程之家官方交流群!
    }

    我要回帖

    更多推荐

    版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

    点击添加站长微信