Android驱动开发知识储备(纯手打,更新:2018.4.11)

Android软件层次结构
(1)操作系统层
显示驱动(Frame Buffer),Flash内存驱动,照相机驱动,音频驱动,WiFi驱动,键盘驱动,蓝牙驱动,Binder IPC驱动,Power Management  ,这些都包括在Linux内核里。

(2)各种库和Android运行环境
C库、多媒体框架、SGL(2D图像引擎),SSL(安全),OpenGL ES(3D),界面管理工具,SQLite,WebKit,FreeType。
JVM是基于栈的虚拟机,DVM是基于寄存器的虚拟机
(3)应用程序框架
(4)应用程序




Android移植定义

       在硬件系统的基础上构建Android软件系统。
(1)Linux中的相关设备驱动程序
驱动程序是硬件和上层软件的接口,在Android手机系统中,需要基本的屏幕、触摸屏、键盘等驱动程序,以及音频、摄像头、电话、WiFi、蓝牙等设备驱动程序。
(2)Android本地框架中的硬件抽象层
在Android中,硬件抽象层工作在用户空间,介于驱动程序和Android系统之间。Android系统对硬件抽象层通常都有标准的接口定义,在开发过程中,实现这些接口也就给Android系统提供了硬件抽象层。

Android移植类型
(1)基础图形用户界面部分:包括显示部分、用户输入部分和硬件相关的加速部分,还包括媒体编解码和OpenGL等。
(2)音视频输入输出部分:包括音频、视频输出和摄像头等。
(3)连接部分:包括无线局网、蓝牙、GPS等。
(4)电话部分
(5)附属部分:包括传感器、背光、震动器等。

Android的核心驱动
(1)Binder:进程间通信IPC
(2)电源管理(PM)
(3)低内存管理器(Low Memory Killer):根据需要杀死进程来释放需要的内存
(4)匿名共享内存(ashmem)
(5)PMEM:用于向用户空间提供连续的物理内存区域。
(6)Logger
(7)Alarm:提供了一个定时器用于把设备从睡眠状态唤醒。
(8)USB Gadget驱动
(9)Android Ram Console:这是为了提供调试功能而推出的,Android允许将调试日志信息写入一个被称为RAM Console的设备里,是一个基于RAM的Buffer。
(10)Android timed device:提供了对设备进行定时控制功能。
(11)Yaffs2文件系统:作为MTD nand flash文件系统



在linux系统中,主要有3类设备驱动,分别是字符设备驱动、块设备驱动和网络接口驱动。
字符设备是指在I/O传输过程中以字符为单位进行传输的设备,例如键盘、打印机等。
块设备只能以块为单位接收输入返回输出,而字符设备则以Byte为单位。大多数设备是字符设备,它们不需要缓冲并且不以固定块大小进行操作。
网络设备驱动程序,分别是网络设备媒介层、网络设备驱动层、网络设备接口层以及网络协议接口层。




Binder的工作流程
(1)客户端首先获得服务器端的代理对象。所谓的代理对象实际上就是在客户端建立一个服务端的”引用“,该代理对象具有服务端的功能,使其在客户端访问服务端的方法就想访问本地方法一样。
(2)客户端通过调用服务器代理对象的方式向服务器端发送请求。
(3)代理对象将用户请求通过BInder驱动发送到服务器进程。
(4)服务器进程处理用户请求,并通过BInder驱动返回处理结果给客户端的服务器代理对象。

(5)客户端收到服务器端的返回结果。



Android HAL概述

在Android的4层构架中,在C++库层的最底部,有一个子层--硬件抽象层(Hardware Abstract Layer,HAL)。该层实现Android上层对Linux中驱动程序的调用,向Android上层提供访问底层设备的接口,以便Android系统的其他部分访问这些硬件。

Android驱动与一般Linux驱动的最大区别就是把对硬件的支持分为了两层:一层放在了用户空间,我们称之为硬件抽象层;另一层则放到了内核空间,我们称之为内核驱动层。其中,一般内核驱动层只提供简单的访问硬件的逻辑,例如提供读写硬件寄存器方法,而具体读写什么值到硬件的逻辑,则放到硬件抽象层中。

通过这种分层的方式,硬件厂商不用再提供其HAL层的源码,进而有利于保护这些硬件厂商的知识产权。另外通过这种分层的方式,有利于屏蔽底层驱动的差异性,为同一类设备向上提供统一的接口。

