在 Android L(包含Android L)之后,Andoird使用了全新的日志系统,也非之前结合Kernel Ring Buffer的方式来存储,读写Log。替而代之是使用新的日志机制Logd。所以说,在/dev/log/下面创建的几个设备,根本就没有使用!没有使用!
其实,init在创建它们的时候,就有说明,只是没有注意到了。
INFO(“kernel logger is deprecatedn”);
就来分析Android L的日志系统。
从上一篇文章《
Kernel的环形Buffer(Ring Buffer)——以Logger Buffer为例
》分析可知,Android系统的Log都是用一个环形buffer来存储管理的,换成Logd之后,应该也是通过Ring Buffer来管理,只是由Kernel空间,改成用户空间。那么现在就来看看用户层是如何,往这个buffer中写Log,以及从这个buffer中读出来Log。
在Java层写APP时,一般都会调用
android.util.Log
这个包的一些静态方式来打印Log;
java.lang.Object ↳ android.util.LogAPI for sending log output.Generally, use the Log.v() Log.d() Log.i() Log.w() and Log.e() methods.
分析Log.java,Log.v() Log.d() Log.i等等最终都调用到
public static int v(String tag, String msg) { return println_native(LOG_ID_MAIN, VERBOSE, tag, msg); }
=》通过JNI调用android_util_Log.cpp
{ "println_native", "(IILjava/lang/String;Ljava/lang/String;)I", (void*) android_util_Log_println_native },...>static jint android_util_Log_println_native(JNIEnv* env, jobject clazz, jint bufID, jint priority, jstring tagObj, jstring msgObj){ //先判断ID是否是一个合法LOG_ID_MAX,这个变量定义在system/下面的Log.h里面 - if (bufID < 0 || bufID >= LOG_ID_MAX) {
jniThrowNullPointerException(env, "bad bufID"); return -1; }- //取出来TAG
if (tagObj != NULL) tag = env->GetStringUTFChars(tagObj, NULL);- //取出要写入的Message
msg = env->GetStringUTFChars(msgObj, NULL);//调用__android_log_buf_write来写入到buffer int res = __android_log_buf_write(bufID, (android_LogPriority)priority, tag, msg); if (tag != NULL) env->ReleaseStringUTFChars(tagObj, tag); env->ReleaseStringUTFChars(msgObj, msg); return res;}
__android_log_buf_write是在liblog中实现的。
在liblog中,会通过sokect通讯把要写入log交给logd去处理,大致流程如下:
下一节就来讨论logd的实现。
1,在系统启动到init处理的时候,会解析init.rc启动logd service如下:
service logd /system/bin/logdclass coresocket logd stream 0666 logd logdsocket logdr seqpacket 0666 logd logdsocket logdw dgram 0222 logd logdgroup root system
同时会创建和初始化3个socket::logd, logdr, logdw。分别是用来监听命令,处理读log,和处理写log。
socket logd stream
0666
logd logd
在init中解析socket的处理如下:
service_start(struct service *svc, const char *dynamic_args)@init.cpp
for (si = svc->sockets; si; si = si->next) {//读取socket类型,stream或者dgramint socket_type = (!strcmp(si->type, "stream") ? SOCK_STREAM :(!strcmp(si->type, "dgram") ? SOCK_DGRAM : SOCK_SEQPACKET));//创建socketint s = create_socket(si->name, socket_type,si->perm, si->uid, si->gid, si->socketcon ?: scon);if (s >= 0) {//发布socket,把创建的socketFd写到环境变量,让其它Sokect的Server端通过android_get_control_socket(mSocketName)来获得socketFd.publish_socket(si->name, s);}}
核心是create_socket,来看这里的实现,代码位于init/util.cpp
int create_socket(const char *name, int type, mode_t perm, uid_t uid,gid_t gid, const char *socketcon){struct sockaddr_un addr;int fd, ret;char *filecon;//调用系统调用socket来创建一个PF_UNIX的socketfd = socket(PF_UNIX, type, 0);addr.sun_family = AF_UNIX;snprintf(addr.sun_path, sizeof(addr.sun_path), ANDROID_SOCKET_DIR"/%s",name);//把这个socket绑定到addr上,这个addr就与/dev/socket/*有关了ret = bind(fd, (struct sockaddr *) &addr, sizeof (addr));
这个init基本上就把Socket的Server端的初始化工作准备好了。
2,logd启动之后,会获得相应的socket,并监听socket。
以logdw为例,main()#logd/main.cpp
// 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 valueif (swl->startListener(300)) {exit(1);}
LogListener继承成SocketListener,而startListener正是其父类SocketListener的方法。
先看New LogListener(LogBuf, reader)
LogListener::LogListener(LogBuffer *buf, LogReader *reader) ://同时会构造一个父类SocketListener,getLogSocket()是通过logdw这个名字返回一个SocketFdSocketListener(getLogSocket(), false),- //把两个结构体传过来
logbuf(buf),reader(reader) {}
接下来看SocketListener的构造函数,也就是把相关参数传过来进行赋值传递。
SocketListener.cpp
SocketListener::SocketListener(int socketFd, bool listen) {init(NULL, socketFd, listen, false);}=》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();}
再回到上面,logd/main.cpp中main()。创建完LogListener,紧接着就swl->startListener(300);这个startListener直接由SocketListener实现,我们直接来看SocketListener.cpp
int SocketListener::startListener(int backlog) {if (!mSocketName && mSock == -1) {- ...
- //在构造中mSocketName已经传过来了
} else if (mSocketName) {//获得SocketFdif ((mSock = android_get_control_socket(mSocketName)) < 0) {- ...
}SLOGV("got mSock = %d for %s", mSock, mSocketName);fcntl(mSock, F_SETFD, FD_CLOEXEC);}//调用listen的系统调用,监听SocketFd。此时mListen为NULL,应该不会调用listen??TODO,有编译器有关??if (mListen && listen(mSock, backlog) < 0) {- ...
} else if (!mListen)//创建SocketClient,并放到mClients的,mClients是存储所有SocketClient的List容器。mClients->push_back(new SocketClient(mSock, false, mUseCmdNum));- ...
//创建PID为mThread的线程,线程执行函数是thradStart,并启动 。if (pthread_create(&mThread, NULL, SocketListener::threadStart, this)) {SLOGE("pthread_create (%s)", strerror(errno));return -1;}return 0;}
来看thread执行函数threadStart
void *SocketListener::threadStart(void *obj) {SocketListener *me = reinterpret_cast<SocketListener *>(obj);me->runListener();pthread_exit(NULL);return NULL;}
runListener有点长,主要做了以下几个事情。
void SocketListener::runListener() {...rc = select(max + 1, &read_fds, NULL, NULL, NULL);...c = accept(mSock, &addr, &alen);.../* Process the pending list, since it is owned by the thread, * there is no need to lock it */ 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)) {//这个数据处理函数,由继承SocketListener的类来实现,在这里就是指LogListener.cpp release(c, false); } c->decRef(); }
这些都是UNIX线程通信的系统调用。这样Socket的Server就准备好了。
总结一下,在unix Socket通信中Server端一般有以下几个步骤
The steps involved in establishing a socket on the server side are as follows:
1,Create a socket with the
socket() system call
2,Bind the socket to an address using the
bind() system call. For a server socket on the Internet, an address consists of a port number on the host machine.
3,Listen for connections with the
listen() system call
4,Accept a connection with the
accept() system call. This call typically blocks until a client connects with the server.
Send and receive data
对于logdw,1,2步骤在init里面完成,3,4步是LogListener的父类SocketListener里面完成。
3,Logdw是如何处理来自liblog的请求处理的。
在第2小节中,具体的数据处理是由onDataAvailable()完成,这个函数是LogListener.cpp来实现,
第1步,读取数据,并存在Socket定义的MSG相关结构体内
char buffer[sizeof_log_id_t + sizeof(uint16_t) + sizeof(log_time)+ LOGGER_ENTRY_MAX_PAYLOAD];- //定义iov用于接收Client的writerv的内容。即一条LOG会在在buffer中
struct iovec iov = { buffer, sizeof(buffer) };memset(buffer, 0, sizeof(buffer));//存放Client的进程信息char control[CMSG_SPACE(sizeof(struct ucred))];struct msghdr hdr = {NULL,0,&iov,//真正存放LOG message1,control,sizeof(control),0,};int socket = cli->getSocket();- //通过系统调用 把Client传过来的socket数据存放在hdr这个结构体中。
ssize_t n = recvmsg(socket, &hdr, 0);
这里有必要说一下msghdr这个结构体:
msghdr是用于Socket在两个进程之间通讯定义的消息头
struct msghdr {void *msg_name; /* optional address */socklen_t msg_namelen; /* size of address */struct iovec *msg_iov; /* scatter/gather array */size_t msg_iovlen; /* # elements in msg_iov */void *msg_control; /* ancillary data, see below */size_t msg_controllen; /* ancillary data buffer len */int msg_flags; /* flags on received message */};
msg_control:是一个指向cmsghdr 结构体的指针,
struct cmsghdr {socklen_t cmsg_len; /* data byte count, including header */int cmsg_level; /* originating protocol */int cmsg_type; /* protocol-specific type *//* followed by unsigned char cmsg_data[]; */};
msg_controllen :参见下图,即cmsghdr 结构体可能不止一个;
对于CMSG在LogListener.cpp里面是control变量,char control[CMSG_SPACE(sizeof(struct ucred))];也就是说CMSG是存放Client的PID,UID,GID信息的。
struct ucred {pid_t pid; /* process ID of the sending process */uid_t uid; /* user ID of the sending process */gid_t gid; /* group ID of the sending process */};
第2步,解析CMSG里面进程相关信息,并检查权限
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);}if (cred == NULL) {return false;}- //检查进程的权限
if (cred->uid == AID_LOGD) {// ignore log messages we send to ourself.// Such log messages are often generated by libraries we depend on// which use standard Android logging.return false;}
第3步,处理真正的Log信息,从第1步可以知道,Log信息是存放在iov指向的buffer里面,即对buffer处理就是处理Log信息
android_log_header_t *header = reinterpret_cast<android_log_header_t *>(buffer);if (/* header->id < LOG_ID_MIN || */ 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);// NB: hdr.msg_flags & MSG_TRUNC is not tested, silently passing a// truncated message to the logs.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,然后调用reader->nofifyNewLog()把Log存储到buffer中。
至logd的实现,基本上分析完成。关于LogBuffer和LogReader,读者可以自己深入分析。

本文详细解析了Android L及以后版本中采用的新日志系统Logd的工作原理,包括其如何替代Kernel Ring Buffer,以及Java层如何通过JNI调用与Logd交互。
1547

被折叠的 条评论
为什么被折叠?



