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 总览的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: go 单元测试 testing 打印输出
- 下一篇: 使用Python实现简易的数据标注工具