Bootloader

Bootloader负责Android机器的启动、Image的烧写、关机充电等,这些都是与内核要实现。BootLoader是操作系统运行之前运行的一段程序,他最主要的工作就是将机器的软硬件环境带到一个适当的状态,为操作系统的运行做好准备。具体讲,BootLoader要完成的子任务很多。但这些任务的目的总体说就是要努力把OS拉出来,在机器的CPU等硬件组成的环境上运行。ARM处理器系列的Bootloader则大多数以U-Boot为基础。

U-Boot的启动过程可分成两个阶段:第一阶段,常称汇编代码阶段,在这个阶段用于要考虑存储空间受限和启动速度的要尽量快,所以其代码编写才用效率更高的汇编语言;第二阶段,C代码阶段,到此阶段亦可以开始与用户交互,而且机器的硬件大多已激活,所以一般采用C语言编写。

OS基本概念

现在的PC和常用手机等,都有一组基本的程序--OS(操作系统)。在这组程序中最重要的叫内核,人们常将OS与内核等同。

OS必须完成两个主要目标:

与硬件交互。为在计算机系统上运行的应用程序提供可执行的环境。


一些OS允许用户程序直接与硬件组件交互(如MS-DOS)。相反,Linux OS会屏蔽掉所有与物理硬件相关的东西。当应用程序要用到硬件资源时,它就必须向OS发出请求,而OS则会根据情况许可上层应用使用该硬件资源。

为了加强这种机制,现代的OS将依赖于特定硬件的特性来禁止用户程序直接与底层的硬件组件交互,或直接地随意访问内存的地址。继而,该硬件引入了两个不同的CPU执行模式:非特权模式给上层用户程序用;特权模式则给内核用。

多用户系统

LinuxOS是源于UNIX系统的。通过分时共享等策略,让多个用户可以同时使用一台计算机,却让各用户感受不到自己是在与其他用户共用这台机器。该分享策略,使得机器即便只有一个用户,也可以同时运行多个任务,响应多个进程。

认证机制,以验证用户ID。保护机制1,以对抗bug,不让这些坏程序堵塞了其他用户程序。保护机制2,以对抗恶意的程序,以防它去监听其他用户的活动。审计机制,以限制分派给每个用户的资源。

用户和组

Linux OS作为多用户系统,每个用户在机器上都有一个私有空间;特别是,他会拥有配额的磁盘空间,以存储文件、接收私有的mail消息等。OS必须确保这个部分私有空间只对它的拥有者是可见的。所有用户都是通过唯一的用户ID来识别的,简称UID。当用户想用机器,会被要求输入用户名和密码,这样用户的私密权就得到了保证。

如果要选择与其他用户共享材料,共享的每个用户就应是一个或多个组的成员,这个组由组ID(GID)来识别。每个文件都可与一个确切的组相关联。例如:作为文件拥有者的用户拥有对该文件的读写权限,而组中其他用户则只拥有读权限,系统中非组中的用户则没有任何访问权。

Linux OS中,有一个特殊的用户:root。系统管理员应以root的身份登入机器,以管理其他用户账号,完成维护任务(如系统的备份和程序的升级等)。root用户几乎可以做任何事情,因为OS并没有对它采取一般的防护机制。另外,root用户可以访问系统上的每一个文件,可以管理每一个正运行着的用户程序。

进程

进程是动态的实体,其在系统中的生命是有限的。进程的创建、销毁和同步在内核中体现为一组线程。Linux内核是以线程为调度的单位。

对于支持多用户的OS都有一个基本的抽象:进程。进程是一个执行的实例,是程序执行的上下文。在初始的OS中,一个进程是在一个地址空间中,执行单个顺序的指令集。现代的OS中,则允许进程中有多个执行流,也就是在同样的地址空间里有多个同步运行着的顺序执行指令集。作为Linux OS,它就允许进程中多个执行流可以并发执行。这些执行流在Linux OS中被称为线程或轻量级进程。

对于单处理器,进程、线程或中断处理执行流的并发,是分时的并发,是逻辑上的并发。对于多处理器系统,这些并发执行是真真切切同时在运行的。多用户系统,必须保证有一个这些的并发执行环境:运行几个进程同时执行,他们将对系统的资源共享,且通过竞争来获得使用。这些允许多个活动进程的OS,也被称作多程序或多进程系统。Linux OS就是支持多用户、多进程、多处理器的一款操作系统。

