上一篇文章,在解析初始化GraphicBuffer中,遇到一个ion驱动,对图元进行管理。首先看看ion是怎么使用的:
我们按照这个流程分析ion的源码。
如果对ion使用感兴趣,可以去这篇文章下面看 https://blog.csdn.net/hexiaolong2009/article/details/102596744
本文基于Android的linux内核版本3.1.8
遇到什么问题欢迎来本文讨论 https://www.jianshu.com/p/5fe57566691f
什么是ion?如果是音视频,Camera的工程师会对这个驱动比较熟悉。最早的GPU和其他驱动协作申请一块内存进行绘制是使用比较粗暴的共享内存。在Android系统中使用的是匿名内存。最早由三星实现了一个Display和Camera共享内存的问题,曾经在Linux社区掀起过一段时间。之后各路大牛不断的改进之下,就成为了dma_buf驱动。并在 Linux-3.3 主线版本合入主线。现在已经广泛的运用到各大多媒体开发中。
首先介绍dma_buf的2个角色,importer和exporter。importer是dma_buf驱动中的图元消费者,exporter是dma_buf驱动中的图元生产者。
这里借用大佬的图片:
ion是基于dma_buf设计完成的。经过阅读源码,其实不少思路和Android的匿名内存有点相似。阅读本文之前就算不知道dma_buf的设计思想也没关系,我不会仔细到每一行,我会注重其在gralloc服务中的申请流程,看看ion是如何管理共享内存,为什么要抛弃ashmem。
我们先来看看ion的file_operation:
只有一个open和ioctl函数。但是没有mmap映射。因此mmap映射的时候一定其他对象在工作。
我们关注显卡英伟达的初始化模块。
文件:/ drivers / staging / android / ion / tegra / tegra_ion.c
mole_platform_driver实际上就是我之前经常提到过的mole_init的一个宏,多了一个register注册到对应名字的平台中的步骤。在这里面注册了一个probe方法指针,probe指向的tegra_ion_probe是加载内核模块注册的时候调用。
先来看看对应的结构体:
再来看看对应ion内的堆结构体:
完成的事情如下几个步骤:
我们不关注debug模式。其实整个就是我们分析了很多次的方法。把这个对象注册miscdevice中。等到insmod就会把整个整个内核模块从dev_t的map中关联出来。
我们来看看这个驱动结构体:
文件:/ drivers / staging / android / ion / ion_heap.c
这里有四个不同堆会申请出来,我们主要来看看默认的ION_HEAP_TYPE_SYSTEM对应的heap流程。
其实真正象征ion的内存堆是下面这个结构体
不管原来的那个heap,会新建3个ion_system_heap,分别order为8,4,0,大于4为大内存。意思就是这个heap中持有一个ion_page_pool 页资源池子,里面只有对应order的2的次幂,内存块。其实就和伙伴系统有点相似。
还会设置flag为ION_HEAP_FLAG_DEFER_FREE,这个标志位后面会用到。
文件:/ drivers / staging / android / ion / ion_page_pool.c
在pool中分为2个链表一个是high_items,另一个是low_items。他们之间的区分在此时就是以2为底4的次幂为分界线。
文件:/ drivers / staging / android / ion / ion.c
因为打开了标志位ION_HEAP_FLAG_DEFER_FREE和heap存在shrink方法。因此会初始化两个回收函数。
文件:/ drivers / staging / android / ion / ion_heap.c
此时会创建一个内核线程,调用ion_heap_deferred_free内核不断的循环处理。不过由于这个线程设置的是SCHED_IDLE,这是最低等级的时间片轮转抢占。和Handler那个adle一样的处理规则,就是闲时处理。
在这个循环中,不断的循环销毁处理heap的free_list里面已经没有用的ion_buffer缓冲对象。
文件:/ drivers / staging / android / ion / ion_system_heap.c
注册了heap的销毁内存的方法。当系统需要销毁页的时候,就会调用通过register_shrinker注册进来的函数。
文件:/ drivers / staging / android / ion / ion_page_pool.c
整个流程很简单,其实就是遍历循环需要销毁的页面数量,接着如果是8的次幂就是移除high_items中的page缓存。4和0则销毁low_items中的page缓存。至于为什么是2的次幂其实很简单,为了销毁和申请简单。__free_pages能够整页的销毁。
文件:/ drivers / staging / android / ion / ion.c
主要就是初始化ion_client各个参数,最后把ion_client插入到ion_device的clients。来看看ion_client结构体:
核心还是调用ion_alloc申请一个ion缓冲区的句柄。最后把数据拷贝会用户空间。
这个实际上就是找到最小能承载的大小,去申请内存。如果8kb申请内存,就会拆分积分在0-4kb,4kb-16kb,16kb-128kb区间找。刚好dma也是在128kb之内才能申请。超过这个数字就禁止申请。8kb就会拆成2个4kb保存在第一个pool中。
最后所有的申请的page都添加到pages集合中。
文件:/ drivers / staging / android / ion / ion_page_pool.c
能看到此时会从 ion_page_pool冲取出对应大小区域的空闲页返回上层,如果最早的时候没有则会调用ion_page_pool_alloc_pages申请一个新的page。由于引用最终来自ion_page_pool中,因此之后申请之后还是在ion_page_pool中。
这里的处理就是为了避免DMA直接内存造成的缓存差异(一般的申请,默认会带一个DMA标志位)。换句话说,是否打开cache其实就是,关闭了则使用pool的cache,打开了则不使用pool缓存,只依赖DMA的缓存。
我们可以看另一个dma的heap,它是怎么做到dma内存的一致性.
文件: drivers / staging / android / ion / ion_cma_heap.c
能看到它为了能办到dma缓存的一致性,使用了dma_alloc_coherent创建了一个所有强制同步的地址,也就是没有DMA缓存的地址。
这里出现了几个新的结构体,sg_table和scatterlist
文件:/ lib / scatterlist.c
这里面实际上做的事情就是一件:初始化sg_table.
sg_table中有一个核心的对象scatterlist链表。如果pages申请的对象数量<PAGE_SIZE/sizeof(scatterlist),每一项sg_table只有一个scatterlist。但是超出这个数字就会增加一个scatterlist。
用公式来说:
换句话说,每一次生成scatterlist的链表就会直接尽可能占满一页,让内存更好管理。
返回了sg_table。
初始化ion_handle,并且记录对应的ion_client是当前打开文件的进程,并且设置ion_buffer到handle中。使得句柄能够和buffer关联起来。
每当ion_buffer需要销毁,
㈡ Android跨进程通信-mmap函数
通过mmap或者内存共享的Linux IPC机制
直接世猛将同一段内存映射到数据发送进程和数据接收进程的用户空间,这样数据发送进程只需要将数据拷贝到共享的内存区域,数据接收进程就可以直接使用数据了。
mmap是一个很重要的函数,它可以实现共享内存,但并不像SystemV和Posix的共享内存存粹的只用于共享内存,桥返饥mmap()的设计,主要是用来做文件的映射的,它提供了我们一种新的访问文件的方案。
mmap函数的使用非常简单,我们来看一下
常规文件操作为了提高读写效率和保护磁盘,使用了 页缓存机制 ,这种机制会造成读文件时需要先将文件页从磁盘拷贝到页缓存中,由于 页缓存处在内核空间 ,不能被用户进程直接寻址,所以还需要 将页缓存中数据页再次拷贝到内存 对应的用户空间中。
常规文件操作为了提高读写效率和保护磁盘,使用了页缓存机制,这种机制会造成读文件时需要先将文件页从磁盘拷贝到页缓存中,由于页缓存处在内核空间,不能被用户进程直接寻址,所以还需要将页缓存中数据页再次拷贝到内存对应的用户空间中。
而 使用mmap操作文件中,由于不需要经过内核空间的数据缓存,只使用一次数据拷贝,就从磁盘中将数据传入内存的用户空间中,供进程使用 。
mmap的关键点是实现了用户空间和内核空间的数据直接交互而省去了空间不同数据不通的繁琐过程,因此mmap效率很高。
mmap()使用非常频繁,看过Android系统源码的人,肯定看到过大量的地方使用mmap()函数,比如上面提到的 匿名共享内存的使用就使用到了mmap来映射/dev/ashmem里的文件 。
这里我再介绍一种mmap()在Android系统上的使用场景, mmap的设计目的就是为了让文件的访问更有效率 ,所以当APK进行安装时,为了更高效的读取APK包里面的文件,同样也用到了mmap函数。
Dalvik在安装应敏返用时,需要加载dex文件,然后进行odex优化处理,优化函数为dvmContinueOptimization,我们看一下他的大致实现。
可以看到,dvmContinueOptimization函数中对dex文件的加载便用了mmap内存映射函数。
㈢ android匿名共享内存两个进程间必须知道文件描述符吗
在Android 匿名共享内存驱动源链渣配码分析中详细分析了匿名共享内存在Linux内核空间的实现,虽然内核空间实现了匿名共享内存,但仍然需要在用户空间为用户使用匿名共享内存提供访问接口。Android系统在用户空间,C++应用程序框架层,java层分别提供了访问接口
本文首先介绍匿名共享内存在用户空间提供的C语言接口,在后续文章中在介绍Android匿名共享内存的C++及Java接口,从而全面理解并掌握Android匿名共享内存的使用。
1)匿名共享内存的创建
system\core\libcutils\ashmem-dev.c
Java代码 收藏代码
int ashmem_create_region(const char *name, size_t size)
{
int fd, ret;
//打开"/dev/ashmem"设备文件
fd = open(ASHMEM_DEVICE, O_RDWR);
if (fd < 0)
return fd;
//根据Java空间传过来的名称修改设备文件名
if (name) {
char buf[ASHMEM_NAME_LEN];
strlcpy(buf, name, sizeof(buf));
//进入匿名共享内存驱梁判动修改匿名共享内存名称
ret = ioctl(fd, ASHMEM_SET_NAME, buf);
if (ret < 0)
goto error;
}
////进入匿名共享内存驱动修改匿名共享内存大小
ret = ioctl(fd, ASHMEM_SET_SIZE, size);
if (ret < 0)
goto error;
return fd;
error:
close(fd);
return ret;
}
ASHMEM_DEVICE的宏定义如下:
Java代码 收藏代码
#define ASHMEM_DEVICE "/dev/ashmem"
函数ashmem_create_region首先通过open函数进入匿名共享内存驱动打开/dev/ashmem设备文件,打开过程在Android 匿名共享内存驱动源码分析中已经详细分析了,就是在匿名共享内棚指存初始化过程创建的slab缓冲区ashmem_area_cachep中创建并初始化一个ashmem_area结构体了,接着通过IO命令来修改该ashmem_area结构体的成员name和size,具体设置过程请查看Android 匿名共享内存驱动源码分析。匿名共享内存的创建过程可以归纳为以下三个步骤:
1.打开/dev/ashmem设备文件;
2. 修改匿名共享内存名称
3. 修改匿名共享内存大小
2)设置匿名共享内存属性
通过Ioctl命令控制系统调用进入内核空间的匿名共享内存驱动来设置匿名共享内存块的属性值,比如设置匿名共享内存块的锁定与解锁,设置匿名共享内存块的大小,名称,保护位等属性信息。Android对匿名共享内存的这些属性访问也提供了相应的C语言接口:
1. 设置匿名共享内存的保护位
Java代码 收藏代码
int ashmem_set_prot_region(int fd, int prot)
{
return ioctl(fd, ASHMEM_SET_PROT_MASK, prot);
}
2.锁定匿名共享内存块
Java代码 收藏代码
int ashmem_pin_region(int fd, size_t offset, size_t len)
{
struct ashmem_pin pin = { offset, len };
return ioctl(fd, ASHMEM_PIN, &pin);
}
3.解锁指定匿名共享内存块
Java代码 收藏代码
int ashmem_unpin_region(int fd, size_t offset, size_t len)
{
struct ashmem_pin pin = { offset, len };
return ioctl(fd, ASHMEM_UNPIN, &pin);
}
4.获取创建的匿名共享内存大小
Java代码 收藏代码
int ashmem_get_size_region(int fd)
{
return ioctl(fd, ASHMEM_GET_SIZE, NULL);
}
无论是匿名共享内存的属性设置还是获取,都是直接使用ioctl系统调用进入匿名共享内存驱动中实现的,关于匿名共享内存驱动是如何实现这些功能的,在Android 匿名共享内存驱动源码分析中有详细的介绍,这里就不重复介绍了。这里我们知道Android提供的匿名共享内存C语言接口比较简单。了解了匿名共享内存的C语言接口之后也为以后学习匿名共享内存的C++接口提供基础。
㈣ 如何写一个Android USB接口驱动
说到 android 驱动是离不开 Linux 驱动的。Android 内核采用的是 Linux2.6 内核 (最近Linux 3.3 已经包含了一些 Android 代码)。但 Android 并没有完全照搬 Linux 系统内核,除了对Linux 进行部分修正,还增加了不少内容。android 驱动 主要分两种类型:Android 专用驱动 和 Android 使用的设备驱动(linux)。
Android 专有驱动程序:
1)Android Ashmem 匿名共享内存; 为用户空间程序提供分配内存的机制,为进程间提供大块共享内存,同时为内核提供回收和管理这个内存。
2)Android Logger 轻量级的LOG(日志) 驱动;
3)Android Binder 基于 OpenBinder 框架的一个驱动;
4)Android Power Management 电源管理模块;
5)Low Memory Killer 低内存管理器;
6)Android PMEM 物理内存驱动;
7)USB Gadget USB 驱动(基于 gaeget 框架);
8)Ram Console 用于调试写入日志信息的设备;
9)Time Device 定时控制设备;
10)Android Alarm 硬件时钟;
Android 上的设备驱动:
1)Framebuff 显示驱动;
2)Event 输入设备驱动;
3)ALSA 音频驱动;
4)OSS 音频驱动;
5)v412摄像头:视频驱动;
6)MTD 驱动;
7)蓝牙驱动;
8)WLAN 设备驱动;
Android 专有驱动程序
1.Android Ashmem
为用户空间程序提供分配内存的机制,为进程间提供大块共享内存,同时为内核提供回收和管理这个内存。
设备节点:/dev/ashmen .主设备号 10.
源码位置: include/linux/ashmen.h Kernel /mm/ashmen.c
相比于 malloc 和 anonymous/named mmap 等传统的内存分配机制,其优势是通过内核驱动提供了辅助内核的内存回收算法机制(pin/unoin)
2.Android Logger
无论是底层的源代码还上层的应用,我们都可以使用 logger 这个日志设备看、来进行调试。
设备节点: /dev/log/main /dev/log/event /dev/log/radio
源码位置:include/linux/logger.h include/linux/logger.c
3.Android Binder
IPC Binder 一种进程间通信机制。他的进程能够为其它进程提供服务 ----- 通过标准的 Linux 系统调用 API。
设备节点 :/dev/binder
源码位置:Kernel/include/linux/binder.h Kernel/drivers/misc/binder.c
4.Android Power Management
一个基于标准 linux 电源管理的轻量级 Android 电源管理系统,在 drivers/android/power.c kernel/power/
5.Low Memory Killer
它在用户空间中指定了一组内存临界值,当其中某个值与进程描述中的 oom_adj 值在同一范围时,该进程将被Kill掉(在parameters/adj中指定oome_adj 的最小值)。它与标准的Linux OOM机制类似,只是实现方法不同
源码位置:drivers/misc/lowmemorykiller.c
6.Android PMEM
PMEM 主要作用就是向用户空间提供连续的物理内存区域。
1.让 GPU 或 VPU 缓冲区共享 CPU 核心。
2.用于 Android service 堆。
源码位置:include/linux/android_pmem.h drivers/android/pmem.c
7.USB Gadget
基于标准 Linux USB gaeget 驱动框架的设备驱动。
源码位置:drivers/usb/gadet/
8.Ram Console
为了提供调试功能,android 允许将调试日志信息写入这个设备,它是基于 RAM 的 buffer.
源码位置: drivers/staging/android/ram_console.c
9.Time Device
定时控制,提供了对设备进行定时控制的功能。
源码位置:drivers/staging/android/timed_output.c(timed_gpio.c)
10.Android Alarm
提供一个定时器,用于把设备从睡眠状态唤醒,同时它还提供了一个即使在设备睡眠时也会运行的时钟基准。
设备节点:/dev/alarm
源码位置:drivers/trc/alarm.c
Android 设备驱动
1. Framebuffer 帧缓存设备
Framebuffer 驱动在 Linux 中是标准的显示设备的驱动。对于 PC 系统,它是显卡的驱动 ; 对于嵌入式 SOC 处理器系统,它是 LCD 控制器或者其他显示控制器的驱动。它是一个字符设备,在文件系统中设备节点通常是 /dev/fbx 。 每个系统可以有多个显示设备 , 依次用 /dev/fbO 、 /dev/fb l
等来表示。在 Android 系统中主设备号为 29 ,次设备号递增生成。
Android 对 Framebuffer 驱动的使用方式是标准的 , 在 / dev / graphie / 中的 Framebuffer 设备节点由 init 进程自动创建 , 被 libui 库调用 。 Android 的 GUI 系统中 , 通过调用 Framebuffer 驱动的标准接口,实现显示设备的抽象。
Framebuff的结构框架和实现 :
linux LCD驱动(二)--FrameBuffer
Linux LCD驱动(四)--驱动的实现
2.Event输入设备驱动
Input 驱动程序是 Linux 输入设备的驱动程序 , 分为游戏杆 (joystick) 、 鼠标 (mouse 和 mice)和事件设备 (Event queue)3 种驱动程序。其中事件驱动程序是目前通用的程序,可支持键盘 、 鼠标、触摸屏等多种输入设备。 Input 驱动程序的主设备号是 l3 ,每一种 Input 设备从设备号占 用5 位 , 3 种从设备号分配是 : 游戏杆 0 ~ 61 ; Mouse 鼠标 33 ~ 62 ; Mice 鼠标 63 ; 事件设备 64 ~ 95 ,各个具体的设备在 misc 、 touchscreen 、 keyboard 等目录中。
Event 设备在用户空问使用 read 、 ioctl 、 poll 等文件系统的接口操作, read 用于读取输入信息, ioctl 用于获取和设置信息, poll 用于用户空间的阻塞,当内核有按键等中断时,通过在中断中唤醒内核的 poll 实现。
Event 输入驱动的架构和实现:
Linux设备驱动之——input子系统
3.ALSA音频驱动
高级 Linux 声音体系 ALSA(Advanced Linux Sound Architecture ) 是为音频系统提供驱动 的Linux 内核组件,以替代原先的开发声音系统 OSS 。它是一个完全开放源代码的音频驱动程序集 ,除了像 OSS 那样提供一组内核驱动程序模块之外 , ALSA 还专门为简化应用程序的编写提供相应的函数库,与 OSS 提供的基于 ioctl 等原始编程接口相比, ALSA 函数库使用起来要更加方便一些
利用该函数库,开发人员可以方便、快捷地开发出自己的应用程序,细节则留给函数库进行内部处理 。 所以虽然 ALSA 也提供了类似于 OSS 的系统接口 , 但建议应用程序开发者使用音频函数库,而不是直接调用驱动函数。
ALSA 驱动的主设备号为 116 ,次设备号由各个设备单独定义,主要的设备节点如下:
/ dev / snd / contmlCX —— 主控制 ;
/ dev / snd / pcmXXXc —— PCM 数据通道 ;
/ dev / snd / seq —— 顺序器;
/ dev / snd / timer —— 定义器。
在用户空问中 , ALSA 驱动通常配合 ALsA 库使用 , 库通过 ioctl 等接口调用 ALSA 驱动程序的设备节点。对于 AIJSA 驱动的调用,调用的是用户空间的 ALsA 库的接口,而不是直接调用 ALSA 驱动程序。
ALSA 驱动程序的主要头文件是 include / sound ./ sound . h ,驱动核心数据结构和具体驱动的注册函数是 include / sound / core . h ,驱动程序 的核心实现是 Sound / core / sound . c 文件。
ALSA 驱动程序使用下面的函数注册控制和设备:
int snd _ pcm _ new (struct snd _ card * card , char * id , int device , int playback _ count , int capture _ count , struct snd _ pcm ** rpcm) ;
int snd ctl _ add(struct snd _ card * card , struct snd _ kcontrol * kcontro1) ;
ALSA 音频驱动在内核进行 menuconfig 配置时 , 配置选项为 “ Device Drivers ” > “ Sound c ard support ” 一 > “ Advanced Linux Sound Architecture ” 。子选项包含了 Generic sound devices( 通用声音设备 ) 、 ARM 体系结构支持,以及兼容 OSS 的几个选项。 ALsA 音频驱动配置对应的文件是sound / core / Kconfig 。
Android 没有直接使用 ALSA 驱动,可以基于 A-LSA 驱动和 ALSA 库实现 Android Audio 的硬件抽象层; ALSA 库调用内核的 ALSA 驱动, Audio 的硬件抽象层调用 ALSA 库。
4.OSS音频驱动
OSS(Open Sound System开放声音系统)是 linux 上最早出现的声卡驱动。OSS 由一套完整的内核驱动程序模块组成,可以为绝大多数声卡提供统一的编程接口。
OSS 是字符设备,主设备号14,主要包括下面几种设备文件:
1) /dev/sndstat
它是声卡驱动程序提供的简单接口,它通常是一个只读文件,作用也只限于汇报声卡的当前状态。(用于检测声卡)
2)/dev/dsp
用于数字采样和数字录音的设备文件。对于音频编程很重要。实现模拟信号和数字信号的转换。
3)/dev/audio
类似于/dev/dsp,使用的是 mu-law 编码方式。
4)/dev/mixer
用于多个信号组合或者叠加在一起,对于不同的声卡来说,其混音器的作用可能各不相同。
5)/dev/sequencer
这个设备用来对声卡内建的波表合成器进行操作,或者对 MIDI 总线上的乐器进行控制。
OSS 驱动所涉及的文件主要包括:
kernel/include/linux/soundcard.h
kernel/include/linux/sound.h 定义 OSS 驱动的次设备号和注册函数
kernel/sound_core.c OSS核心实现部分
5.V4l2视频驱动
V4L2是V4L的升级版本,为linux下视频设备程序提供了一套接口规范。包括一套数据结构和底层V4L2驱动接口。V4L2提供了很多访问接口,你可以根据具体需要选择操作方法。需要注意的是,很少有驱动完全实现了所有的接口功能。所以在使用时需要参考驱动源码,或仔细阅读驱动提供者的使用说明。
V4L2的主设备号是81,次设备号:0~255,这些次设备号里也有好几种设备(视频设备、Radio设备、Teletext、VBI)。
V4L2的设备节点: /dev/videoX, /dev/vbiX and /dev/radioX
Android 设备驱动(下)
MTD 驱动
Flash 驱动通常使用 MTD (memory technology device ),内存技术设备。
MTD 的字符设备:
/dev/mtdX
主设备号 90.
MTD 的块设备:
/dev/block/mtdblockX
主设备号 13.
MTD 驱动源码
drivers/mtd/mtdcore.c:MTD核心,定义MTD原始设备
drivers/mtd/mtdchar.c:MTD字符设备
drivers/mtd/mtdblock.c:MTD块设备
MTD 驱动程序是 Linux 下专门为嵌入式环境开发的新一类驱动程序。Linux 下的 MTD 驱动程序接口被划分为用户模块和硬件模块:
用户模块 提供从用户空间直接使用的接口:原始字符访问、原始块访问、FTL (Flash Transition Layer)和JFS(Journaled File System)。
硬件模块 提供内存设备的物理访问,但不直接使用它们,二十通过上述的用户模块来访问。这些模块提供了闪存上读、写和擦除等操作的实现。
蓝牙驱动
在 Linux 中,蓝牙设备驱动是网络设备,使用网络接口。
Android 的蓝牙协议栈使用BlueZ实现来对GAP, SDP以及RFCOMM等应用规范的支持,并获得了SIG认证。由于Bluez使用GPL授权, 所以Android 框架通过D-BUS IPC来与bluez的用户空间代码交互以避免使用未经授权的代码。
蓝牙协议部分头文件:
include/net/bluetooth/hci_core.h
include/net/bluetooth/bluetooth.h
蓝牙协议源代码文件:
net/bluetooth/*
蓝牙驱动程序部分的文件:
drivers/bluetooth/*
蓝牙的驱动程序一般都通过标准的HCI控制实现。但根据硬件接口和初始化流程的不同,又存在一些差别。这类初始化动作一般是一些晶振频率,波特率等基础设置。比如CSR的芯片一般通过BCSP协议完成最初的初始化配置,再激活标准HCI控制流程。对Linux来说,一旦bluez可以使用HCI与芯片建立起通信(一般是hciattach + hciconfig),便可以利用其上的标准协议(SCO, L2CAP等),与蓝牙通信,使其正常工作了。
WLAN 设备驱动(Wi-Fi)(比较复杂我面会专门写个wifi分析)
在linux中,Wlan设备属于网络设备,采用网络接口。
Wlan在用户空间采用标准的socket接口进行控制。
WiFi协议部分头文件:
include/net/wireless.h
WiFi协议部分源文件:
net/wireless/*
WiFi驱动程序部分:
drivers/net/wireless/*
㈤ Android怎么生成设备节点
Android如何生成设备节点 在Android中,由于没有mdev和udev,所以它没有办法动态的生成设备节点,那么它是如何做的呢? 我们可以在system/core/init/下的init.c和devices.c中找到答案: init.c中 int main(int argc, char **argv) { ... /* Get the basic filesystem setup we need put * together in the initramdisk on / and then we'll * let the rc file figure out the rest. */ mkdir("/dev", 0755); mkdir("/proc", 0755); mkdir("/sys", 0755); mount("tmpfs", "/dev", "tmpfs", 0, "mode=0755"); mkdir("/dev/pts", 0755); mkdir("/dev/socket", 0755); mount("devpts", "/dev/pts", "devpts", 0, NULL); mount("proc", "/proc", "proc", 0, NULL); mount("sysfs", "/sys", "sysfs", 0, NULL); for(;;) { ... if (ufds[0].revents == POLLIN) handle_device_fd(device_fd); if (ufds[1].revents == POLLIN) handle_property_set_fd(property_set_fd); if (ufds[3].revents == POLLIN) handle_keychord(keychord_fd); } return 0; } 我们再来看看handle_device_fd(),该函数定义在devices.c中 void handle_device_fd(int fd) { ... handle_device_event(&uevent); handle_firmware_event(&uevent); } } 而handle_device_event定义如下: static void handle_device_event(struct uevent *uevent) { ... if(!strcmp(uevent->action, "add")) { make_device(devpath, block, uevent->major, uevent->minor); return; } ... } make_device定义如下: static void make_device(const char *path, int block, int major, int minor) { ... mode = get_device_perm(path, &uid, &gid) (block S_IFBLK : S_IFCHR); dev = (major $amp; ... setegid(gid); mknod(path, mode, dev); chown(path, uid, -1); setegid(AID_ROOT); } 我们看看get_device_perm如下实现: static mode_t get_device_perm(const char *path, unsigned *uid, unsigned *gid) { mode_t perm; if (get_device_perm_inner(qemu_perms, path, uid, gid, &perm) == 0) { return perm; } else if (get_device_perm_inner(devperms, path, uid, gid, &perm) == 0) { return perm; } else { struct listnode *node; struct perm_node *perm_node; struct perms_ *dp; /* Check partners list. */ list_for_each(node, &devperms_partners) { perm_node = node_to_item(node, struct perm_node, plist); dp = &perm_node->dp; if (dp->prefix) { if (strncmp(path, dp->name, strlen(dp->name))) continue; } else { if (strcmp(path, dp->name)) continue; } /* Found perm in partner list. */ *uid = dp->uid; *gid = dp->gid; return dp->perm; } /* Default if nothing found. */ *uid = 0; *gid = 0; return 0600; } } 我们最后可以看到在devperms中定义了要生成的设备节点: static struct perms_ devperms[] = { { "/dev/null", 0666, AID_ROOT, AID_ROOT, 0 }, { "/dev/zero", 0666, AID_ROOT, AID_ROOT, 0 }, { "/dev/full", 0666, AID_ROOT, AID_ROOT, 0 }, { "/dev/ptmx", 0666, AID_ROOT, AID_ROOT, 0 }, { "/dev/tty", 0666, AID_ROOT, AID_ROOT, 0 }, { "/dev/random", 0666, AID_ROOT, AID_ROOT, 0 }, { "/dev/urandom", 0666, AID_ROOT, AID_ROOT, 0 }, { "/dev/ashmem", 0666, AID_ROOT, AID_ROOT, 0 }, { "/dev/binder", 0666, AID_ROOT, AID_ROOT, 0 }, /* logger should be world writable (for logging) but not readable */ { "/dev/log/", 0662, AID_ROOT, AID_LOG, 1 }, /* the msm hw3d client device node is world writable/readable. */ { "/dev/msm_hw3dc", 0666, AID_ROOT, AID_ROOT, 0 }, /* gpu driver for adreno200 is globally accessible */ { "/dev/kgsl", 0666, AID_ROOT, AID_ROOT, 0 }, /* these should not be world writable */ { "/dev/diag", 0660, AID_RADIO, AID_RADIO, 0 }, { "/dev/diag_arm9", 0660, AID_RADIO, AID_RADIO, 0 }, { "/dev/android_adb", 0660, AID_ADB, AID_ADB, 0 }, { "/dev/android_adb_enable", 0660, AID_ADB, AID_ADB, 0 }, { "/dev/ttyMSM0", 0600, AID_BLUETOOTH, AID_BLUETOOTH, 0 }, { "/dev/ttyHS0", 0600, AID_BLUETOOTH, AID_BLUETOOTH, 0 }, { "/dev/uinput", 0660, AID_SYSTEM, AID_BLUETOOTH, 0 }, { "/dev/alarm", 0664, AID_SYSTEM, AID_RADIO, 0 }, { "/dev/tty0", 0660, AID_ROOT, AID_SYSTEM, 0 }, { "/dev/graphics/", 0660, AID_ROOT, AID_GRAPHICS, 1 }, { "/dev/msm_hw3dm", 0660, AID_SYSTEM, AID_GRAPHICS, 0 }, { "/dev/input/", 0660, AID_ROOT, AID_INPUT, 1 }, { "/dev/eac", 0660, AID_ROOT, AID_AUDIO, 0 }, { "/dev/cam", 0660, AID_ROOT, AID_CAMERA, 0 }, { "/dev/pmem", 0660, AID_SYSTEM, AID_GRAPHICS, 0 }, { "/dev/pmem_adsp", 0660, AID_SYSTEM, AID_AUDIO, 1 }, { "/dev/pmem_camera", 0660, AID_SYSTEM, AID_CAMERA, 1 }, { "/dev/oncrpc/", 0660, AID_ROOT, AID_SYSTEM, 1 }, { "/dev/adsp/", 0660, AID_SYSTEM, AID_AUDIO, 1 }, { "/dev/snd/", 0660, AID_SYSTEM, AID_AUDIO, 1 }, { "/dev/mt9t013", 0660, AID_SYSTEM, AID_SYSTEM, 0 }, { "/dev/msm_camera/", 0660, AID_SYSTEM, AID_SYSTEM, 1 }, { "/dev/akm8976_daemon",0640, AID_COMPASS, AID_SYSTEM, 0 }, { "/dev/akm8976_aot", 0640, AID_COMPASS, AID_SYSTEM, 0 }, { "/dev/akm8973_daemon",0640, AID_COMPASS, AID_SYSTEM, 0 }, { "/dev/akm8973_aot", 0640, AID_COMPASS, AID_SYSTEM, 0 }, { "/dev/bma150", 0640, AID_COMPASS, AID_SYSTEM, 0 }, { "/dev/cm3602", 0640, AID_COMPASS, AID_SYSTEM, 0 }, { "/dev/akm8976_pffd", 0640, AID_COMPASS, AID_SYSTEM, 0 }, { "/dev/lightsensor", 0640, AID_SYSTEM, AID_SYSTEM, 0 }, { "/dev/msm_pcm_out", 0660, AID_SYSTEM, AID_AUDIO, 1 }, { "/dev/msm_pcm_in", 0660, AID_SYSTEM, AID_AUDIO, 1 }, { "/dev/msm_pcm_ctl", 0660, AID_SYSTEM, AID_AUDIO, 1 }, { "/dev/msm_snd", 0660, AID_SYSTEM, AID_AUDIO, 1 }, { "/dev/msm_mp3", 0660, AID_SYSTEM, AID_AUDIO, 1 }, { "/dev/audience_a1026", 0660, AID_SYSTEM, AID_AUDIO, 1 }, { "/dev/tpa2018d1", 0660, AID_SYSTEM, AID_AUDIO, 1 }, { "/dev/msm_audpre", 0660, AID_SYSTEM, AID_AUDIO, 0 }, { "/dev/msm_audio_ctl", 0660, AID_SYSTEM, AID_AUDIO, 0 }, { "/dev/htc-acoustic", 0660, AID_SYSTEM, AID_AUDIO, 0 }, { "/dev/vdec", 0660, AID_SYSTEM, AID_AUDIO, 0 }, { "/dev/q6venc", 0660, AID_SYSTEM, AID_AUDIO, 0 }, { "/dev/snd/dsp", 0660, AID_SYSTEM, AID_AUDIO, 0 }, { "/dev/snd/dsp1", 0660, AID_SYSTEM, AID_AUDIO, 0 }, { "/dev/snd/mixer", 0660, AID_SYSTEM, AID_AUDIO, 0 }, { "/dev/smd0", 0640, AID_RADIO, AID_RADIO, 0 }, { "/dev/qemu_trace", 0666, AID_SYSTEM, AID_SYSTEM, 0 }, { "/dev/qmi", 0640, AID_RADIO, AID_RADIO, 0 }, { "/dev/qmi0", 0640, AID_RADIO, AID_RADIO, 0 }, { "/dev/qmi1", 0640, AID_RADIO, AID_RADIO, 0 }, { "/dev/qmi2", 0640, AID_RADIO, AID_RADIO, 0 }, /* CDMA radio interface MUX */ { "/dev/ts0710mux", 0640, AID_RADIO, AID_RADIO, 1 }, { "/dev/ppp", 0660, AID_RADIO, AID_VPN, 0 }, { "/dev/tun", 0640, AID_VPN, AID_VPN, 0 }, { NULL, 0, 0, 0, 0 }, };
㈥ 033 Android多进程-共享内存
要使用一块共享内存
还是先看共享内存的使用方法,我主要介绍两个函数:
通过 shmget() 函数申请共享内存,它的入参如下
通过 shmat() 函数将我们申请到的共享内存映射到自己的用户空间,映射成功会返回地址,有了这个地址,我们就可以随意的读写数据了,我们继续看一下这个函数的入参
共享内存的原理是在内存中单独开辟的一段内存空间,这段内存空间其实就是一个tempfs(临时虚拟文件),tempfs是VFS的一种文件系统,挂载在/dev/shm上,前面提到的管道pipefs也是VFS的一种文件系统。
由于共享的内存空间对使用和接收进程来讲,完全无感知,就像是在自己的内存上读写数据一样,所以也是 效率最高 的一种IPC方式。
上面提到的IPC的方式都是 在内核空间中开辟内存来存储数据 ,写数据时,需要将数据从用户空间拷贝到内核空间,读数据时,需要从内核空间拷贝到自己的用户空间,
共享内存就只需要一次拷贝 ,而且共享内存不是在内核开辟空间,所以可以 传输的数据量大 。
但是 共享内存最大的缺点就是没有并发的控制,我们一般通过信号量配合共享内存使用,进行同步和并发的控制 。
共享内存在Android系统中主要的使用场景是 用来传输大数据 ,并且 Android并没有直接使用Linux原生的共享内存方式,而是设计了Ashmem匿名共享内存 。
之前说到有名管道和匿名管道的区别在于有名管道可以在vfs目录树中查看到这个管道的文件,但是匿名管道不行, 所以匿名共享内存同样也是无法在vfs目录中查看到 的, Android之所以要设计匿名共享内存 ,我觉得主要是为了安全性的考虑吧。
我们来看看共享内存的一个使用场景,在Android中,如果我们想要将当前的界面显示出来,需要将当前界面的图元数据传递Surfaceflinger去做图层混合,图层混合之后的数据会直接送入帧缓存,送入帧缓存后,显卡就会直接取出帧缓存里的图元数据显示了。
那么我们如何将应用的Activity的图元数据传递给SurfaceFlinger呢?想要将图像数据这样比较大的数据跨进程传输,靠binder是不行的,所以这儿便用到匿名共享内存。
从谷歌官方提供的架构图可以看到,图元数据是通过BufferQueue传递到SurfaceFlinger去的,当我们想要绘制图像的时候, 需要从BufferQueue中申请一个Buffer,Buffer会调用Gralloc模块来分配共享内存 当作图元缓冲区存放我们的图元数据。
可以看到Android的匿名共享内存是通过 ashmem_create_region() 函数来申请共享内存的,它会在/dev/ashmem下创建一个虚拟文件,Linux原生共享内存是通过shmget()函数,并会在/dev/shm下创建虚拟文件。
匿名共享内存是通过 mmap() 函数将申请到的内存映射到自己的进程空间,而Linux是通过*shmat()函数。
虽然函数不一样,但是Android的匿名共享内存和Linux的共享内存在本质上是大同小异的。
。
㈦ 存储性能优化 MMKV源码解析
好久没有更新常用的第三方库了。让我们来聊聊MMKV这个常用的第三方库。MMKV这个库是做什么的呢?他本质上的定位和sp有点相似,经常用于持久化小数据的键值对。其速度可以说是当前所有同类型中速度最快,性能最优的库。
它的最早的诞生,主要是因为在微信iOS端有一个重大的bug,一个特殊的文本可以导致微信的iOS端闪退,而且还出现了不止一次。为了统计这种闪退的字符出现频率以及过滤,但是由于出现的次数,发现原来的键值对存储组件NSUserDefaults根本达不到要求,会导致cell的滑动卡顿。
因此iOS端就开始创造一个高新性能的键值对存储组件。于此同时,Android端SharedPreferences也有如下几个缺点:
因此Android也开始复用iOS的MMKV,而后Android有了多进程的写入数据的需求,Android组又在这个基础上进行改进。
这里是官方的性能的比较图:
能看到mmkv比起我们开发常用的组件要快上数百倍。
那么本文将会从源码角度围绕MMKV的性能为什么会如此高,以及SharePrefences为什么可能出现ANR的原因。
请注意下文是以MMKV 1.1.1版本源码为例子分析。如果遇到什么问题欢迎来到本文 https://www.jianshu.com/p/c12290a9a3f7 互相讨论。
老规矩,先来看看MMKV怎么使用。mmkv其实和SharePrefences一样,有增删查改四种操作。
MMKV作为一个键值对存储组件,也对了存储对象的序列化方式进行了优化。常用的方式比如有json,Twitter的Serial。而MMKV使用的是Google开源的序列化方案:Protocol Buffers。
Protocol Buffers这个方案比起json来说就高级不少:
使用方式可以阅读下面这篇文章: https://www.jianshu.com/p/e8712962f0e9
下面进行比较几个对象序列化之间的要素比较
而MMKV就是看重了Protocol Buffers的时间开销小,选择Protocol Buffers进行对象缓存的核心。
使用前请初始化:
当然mmkv除了能够写入这些基本类型,只要SharePrefences支持的,它也一定能够支持。
同上,每一个key读取的数据类型就是decodexxx对应的类型名字。使用起来十分简单。
能够删除单个key对应的value,也能删除多个key分别对应的value。containsKey判断mmkv的磁盘缓存中是否存在对应的key。
mmkv和SharePrefences一样,还能根据模块和业务划分对应的缓存文件:
这里创建了一个id为a的实例在磁盘中,进行数据的缓存。
当需要多进程缓存的时候:
MMKV可以使用Ashmem的匿名内存进行更加快速的大对象传输:
进程1:
最重要的一点,mmkv把SharePrefences的缓存迁移到mmkv中,之后的使用就和SharePrefences一致。
这里就是把SharedPreferences的myData数据迁移到mmkv中。当然如果我们需要保持SharePreferences的用法不变需要自己进行自定义一个SharePreferences。
mmkv的用法极其简单,接下来我们关注他的原理。
首先来看看MMKV的初始化。
能看到实际上initialize分为如下几个步骤:
能看到其实就是做这个判断。由于此时设置的是libc++的打包方式。此时BuildConfig.FLAVOR就是StaticCpp,就不会加载c++_shared。当然,如果我们已经使用了c++_shared库,则没有必要打包进去,使用defaultPublishConfig "SharedCppRelease"会尝试的查找动态链接库_shared。这样就能少2M的大小。
请注意一个前提的知识,jni的初始化,在调用了 System.loadLibrary之后,会通过dlopen把so加载到内存后,调用dlsym,调用jni中的JNI_OnLoad方法。
实际上这里面做的事情十分简单:
能从这些native方法中看到了所有MMKV的存储方法,设置支持共享内存ashemem的存储,支持直接获取native malloc申请的内存
接下来就是MMKV正式的初始化方法了。
这个方法实际上调用的是pthread_once方法。它一般是在多线程环境中,根据内核的调度策略,选择一个线程初始化一次的方法。
其实这里面的算法很简单:
defaultMMKV此时调用的是getDefaultMMKV这个native方法,默认是单进程模式。从这里的设计都能猜到getDefaultMMKV会从native层实例化一个MMKV对象,并且让实例化好的Java层MMKV对象持有。之后Java层的方法和native层的方法一一映射就能实现一个直接操作native对象的Java对象。
我们再来看看MMKV的mmkvWithID。
感觉上和defaultMMKV有点相似,也是调用native层方法进行初始化,并且让java层MMKV对象持有native层。那么我们可否认为这两个实例化本质上在底层调用同一个方法,只是多了一个id设置呢?
可以看看MMKV.h文件:
这里就能看到上面的推测是正确的,只要是实例化,最后都是调用mmkvWithID进行实例化。默认的mmkv的id就是mmkv.default。Android端则会设置一个默认的page大小,假设4kb为例子。
所有的mmkvID以及对应的MMKV实例都会保存在之前实例化的g_instanceDic散列表中。其中mmkv每一个id对应一个文件的路径,其中路径是这么处理的:
如果发现对应路径下的mmkv在散列表中已经缓存了,则直接返回。否则就会把相对路径保存下来,传递给MMKV进行实例化,并保存在g_instanceDic散列表中。
我们来看看MMKV构造函数中几个关键的字段是怎么初始化。
mmkvID就是经过md5后对应缓存文件对应的路径。
能看到这里是根据当前的mode初始化id,如果不是ashmem匿名共享内存模式进行创建,则会和上面的处理类似。id就是经过md5后对应缓存文件对应的路径。
注意这里mode设置的是MMKV_ASHMEM,也就是ashmem匿名共享内存模式则是如下创建方法:
实际上就是在驱动目录下的一个内存文件地址。
接下来,在构造函数中使用了共享的文件锁进行保护后,调用loadFromFile进一步的初始化MMKV内部的数据。
我们大致的了解MMKV中每一个字段的负责的职责,但是具体如何进行工作下文都会解析。
在这里面我们遇到了看起来十分核心的类MemoryFile,它的名字有点像 Ashmem匿名共享内存 一文中描述过Java层的映射的匿名内存文件。
我们先来看看MemoryFile的初始化。
MemeoryFile分为两个模式进行初始化:
这里的处理很简单:
能看到此时将会调用mmap系统调用,通过设置标志位可读写,MAP_SHARED的模式进行打开。这样就file就在在内核中映射了一段4kb内存,以后访问文件可以不经过内核,直接访问file映射的这一段内存。
关于mmap系统调用的源码解析可以看这一篇 Binder驱动的初始化 映射原理 。
能看到在这个过程中实际上还是通过ftruncate进行扩容,接着调用zeroFillFile,先通过lseek把指针移动当前容量的最后,并把剩余的部分都填充空数据'\0'。最后映射指向的地址是有效的,会先解开后重新进行映射。
为什么要做最后这个步骤呢?如果阅读过我解析的mmap的源码一文,实际上就能明白,file使用MAP_SHARED的模式本质上是给file结构体绑定一段vma映射好的内存。ftruncate只是给file结构体进行了扩容,但是还没有对对应绑定虚拟内存进行扩容,因此需要解开一次映射后,重新mmap一次。
MMKV在如果使用Ashmem模式打开:
接下来loadFromFile 这个方法可以说是MMKV的核心方法,所有的读写,还是扩容都需要这个方法,从映射的文件内存,缓存到MMKV的内存中。
进入到这个方法后进行如下的处理:
在这里,遇到了一个比较有歧义的字段m_version ,从名字看起来有点像MMKV的版本号。其实它指代的是MMKV当前的状态,由一个枚举对象代表:
注意m_vector是一个长度16的char数组。其实很简单,就是把文件保存的m_vector获取16位拷贝到m_metaInfo的m_vector中。因为aes的加密必须以16的倍数才能正常运作。
初始化分为这6点,我们从最后三点开始聊聊MMKV的初始化的核心逻辑。我们还需要开始关注MMKV中内存存储的结构。
能看到首先从m_file获取映射的指针地址,往后读取4位数据。这4位数据就是actualSize 真实数据。但是如果是m_metaInfo的m_version 大于等于3,则获取m_metaInfo中保存的actualSize。
其校验的手段,是通过比较m_metaInfo保存的crcDigest和从m_file中读取的crcDigest进行比较,如果一致说明数据无误,则返回true,设置loadFromFile为true。
其实这里面只处理m_metaInfo的m_version的状态大于等于3的状态。我们回忆一下,在readActualSize方法中,把读取当前存储的数据长度,分为两个逻辑进行读取。如果大于等于3,则从m_metaInfo中获取。
crc校验失败,说明我们写入的时候发生异常。需要强制进行recover恢复数据。
首先要清除crc校验校验了什么东西:
MMKV做了如下处理,只处理状态等级在MMKVVersionActualSize情况。这个情况,在m_metaInfo记录上一次MMKV中的信息。因此可以通过m_metaInfo进行校验已经存储的数据长度,进而更新真实的已经记录数据的长度。
最后读取上一次MMKV还没有更新的备份数据长度和crc校验字段,通过writeActualSize记录在映射的内存中。
如果最后弥补的校验还是crc校验错误,最后会回调onMMKVCRCCheckFail这个方法。这个方法会反射Java层实现的异常处理策略
如果是OnErrorRecover,则设置loadFromFile和needFullWriteback都为true,尽可能的恢复数据。当然如果OnErrorDiscard,则会丢弃掉所有的数据。
㈧ 如何在Android上实现FrameBuffer和Overlay的blend
1.SurfaceFlinger是一个服务,主要是负责合成各窗口的Surface,然后通过OpenGLES显示到FrameBuffer上。
2.DisplayHardware是对显示设备的抽象,包括FrameBuffer和Overlay。加载FrameBuffer和Overlay插件,羡罩并初始化OpenGLES:
view plain
mNativeWindow = new FramebufferNativeWindow();
framebuffer_device_t const * fbDev = mNativeWindow->getDevice();
if (hw_get_mole(OVERLAY_HARDWARE_MODULE_ID, &mole) == 0) {
overlay_control_open(mole, &mOverlayEngine);
}
surface = eglCreateWindowSurface(display, config, mNativeWindow.get(), NULL);
eglMakeCurrent(display, surface, surface, context);
3.FramebufferNativeWindow 是framebuffer 的抽象,它负责加载libgralloc,并握大打开framebuffer设备。FramebufferNativeWindow并不直接使用 framebuffer,而是自己创建了两个Buffer:
queueBuffer负责显示一个Buffer到屏幕上,它调用fb->post去显示。
dequeueBuffer获取一个空闲的Buffer,用来在后台绘制。
这两个函数由eglSwapBuffers调过来,调到
view plain
egl_window_surface_v2_t::swapBuffers:
nativeWindow->queueBuffer(nativeWindow, buffer);
nativeWindow->dequeueBuffer(nativeWindow, &buffer);
4.msm7k/liboverlay是Overlay的实现,与其它平台不同的是,高通平台上的Overlay并不是提供一个framebuffer设备,而通过fb0的ioctl来实现的,ioctl分为两类操作:
OverlayControlChannel用于设置参数,比如设置Overlay的位置,宽度和高度:
view plain
bool OverlayControlChannel::setPosition(int x, int y, uint32_t w, uint32_t h) {
ov.dst_rect.x = x;
ov.dst_rect.y = y;
ov.dst_rect.w = w;
ov.dst_rect.h = h;
ioctl(mFD, MSMFB_OVERLAY_SET, &ov);
}
OverlayDataChannel用于显示Overlay,其中最重要的函数就是queueBuffer:
view plain
bool OverlayDataChannel::queueBuffer(uint32_t offset) {
mOvData.data.offset = offset;
ioctl(mFD, MSMFB_OVERLAY_PLAY, odPtr))
}
5.msm7k/libgralloc 是显示缓存的抽象,包括framebuffer和普通Surface的Buffer。framebuffer只是/dev/graphic/fb0的包 装,Surface的Buffer则是对/dev/pmem、ashmem和GPU内存(msm_hw3dm)的包装,它的目标主要是方便硬件加速,因为 DMA传输使用物理地址,兄皮闹要求内存在物理地址上连续。
6.msm7k/libbit这是2D加速库,主要负责Surface的拉伸、旋转和合成等操作。它有两种实现方式:
bit.cpp: 基于fb0的ioctl(MSMFB_BLIT)的实现。
bit_c2d.cpp: 基于kgsl的实现,只是对libC2D2.so的包装,libC2D2.so应该是不开源的。
7.pmem
misc/pmem.c: 对物理内存的管理,算法和用户空间的接口。
board-msm7x27.c定义了物理内存的缺省大小:
view plain
#define MSM_PMEM_MDP_SIZE 0x1B76000
#define MSM_PMEM_ADSP_SIZE 0xB71000
#define MSM_PMEM_AUDIO_SIZE 0x5B000
#define MSM_FB_SIZE 0x177000
#define MSM_GPU_PHYS_SIZE SZ_2M
#define PMEM_KERNEL_EBI1_SIZE 0x1C00
msm_msm7x2x_allocate_memory_regions分配几大块内存用于给pmem做二次分配。
8.KGSL
Kernel Graphics System Layer (KGSL),3D图形加速驱动程序,源代码drivers/gpu/msm目录下,它是对GPU的包装,给OpenGLES 2.0提供抽象的接口。
9.msm_hw3dm
这个我在内核中没有找到相关代码。
10.msm_fb
msm_fb.c: framebuffer, overlay和blit的用户接口。
mdp_dma.c: 对具体显示设备的包装,提供两种framebuffer更新的方式:
mdp_refresh_screen: 定时更新。
mdp_dma_pan_update: 通过pan display主动更新。
mdp_dma_lcdc.c:针对LCD实现的显示设备,mdp_lcdc_update用更新framebuffer。
㈨ 如何检查 Android 应用的内存使用情况
解析日志信息
最简单的调查应用内存使用情况的地方就是Dalvik日志信息。可以在logcat(输出信息可以在Device Monitor或者IDE中查看到,例如Eclipse和Android Studio)中找到这些日志信息。每次有垃圾回收发生,logcat会打印出带有下面信息的日志消息:
Java
1
D/dalvikvm: <GC_Reason> <Amount_freed>, <Heap_stats>, <External_memory_stats>, <Pause_time>
GC原因
触发垃圾回收执行的原因和垃圾回收的类型。原因主要包括:
GC_CONCURRENT
并发垃圾回收,当堆开始填满时触发来释放内存。
GC_FOR_MALLOC
堆已经满了时应用再去尝试分配内存触发的垃圾回收,这时系统必须暂停应用运行来回收内存。
GC_HPROF_DUMP_HEAP
创建HPROF文件来分析应用时触发的垃圾回收。
GC_EXPLICIT
显式垃圾回收,例如当调用 gc()(应该避免手动调用而是要让垃圾回收器在需要时主动调用)时会触发。
GC_EXTERNAL_ALLOC
这种只会在API 10和更低的版本(新版本内存都只在Dalvik堆中分配)中会有。回收外部分配的内存(例如存储在本地内存或NIO字节缓冲区的像素数据)。
释放数量
执行垃圾回收后内存释放的数量。
堆状态
空闲的百分比和(活动对象的数量)/(总的堆大小)。
外部内存状态
API 10和更低版本中的外部分配的内存(分配的内存大小)/(回收发生时的限制值)。
暂停时间
越大的堆的暂停时间就越长。并发回收暂停时间分为两部分:一部分在回收开始时,另一部分在回收将近结束时。
例如:
Java
1
D/dalvikvm( 9050): GC_CONCURRENT freed 2049K, 65% free 3571K/9991K, external 4703K/K, paused 2ms+2ms
随着这些日志消息的增多,注意堆状态(上面例子中的3571K/9991K)的变化。如果值一直增大并且不会减小下来,那么就可能有内存泄露了。
查看堆的更新
为了得到应用内存的使用类型和时间,可以在Device Monitor中实时查看应用堆的更新:
1.打开Device Monitor。
从<sdk>/tools/路径下加载monitor工具。
2.在Debug Monitor窗口,从左边的进程列表中选择要查看的应用进程。
3.点击进程列表上面的Update Heap。
4.在右侧面板中选择Heap标签页。
Heap视图显示了堆内存使用的基本状况,每次垃圾回收后会更新。要看更新后的状态,点击Gause GC按钮。
图1.Device Monitor工具显示[1] Update Heap和 [2] Cause GC按钮。右边的Heap标签页显示堆的情况。
跟踪内存分配
当要减少内存问题时,应该使用Allocation Tracker来更好的了解内存消耗大户在哪分配。Allocation Tracker不仅在查看内存的具体使用上很有用,也可以分析应用中的关键代码路径,例如滑动。
例如,在应用中滑动列表时跟踪内存分配,可以看到内存分配的动作,包括在哪些线程上分配和哪里进行的分配。这对优化代码路径来减轻工作量和改善UI流畅性都极其有用。
使用Allocation Tracker:
1.打开Device Monitor 。
从<sdk>/tools/路径下加载monitor工具。
2.在DDMS窗口,从左侧面板选择应用进程。
3.在右侧面板中选择Allocation Tracker标签页。
4.点击Start Tracking。
5.执行应用到需要分析的代码路径处。
6.点击Get Allocations来更新分配列表。
列表显示了所有的当前分配和512大小限制的环形缓冲区的情况。点击行可以查看分配的堆栈跟踪信息。堆栈不只显示了分配的对象类型,还显示了属于哪个线程哪个类哪个文件和哪一行。
图2. Device Monitor工具显示了在Allocation Tracker中当前应用的内存分配和堆栈跟踪的情况。
注意:总会有一些分配是来自与 DdmVmInternal 和 allocation tracker本身。
尽管移除掉所有严重影响性能的代码是不必要的(也是不可能的),但是allocation tracker还是可以帮助定位代码中的严重问题。例如,应用可能在每个draw操作上创建新的Paint对象。把对象改成全局变量就是一个很简单的改善性能的修改。
查看总体内存分配
为了进一步的分析,查看应用内存中不同内存类型的分配情况,可以使用下面的 adb 命令:
Java
1
adb shell mpsys meminfo <package_name>
应用当前的内存分配输出列表,单位是千字节。
当查看这些信息时,应当熟悉下面的分配类型:
私有(Clean and Dirty) 内存
进程独占的内存。也就是应用进程销毁时系统可以直接回收的内存容量。通常来说,“private dirty”内存是其最重要的部分,因为只被自己的进程使用。它只在内存中存储,因此不能做分页存储到外存(Android不支持swap)。所有分配的Dalvik堆和本地堆都是“private dirty”内存;Dalvik堆和本地堆中和Zygote进程共享的部分是共享dirty内存。
实际使用内存 (PSS)
这是另一种应用内存使用的计算方式,把跨进程的共享页也计算在内。任何独占的内存页直接计算它的PSS值,而和其它进程共享的页则按照共享的比例计算PSS值。例如,在两个进程间共享的页,计算进每个进程PPS的值是它的一半大小。
PSS计算方式的一个好处是:把所有进程的PSS值加起来就可以确定所有进程总共占用的内存。这意味着用PSS来计算进程的实际内存使用、进程间对比内存使用和总共剩余内存大小是很好的方式。
例如,下面是平板设备中Gmail进程的输出信息。它显示了很多信息,但是具体要讲解的是下面列出的一些关键信息。
注意:实际看到的信息可能和这里的稍有不同,输出的详细信息可能会根据平台版本的不同而不同。
Java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
** MEMINFO in pid 9953 [com.google.android.gm] **
Pss Pss Shared Private Shared Private Heap Heap Heap
Total Clean Dirty Dirty Clean Clean Size Alloc Free
------ ------ ------ ------ ------ ------ ------ ------ ------
Native Heap 0 0 0 0 0 0 7800 7637(6) 126
Dalvik Heap 5110(3) 0 4136 4988(3) 0 0 9168 8958(6) 210
Dalvik Other 2850 0 2684 2772 0 0
Stack 36 0 8 36 0 0
Cursor 136 0 0 136 0 0
Ashmem 12 0 28 0 0 0
Other dev 380 0 24 376 0 4
.so mmap 5443(5) 1996 2584 2664(5) 5788 1996(5)
.apk mmap 235 32 0 0 1252 32
.ttf mmap 36 12 0 0 88 12
.dex mmap 3019(5) 2148 0 0 8936 2148(5)
Other mmap 107 0 8 8 324 68
Unknown 6994(4) 0 252 6992(4) 0 0
TOTAL 24358(1) 4188 9724 17972(2)16388 4260(2)16968 16595 336
Objects
Views: 426 ViewRootImpl: 3(8)
AppContexts: 6(7) Activities: 2(7)
Assets: 2 AssetManagers: 2
Local Binders: 64 Proxy Binders: 34
Death Recipients: 0
OpenSSL Sockets: 1
SQL
MEMORY_USED: 1739
PAGECACHE_OVERFLOW: 1164 MALLOC_SIZE: 62
通常来说,只需关心Pss Total列和Private Dirty列就可以了。在一些情况下,Private Clean列和Heap Alloc列也会提供很有用的信息。下面是一些应该查看的内存分配类型(行中列出的类型):
Dalvik Heap
应用中Dalvik分配使用的内存。Pss Total包含所有的Zygote分配(如上面PSS定义所描述的,共享跨进程的加权)。Private Dirty是应用堆独占的内存大小,包含了独自分配的部分和应用进程从Zygote复制分裂时被修改的Zygote分配的内存页。
注意:新平台版本有Dalvik Other这一项。Dalvik Heap中的Pss Total和Private Dirty不包括Dalvik的开销,例如即时编译(JIT)和垃圾回收(GC),然而老版本都包含在Dalvik的开销里面。
Heap Alloc是应用中Dalvik堆和本地堆已经分配使用的大小。它的值比Pss Total和Private Dirty大,因为进程是从Zygote中复制分裂出来的,包含了进程共享的分配部分。
.so mmap和.dex mmap
mmap映射的.so(本地) 和.dex(Dalvik)代码使用的内存。Pss Total 包含了跨应用共享的平台代码;Private Clean是应用独享的代码。通常来说,实际映射的内存大小要大一点——这里显示的内存大小是执行了当前操作后应用使用的内存大小。然而,.so mmap 的private dirty比较大,这是由于在加载到最终地址时已经为本地代码分配好了内存空间。
Unknown
无法归类到其它项的内存页。目前,这主要包含大部分的本地分配,就是那些在工具收集数据时由于地址空间布局随机化(Address Space Layout Randomization ,ASLR)不能被计算在内的部分。和Dalvik堆一样, Unknown中的Pss Total把和Zygote共享的部分计算在内,Unknown中的Private Dirty只计算应用独自使用的内存。
TOTAL
进程总使用的实际使用内存(PSS),是上面所有PSS项的总和。它表明了进程总的内存使用量,可以直接用来和其它进程或总的可以内存进行比较。
Private Dirty和Private Clean是进程独自占用的总内存,不会和其它进程共享。当进程销毁时,它们(特别是Private Dirty)占用的内存会重新释放回系统。Dirty内存是已经被修改的内存页,因此必须常驻内存(因为没有swap);Clean内存是已经映射持久文件使用的内存页(例如正在被执行的代码),因此一段时间不使用的话就可以置换出去。
ViewRootImpl
进程中活动的根视图的数量。每个根视图与一个窗口关联,因此可以帮助确定涉及对话框和窗口的内存泄露。
AppContexts和Activities
当前驻留在进程中的Context和Activity对象的数量。可以很快的确认常见的由于静态引用而不能被垃圾回收的泄露的 Activity对象。这些对象通常有很多其它相关联的分配,因此这是追查大的内存泄露的很好办法。
注意:View 和 Drawable 对象也持有所在Activity的引用,因此,持有View 或 Drawable 对象也可能会导致应用Activity泄露。
获取堆转储
堆转储是应用堆中所有对象的快照,以二进制文件HPROF的形式存储。应用堆转储提供了应用堆的整体状态,因此在查看堆更新的同时,可以跟踪可能已经确认的问题。
检索堆转储:
1.打开Device Monitor。
从<sdk>/tools/路径下加载monitor工具。
2.在DDMS窗口,从左侧面板选择应用进程。
3.点击Dump HPROF file,显示见图3。
4.在弹出的窗口中,命名HPROF文件,选择存放位置,然后点击Save。
图3.Device Monitor工具显示了[1] Dump HPROF file按钮。
如果需要能更精确定位问题的堆转储,可以在应用代码中调用mpHprofData()来生成堆转储。
堆转储的格式基本相同,但与Java HPROF文件不完全相同。Android堆转储的主要不同是由于很多的内存分配是在Zygote进程中。但是由于Zygote的内存分配是所有应用进程共享的,这些对分析应用堆没什么关系。
为了分析堆转储,你需要像jhat或Eclipse内存分析工具(MAT)一样的标准工具。当然,第一步需要做的是把HPROF文件从Android的文件格式转换成J2SE HRPOF的文件格式。可以使用<sdk>/platform-tools/路径下的hprof-conv工具来转换。hprof-conv的使用很简单,只要带上两个参数就可以:原始的HPROF文件和转换后的HPROF文件的存放位置。例如:
Java
1
hprof-conv heap-original.hprof heap-converted.hprof
注意:如果使用的是集成在Eclipse中的DDMS,那么就不需要再执行HPROF转换操作——默认已经转换过了。
现在就可以在MAT中加载转换过的HPROF文件了,或者是在可以解析J2SE HPROF格式的其它堆分析工具中加载。
分析应用堆时,应该查找由下导致的内存泄露:
对Activity、Context、View、Drawable的长期引用,以及其它可能持有Activity或Context容器引用的对象
非静态内部类(例如持有Activity实例的Runnable)
不必要的长期持有对象的缓存
使用Eclipse内存分析工具
Eclipse内存分析工具(MAT)是一个可以分析堆转储的工具。它是一个功能相当强大的工具,功能远远超过这篇文档的介绍,这里只是一些入门的介绍。
在MAT中打开类型转换过的HPROF文件,在总览界面会看到一张饼状图,它展示了占用堆的最大对象。在图表下面是几个功能的链接:
Histogram view显示所有类的列表和每个类有多少实例。
正常来说类的实例的数量应该是确定的,可以用这个视图找到额外的类的实例。例如,一个常见的源码泄露就是Activity类有额外的实例,而正确的是在同一时间应该只有一个实例。要找到特定类的实例,在列表顶部的<Regex>域中输入类名查找。
当一个类有太多的实例时,右击选择List objects>with incoming references。在显示的列表中,通过右击选择Path To GC Roots> exclude weak references来确定保留的实例。
Dominator tree是按照保留堆大小来显示的对象列表。
应该注意的是那些保留的部分堆大小粗略等于通过GC logs、heap updates或allocation tracker观察到的泄露大小的对象。
当看到可疑项时,右击选择Path To GC Roots>exclude weak references。打开新的标签页,标签页中列出了可疑泄露的对象的引用。
注意:在靠近饼状图中大块堆的顶部,大部分应用会显示Resources的实例,但这通常只是因为在应用使用了很多res/路径下的资源。
图4.MAT显示了Histogram view和搜索”MainActivity”的结果。
想要获得更多关于MAT的信息,请观看2011年Google I/O大会的演讲–《Android 应用内存管理》(Memory management for Android apps),在大约21:10 的时候有关于MAT的实战演讲。也可以参考文档《Eclipse 内存分析文档》(Eclipse Memory Analyzer documentation)。
对比堆转储
为了查看内存分配的变化,比较不同时间点应用的堆状态是很有用的方法。对比两个堆转储可以使用MAT:
1.按照上面描述得到两个HPROF文件,具体查看获取堆转储章节。
2.在MAT中打开第一个HPROF文件(File>Open Heap Dump)。
3.在Navigation History视图(如果不可见,选择Window>Navigation History),右击Histogram,选择Add to Comp are Basket。
4.打开第二个HRPOF文件,重复步骤2和3。
5.切换到Compare Basket视图,点击Compare the Results(在视图右上角的红色“!”图标)。
触发内存泄露
使用上述描述工具的同时,还应该对应用代码做压力测试来尝试复现内存泄露。一个检查应用潜在内存泄露的方法,就是在检查堆之前先运行一会。泄露会慢慢达到分配堆的大小的上限值。当然,泄露越小,就要运行应用越长的时间来复现。
也可以使用下面的方法来触发内存泄露:
1.在不同Activity状态时,重复做横竖屏切换操作。旋转屏幕可能导致应用泄露 Activity、Context 或 View对象,因为系统会重新创建 Activity,如果应用在其它地方持有这些对象的引用,那么系统就不能回收它们。
2.在不同Activity状态时,做切换应用操作(切换到主屏幕,然后回到应用中)。
提示:也可以使用monkey测试来执行上述步骤。想要获得更多运行 monkey 测试的信息,请查阅 monkeyrunner 文档。
㈩ Android 5.0 SEAndroid下怎么获得对一个内核节点的访问权限
第一步:找到需要访问该内核节点的进程(process),笔者自己这个节点由system_server进程来访问
第二步:打开文件AndroidL/android/external/sepolicy/file_contexts.be
仿照这个逗脊文件里的写法,为这个定义一个自己想要的名字:
/dev/tegra.* u:object_r:video_device:s0
/dev/tf_driver u:object_r:tee_device:s0
/dev/tty u:object_r:owntty_device:s0
/dev/tty[0-9]* u:object_r:tty_device:s0
/dev/ttyS[0-9]* u:object_r:serial_device:s0
/dev/wf_bt u:object_r: wf_bt_device:s0
wf_bt_device是自定义,其他左右两边谈指滚的内容都和上面的范例一致。
第三步:打开文件AndroidL/android/external/sepolicy/device.te
仿照这个文件里的写法,将刚刚第二步写的wf_bt_device声明为dev_type:
# Device types
type device, dev_type, fs_type;
type alarm_device, dev_type, mlstrustedobject;
type adb_device, dev_type;
type ashmem_device, dev_type, mlstrustedobject;
type audio_device, dev_type;
type binder_device, dev_type, mlstrustedobject;
type block_device, dev_type;
type camera_device, dev_type;
type wf_bt_device, dev_type;
第四步:
AndroidL/android/external/sepolicy/目录含余下很多.te文件都是以进程名来结尾的,比如有针对surfaceflinger进程的surfaceflinger,有针对vold进程的vold.te,
刚刚从第一步得到,这个节点是由system_server进程来访问,所以,找到system_server.te打开,加入允许这个进程对/dev/wf_bt的读写权限,
# Read/Write to /proc/net/xt_qtaguid/ctrl and and /dev/xt_qtaguid.
allow system_server qtaguid_proc:file rw_file_perms;
allow system_server qtaguid_device:chr_file rw_file_perms;
# chr_file表示字符设备文件,如果是普通文件用file,目录请用dir
# rw_file_perms代表读写权限
allow system_server wf_bt_device:chr_file rw_file_perms;
这句话的意思是:允许system_server进程拥有对wf_bt_device的这个字符设备的读写权限。
改了这些之后,就可以make installclean;make -j16编译image来验证权限是否获取成功。
fd =open("/dev/wf_bt",O_RDONLY | O_NOCTTY);