日志设备的初始化过程是在Logger日志驱动程序的入口函数logger_init中进行的。在分析这个函数之前,我们首先介绍三个结构体变量log_main、log_events和log_radio,它们的类型均为struct logger_log,分别用来保存main、events和radio三种类型的日志记录,如下所示。
**kernel/goldfish/drivers/staging/android/logger.c**
~~~
/*
* Defines a log structure with name 'NAME' and a size of 'SIZE' bytes, which
* must be a power of two, greater than LOGGER_ENTRY_MAX_LEN, and less than
* LONG_MAX minus LOGGER_ENTRY_MAX_LEN.
*/
#define DEFINE_LOGGER_DEVICE(VAR, NAME, SIZE) \
static unsigned char _buf_ ## VAR[SIZE]; \
static struct logger_log VAR = { \
.buffer = _buf_ ## VAR, \
.misc = { \
.minor = MISC_DYNAMIC_MINOR, \
.name = NAME, \
.fops = &logger_fops, \
.parent = NULL, \
}, \
.wq = __WAIT_QUEUE_HEAD_INITIALIZER(VAR .wq), \
.readers = LIST_HEAD_INIT(VAR .readers), \
.mutex = __MUTEX_INITIALIZER(VAR .mutex), \
.w_off = 0, \
.head = 0, \
.size = SIZE, \
};
DEFINE_LOGGER_DEVICE(log_main, LOGGER_LOG_MAIN, 64*1024)
DEFINE_LOGGER_DEVICE(log_events, LOGGER_LOG_EVENTS, 256*1024)
DEFINE_LOGGER_DEVICE(log_radio, LOGGER_LOG_RADIO, 64*1024)
~~~
结构体变量log_main、log_events和log_radio都是通过宏DEFINE_LOGGER_DEVICE来定义的,需要传入的三个参数分别为变量名、设备文件路径,以及要分配的日志缓冲区的大小。前面提到,Logger日志系统将日志记录划分为四种类型。从理论上说,每一种类型的日志记录都应该对应有一个日志缓冲区,即在Logger日志驱动程序中对应有一个logger_log结构体,但是在目前的Logger日志驱动程序实现中,只定义了三个日志缓冲区,其中,类型为system和main的日志记录保存在同一个日志缓冲区log_main中,而类型为events和radio的日志记录分别保存在日志缓冲区log_events和log_radio中。
从宏DEFINE_LOGGER_DEVICE的定义可以看出,每一种类型的日志缓冲区都是将各自的日志记录保存在一个静态分配的unsigned char数组中,数组的大小由参数SIZE决定,其中,类型为main和radio的日志缓冲区的大小为64K,而类型为events的日志缓冲区的大小为256K。
每一个日志缓冲区都对应有一个misc类型的日志设备,以便运行在用户空间的程序可以访问它们。每一个日志设备都对应有一个文件名,它是由参数NAME指定的。从结构体变量log_main、log_events和log_radio的定义可以看出,类型为main、radio和events的日志缓冲区对应的日志设备文件分别为LOGGER_LOG_MAIN、LOGGER_LOG_RADIO和LOGGER_LOG_EVENTS。
**kernel/goldfish/drivers/staging/android/logger.h**
~~~
#define LOGGER_LOG_RADIO "log_radio" /* radio-related messages */
#define LOGGER_LOG_EVENTS "log_events" /* system/hardware events */
#define LOGGER_LOG_MAIN "log_main" /* everything else */
~~~
Logger日志驱动程序初始化完成之后,我们就可以在/dev/log目录下看到三个日志设备文件main、events和radio。这三个日志设备对应的文件操作函数表均为logger_fops,它的定义如下所示。
**kernel/goldfish/drivers/staging/android/logger.c**
~~~
static struct file_operations logger_fops = {
.owner = THIS_MODULE,
.read = logger_read,
.aio_write = logger_aio_write,
.poll = logger_poll,
.unlocked_ioctl = logger_ioctl,
.compat_ioctl = logger_ioctl,
.open = logger_open,
.release = logger_release,
};
~~~
文件操作函数表logger_fops指定的日志设备文件操作函数比较多,但是我们只关注日志设备打开、读取和写入函数open、read和aio_write的定义,它们分别被设置为logger_open、logger_read和logger_aio_write函数。
每一个硬件设备都有自己的主设备号和从设备号。由于日志设备的类型为misc,并且同一类型的设备都具有相同的主设备号,因此,在定义这些日志设备时,就不需要指定它们的主设备号了,等到注册这些日志设备时,再统一指定。从宏DEFINE_LOGGER_DEVICE的定义可以看出,这些日志设备的从设备号被设置为MISC_DYNAMIC_MINOR,表示由系统动态分配,它的定义如下所示。
**kernel/goldfish/drivers/staging/android/logger.h**
~~~
#define MISC_DYNAMIC_MINOR 255
~~~
一般来说,我们在开发驱动程序时,都不应该去静态指定设备的从设备号,因为静态指定的从设备号会比较容易发生冲突,而动态分配的从设备号能够保证唯一性。
结构体变量log_main、log_events和log_radio的其余成员变量的初始化过程都比较简单,这里就不详细介绍了。例如,它们的成员变量wq、reader和mutex分别使用宏__WAIT_QUEUE_HEAD_INITIALIZER、LIST_HEAD_INIT和__MUTEX_INITIALIZER来初始化,因为它们的类型分别为等待队列、队列和互斥量。
我们就从函数logger_init开始,分析日志设备的初始化过程。
**kernel/goldfish/drivers/staging/android/logger.c**
~~~
static int __init logger_init(void)
{
int ret;
ret = init_log(&log_main);
if (unlikely(ret))
goto out;
ret = init_log(&log_events);
if (unlikely(ret))
goto out;
ret = init_log(&log_radio);
if (unlikely(ret))
goto out;
out:
return ret;
}
~~~
日志设备的初始化过程实际上就是将三个日志设备注册到系统中,这是分别通过调用函数init_log来实现的。
**kernel/goldfish/drivers/staging/android/logger.c**
~~~
static int __init init_log(struct logger_log *log)
{
int ret;
ret = misc_register(&log->misc);
if (unlikely(ret)) {
printk(KERN_ERR "logger: failed to register misc "
"device for log '%s'!\n", log->misc.name);
return ret;
}
printk(KERN_INFO "logger: created %luK log '%s'\n",
(unsigned long) log->size >> 10, log->misc.name);
return 0;
}
~~~
在init_log函数中,主要是通过调用misc_register函数将相应的日志设备注册到系统中。
**kernel/goldfish/drivers/char/misc.c**
~~~
int misc_register(struct miscdevice * misc)
{
struct miscdevice *c;
dev_t dev;
int err = 0;
INIT_LIST_HEAD(&misc->list);
mutex_lock(&misc_mtx);
list_for_each_entry(c, &misc_list, list) {
if (c->minor == misc->minor) {
mutex_unlock(&misc_mtx);
return -EBUSY;
}
}
if (misc->minor == MISC_DYNAMIC_MINOR) {
int i = DYNAMIC_MINORS;
while (--i >= 0)
if ( (misc_minors[i >> 3] & (1 << (i&7))) == 0)
break;
if (i<0) {
mutex_unlock(&misc_mtx);
return -EBUSY;
}
misc->minor = i;
}
if (misc->minor < DYNAMIC_MINORS)
misc_minors[misc->minor >> 3] |= 1 << (misc->minor & 7);
dev = MKDEV(MISC_MAJOR, misc->minor);
misc->this_device = device_create(misc_class, misc->parent, dev, NULL,
"%s", misc->name);
if (IS_ERR(misc->this_device)) {
err = PTR_ERR(misc->this_device);
goto out;
}
/*
* Add it to the front, so that later devices can "override"
* earlier defaults
*/
list_add(&misc->list, &misc_list);
out:
mutex_unlock(&misc_mtx);
return err;
}
~~~
系统中所有的misc设备都保存在一个misc_list列表中。函数第10行到第15行检查所要注册的misc设备的从设备号是否已经被注册。如果是的话,注册就失败了,因为同一类型设备的从设备号是不能相同的。由于Logger日志驱动程序已经指定它所要注册的日志设备的从设备号是动态分配的,即指定为MISC_DYNAMIC_MINOR,因此,这一步检查就通过了。
函数第17行到第27行为所要注册的日志设备分配从设备号。系统可分配的misc从设备号一共有64个,即从0到63。这些从设备号的使用情况记录在数组misc_minors中,它的定义如下所示。
**kernel/goldfish/drivers/char/misc.c**
~~~
#define DYNAMIC_MINORS 64 /* like dynamic majors */
static unsigned char misc_minors[DYNAMIC_MINORS / 8];
~~~
数组misc_minors的类型为unsigned char,因此,每一个元素可以管理8个从设备号,64个从设备号就需要8个元素,即数组misc_minors的大小为8。如果某一个从设备号已经被分配了,那么它在数组misc_minors中所对应的位就等于1,否则就等于0。如何在数组misc_minors中找到从设备号i所对应的位呢?首先找到它对应的数组元素位置,即i >> 3,然后取出从设备号i的低三位,即i & 7,那么从设备号i在数组misc_minors中对应的位便是第i >> 3个元素的第i & 7位了。因此,第20行只要检查这一位是否等于0,就可以知道从设备号i是否已经被分配了。如果没有被分配,那么第26行就将它分配给当前正在注册的日志设备,接着第30行在数组misc_minors中记录该从设备号已经被分配。
函数第31行使用misc主设备号MISC_MAJOR和前面得到的从设备号来创建一个设备号对象dev,接着第33行以它为参数调用函数device_create将日志设备misc注册到系统中。MISC_MAJOR是一个宏,它的定义如下所示。
**kernel/goldfish/include/linux/major.h**
~~~
#define MISC_MAJOR 10
~~~
日志设备注册成功之后,函数第44行就将它添加到misc设备列表misc_list中,表示日志设备注册成功了。
第33行调用device_create函数注册日志设备时,最后一个参数用来指定日志设备的名称,即它在设备系统目录/dev中对应的文件名称。从前面的调用过程可以知道,我们要注册的三个日志设备的名称分别为“log_main”、“log_events”和“log_radio”,因此,应当在设备上的/dev目录中看到log_main、log_events和log_radio三个文件。然而,在本章的开头提到,这三个日志设备对应的文件分别为/dev/log/main、/dev/log/events和/dev/log/radio,这是为什么呢?
在前面的2.3.4小节中提到,Android系统使用一种uevent机制来管理系统的设备文件。当Logger日志驱动程序注册一个日志设备时,内核就会发出一个uevent事件,这个uevent最终由init进程中的handle_device_event函数来处理,它的实现如下所示。
**system/core/init/devices.c**
~~~
static void handle_device_event(struct uevent *uevent)
{
char devpath[96];
int devpath_ready = 0;
char *base, *name;
char **links = NULL;
int block;
int i;
......
name = strrchr(uevent->path, '/');
......
name++;
......
if(!strncmp(uevent->subsystem, "block", 5)) {
......
} else {
......
if (!strncmp(uevent->subsystem, "usb", 3)) {
......
} else if (!strncmp(uevent->subsystem, "graphics", 8)) {
......
} else if (!strncmp(uevent->subsystem, "oncrpc", 6)) {
......
} else if (!strncmp(uevent->subsystem, "adsp", 4)) {
......
} else if (!strncmp(uevent->subsystem, "msm_camera", 10)) {
......
} else if(!strncmp(uevent->subsystem, "input", 5)) {
......
} else if(!strncmp(uevent->subsystem, "mtd", 3)) {
......
} else if(!strncmp(uevent->subsystem, "sound", 5)) {
......
} else if(!strncmp(uevent->subsystem, "misc", 4) &&
!strncmp(name, "log_", 4)) {
base = "/dev/log/";
mkdir(base, 0755);
name += 4;
} else
......
}
if (!devpath_ready)
snprintf(devpath, sizeof(devpath), "%s%s", base, name);
if(!strcmp(uevent->action, "add")) {
make_device(devpath, uevent->path, block, uevent->major, uevent->minor);
if (links) {
for (i = 0; links[i]; i++)
make_link(devpath, links[i]);
}
}
......
}
~~~
函数handle_device_event如果发现新注册的设备类型为misc,并且设备名称是以“log_”开头的,它便会在/dev目录中创建一个log目录,接着将设备名称的前四个字符去掉,即去掉前面的“log_”子字符串,最后就使用新的设备名称在/dev/log目录下创建一个设备文件。因此,我们就不会在设备上的/dev目录中看到log_main、log_events和log_radio三个设备文件,而是在/dev/log目录中看到main、events和radio三个设备文件。
- 文章概述
- 下载Android源码以及查看源码
- win10 平台通过VMware Workstation安装Ubuntu
- Linux系统安装Ubuntu编译Android源码
- Eclipse快捷键大全
- 前言
- 第一篇 初识Android系统
- 第一章 准备知识
- 1.1 Linux内核参考书籍
- 1.2 Android应用程序参考书籍
- 1.3 下载、编译和运行Android源代码
- 1.3.1 下载Android源代码
- 1.3.2 编译Android源代码
- 1.3.3 运行Android模拟器
- 1.4 下载、编译和运行Android内核源代码
- 1.4.1 下载Android内核源代码
- 1.4.2 编译Android内核源代码
- 1.4.3 运行Android模拟器
- 1.5 开发第一个Android应用程序
- 1.6 单独编译和打包Android应用程序模块
- 1.6.1 导入单独编译模块的mmm命令
- 1.6.2 单独编译Android应用程序模块
- 1.6.3 重新打包Android系统镜像文件
- 第二章 硬件抽象层
- 2.1 开发Android硬件驱动程序
- 2.1.1 实现内核驱动程序模块
- 2.1.2 修改内核Kconfig文件
- 2.1.3 修改内核Makefile文件
- 2.1.4 编译内核驱动程序模块
- 2.1.5 验证内核驱动程序模块
- 2.2 开发C可执行程序验证Android硬件驱动程序
- 2.3 开发Android硬件抽象层模块
- 2.3.1 硬件抽象层模块编写规范
- 2.3.1.1 硬件抽象层模块文件命名规范
- 2.3.1.2 硬件抽象层模块结构体定义规范
- 2.3.2 编写硬件抽象层模块接口
- 2.3.3 硬件抽象层模块的加载过程
- 2.3.4 处理硬件设备访问权限问题
- 2.4 开发Android硬件访问服务
- 2.4.1 定义硬件访问服务接口
- 2.4.2 实现硬件访问服务
- 2.4.3 实现硬件访问服务的JNI方法
- 2.4.4 启动硬件访问服务
- 2.5 开发Android应用程序来使用硬件访问服务
- 第三章 智能指针
- 3.1 轻量级指针
- 3.1.1 实现原理分析
- 3.1.2 使用实例分析
- 3.2 强指针和弱指针
- 3.2.1 强指针的实现原理分析
- 3.2.2 弱指针的实现原理分析
- 3.2.3 应用实例分析
- 第二篇 Android专用驱动系统
- 第四章 Logger日志系统
- 4.1 Logger日志格式
- 4.2 Logger日志驱动程序
- 4.2.1 基础数据结构
- 4.2.2 日志设备的初始化过程
- 4.2.3 日志设备文件的打开过程
- 4.2.4 日志记录的读取过程
- 4.2.5 日志记录的写入过程
- 4.3 运行时库层日志库
- 4.4 C/C++日志写入接口
- 4.5 Java日志写入接口
- 4.6 Logcat工具分析
- 4.6.1 基础数据结构
- 4.6.2 初始化过程
- 4.6.3 日志记录的读取过程
- 4.6.4 日志记录的输出过程
- 第五章 Binder进程间通信系统
- 5.1 Binder驱动程序
- 5.1.1 基础数据结构
- 5.1.2 Binder设备的初始化过程
- 5.1.3 Binder设备文件的打开过程
- 5.1.4 设备文件内存映射过程
- 5.1.5 内核缓冲区管理
- 5.1.5.1 分配内核缓冲区
- 5.1.5.2 释放内核缓冲区
- 5.1.5.3 查询内核缓冲区
- 5.2 Binder进程间通信库
- 5.3 Binder进程间通信应用实例
- 5.4 Binder对象引用计数技术
- 5.4.1 Binder本地对象的生命周期
- 5.4.2 Binder实体对象的生命周期
- 5.4.3 Binder引用对象的生命周期
- 5.4.4 Binder代理对象的生命周期
- 5.5 Binder对象死亡通知机制
- 5.5.1 注册死亡接收通知
- 5.5.2 发送死亡接收通知
- 5.5.3 注销死亡接收通知
- 5.6 Service Manager的启动过程
- 5.6.1 打开和映射Binder设备文件
- 5.6.2 注册成为Binder上下文管理者
- 5.6.3 循环等待Client进程请求
- 5.7 Service Manager代理对象接口的获取过程
- 5.8 Service的启动过程
- 5.8.1 注册Service组件
- 5.8.1.1 封装通信数据为Parcel对象
- 5.8.1.2 发送和处理BC_TRANSACTION命令协议
- 5.8.1.3 发送和处理BR_TRANSACTION返回协议
- 5.8.1.4 发送和处理BC_REPLY命令协议
- 5.8.1.5 发送和处理BR_REPLY返回协议
- 5.8.2 循环等待Client进程请求
- 5.9 Service代理对象接口的获取过程
- 5.10 Binder进程间通信机制的Java实现接口
- 5.10.1 获取Service Manager的Java代理对象接口
- 5.10.2 AIDL服务接口解析
- 5.10.3 Java服务的启动过程
- 5.10.4 获取Java服务的代理对象接口
- 5.10.5 Java服务的调用过程
- 第六章 Ashmem匿名共享内存系统
- 6.1 Ashmem驱动程序
- 6.1.1 相关数据结构
- 6.1.2 设备初始化过程
- 6.1.3 设备文件打开过程
- 6.1.4 设备文件内存映射过程
- 6.1.5 内存块的锁定和解锁过程
- 6.1.6 解锁状态内存块的回收过程
- 6.2 运行时库cutils的匿名共享内存接口
- 6.3 匿名共享内存的C++访问接口
- 6.3.1 MemoryHeapBase
- 6.3.1.1 Server端的实现
- 6.3.1.2 Client端的实现
- 6.3.2 MemoryBase
- 6.3.2.1 Server端的实现
- 6.3.2.2 Client端的实现
- 6.3.3 应用实例
- 6.4 匿名共享内存的Java访问接口
- 6.4.1 MemoryFile
- 6.4.2 应用实例
- 6.5 匿名共享内存的共享原理分析
- 第三篇 Android应用程序框架篇
- 第七章 Activity组件的启动过程
- 7.1 Activity组件应用实例
- 7.2 根Activity的启动过程
- 7.3 Activity在进程内的启动过程
- 7.4 Activity在新进程中的启动过程
- 第八章 Service组件的启动过程
- 8.1 Service组件应用实例
- 8.2 Service在新进程中的启动过程
- 8.3 Service在进程内的绑定过程
- 第九章 Android系统广播机制
- 9.1 广播应用实例
- 9.2 广播接收者的注册过程
- 9.3 广播的发送过程
- 第十章 Content Provider组件的实现原理
- 10.1 Content Provider组件应用实例
- 10.1.1 ArticlesProvider
- 10.1.2 Article
- 10.2 Content Provider组件的启动过程
- 10.3 Content Provider组件的数据共享原理
- 10.4 Content Provider组件的数据更新通知机制
- 10.4.1 内容观察者的注册过程
- 10.4.2 数据更新的通知过程
- 第十一章 Zygote和System进程的启动过程
- 11.1 Zygote进程的启动脚本
- 11.2 Zygote进程的启动过程
- 11.3 System进程的启动过程
- 第十二章 Android应用程序进程的启动过程
- 12.1 应用程序进程的创建过程
- 12.2 Binder线程池的启动过程
- 12.3 消息循环的创建过程
- 第十三章 Android应用程序的消息处理机制
- 13.1 创建线程消息队列
- 13.2 线程消息循环过程
- 13.3 线程消息发送过程
- 13.4 线程消息处理过程
- 第十四章 Android应用程序的键盘消息处理机制
- 14.1 InputManager的启动过程
- 14.1.1 创建InputManager
- 14.1.2 启动InputManager
- 14.1.3 启动InputDispatcher
- 14.1.4 启动InputReader
- 14.2 InputChannel的注册过程
- 14.2.1 创建InputChannel
- 14.2.2 注册Server端InputChannel
- 14.2.3 注册当前激活窗口
- 14.2.4 注册Client端InputChannel
- 14.3 键盘消息的分发过程
- 14.3.1 InputReader处理键盘事件
- 14.3.2 InputDispatcher分发键盘事件
- 14.3.3 当前激活的窗口获得键盘消息
- 14.3.4 InputDispatcher获得键盘事件处理完成通知
- 14.4 InputChannel的注销过程
- 14.4.1 销毁应用程序窗口
- 14.4.2 注销Client端InputChannel
- 14.4.3 注销Server端InputChannel
- 第十五章 Android应用程序线程的消息循环模型
- 15.1 应用程序主线程消息循环模型
- 15.2 界面无关的应用程序子线程消息循环模型
- 15.3 界面相关的应用程序子线程消息循环模型
- 第十六章 Android应用程序的安装和显示过程
- 16.1 应用程序的安装过程
- 16.2 应用程序的显示过程