技术标签: logd logcat Android取经之路 # 6.系统服务 Android10.0 日志系统 liblog
摘要:本节主要来讲解Android10.0 日志系统的架构分析,以及logd、logcat的初始化操作
阅读本文大约需要花费15分钟。
文章首发微信公众号:IngresGe
专注于Android系统级源码分析,Android的平台设计,欢迎关注我,谢谢!
欢迎关注我的公众号!
[Android取经之路] 的源码都基于Android-Q(10.0) 进行分析
[Android取经之路]系列文章:
《系统启动篇》
《日志系统篇》
《Binder通信原理》
《HwBinder通信原理》
《编译原理》
上一节我们看了Logd、logcat的指令说明,这一节我们来看看Android的日志系统架构,以及logd\logcat的初始化操作
在Android5.0(Android-L)之前,log由kernel的环形 buffer 保存,在Android5.0 之后,log保存在用户空间,通过Socket进行访问。
在Android5.0之后,引入了Logd的守护进程用来进行日志的读写操作。
不管是应用层,还是Native层,读写日志都是通过liblog提供的接口,访问logd的两个socket buffer:logdr、logdw来实现读写。
图片来自于CSDN-私房菜:
在应用层可以通过android.util.Log,android.util.SLog,android.util.EventLog接口,把日志写入到main,system,event的不同缓冲区中去。
在JAVA中想调用日志,就需要import下面的内容:
import android.util.Log;
import android.util.SLog;
import android.util.EventLog;
应用层写日志方法如下:
在Native C/C++中,进程通过加载liblog.so,调用ALOGD()、ALOGI()来进行日志的写入,最终也是通过logd写入到logdw的socket中。
如果在Native中想要调用liblog的内容,需要在Android.mk 或者Android.bp中加入liblog,并引入头文件:#include <android/log.h>
Native 层写日志方法如下:
Android中主要通过logcat进程来读取日志,logcat属于native-C的进程,通过加载liblog,从而调用logd的read接口读取 logdr socket的日志内容。
Android系统日志主要有三个部分需要关注:
logd守护进程:日志系统的大管家,管理三个日志的socket:logd、logdr、logdw。
logcat进程:日志读取工具。
liblog:提供日志读写、过滤等接口,供logcat、JAVA、Native等程序使用
在Android 系统启动后,init进程加载,会解析logd.rc启动logd service如下:
service logd /system/bin/logd
socket logd stream 0666 logd logd
socket logdr seqpacket 0666 logd logd
socket logdw dgram+passcred 0222 logd logd
file /proc/kmsg r
file /dev/kmsg w
user logd
group logd system package_info readproc
capabilities SYSLOG AUDIT_CONTROL
priority 10
writepid /dev/cpuset/system-background/tasks
从上面的service可以看出,启动了一个守护进程为logd,存放在手机的/system/bin中,同时创建并启动三个socket:
logd 接收logcat 传递的指令然后处理 ,比如logcat -g, logcat -wrap等
logdr logcat从此buffer中读取buffer
logdw 日志写入的buffer
logd初始化调用栈如下:
logd的初始化流程:
打开/dev/kmsg 来读取内核日志,通过LogKlog来进行存储
如果属性"ro.logd.kernel" 配置了,打开/proc/kmsg来读取内核日志
设置运行时优先级、权限
启动 Reinit线程,当logd-reinit传入参数reinit时,进行调用,reinit开机只启动一次
启动各个 log 监听器:LogBuffer、LogReader、LogListener、CommandListener、LogAudit和LogKlog
源码:
int main(int argc, char* argv[]) {
//logd是在假设时区是UTC的情况下编写的。
//如果未设置TZ,则在某些时间实用程序libc函数(包括mktime)中查找persist.sys.timezone。
//它混淆了logd时间处理,因此这里显式地将TZ设置为UTC,这将重写属性。
setenv("TZ", "UTC", 1);
// issue reinit command. KISS argument parsing.
if ((argc > 1) && argv[1] && !strcmp(argv[1], "--reinit")) {
return issueReinit();
}
//1.打开/dev/kmsg 来读取内核日志,通过LogKlog来进行存储
static const char dev_kmsg[] = "/dev/kmsg";
fdDmesg = android_get_control_file(dev_kmsg);
if (fdDmesg < 0) {
fdDmesg = TEMP_FAILURE_RETRY(open(dev_kmsg, O_WRONLY | O_CLOEXEC));
}
//2.如果属性"ro.logd.kernel" 配置了,打开/proc/kmsg来读取内核日志
int fdPmesg = -1;
bool klogd = __android_logger_property_get_bool(
"ro.logd.kernel",
BOOL_DEFAULT_TRUE | BOOL_DEFAULT_FLAG_ENG | BOOL_DEFAULT_FLAG_SVELTE);
if (klogd) {
static const char proc_kmsg[] = "/proc/kmsg";
fdPmesg = android_get_control_file(proc_kmsg);
if (fdPmesg < 0) {
fdPmesg = TEMP_FAILURE_RETRY(
open(proc_kmsg, O_RDONLY | O_NDELAY | O_CLOEXEC));
}
if (fdPmesg < 0) android::prdebug("Failed to open %s\n", proc_kmsg);
}
//3.设置运行时优先级、权限
bool auditd = __android_logger_property_get_bool("ro.logd.auditd", BOOL_DEFAULT_TRUE);
if (drop_privs(klogd, auditd) != 0) {
return EXIT_FAILURE;
}
//4.启动 Reinit线程,当logd-reinit传入参数reinit时,进行调用,reinit开机只启动一次
sem_init(&reinit, 0, 0);
pthread_attr_t attr;
if (!pthread_attr_init(&attr)) {
struct sched_param param;
memset(¶m, 0, sizeof(param));
pthread_attr_setschedparam(&attr, ¶m);
pthread_attr_setschedpolicy(&attr, SCHED_BATCH);
if (!pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED)) {
pthread_t thread;
reinit_running = true;
if (pthread_create(&thread, &attr, reinit_thread_start, nullptr)) {
reinit_running = false;
}
}
pthread_attr_destroy(&attr);
}
//用于管理在SOCKET连接上读取的最后日志时间,以及作为一个对一系列日志项的读卡器锁。
LastLogTimes* times = new LastLogTimes();
//5.启动各个 log 监听器
//5.1先创建一个LogBuffer的对象,LogBuffer是负责保存所有日志项的对象
logBuf = new LogBuffer(times);
signal(SIGHUP, reinit_signal_handler);
if (__android_logger_property_get_bool(
"logd.statistics", BOOL_DEFAULT_TRUE | BOOL_DEFAULT_FLAG_PERSIST |
BOOL_DEFAULT_FLAG_ENG |
BOOL_DEFAULT_FLAG_SVELTE)) {
logBuf->enableStatistics();
}
//5.2 LogReader监听/dev/socket/logdr,当客户端连接时,比如logcat,日志缓冲区中的日志条目将写入客户端。
LogReader* reader = new LogReader(logBuf);
if (reader->startListener()) {
return EXIT_FAILURE;
}
//5.3 LogListener在/dev/socket/logdw 上监听客户端启动的日志消息,监听是否有日志写入
LogListener* swl = new LogListener(logBuf, reader);
// Backlog and /proc/sys/net/unix/max_dgram_qlen set to large value
if (swl->startListener(600)) {
return EXIT_FAILURE;
}
//5.4 CommandListener 在/dev/socket/logd上 监听传入的logd 的command,即监听是否有命令发送给logd
CommandListener* cl = new CommandListener(logBuf, reader, swl);
if (cl->startListener()) {
return EXIT_FAILURE;
}
//5.5 如果配置了属性"ro.logd.auditd",则启动LogAudit,LogAudit 在NETLINK_AUDIT的socket上侦听selinux启动的日志消息
LogAudit* al = nullptr;
if (auditd) {
al = new LogAudit(logBuf, reader,
__android_logger_property_get_bool(
"ro.logd.auditd.dmesg", BOOL_DEFAULT_TRUE)
? fdDmesg
: -1);
}
//5.6如果配置了属性"ro.logd.kernel",则启动LogKlog,用来存储内核日志
LogKlog* kl = nullptr;
if (klogd) {
kl = new LogKlog(logBuf, reader, fdDmesg, fdPmesg, al != nullptr);
}
//5.7通过 LogAudit和 LogKlog来分别读取selinux和kernel的日志
readDmesg(al, kl);
// failure is an option ... messages are in dmesg (required by standard)
if (kl && kl->startListener()) {
delete kl;
}
if (al && al->startListener()) {
delete al;
}
TEMP_FAILURE_RETRY(pause());
return EXIT_SUCCESS;
}
在system/core/lodgd/main.cpp文件的main函数中,默认创建了LogBuffer、LogReader、LogListener和CommandListener四个对象:
LogBuffer:LogBuffer是负责保存所有日志项的对象
LogReader:LogReader监听/dev/socket/logdr,当客户端连接时,比如logcat,日志缓冲区中的日志条目将写入客户端。
LogListener:LogListener在/dev/socket/logdw 上监听客户端启动的日志消息,监听是否有日志写入
CommandListener:CommandListener 在/dev/socket/logd上 监听传入的logd 的command,即监听是否有命令发送给logd
另外,还有两个对象-LogAudit 和LogKlog,受属性控制:
LogAudit:受属性"ro.logd.auditd"控制,在NETLINK_AUDIT的socket上侦听selinux启动的日志消息,新的日志条目将添加到LogBuffer中,并通知LogReader向连接的客户端发送更新
LogKlog:受属性"ro.logd.kernel"控制,用来存储内核日志,内核日志通过"/dev/kmsg", "/proc/kmsg" 获得
logd.rc中启动logd-reinit 如下:
service logd-reinit /system/bin/logd --reinit
oneshot
disabled
user logd
group logd
writepid /dev/cpuset/system-background/tasks
启动logd-reinit的服务,主要工作是重新初始化logd的LogBuffer,在上面的启动脚本中,配置为oneshot,即开机只执行一次。
通过上面logd的初始化,可以看到,logd启动后,创建了一个线程reinit_thread_start(),当logd-reinit 传入参数 reinit后,进行功能执行。
logd-reinit两个步骤:
如果reinit启动后,并且/deg/kmsg打开成功,把 logd.daemon: renit写入kmsg
重新初始化各个log buffer的大小,以及其他参数的初始化,但不会重新生成LogBuffer对象
源码:
static void* reinit_thread_start(void* /*obj*/) {
prctl(PR_SET_NAME, "logd.daemon");
while (reinit_running && !sem_wait(&reinit) && reinit_running) {
if (fdDmesg >= 0) {
static const char reinit_message[] = { KMSG_PRIORITY(LOG_INFO),
'l',
'o',
'g',
'd',
'.',
'd',
'a',
'e',
'm',
'o',
'n',
':',
' ',
'r',
'e',
'i',
'n',
'i',
't',
'\n' };
write(fdDmesg, reinit_message, sizeof(reinit_message));
}
// Anything that reads persist.<property>
//重新初始化各个log buffer的大小,以及其他参数的初始化,但不会重新生成LogBuffer对象
if (logBuf) {
logBuf->init();
logBuf->initPrune(nullptr);
}
android::ReReadEventLogTags();
}
return nullptr;
}
logd.rc中启动logd-auditctl
# Limit SELinux denial generation to 5/second
service logd-auditctl /system/bin/auditctl -r 5
oneshot
disabled
user logd
group logd
capabilities AUDIT_CONTROL
logd-auditctl 的主体是 /system/bin/auditctl,在logd的android.bp中,通过编译 auditctl.cpp得来,并加载了liblogd的 库。
logd-auditctl是Android 10.0中引入的新功能,目的是让selinux denia的日志打印限制为5秒一次。
Android.bp 中auditctl展示如下:
cc_binary {
name: "auditctl",
srcs: ["auditctl.cpp"],
static_libs: [
"liblogd",
],
shared_libs: ["libbase"],
cflags: [
"-Wall",
"-Wextra",
"-Werror",
"-Wconversion"
],
}
logd-auditctl 初始化调用栈如下:
logd-auditctl的主要作用是 让selinux denia的日志打印限制为5秒一次
说明:在logd.rc中配置了logd-auditctl,传入参数为-r5,即限制selinux日志写入频率更新为5秒
源码:
[auditctl.cpp] main()
int main(int argc, char* argv[]) {
uint32_t rate = 0;
bool update_rate = false;
int opt;
//如果logd-auditctl传入了-r的参数,获取参数的值
//即这里的rate为5,并标记update_rate 为启动
while ((opt = getopt(argc, argv, "r:")) != -1) {
switch (opt) {
case 'r':
if (!android::base::ParseUint<uint32_t>(optarg, &rate)) {
error(EXIT_FAILURE, errno, "Invalid Rate");
}
update_rate = true;
break;
default: /* '?' */
usage(argv[0]);
exit(EXIT_FAILURE);
}
}
// In the future, we may add other options to auditctl
// so this if statement will expand.
// if (!update_rate && !update_backlog && !update_whatever) ...
if (!update_rate) {
fprintf(stderr, "Nothing to do\n");
usage(argv[0]);
exit(EXIT_FAILURE);
}
//如果传入了-r参数,更新rate
if (update_rate) {
do_update_rate(rate);
}
return 0;
}
说明:创建一个netlink的socket,协议号为NETLINK_AUDIT,并通过audit_rate_limit发送selinux频率
源码:
[auditctl.cpp] do_update_rate()
static void do_update_rate(uint32_t rate) {
//创建socket PF_NETLINK
int fd = audit_open();
if (fd == -1) {
error(EXIT_FAILURE, errno, "Unable to open audit socket");
}
int result = audit_rate_limit(fd, rate);
close(fd);
if (result < 0) {
fprintf(stderr, "Can't update audit rate limit: %d\n", result);
exit(EXIT_FAILURE);
}
}
说明:组装结构体audit_status,传入频率为5秒,最终通过sendto()发送message到内核,由用户态切入到内核态
源码:
[libaudit.c] audit_rate_limit()
int audit_rate_limit(int fd, uint32_t limit) {
struct audit_status status;
memset(&status, 0, sizeof(status));
status.mask = AUDIT_STATUS_RATE_LIMIT;
status.rate_limit = limit; /* audit entries per second */
return audit_send(fd, AUDIT_SET, &status, sizeof(status));
}
logcat编译时,会编译两个进程/system/bin/logcat 和/system/bin/logcatd。
和logd一样,logcat进程启动,是init进程解析了logcatd.rc来进行加载。
logcatd.rc 如下所示:
service logcatd /system/bin/logcatd -L -b ${logd.logpersistd.buffer:-all} -v threadtime -v usec -v printable -D -f /data/misc/logd/logcat -r ${logd.logpersistd.rotate_kbytes:-1024} -n ${logd.logpersistd.size:-256} --id=${ro.build.id}
class late_start
disabled
# logd for write to /data/misc/logd, log group for read from log daemon
user logd
group log
writepid /dev/cpuset/system-background/tasks
oom_score_adjust -600
从上面的service可以看出,启动了一个守护进程为logcatd,存放在手机的/system/bin中。
启动logcatd时,传入了-b\-v\-f等参数。
说明:logcat启动后,先创建一个context,设置信号量,再启动一个while死循环,用来接收logcat的command
源码:
int main(int argc, char** argv, char** envp) {
android_logcat_context ctx = create_android_logcat();
if (!ctx) return -1;
signal(SIGPIPE, exit);
int retval = android_logcat_run_command(ctx, -1, -1, argc, argv, envp);
int ret = android_logcat_destroy(&ctx);
if (!ret) ret = retval;
return ret;
}
说明:android_logcat_run_command()用来解析logcat传入的command,最终通过函数__logcat()中启动一个while死循环,来执行logcat传入的各种命令。
源码:
int android_logcat_run_command(android_logcat_context ctx,
int output, int error,
int argc, char* const* argv,
char* const* envp) {
android_logcat_context_internal* context = ctx;
context->output_fd = output;
context->error_fd = error;
context->argc = argc;
context->argv = argv;
context->envp = envp;
context->stop = false;
context->thread_stopped = false;
return __logcat(context);
}
Android 日志系统架构及初始化讲完了,下一节我们来分析logd、logcat读写日志的源码分析。
微信公众号:IngresGe
文章浏览阅读3.6k次,点赞2次,收藏2次。DELL7080台式机两块硬盘。_没有u盘怎么装ubuntu
文章浏览阅读32次。题面Bessie wants to navigate her spaceship through a dangerous asteroid field in the shape of an N x N grid (1 <= N <= 500). The grid contains K asteroids (1 <= K <= 10,000), which are conv...
文章浏览阅读2.6w次,点赞21次,收藏112次。机器视觉则主要是指工业领域视觉的应用研究,例如自主机器人的视觉,用于检测和测量的视觉系统等。它通过在工业领域将图像感知、图像处理、控制理论与软件、硬件紧密结合,并研究解决图像处理和计算机视觉理论在实际应用过程中的问题,以实现高效的运动控制或各种实时操作。_工业机器视觉系统的构成与开发过程(理论篇—1
文章浏览阅读5.9w次,点赞32次,收藏58次。legend 传奇、图例。plt.legend()的作用:在plt.plot() 定义后plt.legend() 会显示该 label 的内容,否则会报error: No handles with labels found to put in legend.plt.plot(result_price, color = 'red', label = 'Training Loss') legend作用位置:下图红圈处。..._plt.legend
文章浏览阅读2.2k次,点赞3次,收藏11次。深入理解 C# .NET Core 中 async await 异步编程思想引言一、什么是异步?1.1 简单实例(WatchTV并行CookCoffee)二、深入理解(异步)2.1 当我需要异步返回值时,怎么处理?2.2 充分利用异步并行的高效性async await的秘密引言很久没来CSDN了,快小半年了一直在闲置,也写不出一些带有思想和深度的文章;之前就写过一篇关于async await 的异步理解 ,现在回顾,真的不要太浅和太陋,让人不忍直视!好了,废话不再啰嗦,直入主题:一、什么是异步?_netcore async await
文章浏览阅读6.5w次,点赞166次,收藏309次。当我看到别人的类上面的多行注释是是这样的:这样的:这样的:好装X啊!我也想要!怎么办呢?往下瞅:跟着我左手右手一个慢动作~~~File--->Settings---->Editor---->File and Code Templates --->Includes--->File Header:之后点applay--..._idea作者和日期等注释
文章浏览阅读175次。Netperf是一种网络性能的测量工具,主要针对基于TCP或UDP的传输。Netperf根据应用的不同,可以进行不同模式的网络性能测试,即批量数据传输(bulk data transfer)模式和请求/应答(request/reponse)模式。工作原理Netperf工具以client/server方式工作。server端是netserver,用来侦听来自client端的连接,c..._netperf 麒麟
文章浏览阅读1.1k次,点赞2次,收藏3次。作者| qcrao责编 | 屠敏出品 | 程序员宅基地刚开始写这篇文章的时候,目标非常大,想要探索 Go 程序的一生:编码、编译、汇编、链接、运行、退出。它的每一步具体如何进行,力图弄清 Go 程序的这一生。在这个过程中,我又复习了一遍《程序员的自我修养》。这是一本讲编译、链接的书,非常详细,值得一看!数年前,我第一次看到这本书的书名,就非常喜欢。因为它模仿了周星驰喜剧..._go run 每次都要编译吗
文章浏览阅读1.4k次,点赞4次,收藏2次。0、C++的输入输出分为三种:(1)基于控制台的I/O (2)基于文件的I/O (3)基于字符串的I/O 1、头文件[cpp] view plaincopyprint?#include 2、作用istringstream类用于执行C++风格的字符串流的输入操作。 ostringstream类用_c++ istringstream a >> string
文章浏览阅读2k次,点赞3次,收藏14次。我们在每个修改的地方都记录一条对应的 redo 日志显然是不现实的,因此实现方式是用时间换空间,我们在数据库崩了之后用日志还原数据时,在执行这条日志之前,数据库应该是一个一致性状态,我们用对应的参数,执行固定的步骤,修改对应的数据。1,MySQL 就是通过 undolog 回滚日志来保证事务原子性的,在异常发生时,对已经执行的操作进行回滚,回滚日志会先于数据持久化到磁盘上(因为它记录的数据比较少,所以持久化的速度快),当用户再次启动数据库的时候,数据库能够通过查询回滚日志来回滚将之前未完成的事务。_binglog
文章浏览阅读3k次。概述之前介绍过 移动Web开发基础-flex弹性布局(兼容写法) 里面有提到过想做一个Chrome插件,来生成flexbox布局的css代码直接拷贝出来用。最近把这个想法实现了,给大家分享下。play-flexbox插件介绍play-flexbox一秒搞定flexbox布局,可直接预览效果,拷贝CSS代码快速用于页面重构。 你也可以通过点击以下链接(codepen示例)查_chrome css布局插件
文章浏览阅读308次。我自己的配置是GeForce GTX 1660 +CUDA10.0+CUDNN7.6.0 + TensorFlow-GPU 1.14.0Win10系统安装tensorflow-gpu(按照步骤一次成功)https://blog.csdn.net/zqxdsy/article/details/103152190环境配置——win10下TensorFlow-GPU安装(GTX1660 SUPER+CUDA10+CUDNN7.4)https://blog.csdn.net/jiDxiaohuo/arti