解讀Android LOG機制的實現:(4)LOG裝置驅動Logger

來源:互聯網
上載者:User

 

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

 

另文闡述。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.