程序与进程是有区别的。程序是静止的,由代码、库或资源组成的集合体;而进程则是程序在计算机系统里运行,是程序的动态体现。

Linux单核架构

OS的内核架构主要分为两种:一种是单内核的;另一种是微内核的。Linux内核是单核:每个内核层被集成为一个整体的内核程序,并运行在当前进程的内核模式中。相反,微内核OS则只有上述内核的最核心功能,一般包括:一些同步用的原语、调度器、与进程间通信的机制;而几个系统进程会运行在上述微内核之上,来完成OS其他系统层的功能,如内存分配、设备驱动,以及系统调用处理例程(Handlers)。

微内核OS速度上较单核的架构要慢,微内核强制系统程序员才用模块化的方法,因为每个OS层是一个相对独立的程序,它要通过已定义好的、简洁的软件接口与其它层交互。

Linux引入了模块的概念。一个模块包含一个object文件,该Object代码由一组功能组成;归属于某一文件系统、底层设备功能驱动,以及其他属于内核上层的特性。这个模块又不同于微内核OS的外部层,他不是运行在专门的进程中,而是运行在当前进程的内核模式中。

linux OS使用模块机制的好处:模块化的方法。保持平台的独立性。节俭的主内存使用。没有任何性能上的损失。

Linux内核概述

Linux内核必须实现一组服务和相应的接口,应用程序则可以用这些接口,间接与硬件打交道。主要由五个子系统组成:进程调度、内存管理、虚拟文件系统、进程间通信以及设备驱动。

Linux内核是通过设备驱动与I/O设备交互的。设备驱动被包含在linux内核中。


linux内核分为两部分
(1)体系相关部分:这部分内核为体系结构和硬件所持有。
(2)体系无关部分:这部分内核是可移植的。体系无关部分通常会定义与体系相关部分的接口,这样,内核向新的体系结构移植的过程就变成确认这些接口的特性并将他们加以实现的过程。

系统调用接口

为了与用户应用程序进行交互,内核提供了一组系统调用接口,通过这组接口,应用程序可以访问系统硬件和各种硬件系统资源。系统调用接口层在用户应用程序和内核之间添加了一个中间层

在Linux OS进程/内核模型中,每个进程就是执行在机器上的唯一的镜像,他们对系统服务的访问具有排他性。当进程需要访问系统服务时,他会发出系统调用(就是对内核的请求),硬件则会将权利模式由用户模式改为内核模式。此时,该进程就开始执行某内核的过程,这个过程是有严格限制的。通过这种方法,OS(确切的讲应该是内核代码)运行在该进程的上下文(context)中,却又保证其请求是安全的。该内核过程在合适时机会通过硬件强制返回用户模式,此时该进程将继续执行程序中紧接着系统调用的下一条指令。

当一个程序执行在用户模式下,他就不能直接的访问内核数据结构或内核的程序。当程序处于内核模式时,这些限制就不再存在了。现在CPU一般提供了特定的指令在用户模式与内核模式之间切换。通常,在用户模式下执行的程序向内核发出服务请求时,就会切换到内核模式。当内核已响应程序的请求时,就会设置程序退回用户模式。

内核不是进程,而是进程的管理者。

除了用户进程,Linux系统包括一些特权线程,这些内核线程有以下特点:他们运行在内核模式,用的是内核地址空间。他们不与用户直接交互。他们通常在系统启动时被创建,并一直存活到系统关闭。

设备驱动简述

Linux内核是通过设备驱动与I/O设备交互的。设备驱动被包含在Linux内核中,由控件一个或多个设备的数据结构与功能函数构成,如硬盘、键盘等。每个驱动会通过一个特定的接口,与内核的其他部分(甚至是其他驱动)交互。

这个方法有以下好处:特定于设备的代码,可以被封装在特定的模块中。厂商可以在不知道内核源码的情况下,增加新的设备,只要知道特定的接口即可。内核会用统一的方法来处理所有设备,并通过同样的接口来访问这些设备。可以以模块的方式来写驱动,并可以动态地加载这些模块,也可动态地卸载他们。

