ANDROID L日志系统——JAVAAPI与LIBLOG
生活随笔
收集整理的這篇文章主要介紹了
ANDROID L日志系统——JAVAAPI与LIBLOG
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
在 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.Log API 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去處理,大致流程如下:
service logd /system/bin/logd class core socket logd stream 0666 logd logd socket logdr seqpacket 0666 logd logd socket logdw dgram 0222 logd logd group root system for (si = svc->sockets; si; si = si->next) { ? //讀取socket類型,stream或者dgram int socket_type = ( !strcmp(si->type, "stream") ? SOCK_STREAM : (!strcmp(si->type, "dgram") ? SOCK_DGRAM : SOCK_SEQPACKET)); //創建socket int 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的socket fd = 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 value if (swl->startListener(300)) { exit(1); } LogListener繼承成SocketListener,而startListener正是其父類SocketListener的方法。
先看New LogListener(LogBuf, reader)
LogListener::LogListener(LogBuffer *buf, LogReader *reader) :? //同時會構造一個父類SocketListener,getLogSocket()是通過logdw這個名字返回一個SocketFd SocketListener(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) { //獲得SocketFd if ((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.cpprelease(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 message 1, 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,讀者可以自己深入分析。
msghdr部分參考了:http://blog.csdn.net/jnu_simba/article/details/9079627
從上一篇文章《Kernel的環形Buffer(Ring?Buffer)——以Logger?Buffer為例》分析可知,Android系統的Log都是用一個環形buffer來存儲管理的,換成Logd之后,應該也是通過Ring Buffer來管理,只是由Kernel空間,改成用戶空間。那么現在就來看看用戶層是如何,往這個buffer中寫Log,以及從這個buffer中讀出來Log。 在Java層寫APP時,一般都會調用android.util.Log這個包的一些靜態方式來打印Log;
下一節就來討論logd的實現。
1,在系統啟動到init處理的時候,會解析init.rc啟動logd service如下:
同時會創建和初始化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
總結
以上是生活随笔為你收集整理的ANDROID L日志系统——JAVAAPI与LIBLOG的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: android log丢失(三)动态切换
- 下一篇: Android6.0 Log的工作机制