当进程调用函数write、writev或者aio_write往日志设备写入日志记录时,Logger日志驱动程序中的函数logger_aio_write就会被调用来执行日志记录的写入操作。
**kernel/goldfish/drivers/staging/android/logger.c**
~~~
/*
* logger_aio_write - our write method, implementing support for write(),
* writev(), and aio_write(). Writes are our fast path, and we try to optimize
* them above all else.
*/
ssize_t logger_aio_write(struct kiocb *iocb, const struct iovec *iov,
unsigned long nr_segs, loff_t ppos)
{
struct logger_log *log = file_get_log(iocb->ki_filp);
size_t orig = log->w_off;
struct logger_entry header;
struct timespec now;
ssize_t ret = 0;
now = current_kernel_time();
header.pid = current->tgid;
header.tid = current->pid;
header.sec = now.tv_sec;
header.nsec = now.tv_nsec;
header.len = min_t(size_t, iocb->ki_left, LOGGER_ENTRY_MAX_PAYLOAD);
/* null writes succeed, return zero */
if (unlikely(!header.len))
return 0;
mutex_lock(&log->mutex);
/*
* Fix up any readers, pulling them forward to the first readable
* entry after (what will be) the new write offset. We do this now
* because if we partially fail, we can end up with clobbered log
* entries that encroach on readable buffer.
*/
fix_up_readers(log, sizeof(struct logger_entry) + header.len);
do_write_log(log, &header, sizeof(struct logger_entry));
while (nr_segs-- > 0) {
size_t len;
ssize_t nr;
/* figure out how much of this vector we can keep */
len = min_t(size_t, iov->iov_len, header.len - ret);
/* write out this segment's payload */
nr = do_write_log_from_user(log, iov->iov_base, len);
if (unlikely(nr < 0)) {
log->w_off = orig;
mutex_unlock(&log->mutex);
return nr;
}
iov++;
ret += nr;
}
mutex_unlock(&log->mutex);
/* wake up any blocked readers */
wake_up_interruptible(&log->wq);
return ret;
}
~~~
函数的第一个参数iocb表示一个IO上下文。第二个参数iov保存了要写入的日志记录的内容,它是一个数组向量,长度由第三个参数nr_segs决定。第四个参数ppos指定日志记录的写入位置。由于日志设备中保存了下一条日志记录的写入位置,因此,第四个参数ppos是不需要使用的。
对于类型为main、system和radio的日志记录来说,它们的内容是由优先级(priority)、标签(tag)和内容(msg)三个字段组成的,分别保存在iov[0]、iov[1]和iov[2]中,这时候第三个参数nr_segs的值就为3。对于类型为events的日志记录来说,它们的内容只有标签和内容两个字段,分别保存在iov[0]和iov[1]中,这时候第三个参数nr_segs的值就为2。在特殊情况下,当类型为events的日志记录的内容只有一个值时,第二个参数iov的大小也会等于3,这时候,iov[0]表示日志标签,iov[1]表示日志记录的内容值类型,iov[2]表示日志记录的内容值。在后面的4.4小节中分析日志的写入接口时,我们再详细介绍参数iov的使用方法。无论要写入的是什么类型的日志记录,它的总长度值都是保存在参数iocb的成员变量ki_left中的,单位是字节。
在前面的4.2.3小节提到,当进程以写模式打开日志设备时,Logger日志驱动程序会把对应的日志缓冲区结构体log保存在一个打开文件结构体file的成员变量private_data中。参数iocb的成员变量ki_filp正好指向该打开文件结构体,因此,第9行就调用函数file_get_log安全地将对应的日志缓冲区结构体log获取回来。
**kernel/goldfish/drivers/staging/android/logger.c**
~~~
static inline struct logger_log * file_get_log(struct file *file)
{
if (file->f_mode & FMODE_READ) {
struct logger_reader *reader = file->private_data;
return reader->log;
} else
return file->private_data;
}
~~~
有了这个日志缓冲区结构体log之后,就可以执行日志记录的写入操作了。
回到函数logger_aio_write中,第11行首先定义一个日志记录结构体header,接着第17行到第21行对它进行初始化,即记录日志记录写入进程的TGID和PID,以及日志记录的写入时间和长度。由于一条日志记录的最大长度为LOGGER_ENTRY_MAX_PAYLOAD,因此,第21行在初始化日志记录的长度时,要取参数iocb的成员变量ki_left和宏LOGGER_ENTRY_MAX_PAYLOAD的较小值。
在将日志记录写入到日志缓冲区之前,还有一件重要的事情要做,就是修正那些正在读取该日志缓冲区的进程的当前日志记录的读取位置,即日志读取进程结构体logger_reader的成员变量r_off的值,以及修正新的日志读取进程的日志记录开始读取位置,即日志缓冲区结构体logger_log的成员变量head的值。我们知道,日志缓冲区是循环使用的,即当日志缓冲区写满之后,新的日志记录会覆盖旧的日志记录,而这些将要被覆盖的日志记录有可能正好是某些进程的下一条日志记录的读取位置。由于这些位置即将被新的日志记录覆盖,因此,我们就需要对它们进行修正,以便这些受影响的进程下次能够读取到正确的日志记录。这个修正工作要在日志记录写入前去做,这是为了保证在日志记录写入的过程中出现部分失败时,不会破坏整个日志缓冲区的内容。
修正日志记录读取进程的读取位置的工作是通过调用函数fix_up_readers来完成的,它的实现如下所示。
**kernel/goldfish/drivers/staging/android/logger.c**
~~~
/*
* fix_up_readers - walk the list of all readers and "fix up" any who were
* lapped by the writer; also do the same for the default "start head".
* We do this by "pulling forward" the readers and start head to the first
* entry after the new write head.
*
* The caller needs to hold log->mutex.
*/
static void fix_up_readers(struct logger_log *log, size_t len)
{
size_t old = log->w_off;
size_t new = logger_offset(old + len);
struct logger_reader *reader;
if (clock_interval(old, new, log->head))
log->head = get_next_entry(log, log->head, len);
list_for_each_entry(reader, &log->readers, list)
if (clock_interval(old, new, reader->r_off))
reader->r_off = get_next_entry(log, reader->r_off, len);
}
~~~
第二个参数len表示即将要写入的日志记录的总长度,它等于日志记录结构体logger_entry的长度加上日志记录的有效负载长度。第11行得到日志缓冲区结构体log的下一条日志记录的写入位置,保存在变量old中。第12行计算将新的日志记录写入到日志缓冲区结构体log之后,下一条日志记录的写入位置,保存在变量new中。位于old值和new值之间的那些日志记录读取位置是无效的,因为它们即将被新写入的日志记录覆盖。
给定一个日志记录读取位置c,判断它是否位于a和b之间是通过调用函数clock_interval来实现的,如下所示。
**kernel/goldfish/drivers/staging/android/logger.c**
~~~
/*
* clock_interval - is a < c < b in mod-space? Put another way, does the line
* from a to b cross c?
*/
static inline int clock_interval(size_t a, size_t b, size_t c)
{
if (b < a) {
if (a < c || b >= c)
return 1;
} else {
if (a < c && b >= c)
return 1;
}
return 0;
}
~~~
函数clock_interval分两种情况来判断c是否位于a和b之间:第一种情况是b小于a;第二种情况是b大于等于a。在这两种情况下,如果c位于a和b之间,那么函数的返回值为1,否则为0。
回到函数fix_up_readers中,第16行修正的是新的日志读取进程在日志缓冲区结构体log中的开始读取位置,即日志缓冲区结构体log的成员变量head的值。第18行到第20行的循环语句修正的是那些正在读取日志缓冲区结构体log中的日志记录的进程的下一条日志记录的位置,即日志读取进程结构体reader的成员变量r_off的值。如果这些位置正好位于变量old和new的值之间,那么就调用函数get_next_entry将它们的值修改为下一条日志记录的位置。
**kernel/goldfish/drivers/staging/android/logger.c**
~~~
/*
* get_next_entry - return the offset of the first valid entry at least 'len'
* bytes after 'off'.
*
* Caller must hold log->mutex.
*/
static size_t get_next_entry(struct logger_log *log, size_t off, size_t len)
{
size_t count = 0;
do {
size_t nr = get_entry_len(log, off);
off = logger_offset(off + nr);
count += nr;
} while (count < len);
return off;
}
~~~
参数off表示要修正的位置,而参数len表示即将要写入的日志记录的长度。第11行到第15行的while循环首先调用函数get_entry_len来获得在位置off的日志记录的长度,然后将它增加到位置off中去。如果累计增加的位置大于或者等于参数len的值,那么就完成了对位置off的修正;否则,就要继续将位置off移到再下一条日志记录的位置。
回到函数logger_aio_write中,第37行到第56行代码用来将参数iov中的日志记录内容写入到日志缓冲区结构体log中。由于一条日志记录在日志缓冲区中是由一个日志记录结构体和一个有效负载组成的,因此,函数logger_aio_write就分两步将它们写入到日志缓冲区中。
函数前面已经为即将要写入的日志记录准备好了一个日志记录结构体header,因此,第37行就调用函数do_write_log将它的内容写入到日志缓冲区结构体log中。
**kernel/goldfish/drivers/staging/android/logger.c**
~~~
/*
* do_write_log - writes 'len' bytes from 'buf' to 'log'
*
* The caller needs to hold log->mutex.
*/
static void do_write_log(struct logger_log *log, const void *buf, size_t count)
{
size_t len;
len = min(count, log->size - log->w_off);
memcpy(log->buffer + log->w_off, buf, len);
if (count != len)
memcpy(log->buffer, buf + len, count - len);
log->w_off = logger_offset(log->w_off + count);
}
~~~
由于一个日志记录结构体的内容可能会被分别写在日志缓冲区的尾部和头部,因此,函数do_write_log就分两次来写入它的内容。第10行计算写入到日志缓冲区尾部的内容的长度,接着第11行就直接将对应的日志记录结构体的内容拷贝到日志缓冲区中。第13行判断前面是否已经将日志记录结构体的内容全部写入到日志缓冲区中了。如果不是,第14行就会将剩余的内容写入到日志缓冲区的头部。
> 参数buf指向的内容即为要写入的日志记录结构体的内容,它位于内核空间中,因此,第11行和第14行将它的内容写入到日志缓冲区中时,直接调用函数memcpy来拷贝就可以了。将日志记录结构体的内容写入到日志缓冲区中之后,第16行就要更新它的成员变量w_off的值,因为它始终指向下一条日志记录的写入位置。
回到函数logger_aio_write中,第39行到第56行的while循环把参数iov的内容写入到日志缓冲区结构体log中。参数iov里面所保存的内容对应于要写入的日志记录的有效负载,它们是从用户空间传进来的,因此,函数logger_aio_write不能直接调用函数memcpy将它们拷贝到日志缓冲区结构体log中,而是调用函数do_write_log_from_user来执行拷贝操作。
**kernel/goldfish/drivers/staging/android/logger.c**
~~~
/*
* do_write_log_user - writes 'len' bytes from the user-space buffer 'buf' to
* the log 'log'
*
* The caller needs to hold log->mutex.
*
* Returns 'count' on success, negative error code on failure.
*/
static ssize_t do_write_log_from_user(struct logger_log *log,
const void __user *buf, size_t count)
{
size_t len;
len = min(count, log->size - log->w_off);
if (len && copy_from_user(log->buffer + log->w_off, buf, len))
return -EFAULT;
if (count != len)
if (copy_from_user(log->buffer, buf + len, count - len))
return -EFAULT;
log->w_off = logger_offset(log->w_off + count);
return count;
}
~~~
与日志记录结构体的写入过程类似,日志记录的有效负载也有可能会被分别写入到日志缓冲区的尾部和头部,因此,函数do_write_log_from_user就分两次来写入它的内容。第14行计算写入到日志缓冲区尾部的内容的长度,接着第15行就调用函数copy_from_user将对应的日志记录的有效负载拷贝到日志缓冲区中。第18行判断前面是否已经将日志记录的有效负载全部写入到日志缓冲区了。如果不是,第19行就会将剩余的内容写入到日志缓冲区的头部。
> 参数buf指向的内容是保存在用户空间中的,因此,第15行和第19行将它的内容写入到日志缓冲区中时,需要调用函数copy_from_user来执行拷贝操作。将日志记录的有效负载写入到日志缓冲区中之后,接下来第22行就要更新它的成员变量w_off的值,因为它始终指向下一条日志记录的写入位置。
在前面的4.2.4小节中,我们在分析日志记录的读取过程时提到,如果日志缓冲区当前没有新的日志记录可读,那么日志读取进程就会进入到日志缓冲区的等待队列wq中去睡眠等写入新的日志记录。现在既然已经往日志缓冲区中写入新的日志记录了,函数logger_aio_write在第61行就会调用函数wake_up_interruptible来唤醒那些睡眠在日志缓冲区的等待队列wq中的进程,目的是通知它们有新的日志记录可读了。
- 文章概述
- 下载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 应用程序的显示过程