一些用户程序会想要操作硬件设备,它们会向内核发出请求,请求访问/dev目录下的文件。事实上,设备文件就是用户可见的设备驱动的接口。每个设备文件都会引用到特定的设备驱动,他会被内核调用,以完成对相应硬件组件的操作。

Linux内核源代码目录结构

arch:该目录包含与CPU硬件系统结构相关的代码。每个CPU系列都独自占有一个目录,如ARM、MIPS、AVR32、x86、ia64等。

block:该目录包含块设备驱动程序中进程I/O调度的功能代码。

crypto:该目录包含加密/解密算法,以及压缩和校验等功能代码。

documentation:该部分是一些文档,在该文档中对内核的各个部分进行了一般性的阐述。

drivers:该目录包含各设备程序的功能代码。每种类型的设备驱动常占有一个独立的子目录,如char、block、net、input、power等。

fs:该目录包含Linux内核所支持的各种文件系统,如ext、jffs2、yaffs2、fat、ntfs等。

include:该目录包含一些头文件,其中与Linux系统相关的头文件就放置在该目录下的linux子目录中。

init:该目录包含Linux内核的初始化功能代码。

ipc:该目录包含进程间通信的功能代码。

kernel:该目录包含进程调度、定时器等功能代码。

lib:该目录包含库或用于生产库的代码。

mm:该目录包含内存管理功能代码。

net:该目录包含网络相关的功能代码,其实现了各种常见的网路协议。

scripts:该目录包含了一些脚本文件,用于配置内核。

security:该目录包含Linux安全管理方面的代码,如账号等。

sound:该目录包含ALSA、OSS音频子系统的核心代码,以及一些常用的音频驱动。

usr:该目录包含实现cpio工具的功能代码。

Linux内核模块基础与骨架

模块特点:模块本身不被编译入内核映像,从而控制了内核的大小。模块一旦被加载,他就与内核中的其他部分完全一样。

例子代码:

#include 
#include 
MODULE_LICENSE("Dual BSD/GPL");
static int hello_init(void)
{
     printk(KERN_ALERT " Hello World enter\n");
     return 0;
}
static void hello_exit(void)
{
     printk(KERN_ALERT " Hello World exit\n ");
}
module_init(hello_init);
module_exit(hello_exit);

MODULE_AUTHOR("Tony Liu");
MODULE_DESCRIPTION("A simple Hello World Module");
MODULE_ALIAS("a module skeleton");

编译上面的代码生成hello.o的内核模块文件。

将该模块加载进Linux内核:

#insmod ./hello.o

将该模块从Linux内核卸载

#rmmod hello

Linux内核模块主要由以下6个部分组成:

1.模块加载函数:这部分是必需的;一般用于完成模块的初始化工作。2.模块卸载函数:这部分是必需的;一般用于卸载模块所占用的相关系统资源。3.模块许可声明:必需;用于描述该模块的许可权限;Linux内核中常用的许可是GPL。4.模块参数:可选;用于在模块加载时,向模块传递参数;这些参数对应着模块内的全局变量。5.模块导出符号:可选;用于导出可供内核其他模块使用。6.模块作者、描述、别名。

Linux模块的加载与卸载

Linux内核初始化函数,一般会加上_ _init,比如

static int _ _init hello_init(void)

模块卸载函数,一般会加上_ _exit标识声明,比如

static void _ _exit hello_exit(void)

若模块加载函数注册了XXX,则模块卸载函数应该注销XXX。若模块加载函数动态申请了内存,则模块卸载函数应释放该内存。若模块加载函数申请了硬件资源的占用,则模块卸载函数应释放这些硬件资源。若模块加载函数开启了硬件,则模块卸载函数一般要关闭硬件。

LInux模块的参数与导出符号

我们可以用“module_param(参数名,参数类型,参数读写权限)”为模块定义一个参数,例如:

static char *my_name = "Tony";
static int num = 5000;
module_param(num, int, S_IRUGO);
module_param(my_name, charp, S_IRUGO);

在装载内核模块时,用户可以向模块传递参数,形式为“insmode 模块名 参数名 = 参数值”,如果不传递,参数将使用模块内定义的默认值。运行insmod命令时,应使用逗号分隔输入的数组元素。

带参数的模块代码:

