解讀Android LOG機制的實現:(4)LOG設備驅動Logger – Android移動開發技術文章_手機開發 Android移動開發教學課程

 

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

 

另文闡述。

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *