Android中Log机制
写Log过程
首先从Java层入手
下面是定义的Log级别:
public static final int VERBOSE = 2;
public static final int DEBUG = 3;
public static final int INFO = 4;
public static final int WARN = 5;
public static final int ERROR = 6;
public static final int ASSERT = 7;
对应不同的Log缓冲区
public static final int LOG_ID_MAIN = 0;
public static final int LOG_ID_RADIO = 1;
public static final int LOG_ID_EVENTS = 2;
public static final int LOG_ID_SYSTEM = 3;
public static final int LOG_ID_CRASH = 4;
public static final int LOG_ID_WSEVENTS = 5;
我们着重看一个方法,顺藤摸瓜摸下去:
public static int i(String tag, String msg) {
return println_native(LOG_ID_MAIN, INFO, tag, msg);
}
println_native是调用jni层的方法
对应到jni方法
static jint android_util_Log_println_native(JNIEnv* env, jobject clazz,
jint bufID, jint priority, jstring tagObj, jstring msgObj)
{
const char* tag = NULL;
const char* msg = NULL;
...
//bufID代表不同缓冲区
//priority代表自己定义的log级别
//tag代表自己设定的标记
//msg代表自己要打印的信息
int res = __android_log_buf_write(bufID, (android_LogPriority)priority, tag, msg);
...
return res;
}
继续顺着__android_log_buf_write()追寻下去
int __android_log_buf_write(int bufID, int prio, const char *tag, const char *msg)
{
struct iovec vec[3];
char tmp_tag[32];
if (!tag)
tag = "";
if ((bufID != LOG_ID_RADIO) &&(!strcmp(tag, "HTC_RIL") ||!strncmp(tag, "RIL", 3) ||
!strncmp(tag, "IMS", 3) ||!strcmp(tag, "AT") ||!strcmp(tag, "GSM") ||!strcmp(tag, "STK") ||
!strcmp(tag, "CDMA") ||!strcmp(tag, "PHONE") ||!strcmp(tag, "SMS"))) {
bufID = LOG_ID_RADIO;
snprintf(tmp_tag, sizeof(tmp_tag), "use-Rlog/RLOG-%s", tag);
tag = tmp_tag;
}
#if __BIONIC__
if (prio == ANDROID_LOG_FATAL) {
android_set_abort_message(msg);
}
#endif
vec[0].iov_base = (unsigned char *) &prio;
vec[0].iov_len = 1;
vec[1].iov_base = (void *) tag;
vec[1].iov_len = strlen(tag) + 1;
vec[2].iov_base = (void *) msg;
vec[2].iov_len = strlen(msg) + 1;
return write_to_log(bufID, vec, 3);
}
struct iovec {
void* iov_base;
size_t iov_len;
};
这里面把信息复制到了iovec结构体里面,然后继续执行write_to_log()方法
在Logd_write.c中定义的核心数组:
static const char *LOG_NAME[LOG_ID_MAX] = {
[LOG_ID_MAIN] = “main”,
[LOG_ID_RADIO] = “radio”,
[LOG_ID_EVENTS] = “events”,
[LOG_ID_SYSTEM] = “system”,
[LOG_ID_CRASH] = “crash”,
[LOG_ID_KERNEL] = “kernel”,
};
static int (*write_to_log)(log_id_t, struct iovec *vec, size_t nr) = __write_to_log_init;
可见write_to_log是一个函数指针,并且指向_write_to_log_init入口,我么现在继续看看_write_to_log_init函数
static int __write_to_log_init(log_id_t log_id, struct iovec *vec, size_t nr)
{
#if !defined(_WIN32)
pthread_mutex_lock(&log_init_lock);
#endif
if (write_to_log == __write_to_log_init) {
int ret;
ret = __write_to_log_initialize();//第一次会先进入这个方法
if (ret < 0) {
#if !defined(_WIN32)
pthread_mutex_unlock(&log_init_lock);
#endif
#if (FAKE_LOG_DEVICE == 0)
if (pstore_fd >= 0) {
__write_to_log_daemon(log_id, vec, nr);
}
#endif
return ret;
}
write_to_log = __write_to_log_daemon;//以后会进入这个方法
}
#if !defined(_WIN32)
pthread_mutex_unlock(&log_init_lock);
#endif
return write_to_log(log_id, vec, nr);
}
既然第一次进入__write_to_log_initialize()方法,那么我们来看看这个方法干了什么事情
static int __write_to_log_initialize()
{
int i, ret = 0;
#if FAKE_LOG_DEVICE
for (i = 0; i < LOG_ID_MAX; i++) {
char buf[sizeof("/dev/log_system")];
snprintf(buf, sizeof(buf), "/dev/log_%s", android_log_id_to_name(i));
log_fds[i] = fakeLogOpen(buf, O_WRONLY);
}
#else
if (pstore_fd < 0) {
pstore_fd = TEMP_FAILURE_RETRY(open("/dev/pmsg0", O_WRONLY));
}
if (logd_fd < 0) {
i = TEMP_FAILURE_RETRY(socket(PF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC, 0));
if (i < 0) {
ret = -errno;
} else if (TEMP_FAILURE_RETRY(fcntl(i, F_SETFL, O_NONBLOCK)) < 0) {
ret = -errno;
close(i);
} else {
struct sockaddr_un un;
memset(&un, 0, sizeof(struct sockaddr_un));
un.sun_family = AF_UNIX;
strcpy(un.sun_path, "/dev/socket/logdw");
if (TEMP_FAILURE_RETRY(connect(i, (struct sockaddr *)&un,
sizeof(struct sockaddr_un))) < 0) {
ret = -errno;
close(i);
} else {
logd_fd = i;
}
}
}
#endif
return ret;
}
我们看到了:
- socket(PF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC, 0)创建socket
- strcpy(un.sun_path, “/dev/socket/logdw”);设备节点路径/dev/socket/logdw
- connect(i, (struct sockaddr *)&un, sizeof(struct sockaddr_un))链接socket,
connect链接成功则返回0,所以logd_fd = i;logd_fd记录了链接句柄
既然第一次进入函数已经分析,下面也来说下第二次进入的函数__write_to_log_daemon
static int __write_to_log_daemon(log_id_t log_id, struct iovec *vec, size_t nr)
{
...
if (logd_fd > 0) {
int32_t snapshot = atomic_exchange_explicit(&dropped, 0, memory_order_relaxed);
if (snapshot) {
...
/*struct timespec {
time_t tv_sec;//long
long tv_nsec;
};
header.tid = gettid();//当前线程id
header.realtime.tv_sec = ts.tv_sec;
header.realtime.tv_nsec = ts.tv_nsec;*/
//logd_fd是传递进来的句柄文件
newVec[1].iov_base = (unsigned char *) &header;
newVec[1].iov_len = sizeof(header);
ret = TEMP_FAILURE_RETRY(writev(logd_fd, newVec + 1, 2));
...
}
}
...
return ret;
}
此时注意传递进去的参数有:
bufID=LOG_ID_MAIN=0
vec为结构体:
{
vec[0].iov_base = (unsigned char *) &prio;
vec[0].iov_len = 1;
vec[1].iov_base = (void *) tag;
vec[1].iov_len = strlen(tag) + 1;
vec[2].iov_base = (void *) msg;
vec[2].iov_len = strlen(msg) + 1;
}
nr=3
其中调用了writev函数,继续追寻writev函数
int writev( int fd, const struct iovec* vecs, int count )
{
int total = 0;
for ( ; count > 0; count--, vecs++ ) {
const char* buf = vecs->iov_base;
int len = vecs->iov_len;
while (len > 0) {
int ret = write( fd, buf, len );
if (ret < 0) {
if (total == 0)
total = -1;
goto Exit;
}
if (ret == 0)
goto Exit;
total += ret;
buf += ret;
len -= ret;
}
}
Exit:
return total;
}
上面传递的值有:
传递进来的值有:
logd_fd:socket句柄信息
struct timespec {
time_t tv_sec;//long
long tv_nsec;
};
header.tid = gettid();//当前线程id
header.realtime.tv_sec = ts.tv_sec;
header.realtime.tv_nsec = ts.tv_nsec;
newVec[1].iov_base = (unsigned char *) &header;
newVec[1].iov_len = sizeof(header);
vecs=newVec[1];
count = 2;
可以看出里面调用了write函数,这个函数进行写入操作,这下我们就将我们的log写入到log缓冲区了.
由于代码中调用writev多次,多次的结果是将时间信息,还有消息信息
但是总结来说都是传递newVec数组,所以只要看下newVec数组都有那些信息就好
pmsg_header.magic = LOGGER_MAGIC;//#define LOGGER_MAGIC 'l'
pmsg_header.len = sizeof(pmsg_header) + sizeof(header);
pmsg_header.uid = last_uid;//getuid()返回实际用户的ID。
pmsg_header.pid = last_pid;//getpid();进程识别码
header.tid = gettid();
header.realtime.tv_sec = ts.tv_sec;
header.realtime.tv_nsec = ts.tv_nsec;
buffer.header.tag = htole32(LIBLOG_LOG_TAG);
buffer.payload.type = EVENT_TYPE_INT;//0
buffer.payload.data = htole32(snapshot);
newVec[0].iov_base = (unsigned char *) &pmsg_header;
newVec[0].iov_len = sizeof(pmsg_header);
newVec[1].iov_base = (unsigned char *) &header;
newVec[1].iov_len = sizeof(header);
newVec[2].iov_base = &buffer;
newVec[2].iov_len = sizeof(buffer);
读取Log过程
我们每次把手机链接到AS或者Eclipse的时候,会有很多Log输出,那么Log是在什么时候就一直输出的.
答案是:在系统启动的时候Log系统就会启动,也就是已经在init.rc文件中配置好了.我们暂时不把重点放到这块,先来看看到底启动服务了之后,Log怎么就被读出来了.
@(system\core\logd\Main.cpp)
我们先来查看一下进程和线程信息:
shell@cancro:/ $ ps |grep logd
logd 220 1 33996 16500 sys_rt_sig 00000000 S /system/bin/logd
--------------------------------------------------------------------------------
shell@cancro:/ $ ps -t|grep 220
system 221 220 33996 16296 futex_wait 00000000 S logd.daemon
logd 222 220 33996 16296 poll_sched 00000000 S logd.reader
logd 223 220 33996 16296 poll_sched 00000000 S logd.writer
logd 224 220 33996 16296 poll_sched 00000000 S logd.control
logd 226 220 33996 16296 poll_sched 00000000 S logd.auditd
logd 5193 220 33996 16296 futex_wait 00000000 S logd.reader.per
logd 14611 220 33996 16296 futex_wait 00000000 S logd.reader.per
logd 15660 220 33996 16296 futex_wait 00000000 S logd.reader.per
//main方法进入
int main(int argc, char *argv[]) {
...
LastLogTimes *times = new LastLogTimes();
logBuf = new LogBuffer(times);
signal(SIGHUP, reinit_signal_handler);
if (property_get_bool_svelte("logd.statistics")) {
logBuf->enableStatistics();
}
LogReader *reader = new LogReader(logBuf);
if (reader->startListener()) {
exit(1);
}
LogListener *swl = new LogListener(logBuf, reader);
if (swl->startListener(300)) {
exit(1);
}
CommandListener *cl = new CommandListener(logBuf, reader, swl);
if (cl->startListener()) {
exit(1);
}
...
exit(0);
}
LastLogTimes():作用于管理最后的日志的时间,在socket链接时,并且锁定一系列log
然后我们看下LogBuffer类做些啥东东
void LogBuffer::init() {
static const char global_tuneable[] = "persist.logd.size"; // Settings App
...
unsigned long default_size = property_get_size(global_tuneable);//得到log打印的最大条目个数
...
}
我们看到有三个监听类,他们都继承于SocketListener类
我们先看第一个类的构造做了什么事情
LogReader::LogReader(LogBuffer *logbuf) :
SocketListener(getLogSocket(), true),
mLogbuf(*logbuf) {
}
--------------------------------------------------------------------------------------------
int LogReader::getLogSocket() {
static const char socketName[] = "logdr";
/*android_get_control_socket - 简单的帮助函数来获取我们的init管理的Unix域套接字的文件描述符。 `name'是套接字的名称,如init.rc.中给出。 在出错时返回-1。*/
int sock = android_get_control_socket(socketName);
if (sock < 0) {
sock = socket_local_server(socketName,
ANDROID_SOCKET_NAMESPACE_RESERVED,
SOCK_SEQPACKET);
}
return sock;
}
也就是说这个构造干了两件事情,一个是logdr明明的socket通道打开,其中logdr对应的socket在init.rc中给出
一个是将logbuf赋值给mLogbuf
然后我们看一看reader->startListener()干了什么事情
if (pipe(mCtrlPipe)) {
SLOGE("pipe failed (%s)", strerror(errno));
return -1;
}
if (pthread_create(&mThread, NULL, SocketListener::threadStart, this)) {
SLOGE("pthread_create (%s)", strerror(errno));
return -1;
}
--------------------------------------------------------------------------------------------
void *SocketListener::threadStart(void *obj) {
SocketListener *me = reinterpret_cast<SocketListener *>(obj);
me->runListener();
pthread_exit(NULL);
return NULL;
}
开启管道,创建线程并且开始监听,在threadStart运行
继续看runListener()
void SocketListener::runListener(){
SocketClientCollection pendingList;
while(1) {
...
if (FD_ISSET(mCtrlPipe[0], &read_fds)) {
char c = CtrlPipe_Shutdown;
TEMP_FAILURE_RETRY(read(mCtrlPipe[0], &c, 1));
...
continue;
}
if (mListen && FD_ISSET(mSock, &read_fds)) {
struct sockaddr addr;
socklen_t alen;
int c;
do {
alen = sizeof(addr);
c = accept(mSock, &addr, &alen);
SLOGV("%s got %d from accept", mSocketName, c);
} while (c < 0 && errno == EINTR);
if (c < 0) {
SLOGE("accept failed (%s)", strerror(errno));
sleep(1);
continue;
}
fcntl(c, F_SETFD, FD_CLOEXEC);
pthread_mutex_lock(&mClientsLock);
mClients->push_back(new SocketClient(c, true, mUseCmdNum));
pthread_mutex_unlock(&mClientsLock);
}
...
while (!pendingList.empty()) {
/* Pop the first item from the list */
it = pendingList.begin();
SocketClient* c = *it;
pendingList.erase(it);
/* Process it, if false is returned, remove from list */
if (!onDataAvailable(c)) {
release(c, false);
}
c->decRef();
}
}
}
可以看出先接收链接,new一个SocketClient放到SocketClientCollection 队列中
最后当SocketClientCollection不为空,调用方法SocketListener的回调onDataAvailable
然后我们看看回调方法:
bool LogReader::onDataAvailable(SocketClient *cli){
...
char buffer[255];
int len = read(cli->getSocket(), buffer, sizeof(buffer) - 1);
if (len <= 0) {
doSocketDelete(cli);
return false;
}
buffer[len] = '\0';
...
uint64_t sequence = 1;
// Convert realtime to sequence number
if (start != log_time::EPOCH) {
class LogFindStart {
const pid_t mPid;
const unsigned mLogMask;
bool startTimeSet;
log_time &start;
uint64_t &sequence;
uint64_t last;
public:
LogFindStart(unsigned logMask, pid_t pid, log_time &start, uint64_t &sequence) :
mPid(pid),
mLogMask(logMask),
startTimeSet(false),
start(start),
sequence(sequence),
last(sequence) {
}
static int callback(const LogBufferElement *element, void *obj) {
LogFindStart *me = reinterpret_cast<LogFindStart *>(obj);
if ((!me->mPid || (me->mPid == element->getPid()))
&& (me->mLogMask & (1 << element->getLogId()))) {
if (me->start == element->getRealTime()) {
me->sequence = element->getSequence();
me->startTimeSet = true;
return -1;
} else {
if (me->start < element->getRealTime()) {
me->sequence = me->last;
me->startTimeSet = true;
return -1;
}
me->last = element->getSequence();
}
}
return false;
}
bool found() { return startTimeSet; }
} logFindStart(logMask, pid, start, sequence);
logbuf().flushTo(cli, sequence, FlushCommand::hasReadLogs(cli), logFindStart.callback, &logFindStart);//核心代码
if (!logFindStart.found()) {
if (nonBlock) {
doSocketDelete(cli);
return false;
}
sequence = LogBufferElement::getCurrentSequence();
}
}
FlushCommand command(*this, nonBlock, tail, logMask, pid, sequence);
...
struct timeval t = { 32, 0 };
setsockopt(cli->getSocket(), SOL_SOCKET, SO_SNDTIMEO, (const char *)&t, sizeof(t));
command.runSocketCommand(cli);
return true;
}
到这里我们必须小结一下了,我们现在知道onDataAvailable这个回调的函数有如下操作:
- read函数读取客户端传入参数
- 将logbuf中的日志全部写到链接的客户端
- 然后执行客户端过来的命令 command.runSocketCommand(cli);
查看核心代码:
uint64_t LogBuffer::flushTo(
SocketClient *reader, const uint64_t start, bool privileged,
int (*filter)(const LogBufferElement *element, void *arg), void *arg){
...
max = element->flushTo(reader, this);
...
return max;
}
然后调用LogBufferElement的flushTo方法:
uint64_t LogBufferElement::flushTo(SocketClient *reader, LogBuffer *parent){
struct logger_entry_v3 entry;
memset(&entry, 0, sizeof(struct logger_entry_v3));
entry.hdr_size = sizeof(struct logger_entry_v3);
entry.lid = mLogId;
entry.pid = mPid;
entry.tid = mTid;
entry.sec = mRealTime.tv_sec;
entry.nsec = mRealTime.tv_nsec;
struct iovec iovec[2];
iovec[0].iov_base = &entry;
iovec[0].iov_len = sizeof(struct logger_entry_v3);
char *buffer = NULL;
if (!mMsg) {
entry.len = populateDroppedMessage(buffer, parent);
if (!entry.len) {
return mSequence;
}
iovec[1].iov_base = buffer;
} else {
entry.len = mMsgLen;
iovec[1].iov_base = mMsg;
}
iovec[1].iov_len = entry.len;
uint64_t retval = reader->sendDatav(iovec, 2) ? FLUSH_ERROR : mSequence;
if (buffer) {
free(buffer);
}
return retval;
}
这里把数据(mLogId,mPid,mTid,mRealTime.tv_sec,mRealTime.tv_nsec)封装成logger_entry_v3 entry
struct logger_entry_v3 {
uint16_t len; /* length of the payload */
uint16_t hdr_size; /* sizeof(struct logger_entry_v3) */
int32_t pid; /* generating process's pid */
int32_t tid; /* generating process's tid */
int32_t sec; /* seconds since Epoch */
int32_t nsec; /* nanoseconds */
uint32_t lid; /* log id of the payload */
char msg[0]; /* the entry's payload */
}
然后把数据统一放到iovec数组中,iovec[0]放入logger_entry_v3
自己的数据msg放入iovec[1]中
然后调用SocketClient *reader的sendDatav(iovec,2)方法
int SocketClient::sendDatav(struct iovec *iov, int iovcnt) {
pthread_mutex_lock(&mWriteMutex);
int rc = sendDataLockedv(iov, iovcnt);
pthread_mutex_unlock(&mWriteMutex);
return rc;
}
int SocketClient::sendDataLockedv(struct iovec *iov, int iovcnt) {
...
for (;;) {
ssize_t rc = TEMP_FAILURE_RETRY(writev(mSocket, iov + current, iovcnt - current));
if (rc > 0) {
size_t written = rc;
while ((current < iovcnt) && (written >= iov[current].iov_len)) {
written -= iov[current].iov_len;
current++;
}
if (current == iovcnt) {
break;
}
iov[current].iov_base = (char *)iov[current].iov_base + written;
iov[current].iov_len -= written;
continue;
}
break;
}
return ret;
}
同样是writev写log
下面我们看一看runSocketCommand方法:
void FlushCommand::runSocketCommand(SocketClient *client) {
LogTimeEntry *entry = NULL;
LastLogTimes × = mReader.logbuf().mTimes;
LogTimeEntry::lock();
LastLogTimes::iterator it = times.begin();
while(it != times.end()) {
entry = (*it);
if (entry->mClient == client) {
entry->triggerReader_Locked();
if (entry->runningReader_Locked()) {
LogTimeEntry::unlock();
return;
}
entry->incRef_Locked();
break;
}
it++;
}
if (it == times.end()) {
if (mTail == (unsigned long) -1) {
LogTimeEntry::unlock();
return;
}
entry = new LogTimeEntry(mReader, client, mNonBlock, mTail, mLogMask, mPid, mStart);
times.push_front(entry);
}
client->incRef();
entry->startReader_Locked();
LogTimeEntry::unlock();
}
继续调用startReader_Locked()
void LogTimeEntry::startReader_Locked(void) {
pthread_attr_t attr;
threadRunning = true;
if (!pthread_attr_init(&attr)) {
if (!pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED)) {
if (!pthread_create(&mThread, &attr,
LogTimeEntry::threadStart, this)) {
pthread_attr_destroy(&attr);
return;
}
}
pthread_attr_destroy(&attr);
}
threadRunning = false;
if (mClient) {
mClient->decRef();
}
decRef_Locked();
}
创建一个线程调用LogTimeEntry::threadStart,传入LogTimees.cpp这个对象类
void *LogTimeEntry::threadStart(void *obj) {
prctl(PR_SET_NAME, "logd.reader.per");
LogTimeEntry *me = reinterpret_cast<LogTimeEntry *>(obj);
pthread_cleanup_push(threadStop, obj);
SocketClient *client = me->mClient;
if (!client) {
me->error();
return NULL;
}
LogBuffer &logbuf = me->mReader.logbuf();
bool privileged = FlushCommand::hasReadLogs(client);
me->leadingDropped = true;
lock();
uint64_t start = me->mStart;
while (me->threadRunning && !me->isError_Locked()) {
unlock();
if (me->mTail) {
logbuf.flushTo(client, start, privileged, FilterFirstPass, me);
me->leadingDropped = true;
}
start = logbuf.flushTo(client, start, privileged, FilterSecondPass, me);
lock();
if (start == LogBufferElement::FLUSH_ERROR) {
me->error_Locked();
break;
}
me->mStart = start + 1;
if (me->mNonBlock || !me->threadRunning || me->isError_Locked()) {
break;
}
me->cleanSkip_Locked();
pthread_cond_wait(&me->threadTriggeredCondition, ×Lock);
}
unlock();
pthread_cleanup_pop(true);
return NULL;
}
此线程会不断循环 logbuf.flushTo(client, start, privileged, FilterFirstPass, me);发送log给客户端
小结上面
LogReader会监听logdr socket然后处理各个socket请求获取log.
LogReader会新建一个LogTimeEntry对象开启一个线程来调用LogBuffer的flushTo函数发送log,并且也会调用回调函数来过滤log,线程调用完挂起,直到下个相同的socket client请求,才会把这个线程恢复继续调用LogBuffer的flushTo发送log
也就是说:这里LogRead相当于服务端,谁发送命令需要什么log就给我发什么命令,然后我再把log发送回去
三个logd.reader.per线程就是在这里创建的
我们在看看第二个监听器:LogListener
他的构造函数如下:
LogListener::LogListener(LogBuffer *buf, LogReader *reader) :
SocketListener(getLogSocket(), false),
logbuf(buf),
reader(reader) {
}
--------------------------------------------------------------------------------------------
其中SocketListener调用,参数传递的是
socketName="logdw"
socketFd=-1
listen=false
useCmdNum=flase
void SocketListener::init(const char *socketName, int socketFd, bool listen, bool useCmdNum) {
mListen = listen;
mSocketName = socketName;
mSock = socketFd;
mUseCmdNum = useCmdNum;
pthread_mutex_init(&mClientsLock, NULL);
mClients = new SocketClientCollection();
}
- 同样调用getLogSocket方法链接logdw命名的socket
- 将LogReader *reader赋值给成员变量reader
- 将LogBuffer *buf赋值给logbuf
同样的继续调用startListener(300)开启监听
同上也是建立管道,创建线程调用pthread_create(&mThread, NULL, SocketListener::threadStart, this)
步骤同上最后调用到回调函数onDataAvailable()
bool LogListener::onDataAvailable(SocketClient *cli) {
...
prctl(PR_SET_NAME, "logd.writer");//logd.writer就是当前线程
char buffer[sizeof_log_id_t + sizeof(uint16_t) + sizeof(log_time)
+ LOGGER_ENTRY_MAX_PAYLOAD];
struct iovec iov = { buffer, sizeof(buffer) };
memset(buffer, 0, sizeof(buffer));
char control[CMSG_SPACE(sizeof(struct ucred))];
//利用hdr存log,其中iov是buffer
struct msghdr hdr = {
NULL,
0,
&iov,
1,
control,
sizeof(control),
0,
};
int socket = cli->getSocket();
ssize_t n = recvmsg(socket, &hdr, 0);//接收数据
...
struct ucred *cred = NULL;
struct cmsghdr *cmsg = CMSG_FIRSTHDR(&hdr);
while (cmsg != NULL) {
if (cmsg->cmsg_level == SOL_SOCKET
&& cmsg->cmsg_type == SCM_CREDENTIALS) {
cred = (struct ucred *)CMSG_DATA(cmsg);
break;
}
cmsg = CMSG_NXTHDR(&hdr, cmsg);
}
...
android_log_header_t *header = reinterpret_cast<android_log_header_t *>(buffer);//获取header头信息
if (header->id >= LOG_ID_MAX || header->id == LOG_ID_KERNEL) {
return false;
}
char *msg = ((char *)buffer) + sizeof(android_log_header_t);
n -= sizeof(android_log_header_t);
if (logbuf->log((log_id_t)header->id, header->realtime,
cred->uid, cred->pid, header->tid, msg,
((size_t) n <= USHRT_MAX) ? (unsigned short) n : USHRT_MAX) >= 0) {
reader->notifyNewLog();
}
return true;
}
这段代码是获取信息,封装信息,最后调用
logbuf->log((log_id_t)header->id, header->realtime,
cred->uid, cred->pid, header->tid, msg,
((size_t) n <= USHRT_MAX) ? (unsigned short) n : USHRT_MAX)
我们继续看看logbuf->log()怎么处理log的
int LogBuffer::log(log_id_t log_id, log_time realtime,
uid_t uid, pid_t pid, pid_t tid,
const char *msg, unsigned short len) {
...
LogBufferElement *elem = new LogBufferElement(log_id, realtime,
uid, pid, tid, msg, len);
int prio = ANDROID_LOG_INFO;
const char *tag = NULL;
if (log_id == LOG_ID_EVENTS) {
tag = android::tagToName(elem->getTag());
} else {
prio = *msg;
tag = msg + 1;
}
...
// Insert elements in time sorted order if possible
LogBufferElementCollection::iterator it = mLogElements.end();
LogBufferElementCollection::iterator last = it;
while (last != mLogElements.begin()) {
--it;
if ((*it)->getRealTime() <= realtime) {
break;
}
last = it;
}
if (last == mLogElements.end()) {
mLogElements.push_back(elem);
} else {
uint64_t end = 1;
bool end_set = false;
bool end_always = false;
LogTimeEntry::lock();
LastLogTimes::iterator t = mTimes.begin();
while(t != mTimes.end()) {
LogTimeEntry *entry = (*t);
if (entry->owned_Locked()) {
if (!entry->mNonBlock) {
end_always = true;
break;
}
if (!end_set || (end <= entry->mEnd)) {
end = entry->mEnd;
end_set = true;
}
}
t++;
}
if (end_always
|| (end_set && (end >= (*last)->getSequence()))) {
mLogElements.push_back(elem);
} else {
mLogElements.insert(last,elem);
}
LogTimeEntry::unlock();
}
stats.add(elem);
maybePrune(log_id);
pthread_mutex_unlock(&mLogElementsLock);
return len;
}
其中核心方法是:
- stats.add(elem);//将封装的LogBufferElement进行封装,然后添加到 LogBufferElementCollection mLogElements;中
- maybePrune(log_id);
对于maybePrune(log_id)函数let me see
void LogBuffer::maybePrune(log_id_t id) {
size_t sizes = stats.sizes(id);//log的个数,因为stats.add(elem);一直在添加
unsigned long maxSize = log_buffer_size(id);//对应id缓冲区的最大个数,id上文已经给出
if (sizes > maxSize) {
size_t sizeOver = sizes - ((maxSize * 9) / 10);
size_t elements = stats.realElements(id);
size_t minElements = elements / 100;
if (minElements < minPrune) {
minElements = minPrune;
}
unsigned long pruneRows = elements * sizeOver / sizes;
if (pruneRows < minElements) {
pruneRows = minElements;
}
if (pruneRows > maxPrune) {
pruneRows = maxPrune;
}
prune(id, pruneRows);
}
}
修剪百分之十的log条目,然后选择最小者
prune这个函数是从id中除去那些pruneRows
此垃圾回收任务用于使日志条目过期。 它删除所有日志(清除),所有UID日志(非特权清除)或每个
256或10%的总日志(以较少者为准)修剪日志。
小结LogListener.cpp
- 接收客户端传入的参数和数据(写入到缓冲区)
- 将客户端日志写入
- 通知所有客户端,日志写入,此时已经能够并且读取
大结
- 在java程序中调用打印log,会通过socket链接dev/socket/logdw
- 接收是通过在系统启动的时候执行system\core\logd\Main.cpp,然后创建LogListener对象监听socket为logdw,当有数据的时候会先封装,然后通过通知链接的客户端打印数据
- LogReader监听/dev/socket/logdr上的socket客户端有数据就给客户端
- CommandListener是一个管理类,里面封装了很多命令,可以操作log的输出,监听的是/dev/socket/logd
- 对应线程的创建
LogReader::onDataAvailable():logd.reader
LogListener::onDataAvailable():logd.writer
LogTimes::threadStart():logd.reader.per
也就是说:我们Java写一个日志调用,对应的设备文件是dev/socket/logdw,对应的监听器是LogListener,在LogListener中做的操作是将新的日志条目添加到LogBuffer中,并且通知LogReader更新日志条目,然后发送给链接的客户端