#include 
#include 
MODULE_LICENSE("Dual BSD/GPL");

static char *my_name = "Tony";
static int num = 5000;

static int person_init(void)
{
     printk(KERN_INFO " person name:%s\n", book_name);
    printk(KERN_INFO " person num:%s\n", num);
     return 0;
}
static void person_exit(void)
{
    printk(KERN_ALERT " person module exit\n ");
}
module_init(person_init);
module_exit(person_exit);
module_param(num, int, S_IRUGO);
module_param(my_name, charp, S_IRUGO);

MODULE_AUTHOR("Yang Liu");
MODULE_DESCRIPTION("A simple Module for testing module params");
MODULE_VERSION("V1.0");

模块可以使用如下宏导出符号到内核符号表:
EXPORT_SYMBOL(符号名);
EXPORT_SYMBOL_GPL(符号名);
导出的符号将可以被其他模块使用,使用前声明一下即可。EXPORT_SYMBOL_GPL()只适用于包含GPL许可权的模块。在Linux2.6版本中,“/proc/kallsyms"文件对应着内核符号表,他记录了符号以及符号所在的内存地址。

模块的符号导出

#include 
#include 
MODULE_LICENSE("Dual BSD/GPL");

int add_integer(int a, int b)
{
    return a+b;
}

int sub_integer(int a, int b)
{
    return a-b;
}

EXPORT_SYMBOL(add_integer);
EXPORT_SYMBOL(sub_integer);

如果上面的代码在某内核模块顺利执行,我们可以从”/proc/kallsyms"文件中找出add_integer,sub_integer相关消息。

Linux文件系统的目录结构

这里讲的目录结构则是一个处于运行态Linux系统自身所拥有的目录结构。
1./dev
设备文件存储目录,应用程序通过对这些文件的读写和控制就可以访问实际的设备。
2./etc
系统配置文件的所在地,一些服务器的配置文件也在这里,如用户账号及密码配置文件。
3./lib
库文件存放目录
4./mnt
这个目录一般是用于存放存储类设备的挂载目录,这些挂载目录代表了这些存储设备的挂载点,如/mnt/sdcard等目录。有时我们可以让系统开机自动挂载某文件系统,也可以把代表该文件系统的挂载目录放置在/mnt下。
5./proc
操作系统在运行时,进程及内核信息(比如CPU、硬盘分区、内存信息等)存放在这里。/proc目录为伪文件系统proc的挂载目录,proc并不是真正的文件系统,它存在于内存之中。
6./sbin
存放可执行文件,大多是涉及系统管理的命令,是超级权限用户root的可执行命令存放地,普通用户无权限执行这个目录下的命令。
7./tmp
有时用户运行程序的时候会产生临时文件,/tmp用来存放临时文件。
8./var
var表示的是变化的意思,这个目录的内容经常变动。
9./sys
Linux2.6内核所支持的sysfs文件系统被映射在此目录。Linux设备驱动模型中的总线、驱动和设备都可以在sysfs文件系统中找到对应的节点。当内核检测到在系统中出现了新设备后,内核会在sysfs文件系统中为该新设备生成一项新的记录。
10./initrd
若在启动过程中使用了initrd映像作为临时根文件系统,则在执行完其上的/linuxrc挂接真正的根文件系统后,原来的初始RAM文件系统被映射到/initrd目录。

设备驱动与Linux文件系统的关联

Linux系统其实是一个以文件为中心的系统。在Linux文件系统中最大的一个特色就是实现了一个虚拟文件系统VFS。虚拟文件系统下面可以挂在各种文件系统,包括对应设备驱动的设备文件系统。
应用程序和VFS之间的接口是系统调用,而VFS与磁盘文件系统以及普通设备之间的接口是file_operations结构体成员函数,这个结构体包含对文件进行打开、关闭、读写、控制的一系列成员函数。
由于字符设备的上层没有磁盘文件系统,所以字符设备的file_operations成员函数就直接由设备驱动提供了,file_operations正是字符设备驱动的核心。

而对于块存储设备而言,ext2、fat、jffs2等文件系统中会实现针对VFS的file_operations成员函数,设备驱动层将看不到file_operations的存在。磁盘文件系统和设备驱动将对磁盘上文件的访问最终转换成对磁盘上柱面和扇区的访问。





你可能感兴趣的:(Android驱动开发)