问题
Android系统上的C底层库都有啥?(此篇分析基础为Android 7.1.1系统源码)
概述
在Android系统中,Bionic是最基础的C库,但它仅仅是基础库。对于开发者而已,工具库才是真正在编写Native程序时更多被使用到的。主要的工具库在系统源码的/system/core目录中,比如:liblog、libcutils、libutils等。
1 Log库
Log 库是 Android 本地特定 Log 机制的封装支持。Android 系统的所有部分通过直接或者间接地连接这个库来输出 Log。
Log 库头文件:system/core/include/cutils/log.h,另一个相关的头文件是:system/core/include/android/log.h。Log 库源文件路径:system/core/liblog。
2 C语言工具库 libcutils
2.1 libcutils 的结构
C 语言工具库提供了 C 语言中最基本的工具功能。这是 Android 本地中基础的本地库,基本上Android 中所有的本地库和程序都连接了这个库。
C语言工具库头文件的路径:system/core/include/cutils
C语言工具库源文件的路径:system/core/libcutils
C 语言工具库编译的结果:目标机的动态库 libcutils.so 和 静态库 lbcutils.a
C 语言工具库中基本上各个头文件提供独立的功能,其中主要的头文件如下所示:
Pedro@x86:cutils$ tree
.
├── android_reboot.h
├── ashmem.h //匿名共享内存的接口
├── atomic.h //原子操作接口
├── bitops.h
├── compiler.h
├── config_utils.h //配置工具
├── debugger.h
├── fs.h
├── hashmap.h //定义哈希表的工具
├── iosched_policy.h
├── jstring.h //8位和16位字符串的转换 (j的含义为Java)
├── klog.h //内核日志
├── list.h
├── log.h
├── memory.h //内存设置功能
├── misc.h
├── multiuser.h
├── native_handle.h //本地句柄
├── open_memstream.h
├── partition_utils.h
├── process_name.h //进程名称设置工具
├── properties.h //属性机制
├── qtaguid.h
├── record_stream.h
├── sched_policy.h
├── sockets.h //套接字机制
├── str_parms.h //字符串字典
├── threads.h //线程接口,定义线程及相关的互斥量
├── trace.h
└── uevent.h //简单封装NETLINK_KOBJECT_UEVENT
从实现的角度看,以上功能大部分是调用是 Linux 标准功能的封装,少数是 Android特定功能的封装。libcutils 封装后提供的接口,很多都具有面向对象的特点。
2.2 本地句柄 native_handle
native_handle 是一个名称为本地句柄的结构体,它用于在程序中传递若干个文件描述符号和整数。native_handle.h 是其头文件,native_handle.c 是其实现者。
native_handle.h 中定义了名为 native_handle_t 的结构体,如下所示:
typedef struct native_handle
{
int version; /* sizeof(native_handle_t) */
int numFds; /* number of file-descriptors at &data[0] */
int numInts; /* number of ints at &data[numFds] */
int data[0]; /* numFds + numInts ints */
} native_handle_t;
native handle 实际上只是文件描述符和整数的集合,data[0] 的目的是为了指向结构体之后的内容。两个方法提供了对这个结构体的操作,如下所示:
native_handle_t* native_handle_create(int numFds, int numInts);
int native_handle_close(const native_handle_t* h);
int native_handle_delete(native_handle_t* h);
调用 native_handle_create()函数,可以得到一个native_handle_t,其大小实际上是由参数传入的内容来决定的,得到 native_handle_t 结构体后,对其中的文件描述符和整数赋值。调用 native_handle_close() 之后,将关闭其中所使用的文件描述符。libcutils中提供的native_handle 只具有比较简单的功能,在实际应用中,通常通过纯C语言中“继承”的方式来使用。例子如下所示:
struct xxx_handle_t {
struct native_handle nativeHandle;
int fd_1;/*各个文件描述符*/
int fd_2;
int value1;/*各个整数*/
int value2;
/*省略部分内容*/
}
结构体 xxx_handle_t 以 native_handle 作为首成员,表示继承了 native_handle,xxx_handle_t 后面的成员包括若干个文件描述符和整数。在建立 native_handle 时,传入xxx_handle_t 实际的文件描述符和整数的数目,此时返回的结构体指针指向的内存就是xxx_handle_t的大小,因此它实际上是可以作为 xxx_handle_t 使用的。
2.3 哈希表 Hashmap
libcutils 实现了哈希表映射,哈希表作为一个容器,可以容纳若干个键和键值的对。哈希表的头文件在 hashmap.h 中,实现在 hashmap.c 中。哈希表的功能定义主要来自以下的几个功能:
typedef struct Hashmap Hashmap;//哈希表映射结构体,具体内容对外不可见
Hashmap* hashmapCreate(size_t initialCapacity, int (*hash)(void* key), bool (*equals)(void* keyA, void* keyB));//创建哈希表
void hashmapFree(Hashmap* map);//释放哈希表
void* hashmapPut(Hashmap* map, void* key, void* value);//哈希表中添加成员
void* hashmapGet(Hashmap* map, void* key);//获取哈希表中成员
void* hashmapRemove(Hashmap* map, void* key);//删除哈希表中成员
size_t hashmapSize(Hashmap* map);//获得哈希表大小
void hashmapForEach(Hashmap* map, bool (*callback)(void* key, void* value, void* context), void* context);//遍历哈希表
哈希表使用指针来表示键和键值的对,前者通常是一个字符串,后者是一个任意的指针。hashmapForEach() 为哈希表的遍历函数,可以向其中传入一个回调函数,处理遍历的结果。
2.4 原子操作
libcutils 提供原子操作的功能。原子操作相关的头文件包括 atomic.h 以及用于处理不同体系结构的 atomic-inline.h、atomic-arm.h 和atomic-x86.h,相关实现的内容在 atomic.c、atomic-android-sh.c文件中。
原子操作的实现和体系结构密切相关,但是为使用者提供的接口相同。存取的原子操作函数如下所示:
int32_t android_atomic_acquire_load(volatile const int32_t* addr);
int32_t android_atomic_release_load(volatile const int32_t* addr);
void android_atomic_acquire_store(int32_t value, volatile int32_t* addr);
void android_atomic_release_store(int32_t value, volatile int32_t* addr);
int android_atomic_acquire_cas(int32_t oldvalue, int32_t newvalue, volatile int32_t* addr);
int android_atomic_release_cas(int32_t oldvalue, int32_t newvalue, volatile int32_t* addr);
运算的原子操作函数如下所示:
int32_t android_atomic_inc(volatile int32_t* addr);
int32_t android_atomic_dec(volatile int32_t* addr);
int32_t android_atomic_add(int32_t value, volatile int32_t* addr);
int32_t android_atomic_and(int32_t value, volatile int32_t* addr);
int32_t android_atomic_or(int32_t value, volatile int32_t* addr);
原子操作调用的对象实际上通常是一个地址,此处使用的都是 32 位的地址。在调用以上的一个函数对一个地址进行操作时,其他并行线程调用 atomic 中的函数对这个地址进行的操作将被阻塞,直到前面一个操作完成。使用这种机制,是为了避免并行操作一个地址引起冲突。
2.5 线程及互斥
libcutils 提供了对线程功能的封装,封装的对象就是 POSIX 标准的 pthread 线程。其头文件为 threads.h,源文件为 threads.c。thread_store_t 表示储线程记录和 pthread_mutex_t 表示线程的互斥量。
libcutils 的线程部分并不是对线程功能的完整封装,而是只处理互斥(mutex)方面的封装。这部分功能在 Android 使用较少。如果需要使用线程,可以直接使用 pthread,或者使用 Android 的C++部分提供的线程封装。
2.6 匿名共享内存 ashmem
匿名共享内存是Android 中一个特定的功能需要基于Android 的 Linux 内核中提供的特定机制来实现。libcutils 提供的内容只是对内核特定机制的封装。其头文件为 ashmem.h,源代码实现为ashmem-dev.c。
int ashmem_create_region(const char *name, size_t size);//创建ashmem
int ashmem_set_prot_region(int fd, int prot);
int ashmem_pin_region(int fd, size_t offset, size_t len);//对内存pin
int ashmem_unpin_region(int fd, size_t offset, size_t len);//对内存解除pin
int ashmem_get_size_region(int fd);
匿名共享内存则调用 /dev/ashmem 的设备节点来实现,实现的功能类似一个 malloc 的内存分配。ashmem_create_region() 函数的返回值实际上就是一个文件描述符,表示一个进程中打开的文件,在这个文件描述符中可以获得需要使用的内存。内存是使用一个名字进行标识的。
Android 系统的运行依赖匿名共享内存机制,在开发者自己的程序中,则较少通过 libcutils 中提供的函数直接对匿名共享内存进行操作。
2.7 属性机制
属性是 Android 中一种全局的共享内容,在 libcutils 中,properties.h 定义的是属性操作的接口,properties.c 是其实现。
头文件中的定义如下所示:
#define PROPERTY_KEY_MAX PROP_NAME_MAX
#define PROPERTY_VALUE_MAX PROP_VALUE_MAX
int property_get(const char *key, char *value, const char *default_value);
int property_set(const char *key, const char *value);
int property_list(void (*propfn)(const char *key, const char *value, void *cookie), void *cookie);
此部分定义的内容和 Bionic中的 system_properties.h 定义具有对应关系。属性的名称和属性值都是使用字符串 (char*)来表示的。property_get0函数用于获得某名称属性的值如果属性不存在将根据最后一个参数作为这个属性的默认值;property_set()函数用于设置某名称属性的值; property_list0函数用于获得系统所有的属性,其首参数为一个回调函数的指针,用于接收属性列表的内容。
属性功能的定义是一个全局的定义,在任何一个本地程序中,都可以通过以上的儿个函数访问属性。系统的属性区域只有一个,因此无论在任何程序中访问,对象都是一个,也没有进程的限制。由此利用属性机制,各个程序可以实现某种程序上的共享。
2.8 套接字机制
Android 中的套接字基于 Linux 标准的 Socket,关键的改动在于可以使用有名字的 Socket。在 libcutils 中,sockets.h 定义 Android 套接字的本地接口,socket_local.h 则是一个内部使用的头文件,实现的内容由 socket_*.c 的几个源文件实现。libcutils 中的 sockets.h 定义的几个和类型相关的内容:
#define ANDROID_SOCKET_ENV_PREFIX "ANDROID_SOCKET_"
#define ANDROID_SOCKET_DIR "/dev/socket"
/*
* See also android.os.LocalSocketAddress.Namespace
*/
// Linux "abstract" (non-filesystem) namespace
#define ANDROID_SOCKET_NAMESPACE_ABSTRACT 0
// Android "reserved" (/dev/socket) namespace
#define ANDROID_SOCKET_NAMESPACE_RESERVED 1
// Normal filesystem namespace
#define ANDROID_SOCKET_NAMESPACE_FILESYSTEM 2
套接字定义了 3 种类型(第一种使用了 AF_INET 类型的套接字,第二种和第三种使用了 AF_LOCAL 类型的套接字):
- 第一种表示不使用文件系统的抽象套接字。
- 第二种表示放入系统保留的 /dev/socket 路径中的套接字。
- 第三种为常规文件系统中的套接字,它实际上存储在文件系统的/tmp/路径中。
头文件中一个静态的 inline 函数用于根据一个名称表示套接,得到这种套接字所对应的文件描述符:
static inline int android_get_control_socket(const char* name) {}
一般情况下,一个使用 Socket 的守护进程通常使用android_get_control_socket() 获得一个Socket。
sockets.h 中的函数如下所示:
int socket_local_server(const char* name, int namespaceId, int type);
int socket_local_server_bind(int s, const char* name, int namespaceId);
int socket_local_client_connect(int fd, const char *name, int namespaceId, int type);
int socket_local_client(const char* name, int namespaceId, int type);
这几个函数的返回值均为 int,表示一个套接字的文件描述符。socket_local_client() 和socket_local client_connect() 用于作为客户端连接到一个已经建立的套接字,前者通过后者实现。socket_local_server() 和 socket_local_server_bind() 用于绑定到服务器的套接字,这里不需要 listen(),前者通过后者实现。这两个部分的内容分别在 socket_local_client.c 和 socket_local_server.c 文件中实现。
Android 的本地程序主要使用 socket_local_client() 连接到某个名字的 Socket 上面,然后可以对这个 Socket 进行R/W等操作。这些本地的函数是套接字使用的基础,本质是通过名字访问同一个 Socket,以此达到共享通信的效果。
sockets.h 中另外的几个函数用于建立网络 (AF_INET) 类型的套接字,如下所示:
int socket_loopback_client(int port, int type);//网络的回环客户端
cutils_socket_t socket_network_client(const char* host, int port, int type);//网络客户端
int socket_loopback_server(int port, int type);//网络的回环服务端
这个函数都是根据网络地址建立的套接字,其中的类型(type)可以为表示 TCP 协议的 SOCK_STREAM(流)或者表示 UDP 协议的 SOCK_DGRAM(数据报)。它们在 socket_loopback_client.c、socket_ loopback_server.c 和 socket_network_client.c 3 个文件中实现。这几个函数只是对 Linux 的套接字机制的封装,与Android 中的其他部分联系不大。