当进程调用read函数从日志设备读取日志记录时,Logger日志驱动程序中的函数logger_read就会被调用从相应的日志缓冲区中读取日志记录。
**kernel/goldfish/drivers/staging/android/logger.c**
~~~
/*
* logger_read - our log's read() method
*
* Behavior:
*
* - O_NONBLOCK works
* - If there are no log entries to read, blocks until log is written to
* - Atomically reads exactly one log entry
*
* Optimal read size is LOGGER_ENTRY_MAX_LEN. Will set errno to EINVAL if read
* buffer is insufficient to hold next entry.
*/
static ssize_t logger_read(struct file *file, char __user *buf,
size_t count, loff_t *pos)
{
struct logger_reader *reader = file->private_data;
struct logger_log *log = reader->log;
ssize_t ret;
DEFINE_WAIT(wait);
start:
while (1) {
prepare_to_wait(&log->wq, &wait, TASK_INTERRUPTIBLE);
mutex_lock(&log->mutex);
ret = (log->w_off == reader->r_off);
mutex_unlock(&log->mutex);
if (!ret)
break;
if (file->f_flags & O_NONBLOCK) {
ret = -EAGAIN;
break;
}
if (signal_pending(current)) {
ret = -EINTR;
break;
}
schedule();
}
finish_wait(&log->wq, &wait);
if (ret)
return ret;
mutex_lock(&log->mutex);
/* is there still something to read or did we race? */
if (unlikely(log->w_off == reader->r_off)) {
mutex_unlock(&log->mutex);
goto start;
}
/* get the size of the next entry */
ret = get_entry_len(log, reader->r_off);
if (count < ret) {
ret = -EINVAL;
goto out;
}
/* get exactly one entry from the log */
ret = do_read_log_to_user(log, reader, buf, ret);
out:
mutex_unlock(&log->mutex);
return ret;
}
~~~
函数第16行得到日志记录读取进程结构体reader,而第17行得到日志缓冲区结构体log。在前面的4.2.3小节中提到,当进程以读模式打开相应的日志设备时,Logger日志驱动程序就会将上述两个结构体信息保存在打开文件结构体参数file的成员变量private_data中,因此,函数第16行和第17行就可以安全地将它们获取回来。
函数第22行到第42行的while循环用来检查日志缓冲区结构体log中是否有日志记录可读取,这主要是通过第26行代码来判断的。在进入第22行到第42行的while循环时,第23行首先调用函数prepare_to_wait将等待队列项wait加入日志记录读取进程的等待队列log->wq中。等到确认日志缓冲区结构体log有日志记录可读或者其他原因跳出该while循环时,第44行就会调用函数finish_wait将等待队列项wait从日志记录读取进程的等待队列log->wq中移除。
日志缓冲区结构体log的成员变量w_off用来描述下一条新写入的日志记录在日志缓冲区中的位置,而日志记录读取进程结构体reader的成员变量r_off用来描述当前进程下一条要读取的日志记录在日志缓冲区中的位置。当这两者相等时,就说明日志缓冲区结构体log中没有新的日志记录可读,因此,Logger日志驱动程序就会继续执行第22行到第42行的while循环来等待写入新的日志记录。如果日志缓冲区结构体log中有新的日志记录可读,那么第26行得到的变量ret的值就为false,因此,第29行就会跳出第22行到第42行的while循环,准备读取日志缓冲区结构体log中的日志记录。
如果日志缓冲区结构体log中没有日志记录可读,即第26行得到的变量ret为true,那么当前进程就有可能需要进入睡眠状态,直到日志缓冲区结构体log中有新的日志记录可读为止。当前进程是通过调用第41行的函数schedule来请求内核进行一次进程调度,从而进入睡眠状态的。但是有一种特殊情况——当前进程是以非阻塞模式来打开日志设备文件,即第31行的if语句为true时,当前进程就不会因为日志缓冲区结构体log中没有日志记录可读而进入睡眠状态,它直接返回到用户空间中。一般来说,当驱动程序决定使当前进程进入睡眠状态之前,要先通过调用函数signal_pending来检查当前进程是否有信号正在等待处理。如果有的话,即第36行的if语句为true,那么这时候驱动程序就不能使当前进程进入睡眠状态,而必须要让它结束当前系统调用,以便它可以立即返回去处理那些待处理信号。
如果日志缓冲区结构体log中有日志记录可读,那么Logger日志驱动程序就会跳出第22行到第42行的while循环。但是执行到第51行时,又会再次重新判断日志缓冲区结构体log的成员变量w_off 和日志读取进程结构体reader的成员变量r_off是否相等。这是因为在执行第48行代码来获取互斥量log->mutex时,可能会失败。在这种情况下,当前进程就会进入睡眠状态,直到成功获取互斥量log->mutex为止。在当前进程的睡眠过程中,日志缓冲区结构体log中的日志记录可能已经被其他进程访问过了,即log->w_off的值可能已经发生了变化。因此,第51行需要重新判断日志缓冲区结构体log的成员变量w_off 和日志读取进程结构体reader的成员变量r_off是否相等。
第51行再次确认日志缓冲区结构体log中有新的日志记录可读之后,接着第57行就调用函数get_entry_len来得到下一条要读取的日志记录的长度。
**kernel/goldfish/drivers/staging/android/logger.c**
~~~
/*
* get_entry_len - Grabs the length of the payload of the next entry starting
* from 'off'.
*
* Caller needs to hold log->mutex.
*/
static __u32 get_entry_len(struct logger_log *log, size_t off)
{
__u16 val;
switch (log->size - off) {
case 1:
memcpy(&val, log->buffer + off, 1);
memcpy(((char *) &val) + 1, log->buffer, 1);
break;
default:
memcpy(&val, log->buffer + off, 2);
}
return sizeof(struct logger_entry) + val;
}
~~~
在4.2.1小节中介绍日志记录结构体logger_entry时提到,每一条日志记录都是由两部分内容组成的,其中一部分内容用来描述日志记录,即结构体logger_entry本身所占据的内容;另一部分内容是日志记录的有效负载,即真正的日志记录内容。由于结构体logger_entry的大小是固定的,因此只要知道它的有效负载长度,就可以通过计算得到一条日志记录的长度。日志记录结构体logger_entry的有效负载长度记录在它的成员变量len中。由于成员变量len是日志记录结构体logger_entry的第一个成员变量,因此,它们的起始地址是相同的。又由于日志记录结构体logger_entry的成员变量len的类型为__u16,因此,只要读取该结构体变量地址的前两个字节的内容就可以知道对应的日志记录的长度。我们知道,日志缓冲区是循环使用的,因此,一条日志记录的前两个字节有可能分别保存在日志缓冲区的首尾字节中。因此,在计算一条日志记录的长度时,需要分两种情况来考虑。第一种情况是该日志记录的前两个字节是连在一起的;第二种情况就是前两个字节分别保存在日志缓冲区的首字节和尾字节中。
在函数get_entry_len中,第11行将日志缓冲区结构体log的总长度size减去下一条要读取的日志记录的位置off。如果得到的结果等于1,那么就说明该日志记录的长度值分别保存在日志缓冲区的首尾字节中,因此,第13行和第14行就将它的前后两个字节从日志缓冲区的尾字节和首字节中读取出来,并且将它们的内容组合在变量val中。如果得到的结果不等于1,那么就说明该日志记录的长度值保存在两个连在一起的字节中,即保存在地址log->buffer + off中,因此,第17行就直接将日志记录的长度值读取出来,并且保存在变量val中。最后,第20行将变量val的值加上结构体logger_entry的大小,就得到下一条要读取的日志记录的总长度了。
回到logger_read函数中,第64行调用函数do_read_log_to_user执行真正的日志记录读取操作。
**kernel/goldfish/drivers/staging/android/logger.c**
~~~
/*
* do_read_log_to_user - reads exactly 'count' bytes from 'log' into the
* user-space buffer 'buf'. Returns 'count' on success.
*
* Caller must hold log->mutex.
*/
static ssize_t do_read_log_to_user(struct logger_log *log,
struct logger_reader *reader,
char __user *buf,
size_t count)
{
size_t len;
/*
* We read from the log in two disjoint operations. First, we read from
* the current read head offset up to 'count' bytes or to the end of
* the log, whichever comes first.
*/
len = min(count, log->size - reader->r_off);
if (copy_to_user(buf, log->buffer + reader->r_off, len))
return -EFAULT;
/*
* Second, we read any remaining bytes, starting back at the head of
* the log.
*/
if (count != len)
if (copy_to_user(buf + len, log->buffer, count - len))
return -EFAULT;
reader->r_off = logger_offset(reader->r_off + count);
return count;
}
~~~
由于一条日志记录的内容有可能同时位于日志缓冲区的末尾和开头处,即它在地址空间上是不连续的,因此,函数do_read_log_to_user就有可能需要分两次来读取它。第19行计算第一次要读取的日志记录的长度,接着第20行就将这一部分的日志记录拷贝到用户空间缓冲区buf中。第27行检查上一次是否已经将日志记录的内容读取完毕,如果未读取完毕,即第27行的if语句为true,那么第28行就会继续将第二部分的日志记录拷贝到用户空间缓冲区buf中,这样就完成了一条日志记录的读取。
> 日志记录结构体log的成员变量buffer指向的是一个内核缓冲区。在将一个内核缓冲区的内容拷贝到一个用户空间缓冲区时,必须要调用函数copy_to_user来进行,因为用户空间缓冲区的地址有可能是无效的,而函数copy_to_user在拷贝内容之前会对它进行检查,避免访问非法地址。
当前进程从日志缓冲区结构体log中读取了一条日志记录之后,就要修改它的成员变量r_off的值,表示下次要读取的是日志缓冲区结构体log中的下一条日志记录。第31行首先将日志读取进程结构体reader的成员变量r_off的值加上前面已经读取的日志记录的长度count,然后再使用宏logger_offset来对计算结果进行调整。
**kernel/goldfish/drivers/staging/android/logger.c**
~~~
/* logger_offset - returns index 'n' into the log via (optimized) modulus */
#define logger_offset(n) ((n) & (log->size - 1))
~~~
这是由于日志缓冲区是循环使用的,如果计算得到的下一条日志记录的位置大于日志缓冲区的长度,那么就需要将它绕回到日志缓冲区的前面。
> 日志缓冲区的长度是2的N次方,因此,只要它的值减1之后,再与参数n执行按位与运算,就可以得到参数n在日志缓冲区中的正确位置。
至此,日志记录的读取过程就介绍完了。接下来,我们继续分析日志记录的写入过程。
- 文章概述
- 下载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 应用程序的显示过程