android log.d 参数,Android log 机制 - logd 总览
Android 早期版本使用的是一个 log 驱动,后来逐渐使用 logd 进程替代(具体哪个版本我就没有去探究了,至少在 Android 8.0 里,log 驱动已经被移除)。原有 log 驱动负责的功能,都由 logd 完成。此外,logd 还可以读取 Linux 内核 printk、selinux 的 log。
logd 的启动
logd 是由 init 进程启动的:
1
2
3
4
5
6
7
8
9
10
11
12# system/core/rootdir/init.rc
on post-fs
# Load properties from
# /system/build.prop,
# /odm/build.prop,
# /vendor/build.prop and
# /factory/factory.prop
load_system_props
# start essential services
start logd
start servicemanager
# ...
logd 的参数在 logd.rc 中:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24# system/core/logd/logd.rc
service logd /system/bin/logd
socket logd stream 0666 logd logd
socket logdr seqpacket 0666 logd logd
socket logdw dgram 0222 logd logd
file /proc/kmsg r
file /dev/kmsg w
user logd
group logd system readproc
writepid /dev/cpuset/system-background/tasks
service logd-reinit /system/bin/logd --reinit
oneshot
disabled
user logd
group logd
writepid /dev/cpuset/system-background/tasks
on fs
write /dev/event-log-tags "# content owned by logd
"
chown logd logd /dev/event-log-tags
chmod 0644 /dev/event-log-tags
restorecon /dev/event-log-tags
可以看到,init 进程会帮 logd 创建 3 个 Unix 域 socket,分别为 /dev/socket/logd, /dev/socket/logdr, /dev/socket/logdw。
创建 socket 系统调用原型如下:
1
2
3
4#include
#include
int socket(int domain, int type, int protocol);
init.rc 中的 stream, seqpacket, dgram 用于设置 socket 函数的第二个参数 type,分别对应 SOCK_STREAM, SOCK_SEQPACKET, SOCK_DGRAM。
SOCK_STREAM 提供的是可靠的流数据(类比于 TCP),SOCK_SEQPACKET 则提供可靠的基于包的数据(可靠的UDP),SOCK_DGRAM 可以用 UDP 来类比,不可靠的包传输。详细信息可以查看 man page 了解。
当然,domain 参数是 PF_UNIX(估计写着代码的程序员比较老派,新的程序建议使用 AF_LOCAL,两者没有区别)。
这三个 socket 的功能如下:
socket logd 用于外接受控制命令
客户端通过 logdr 读取 log 数据。使用 seqpacket,可以让用户在可靠地读取数据的同时,一次读取一条 log
客户端通过 logdw 写入 log 数据。由于类型是 dgram,在非常繁忙的时候,log 可能会丢失。但是,这可以避免客户端阻塞在写 log 的调用上
logd 的初始化
init 进程启动 logd 后,和其他程序一样,首先执行的是 main 函数。main 函数的主要工作如下:
读取系统属性,判断是否需要读内核的 log (klog 和 selinux 的log)
初始化一些信号量,启动 reinit 线程
启动各个子服务,监听上面我们提到的几个 socket
如果需要读内核 log,也监听对应的 log
阻塞等待(main 函数不能退出,否则进程直接会退出,即便还有线程在运行)
下面我们一起看看他的 main 函数:
1
2
3
4
5
6
7
8
9// system/core/logd/main.cpp
int main(int argc, char* argv[]){
// issue reinit command. KISS argument parsing.
if ((argc > 1) && argv[1] && !strcmp(argv[1], "--reinit")) {
return issueReinit();
}
// ...
}
在上面 init.rc 中,我们看到,正常启动 logd 是不带参数的,所以这里的 if 不会执行。当重新启动时,带 --reinit 参数。这里我们就假定是正常启动。
1
2
3
4# system/core/logd/logd.rc
service logd /system/bin/logd
service logd-reinit /system/bin/logd --reinit
接下来获取 /dev/kmsg 的描述符。这个文件用于跟内核的 log 系统通信。正常情况下,init 进程会帮我们打开。如果没有,我们自己打开它。
1
2
3
4
5
6
7
8
9
10
11
12// system/core/logd/main.cpp
int main(int argc, char* argv[]){
// ...
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));
}
// ...
}
接着,读取系统属性,判断是否需要读内核的 log。如果需要,就打开 /proc/kmsg。这个文件用于读取内核 log。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20// system/core/logd/main.cpp
int main(int argc, char* argv[]){
// ...
int fdPmesg = -1;
bool klogd = __android_logger_property_get_bool(
"logd.kernel", BOOL_DEFAULT_TRUE | BOOL_DEFAULT_FLAG_PERSIST |
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);
}
// ...
}
默认情况下,会读取内核 log。这里我们就直接假设 klogd 为 true。
跟着,启动 reinit 线程:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38// system/core/logd/main.cpp
static sem_t uidName;
static uid_t uid;
static char* name;
static sem_t reinit;
static bool reinit_running = false;
static LogBuffer* logBuf = nullptr;
static sem_t sem_name;
int main(int argc, char* argv[]){
// ...
// Reinit Thread
sem_init(&reinit, 0, 0);
sem_init(&uidName, 0, 0);
sem_init(&sem_name, 0, 1);
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);
}
// ...
}
sem_init 用于初始化 POSIX 信号量,它的原型如下:
1
2include
int sem_init(sem_t *sem, int pshared, unsigned int value);
pshared 参数控制是否在多个进程间共享。这里我们只是用于进程内部的通信,所以传入 0。
在读这段代码的时候,有一个值得注意的是,pthread 在成功的时候返回 0,失败则返回一个非 0 的错误码(这一点跟一般的系统调用不同。一般的系统调用,失败的情况下会返回 -1)。
举例来说,中间用于判断 pthread_create 是否成功的 if 语句,只有在线程创建失败的时候才会执行。
1
2
3if (pthread_create(&thread, &attr, reinit_thread_start, nullptr)) {
reinit_running = false;
}
关于 reinit_thread_start 函数,后面我们遇到了再看它的具体内容。
下面判断是否需要读 selinux 的 log。然后,调用 drop_privs 函数根据是否读取内核 log 设置一些权限。关于 drop_privs 函数,有兴趣的读者可以自行阅读源码,这部分并不会影响 logd 的逻辑。
1
2
3
4
5
6
7
8
9
10
11
12// system/core/logd/main.cpp
int main(int argc, char* argv[]){
// ...
bool auditd =
__android_logger_property_get_bool("ro.logd.auditd", BOOL_DEFAULT_TRUE);
if (drop_privs(klogd, auditd) != 0) {
return -1;
}
// ...
}
下面,实例化 LogBuffer 并注册信号处理器。所有的 log 都会写入这个 LogBuffer;当客户端需要读取 log 的时候,也从这个 LogBuffer 读取。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26// system/core/logd/main.cpp
int main(int argc, char* argv[]){
// ...
// Serves the purpose of managing the last logs times read on a
// socket connection, and as a reader lock on a range of log
// entries.
LastLogTimes* times = new LastLogTimes();
// LogBuffer is the object which is responsible for holding all
// log entries.
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();
}
// ...
}
现在,各种准备工作都完成了,启动实际的工作线程:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62// system/core/logd/main.cpp
int main(int argc, char* argv[]){
// ...
// LogReader listens on /dev/socket/logdr. When a client
// connects, log entries in the LogBuffer are written to the client.
LogReader* reader = new LogReader(logBuf);
if (reader->startListener()) {
exit(1);
}
// LogListener listens on /dev/socket/logdw for client
// initiated log messages. New log entries are added to LogBuffer
// and LogReader is notified to send updates to connected clients.
LogListener* swl = new LogListener(logBuf, reader);
// Backlog and /proc/sys/net/unix/max_dgram_qlen set to large value
if (swl->startListener(600)) {
exit(1);
}
// Command listener listens on /dev/socket/logd for incoming logd
// administrative commands.
CommandListener* cl = new CommandListener(logBuf, reader, swl);
if (cl->startListener()) {
exit(1);
}
// LogAudit listens on NETLINK_AUDIT socket for selinux
// initiated log messages. New log entries are added to LogBuffer
// and LogReader is notified to send updates to connected clients.
LogAudit* al = nullptr;
if (auditd) {
al = new LogAudit(logBuf, reader,
__android_logger_property_get_bool(
"ro.logd.auditd.dmesg", BOOL_DEFAULT_TRUE)
? fdDmesg
: -1);
}
LogKlog* kl = nullptr;
if (klogd) {
kl = new LogKlog(logBuf, reader, fdDmesg, fdPmesg, al != nullptr);
}
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;
}
// ...
}
在 logd 进程启动的时候,内核很可能已经有 log 数据存在,readDmesg() 把已有的 log 读出来放到 logBuffer 中。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42// system/core/logd/main.cpp
static void readDmesg(LogAudit* al, LogKlog* kl){
if (!al && !kl) {
return;
}
int rc = klogctl(KLOG_SIZE_BUFFER, nullptr, 0);
if (rc <= 0) {
return;
}
// Margin for additional input race or trailing nul
ssize_t len = rc + 1024;
std::unique_ptr buf(new char[len]);
rc = klogctl(KLOG_READ_ALL, buf.get(), len);
if (rc <= 0) {
return;
}
if (rc < len) {
len = rc + 1;
}
buf[--len] = '\0';
if (kl && kl->isMonotonic()) {
kl->synchronize(buf.get(), len);
}
ssize_t sublen;
for (char *ptr = nullptr, *tok = buf.get();
(rc >= 0) && !!(tok = android::log_strntok_r(tok, len, ptr, sublen));
tok = nullptr) {
if ((sublen <= 0) || !*tok) continue;
if (al) {
rc = al->log(tok, sublen);
}
if (kl) {
rc = kl->log(tok, sublen);
}
}
}
klogctl 是 Linux 内核特有的系统调用,用于读取、设置内核 log,原型如下,详情可以查看 man page:
1
2#include
int klogctl(int type, char *bufp, int len);
第一个 klogctl 的 type 为 KLOG_SIZE_BUFFER,该调用返回内核 log 缓冲的总长度。
值得注意的是,查看 man page 时,man page 里对应的常量为 KLOG_ACTION_**。这些常量跟 KLOG_** 是一一对应的:
1
2
3
4
5
6
7
8
9
10
11
12
13
14// platform/bionic/libc/include/sys/klog.h
/* These correspond to the kernel's SYSLOG_ACTION_whatever constants. */
#define KLOG_CLOSE 0
#define KLOG_OPEN 1
#define KLOG_READ 2
#define KLOG_READ_ALL 3
#define KLOG_READ_CLEAR 4
#define KLOG_CLEAR 5
#define KLOG_CONSOLE_OFF 6
#define KLOG_CONSOLE_ON 7
#define KLOG_CONSOLE_LEVEL 8
#define KLOG_SIZE_UNREAD 9
#define KLOG_SIZE_BUFFER 10
第二个 klogctl 使用 KLOG_READ_ALL 读取所有的 log。随后,使用 LogAudit, LogKlog 讲读取到的 log 写入 LogBuffer。
最后,main 函数在 pause() 上永久等待。这是因为,如果 main 函数退出,进程就会退出(即使没有最后那个 exit(0),main 函数返回也会导致进程退出)。
1
2
3
4
5
6
7
8
9
10// system/core/logd/main.cpp
int main(int argc, char* argv[]){
// ...
TEMP_FAILURE_RETRY(pause());
exit(0);
// ...
}
logd 的启动到这里就结束了,实际的 log 读写逻辑,后面再一一分析。
android log.d 参数,Android log 机制 - logd 总览相关推荐
- android app传递参数,Android APP调起另外一个APP并传值
概述 为了增加用户体验,可能要求在一个APP中打开另外一个APP的需求,一般分为三种: 显式调用跳转 隐式调用跳转 URL Scheme跳转 代码 用到的一些公共方法,当打开APP时,检测到第三方AP ...
- android registerreceiver传参数,Android应用程序注册广播接收器(registerReceiver)的过程分析...
前面我们介绍了Android系统的广播机制,从本质来说,它是一种消息订阅/发布机制,因此,使用这种消息驱动模型的第一步便是订阅消息:而对Android应用程序来说,订阅消息其实就是注册广播接收器,本文 ...
- android 序列化传参数,android 传值 序列化
Activity之间传数据时,为了避免麻烦,往往会将一些值封装成对象,然后将整个对象传递过去.传对象的时候有两种情况,一种是实现Parcelable接口,一种是实现Serializable接口. 0. ...
- android 获取电流参数,Android编程实现添加低电流提醒功能的方法
本文实例讲述了Android编程实现添加低电流提醒功能的方法.分享给大家供大家参考,具体如下: 特殊需求,检测电流是否正常. 监听如下广播: Intent.ACTION_BATTERY_CHANGED ...
- android volley post 参数,android – 使用Volley POST传递参数
我能够使用Postman和这些参数调用HTTP端点: { "name":"Val", "subject":"Test" ...
- android 序列化传参数,Android序列化之Parcelable和Serializable的使用详解
序列化与反序列 首先来了解一下序列化与反序列化. 序列化 由于存在于内存中的对象都是暂时的,无法长期驻存,为了把对象的状态保持下来,这时需要把对象写入到磁盘或者其他介质中,这个过程就叫做序列化. 反序 ...
- android广播传递参数,Android调试助手(AndroidDebugger)
测试:xxx工,你的程序又出bug了!界面切换几下就不行了. 开发:没道理啊,我都测过好几遍了,应该不会 测试:我这复现好几次了. 开发:拉个Log来分析下啦. 测试:拉取日志命令输入中....... ...
- android 画笔设置参数,Android画笔Paint和FontMetrics
有关自定义控件的许多内容,一直都想抽时间梳理一下,当然了梳理的内容不会很深,都是一些比较常用的类或者方法,主要是为了后续用到时方便查阅与参考. 本文主要整理的是Paint的一些基本使用方法和技巧,其中 ...
- android volley 请求参数,android – Volley – 如何发送DELETE请求参数?
同样的问题在这里,但我找到了解决方案 问题是在com.android.volley.toolbox.HttpClientStack.java中实现createHttpRequest方法,只有在请求方法 ...
最新文章
- 开源中国git关联xcode操作步骤
- python爬虫——从此不用再愁找不到小说txt文件
- 大连理工计算机专业等级,大连理工计算机专业全国排第几呀
- Cookie和Session-学习笔记02【Cookie案例、JSP改造Cookie案例】
- google浏览器打开关闭标签
- Scala基础 - 函数和方法的区别
- #ifndef #define #endif用法理解
- EXCEL 代码大全
- 三星android se,安卓小钢炮小米8 SE初体验!华为三星靠边站
- 【前端】html页面的字体代码表及字体效果对比
- css3优惠卷上方锯齿_CSS不规则卡片,纯CSS制作优惠券样式,CSS实现锯齿样式
- Spark - Isotonic Regression 理论与实战
- 基于CUDA的GPU并行计算技术实现网课课表编排
- 百度携手华为麒麟深度合作,李彦宏现场被泼水称AI发展总有曲折 | 百度开发者大会
- Linux关闭/禁用触摸屏,deepin关闭/禁用触摸屏方法
- 图像之超简单方式实现微信头像功能
- Qt 之 QDateEdit 和 QTimeEdit
- Opencat-B——手机APP蓝牙遥控
- miniui实现多附件上传
- 2021-05-17 沟通类书籍阅读进度感悟分享