Android提供瞭用戶級輕量的LOG機制,它的實現貫穿瞭Java,JNI,本地c/c++實現以及LINUX內核驅動等Android的各個層次,並且足夠簡單明晰,是一個相當不錯的解讀案例。本系列文章針對LOG機制的內部實現機理進行解讀,本文是系列之四,解讀LINUX內核中的設備驅動Logger中實現。Logger是Android為Linux寫的一個MISC類型驅動,用循環隊列實現瞭讀者/寫者。Logger是整個LOG機制實現的核心。
關鍵字:LINUX驅動,Android,讀者/寫者,MISC類型驅動,循環隊列,等待隊列,阻塞I/O
Log的驅動是在kernel/drivers/staging/android/Logger.c中實現的。
一、初始化
看一個LINUX驅動,先看它如何初始化的。
static int __init init_log(struct logger_log *log)
{
int ret;
ret = misc_register(&log->misc);
if (unlikely(ret)) {
printk(KERN_ERR "logger: failed to register misc "
"device for log '%s'!\n", log->misc.name);
return ret;
}
printk(KERN_INFO "logger: created %luK log '%s'\n",
(unsigned long) log->size >> 10, log->misc.name);
return 0;
}
static int __init logger_init(void)
{
int ret;
ret = init_log(&log_main);
if (unlikely(ret))
goto out;
ret = init_log(&log_events);
if (unlikely(ret))
goto out;
ret = init_log(&log_radio);
if (unlikely(ret))
goto out;
ret = init_log(&log_system);
if (unlikely(ret))
goto out;
out:
return ret;
}
device_initcall(logger_init);
整個Logger驅動的入口點就是Logger_init(),它用init_log(struct logger_log *log)初始化瞭log_main, log_events, log_radio和log_system四個logger_log類型的結構,而這四個結構變量分別記錄著log的四個存儲體。Logger從這四個變量實現瞭同種設備的四個驅動,而log的驅動是MISC類型的驅動,通過misc_register()向系統註冊。四次註冊之後,它們對應的MINOR ID將是不同的,Looger也是通過minor來區分是哪一個驅動的。
static struct logger_log *get_log_from_minor(int minor)
{
if (log_main.misc.minor == minor)
return &log_main;
if (log_events.misc.minor == minor)
return &log_events;
if (log_radio.misc.minor == minor)
return &log_radio;
if (log_system.misc.minor == minor)
return &log_system;
return NULL;
}
本文將以log_main來講解Logger驅動的實現。
二、關鍵數據結構
上節中,提到瞭log_main這個結構體變量,現在來看它的定義。
Log_main裡保存瞭Logger操作必須的變量。buffer指向的真是一個靜態數組,用來存放用來讀寫的數據,Logger用它組成瞭一個邏輯上的循環隊列,寫者可以往w_off指向的地方寫東西,而一旦有內容,會通知等待隊列wq裡的讀者們來讀取內容。因為buffer實現的是循環隊列,所以buffer的大小size經常用來做除高位的運算,一定要是一個2次冪的數字。mutex用來保護log_main這個關鍵資源的。Logger是MISC類型的驅動,它保留著一個miscdevice類型的變量misc。misc裡面也有最為關鍵的file_operations結構,這正是應用程序通過文件操作,與驅動打交道的入口。
三、Logger實現的功能
從上面log_main的類型定義就能看出,Logger實現瞭什麼。一句話概括Logger就是實現瞭讀寫者,並實現同步操作。不過,Logger的讀寫者有些特殊,寫者寫操作不會被阻塞,也不會寫滿溢出,也就是寫時隻要有內容可以不停的寫,超出Buffer就覆蓋舊的[與應用程序具體的寫操作結合來看];讀者因為要讀的內容為空就會被阻塞掛起,而一旦有內容,所有被掛起的讀者都會被喚醒[與應用程序具體的讀操作結合來看]。
下面看具體實現的時候,就分別從讀者和寫者的角度去看。
3.1. 寫者的實現
看二小節圖中的關鍵結構logger_fops: file_operations,寫者的關鍵實現就看open、release和write這幾個函數的實現瞭,它們被分別賦值給瞭logger_open() / logger_release() / logger_aio_write()。
logger_open()為寫者做的工作就是,通過minor id獲得logger_log的實例,然後賦值給函數參數中傳遞進來的file的private_data中。
logger_release()不需要為寫者做的什麼工作。
logger_poll()因為寫不需要被阻塞。所以這裡檢測到是因為非因為讀而打開的文件(!(file->f_mode &FMODE_READ))時,就直接返回POLLOUT | POLLWRNORM。無論怎樣都可寫。
logger_aio_write()是寫數據(也就是log信息)的關鍵。這裡是通過異步IO的方法,應用程序通過write()/writev()和aio_write()時都能調用到這個方法。
記錄log信息時,寫log用的接口是writev(),寫的是vec形式的數據,這邊寫的過程中來的當然也是vec數據瞭,另外,寫具體之間,還寫入瞭類型為logger_entry的數據,來記錄時間等信息。寫數據到具體buffer時因為存儲的位置可能不是連續的,而寫在buffer的結尾和開頭位置,所以要做判斷,並可能要有兩次寫的buffer的動作。參數裡的數據來自用戶空間,不能在內核空間直接使用,要用copy_from_user()。寫完之後,用wake_up_interruptible(&log->wq)喚醒所有在掛起等待的讀者。
3.2. 讀者的實現
看二小節圖中的關鍵結構logger_fops: file_operations,寫者的關鍵實現就看open、release和read這幾個函數的實現瞭,它們被分別賦值給瞭logger_open() / logger_release() / logger_read()。
logger_open() 為讀者做的工作就是,通過minor id獲得logger_log的實例,然後動態申請一個logger_reader類型的讀者,並把它加入到logger_log的讀者列表readers的結尾,再賦值給函數參數中傳遞進來的file的private_data中。
logger_release() 與logger_open()對應,將這個讀者從讀者列表logger_log.readers中移除,並釋放掉這個動態申請的實例。
logger_poll()因為應用讀之前會調用poll()/select()查看是否可以寫。所以這裡會用poll_wait()把參數中的poll_table加入到logger_log.wq中,並且如果有內容可讀,才設置可讀標志|= POLLIN |POLLRDNORM。
logger_read() 是讀數據(也就是log信息)的關鍵。
讀數據之前,要先保證有數據,否則該讀者就要被掛起在logger_log的等待隊列wq上。從具體buffer讀數據到時因為存儲的位置可能不是連續的,存儲在buffer的結尾和開頭位置,所以要做判斷,並可能要有兩次讀去buffer的動作。數據來自內核空間,要通過用戶空間的參數裡傳遞出去,需要copy_to_user()。
3.3 循環隊列的實現
這個是數據結構裡最經典的案例瞭,這裡不再具體解釋如何實現,隻是列出重要結構,隻是希望讀者還記得數據結構裡邏輯結構和物理結構的說法。
隊列大小:log_main.size
寫頭:log_main.w_off
讀頭:logger_reader.r_off
隊列為空判斷:log_main.w_off == logger_reader.r_off
隊列為滿判斷:不需要
3.4 ioctl的實現
Logger提供給應用程序通過ioctl()來獲取信息或控制LOGbuffer的功能。Logger是把logger_ioctl通過file_operations註冊到文件系統中來實現這一功能的。Logger_ioctl()提供瞭下列ioctl控制命令:LOGGER_GET_LOG_BUF_SIZE / LOGGER_GET_LOG_LEN/ LOGGER_GET_NEXT_ENTRY_LEN / LOGGER_FLUSH_LOG。實現很簡單:
LOGGER_GET_LOG_BUF_SIZE獲取Buffer的大小,直接返回logger_log.size即可;
LOGGER_GET_LOG_LEN隻對讀有效,獲取當前LOG的大小,存儲連續的話就是log->w_off -reader->r_off,否則就是(log->size -reader->r_off) + log->w_off;
LOGGER_GET_NEXT_ENTRY_LEN獲取Entry的長度,隻對讀有效。
LOGGER_FLUSH_LOG隻對寫打開有效。所謂FLUSH LOG,直接重置每個reader的r_off,並設置新reader要訪問用的head即可。
3.5 阻塞I/O、異步I/O
另文闡述。