linux驅動工程面試必問知識點

linux內核原理面試必問(由易到難)

簡單型

1:linux中內核空間及用戶空間的區別?用戶空間與內核通信方式有哪些?
2:linux中內存劃分及如何使用?虛擬地址及物理地址的概念及彼此之間的轉化,高端內存概念?
3:linux中中斷的實現機制,tasklet與workqueue的區別及底層實現區別?爲什麼要區分上半部和下半部?

4:linux中斷的響應執行流程?中斷的申請及何時執行(何時執行中斷處理函數)?
5:linux中的同步機制?spinlock與信號量的區別?

6:linux中RCU原理?

7: linux中軟中斷的實現原理?(2014.03.11)

8:linux系統實現原子操作有哪些方法? (2014.03.22)

9:MIPS Cpu中空間地址是怎麼劃分的?如在uboot中如何操作設備的特定的寄存器? (2014.03.22)

複雜型:

1:linux中netfilter的實現機制?是如何實現對特定數據包進行處理(如過濾,NAT之類的)及HOOK點的註冊?
2:linux中系統調用過程?如:應用程序中read()在linux中執行過程即從用戶空間到內核空間?
3:linux內核的啓動過程(源代碼級)?

4:linux調度原理?

5:linux網絡子系統的認識?

三: 筆試

1:二分法查找

2:大小端轉化及判斷

3: 二維數組最外邊個元素之和?

4:特定比特位置0和1

5:字符串中的第一個和最後一個元素交換(字符串反轉)?

1:linux中內核空間及用戶空間的區別?用戶空間與內核通信方式有哪些?
答:
-在32位架構cpu中,物理內存大小限制在4G。linux將4G內存分爲兩部分,0~1G爲kernel使用,1~4G爲用戶使用;進程運行在kernel,就是運行在0-1G,進程運行在用戶空間,就是運行在1-4G。
-用戶空間和內核空間通信方式有那些?

  1. 使用API:這是最常使用的一種方式了
    A.get_user(x,ptr):在內核中被調用,獲取用戶空間指定地址的數值並保存到內核變量x中。
    B.put_user(x,ptr):在內核中被調用,將內核空間的變量x的數值保存到到用戶空間指定地址處。
    C.Copy_from_user()/copy_to_user():主要應用於設備驅動讀寫函數中,通過系統調用觸發。
  2. 使用proc文件系統:和sysfs文件系統類似,也可以作爲內核空間和用戶空間交互的手段。
  3. netlink
  4. 使用mmap系統調用
  5. 信號
    內核空間和用戶空間通信方式

2:linux中內存劃分及如何使用?虛擬地址及物理地址的概念及彼此之間的轉化,高端內存概念?

  1. 用戶虛擬地址
    這是在用戶空間進程所能看到的常規地址。每個進程多有自己的虛擬地址,並且可以使用大於物理內存大小的空間。

  2. 物理地址
    該地址在處理器和系統內存之間使用,對應與真是物理地址。

  3. 總線地址
    沒看懂,不說了。

  4. 內核邏輯地址
    內核邏輯地址組成了內核的常規地址空間。該地址映射了部分(或者全部)內存,並經常被視爲物理地址。
    邏輯地址使用硬件內建的指針大小,因此在安裝了大量內存的32位系統中,它無法尋址全部的物理內存。
    邏輯地址通常保存在unsigned long或者void *這樣類型的變量中。kmalloc返回的內存就是內核邏輯地址。
    (上面這段話很重要,一定要理解,建議自己使用記號筆標紅)

  5. 內核虛擬地址
    內核虛擬地址與物理地址的映射不必是一對一的,而這是虛擬地址的特點。

    所有邏輯地址都是內核虛擬地址,但是許多內核虛擬地址不是邏輯地址。vmalloc分配的內存就是一個虛擬地址。
    可以參考下面的地址:
    在這裏插入圖片描述

內存詳解

內存結構圖

總結:高端內存的作用就是用於建立臨時地址映射,用於kernel申請user空間內存

3: linux中中斷的實現機制,tasklet與workqueue的區別及底層實現區別?爲什麼要區分上半部和下半部?
答:
tasklet和workqueue區別?
tasklet運行於中斷上下文,不允許阻塞 、休眠,而workqueue運行與進程上下文,可以休眠和阻塞。
爲什麼要區分上半部和下半部?
中斷服務程序異步執行,可能會中斷其他的重要代碼,包括其他中斷服務程序。因此,爲了避免被中斷的代碼延遲太長的時間,中斷服務程序需要儘快運行,而且執行的時間越短越好,所以中斷程序只作必須的工作,其他工作推遲到以後處理。所以Linux把中斷處理切爲兩個部分:上半部和下半部。上半部就是中斷處理程序,它需要完成的工作越少越好,執行得越快越好,一旦接收到一箇中斷,它就立即開始執行。像對時間敏感、與硬件相關、要求保證不被其他中斷打斷的任務往往放在中斷處理程序中執行;而剩下的與中斷有相關性但是可以延後的任務,如對數據的操作處理,則推遲一點由下半部完成。下半部分延後執行且執行期間可以相應所有中斷,這樣可使系統處於中斷屏蔽狀態的時間儘可能的短,提高了系統的響應能力。實現了程序運行快同時完成的工作量多的目標。

4:linux中斷的響應執行流程?中斷的申請及何時執行(何時執行中斷處理函數)?
中斷的響應流程:cpu接受終端->保存中斷上下文跳轉到中斷處理歷程->執行中斷上半部->執行中斷下半部->恢復中斷上下文。
中斷的申請request_irq的正確位置:應該是在第一次打開 、硬件被告知終端之前。

5:linux中的同步機制?spinlock與信號量的區別?
linux中的同步機制:自旋鎖/信號量/讀取所/循環緩衝區
spinlock在得不到鎖的時候,程序會循環訪問鎖,性能下降
信號量在得不到鎖的時候會休眠,等到可以獲得鎖的時候,繼續執行。

1、255.255.254.0網段最多能支持多少主機?(大概有5個備選項)

2、10M網卡傳輸過程中物理層採用什麼編碼?(SNAP?)(大概有4個備選項)

3、棧與隊列的特點?(備選大概只有兩個,A爲FIFO,B爲LIFO)

4、Cache的工作方式劃分?(大概也有4個答案,大概是:write-none,write-all,write-through,write-back)。

5、什麼叫NMI中斷?(四個備選項)

6、RISC主要性能及特性?(大概有6個備選項)

7、在嵌入式系統中,所謂的北橋指的是什麼?

(2)簡答題:

1、說說輪巡任務調度與搶佔式任務調度的區別?(大概爲8分吧,記不清了)

2、什麼叫存儲器高速緩存技術,其主要目的?(大概6分)

3、畫出計算機組成的最小邏輯框圖。(哼,這道題竟然10分)

4、談談Volatile與Register修飾符的作用?

1、linux驅動分類

a. 字符設備

b. 塊設備

c.網絡設備

字符設備指那些必須以串行順序依次進行訪問的設備,如觸摸屏、磁帶驅動器、鼠標等。

塊設備可以用任意順序進行訪問,以塊爲單位進行操作,如硬盤、軟驅等。

字符設備不經過系統的快速緩衝,而塊設備經過系統的快速緩衝。但是,字符設備和塊設備並沒有明顯的界限,如對於Flash設備,符合塊設備的特點,但是我們仍然可以把它作爲一個字符設備來訪問。

網絡設備在Linux裏做專門的處理。Linux的網絡系統主要是基於BSD unix的socket 機制。在系統和驅動程序之間定義有專門的數據結構(sk_buff)進行數據的傳遞。系 統裏支持對發送數據和接收數據的緩存,提供流量控制機制,提供對多協議的支持。

2、信號量與自旋鎖

自旋鎖

自旋鎖是專爲防止多處理器併發而引入的一種鎖,它應用於中斷處理等部分。對於單處理器來說,防止中斷處理中的併發可簡單採用關閉中斷的方式,不需要自旋鎖。

自旋鎖最多隻能被一個內核任務持有,如果一個內核任務試圖請求一個已被爭用(已經被持有)的自旋鎖,那麼這個任務就會一直進行忙循環——旋轉——等待鎖重新可用。要是鎖未被爭用,請求它的內核任務便能立刻得到它並且繼續進行。自旋鎖可以在任何時刻防止多於一個的內核任務同時進入臨界區,因此這種鎖可有效地避免多處理器上併發運行的內核任務競爭共享資源。

事實上,自旋鎖的初衷就是:在短期間內進行輕量級的鎖定。一個被爭用的自旋鎖使得請求它的線程在等待鎖重新可用的期間進行自旋(特別浪費處理器時間),所以自旋鎖不應該被持有時間過長。如果需要長時間鎖定的話, 最好使用信號量。但是自旋鎖節省了上下文切換的開銷。

自旋鎖的基本形式如下:

spin_lock(&mr_lock);

//臨界區

spin_unlock(&mr_lock);

因爲自旋鎖在同一時刻只能被最多一個內核任務持有,所以一個時刻只有一個線程允許存在於臨界區中。這點很好地滿足了對稱多處理機器需要的鎖定服務。在單處理器上,自旋鎖僅僅當作一個設置內核搶佔的開關。如果內核搶佔也不存在,那麼自旋鎖會在編譯時被完全剔除出內核。

簡單的說,自旋鎖在內核中主要用來防止多處理器中併發訪問臨界區,防止內核搶佔造成的競爭。另外自旋鎖不允許任務睡眠(持有自旋鎖的任務睡眠會造成自死鎖——因爲睡眠有可能造成持有鎖的內核任務被重新調度,而再次申請自己已持有的鎖),它能夠在中斷上下文中使用。

死鎖:假設有一個或多個內核任務和一個或多個資源,每個內核都在等待其中的一個資源,但所有的資源都已經被佔用了。這便會發生所有內核任務都在相互等待,但它們永遠不會釋放已經佔有的資源,於是任何內核任務都無法獲得所需要的資源,無法繼續運行,這便意味着死鎖發生了。自死瑣是說自己佔有了某個資源,然後自己又申請自己已佔有的資源,顯然不可能再獲得該資源,因此就自縛手腳了。遞歸使用一個自旋鎖就會出現這種情況。

信號量

信號量是一種睡眠鎖。如果有一個任務試圖獲得一個已被持有的信號量時,信號量會將其推入等待隊列,然後讓其睡眠。這時處理器獲得自由去執行其它代碼。當持有信號量的進程將信號量釋放後,在等待隊列中的一個任務將被喚醒,從而便可以獲得這個信號量。

信號量的睡眠特性,使得信號量適用於鎖會被長時間持有的情況;只能在進程上下文中使用,因爲中斷上下文中是不能被調度的;另外當代碼持有信號量時,不可以再持有自旋鎖。

信號量基本使用形式爲:

  static DECLARE_MUTEX(mr_sem);//聲明互斥信號量

  if(down_interruptible(&mr_sem))

   //可被中斷的睡眠,當信號來到,睡眠的任務被喚醒

   //臨界區

   up(&mr_sem);  

信號量和自旋鎖區別

從嚴格意義上講,信號量和自旋鎖屬於不同層次的互斥手段,前者的實現有賴於後者。

注意以下原則:

如果代碼需要睡眠——這往往是發生在和用戶空間同步時——使用信號量是唯一的選擇。由於不受睡眠的限制,使用信號量通常來說更加簡單一些。如果需要在自旋鎖和信號量中作選擇,應該取決於鎖被持有的時間長短。理想情況是所有的鎖都應該儘可能短的被持有,但是如果鎖的持有時間較長的話,使用信號量是更好的選擇。另外,信號量不同於自旋鎖,它不會關閉內核搶佔,所以持有信號量的代碼可以被搶佔。這意味者信號量不會對影響調度反應時間帶來負面影響。

自旋鎖對信號量

需求              建議的加鎖方法

低開銷加鎖           優先使用自旋鎖

短期鎖定            優先使用自旋鎖

長期加鎖            優先使用信號量

中斷上下文中加鎖        使用自旋鎖

持有鎖是需要睡眠、調度     使用信號量

3、platform總線設備及總線設備如何編寫

Linux設備模型(總線、設備、驅動程序和類)【轉】

文章的例子和實驗使用《LDD3》所配的lddbus模塊(稍作修改)。

提示:在學習這部分內容是一定要分析所有介紹的源代碼,知道他們與上一部分內容(kobject、kset、attribute等等)的關係,最好要分析一個實際的“flatform device”設備,不然會只學到表象,到後面會不知所云的。

總線

總線是處理器和一個或多個設備之間的通道,在設備模型中, 所有的設備都通過總線相連, 甚至是內部的虛擬"platform"總線。總線可以相互插入。設備模型展示了總線和它們所控制的設備之間的實際連接。
在 Linux 設備模型中, 總線由 bus_type 結構表示, 定義在 <linux/device.h> :

struct bus_type {
    const char        * name;/*總線類型名稱*/
    struct module        * owner;/*指向模塊的指針(如果有), 此模塊負責操作這個總線*/

struct kset        subsys;/*與該總線相關的子系統*/
struct kset        drivers;/*總線驅動程序的kset*/
struct kset        devices;/* 掛在該總線的所有設備的kset*/

struct klist        klist_devices;/*與該總線相關的驅動程序鏈表*/
struct klist        klist_drivers;/*掛接在該總線的設備鏈表*/

struct blocking_notifier_head bus_notifier;

struct bus_attribute    * bus_attrs; /*總線屬性*/
struct device_attribute * dev_attrs; /*設備屬性,指向爲每個加入總線的設備建立的默認屬性鏈表*/
struct driver_attribute * drv_attrs; /*驅動程序屬性*/
struct bus_attribute drivers_autoprobe_attr;/*驅動自動探測屬性*/
struct bus_attribute drivers_probe_attr;/*驅動探測屬性*/

int        (*match)(struct device * dev, struct device_driver * drv);
int        (*uevent)(struct device *dev, char **envp,
             int num_envp, char *buffer, int buffer_size);
int        (*probe)(struct device * dev);
int        (*remove)(struct device * dev);
void        (*shutdown)(struct device * dev);

int (*suspend)(struct device * dev, pm_message_t state);
int (*suspend_late)(struct device * dev, pm_message_t state);
int (*resume_early)(struct device * dev);
int (*resume)(struct device * dev);

/*處理熱插拔、電源管理、探測和移除等事件的方法*/
    unsigned int drivers_autoprobe:1;
};

在更新的內核裏,這個結構體變得更簡潔了,隱藏了無需驅動編程人員知道的一些成員:

/*in Linux 2.6.26.5*/

struct bus_type {
    const char        *name;
    struct bus_attribute    *bus_attrs;
    struct device_attribute    *dev_attrs;
    struct driver_attribute    *drv_attrs;

int (*match)(struct device *dev, struct device_driver *drv);
int (*uevent)(struct device *dev, struct kobj_uevent_env *env);
int (*probe)(struct device *dev);
int (*remove)(struct device *dev);
void (*shutdown)(struct device *dev);

int (*suspend)(struct device *dev, pm_message_t state);
int (*suspend_late)(struct device *dev, pm_message_t state);
int (*resume_early)(struct device *dev);
int (*resume)(struct device *dev);

struct bus_type_private *p;

};

struct bus_type_private {
    struct kset subsys;
    struct kset *drivers_kset;
    struct kset *devices_kset;
    struct klist klist_devices;
    struct klist klist_drivers;
    struct blocking_notifier_head bus_notifier;
    unsigned int drivers_autoprobe:1;
    struct bus_type *bus;
};

總線的註冊和刪除

總線的主要註冊步驟:

(1)申明和初始化 bus_type 結構體。只有很少的 bus_type 成員需要初始化,大部分都由設備模型核心控制。但必須爲總線指定名字及一些必要的方法。例如:

struct bus_type ldd_bus_type = {
    .name = "ldd",
    .match = ldd_match,
    .uevent = ldd_uevent,
};

(2)調用bus_register函數註冊總線。

int bus_register(struct bus_type * bus)

調用可能失敗, 所以必須始終檢查返回值。若成功,新的總線子系統將被添加進系統,並可在 sysfs 的 /sys/bus 下看到。之後可以向總線添加設備。
例如:

ret = bus_register(&ldd_bus_type);
if (ret)
 return ret;

當必須從系統中刪除一個總線時, 調用:

void bus_unregister(struct bus_type *bus);

總線方法

在 bus_type 結構中定義了許多方法,它們允許總線核心作爲設備核心和單獨的驅動程序之間提供服務的中介,主要介紹以下兩個方法:

int (*match)(struct device * dev, struct device_driver * drv);
/當一個新設備或者驅動被添加到這個總線時,這個方法會被調用一次或多次,若指定的驅動程序能夠處理指定的設備,則返回非零值。必須在總線層使用這個函數, 因爲那裏存在正確的邏輯,核心內核不知道如何爲每個總線類型匹配設備和驅動程序/

int (*uevent)(struct device *dev, char **envp, int num_envp, char *buffer, int buffer_size);
/在爲用戶空間產生熱插拔事件之前,這個方法允許總線添加環境變量(參數和 kset 的uevent方法相同)/

lddbus的match和uevent方法:

static int ldd_match(struct device *dev, struct device_driver *driver)
{
return !strncmp(dev->bus_id, driver->name, strlen(driver->name));
}/僅簡單比較驅動和設備的名字/
/當涉及實際硬件時, match 函數常常對設備提供的硬件 ID 和驅動所支持的 ID 做比較/

static int ldd_uevent(struct device *dev, char **envp, int num_envp, char *buffer, int buffer_size)
{
envp[0] = buffer;
if (snprintf(buffer, buffer_size, “LDDBUS_VERSION=%s”,
Version) >= buffer_size)
return -ENOMEM;
envp[1] = NULL;
return 0;
}/在環境變量中加入 lddbus 源碼的當前版本號/

對設備和驅動的迭代

若要編寫總線層代碼, 可能不得不對所有已經註冊到總線的設備或驅動進行一些操作,這可能需要仔細研究嵌入到 bus_type 結構中的其他數據結構, 但最好使用內核提供的輔助函數:

int bus_for_each_dev(struct bus_type *bus, struct device *start, void *data, int (*fn)(struct device *, void *));
int bus_for_each_drv(struct bus_type *bus, struct device_driver *start, void *data, int (*fn)(struct device_driver *, void *));

/這兩個函數迭代總線上的每個設備或驅動程序, 將關聯的 device 或 device_driver 傳遞給 fn, 同時傳遞 data 值。若 start 爲 NULL, 則從第一個設備開始; 否則從 start 之後的第一個設備開始。若 fn 返回非零值, 迭代停止並且那個值從 bus_for_each_dev 或bus_for_each_drv 返回。/

總線屬性

幾乎 Linux 設備模型中的每一層都提供添加屬性的函數, 總線層也不例外。bus_attribute 類型定義在 <linux/device.h> 如下:

struct bus_attribute {
struct attribute attr;
ssize_t (*show)(struct bus_type *, char * buf);
ssize_t (*store)(struct bus_type *, const char * buf, size_t count);
};

可以看出struct bus_attribute 和struct attribute 很相似,其實大部分在 kobject 級上的設備模型層都是以這種方式工作。

內核提供了一個宏在編譯時創建和初始化 bus_attribute 結構:

BUS_ATTR(_name,_mode,_show,store)/*這個宏聲明一個結構, 將 bus_attr 作爲給定 _name 的前綴來創建總線的真正名稱*/

/總線的屬性必須顯式調用 bus_create_file 來創建:/
int bus_create_file(struct bus_type *bus, struct bus_attribute *attr);

/刪除總線的屬性調用:/
void bus_remove_file(struct bus_type *bus, struct bus_attribute *attr);

例如創建一個包含源碼版本號簡單屬性文件方法如下:

static ssize_t show_bus_version(struct bus_type *bus, char *buf)
{
return snprintf(buf, PAGE_SIZE, “%s\n”, Version);
}

static BUS_ATTR(version, S_IRUGO, show_bus_version, NULL);

/在模塊加載時創建屬性文件:/
if (bus_create_file(&ldd_bus_type, &bus_attr_version))
printk(KERN_NOTICE “Unable to create version attribute\n”);

/這個調用創建一個包含 lddbus 代碼的版本號的屬性文件(/sys/bus/ldd/version)/

設備

在最底層, Linux 系統中的每個設備由一個 struct device 代表:

struct device {
    struct klist        klist_children;
    struct klist_node    knode_parent;   /* node in sibling list */
    struct klist_node    knode_driver;
    struct klist_node    knode_bus;
    struct device        *parent;/* 設備的 "父" 設備,該設備所屬的設備,通常一個父設備是某種總線或者主控制器. 如果 parent 是 NULL, 則該設備是頂層設備,較少見 */

struct kobject kobj;/*代表該設備並將其連接到結構體系中的 kobject; 注意:作爲通用的規則, device->kobj->parent 應等於 device->parent->kobj*/
char    bus_id[BUS_ID_SIZE];/*在總線上唯一標識該設備的字符串;例如: PCI 設備使用標準的 PCI ID 格式, 包含:域, 總線, 設備, 和功能號.*/
struct device_type    *type;
unsigned        is_registered:1;
unsigned        uevent_suppress:1;
struct device_attribute uevent_attr;
struct device_attribute *devt_attr;

struct semaphore    sem;  /* semaphore to synchronize calls to its driver. */
struct bus_type    * bus;     /*標識該設備連接在何種類型的總線上*/
struct device_driver *driver;    /*管理該設備的驅動程序*/
void        *driver_data;    /*該設備驅動使用的私有數據成員*/
void        *platform_data;    /* Platform specific data, device core doesn't touch it */
struct dev_pm_info    power;

#ifdef CONFIG_NUMA
    int        numa_node;   /* NUMA node this device is close to */
#endif
    u64        *dma_mask;    /* dma mask (if dma'able device) */
    u64        coherent_dma_mask;/* Like dma_mask, but for
                     alloc_coherent mappings as
                     not all hardware supports
                     64 bit addresses for consistent
                     allocations such descriptors. */

struct list_head    dma_pools;    /* dma pools (if dma'ble) */

struct dma_coherent_mem    *dma_mem; /* internal for coherent mem override */
/* arch specific additions */
struct dev_archdata    archdata;

spinlock_t        devres_lock;
struct list_head    devres_head;

/* class_device migration path */
struct list_head    node;
struct class        *class;
dev_t          devt;       /* dev_t, creates the sysfs "dev" */
struct attribute_group    **groups;    /* optional groups */

void    (*release)(struct device * dev);/*當這個設備的最後引用被刪除時,內核調用該方法; 它從被嵌入的 kobject 的 release 方法中調用。所有註冊到核心的設備結構必須有一個 release 方法, 否則內核將打印錯誤信息*/

};
/*在註冊 struct device 前,最少要設置parent, bus_id, bus, 和 release 成員*/

設備註冊

設備的註冊和註銷函數爲:
int device_register(struct device *dev);
void device_unregister(struct device *dev);

一個實際的總線也是一個設備,所以必須單獨註冊,以下爲 lddbus 在編譯時註冊它的虛擬總線設備源碼:

static void ldd_bus_release(struct device *dev)
{
printk(KERN_DEBUG “lddbus release\n”);
}

struct device ldd_bus = {
.bus_id = “ldd0”,
.release = ldd_bus_release

}; /這是頂層總線,parent 和 bus 成員爲 NULL/

/作爲第一個(並且唯一)總線, 它的名字爲 ldd0,這個總線設備的註冊代碼如下:/
ret = device_register(&ldd_bus);
if (ret)
printk(KERN_NOTICE “Unable to register ldd0\n”);
/一旦調用完成, 新總線會在 sysfs 中 /sys/devices 下顯示,任何掛到這個總線的設備會在 /sys/devices/ldd0 下顯示/

設備屬性

sysfs 中的設備入口可有屬性,相關的結構是:

/* interface for exporting device attributes 這個結構體和《LDD3》中的不同,已經被更新過了,請特別注意!*/
struct device_attribute {
struct attribute attr;
ssize_t (*show)(struct device *dev, struct device_attribute *attr,char *buf);
ssize_t (*store)(struct device *dev, struct device_attribute *attr, const char *buf, size_t count);
};

/設備屬性結構可在編譯時建立, 使用以下宏:/
DEVICE_ATTR(_name,_mode,_show,store);
/*這個宏聲明一個結構, 將 dev_attr
作爲給定 _name 的前綴來命名設備屬性

/屬性文件的實際處理使用以下函數:/
int device_create_file(struct device *device, struct device_attribute * entry);
void device_remove_file(struct device * dev, struct device_attribute * attr);

設備結構的嵌入

device 結構包含設備模型核心用來模擬系統的信息。但大部分子系統記錄了關於它們又擁有的設備的額外信息,所以很少單純用 device 結構代表設備,而是,通常將其嵌入一個設備的高層表示中。底層驅動幾乎不知道 struct device。

lddbus 驅動創建了它自己的 device 類型,並期望每個設備驅動使用這個類型來註冊它們的設備:

struct ldd_device {
char *name;
struct ldd_driver *driver;
struct device dev;
};
#define to_ldd_device(dev) container_of(dev, struct ldd_device, dev);

lddbus 導出的註冊和註銷接口如下:

/*

  • LDD devices.
    */

/*

  • For now, no references to LDDbus devices go out which are not
  • tracked via the module reference count, so we use a no-op
  • release function.
    */
    static void ldd_dev_release(struct device *dev)
    { }

int register_ldd_device(struct ldd_device *ldddev)
{
ldddev->dev.bus = &ldd_bus_type;
ldddev->dev.parent = &ldd_bus;
ldddev->dev.release = ldd_dev_release;
strncpy(ldddev->dev.bus_id, ldddev->name, BUS_ID_SIZE);
return device_register(&ldddev->dev);
}
EXPORT_SYMBOL(register_ldd_device);

void unregister_ldd_device(struct ldd_device *ldddev)
{
device_unregister(&ldddev->dev);
}
EXPORT_SYMBOL(unregister_ldd_device);

sculld 驅動添加一個自己的屬性到它的設備入口,稱爲 dev, 僅包含關聯的設備號,源碼如下:

static ssize_t sculld_show_dev(struct device *ddev,struct device_attribute *attr, char *buf)
{
struct sculld_dev *dev = ddev->driver_data;
return print_dev_t(buf, dev->cdev.dev);
}

static DEVICE_ATTR(dev, S_IRUGO, sculld_show_dev, NULL);

/接着, 在初始化時間, 設備被註冊, 並且 dev 屬性通過下面的函數被創建:/
static void sculld_register_dev(struct sculld_dev *dev, int index)
{
sprintf(dev->devname, “sculld%d”, index);
dev->ldev.name = dev->devname;
dev->ldev.driver = &sculld_driver;
dev->ldev.dev.driver_data = dev;
register_ldd_device(&dev->ldev);
if (device_create_file(&dev->ldev.dev, &dev_attr_dev))
printk( “Unable to create dev attribute ! \n”);
} /注意:程序使用 driver_data 成員來存儲指向我們自己的內部的設備結構的指針。請檢查device_create_file的返回值,否則編譯時會有警告。/

設備驅動程序

設備模型跟蹤所有系統已知的驅動,主要目的是使驅動程序核心能協調驅動和新設備之間的關係。一旦驅動在系統中是已知的對象就可能完成大量的工作。驅動程序的結構體 device_driver 定義如下:

/定義在<linux/device.h>/

struct device_driver {
    const char        * name;/*驅動程序的名字( 在 sysfs 中出現 )*/
    struct bus_type        * bus;/*驅動程序所操作的總線類型*/

struct kobject        kobj;/*內嵌的kobject對象*/
struct klist        klist_devices;/*當前驅動程序能操作的設備鏈表*/
struct klist_node    knode_bus;

struct module        * owner;
const char         * mod_name;    /* used for built-in modules */
struct module_kobject    * mkobj;

int    (*probe)    (struct device * dev);/*查詢一個特定設備是否存在及驅動是否可以使用它的函數*/
int    (*remove)    (struct device * dev);/*將設備從系統中刪除*/
void    (*shutdown)    (struct device * dev);/*關閉設備*/
int    (*suspend)    (struct device * dev, pm_message_t state);
int    (*resume)    (struct device * dev);

};

/註冊device_driver 結構的函數是:/
int driver_register(struct device_driver *drv);
void driver_unregister(struct device_driver *drv);

/driver的屬性結構在:/
struct driver_attribute {
struct attribute attr;
ssize_t (*show)(struct device_driver *drv, char *buf);
ssize_t (*store)(struct device_driver *drv, const char *buf, size_t count);
};
DRIVER_ATTR(_name,_mode,_show,_store)

/屬性文件創建的方法:/
int driver_create_file(struct device_driver * drv, struct driver_attribute * attr);
void driver_remove_file(struct device_driver * drv, struct driver_attribute * attr);

/bus_type 結構含有一個成員( drv_attrs ) 指向一組爲屬於該總線的所有設備創建的默認屬性/

在更新的內核裏,這個結構體變得更簡潔了,隱藏了無需驅動編程人員知道的一些成員:

/*in Linux 2.6.26.5*/

struct device_driver {
    const char        *name;
    struct bus_type        *bus;

struct module        *owner;
const char         *mod_name;    /* used for built-in modules */

int (*probe) (struct device *dev);
int (*remove) (struct device *dev);
void (*shutdown) (struct device *dev);
int (*suspend) (struct device *dev, pm_message_t state);
int (*resume) (struct device *dev);
struct attribute_group **groups;

struct driver_private *p;

};


struct driver_private {
    struct kobject kobj;
    struct klist klist_devices;
    struct klist_node knode_bus;
    struct module_kobject *mkobj;
    struct device_driver *driver;
};
#define to_driver(obj) container_of(obj, struct driver_private, kobj)

驅動程序結構的嵌入

對大多數驅動程序核心結構, device_driver 結構通常被嵌入到一個更高層的、總線相關的結構中。

以lddbus 子系統爲例,它定義了ldd_driver 結構:

struct ldd_driver {
char *version;
struct module *module;
struct device_driver driver;
struct driver_attribute version_attr;
};
#define to_ldd_driver(drv) container_of(drv, struct ldd_driver, driver);

lddbus總線中相關的驅動註冊和註銷函數是:

/*
 * Crude driver interface.
 */
static ssize_t show_version(struct device_driver *driver, char *buf)
{
    struct ldd_driver *ldriver = to_ldd_driver(driver);
    sprintf(buf, "%s\n", ldriver->version);
    return strlen(buf);
}

int register_ldd_driver(struct ldd_driver *driver)
{
 int ret;
 driver->driver.bus = &ldd_bus_type;
 ret = driver_register(&driver->driver);/*註冊底層的 device_driver 結構到核心*/
 if (ret)
 return ret;
 driver->version_attr.attr.name = "version";/* driver_attribute 結構必須手工填充*/
 driver->version_attr.attr.owner = driver->module;/*注意:設定 version 屬性的擁有者爲驅動模塊, 不是 lddbus 模塊!因爲 show_version 函數是使用驅動模塊所創建的 ldd_driver 結構,若 ldd_driver 結構在一個用戶空間進程試圖讀取版本號時已經註銷,就會出錯*/
 driver->version_attr.attr.mode = S_IRUGO;
 driver->version_attr.show = show_version;
 driver->version_attr.store = NULL;
 return driver_create_file(&driver->driver, &driver->version_attr);/*建立版本屬性,因爲這個屬性在運行時被創建,所以不能使用 DRIVER_ATTR 宏*/
}

void unregister_ldd_driver(struct ldd_driver *driver)
{
    driver_unregister(&driver->driver);
}
EXPORT_SYMBOL(register_ldd_driver);
EXPORT_SYMBOL(unregister_ldd_driver);

在sculld 中創建的 ldd_driver 結構如下:

/* Device model stuff */
static struct ldd_driver sculld_driver = {
    .version = "$Revision: 1.21 $",
    .module = THIS_MODULE,
    .driver = {
        .name = "sculld",
    },
};/*只要一個簡單的 register_ldd_driver 調用就可添加它到系統中。一旦完成初始化, 驅動信息可在 sysfs 中顯示*/

類 子系統

類是一個設備的高層視圖, 它抽象出了底層的實現細節,從而允許用戶空間使用設備所提供的功能, 而不用關心設備是如何連接和工作的。類成員通常由上層代碼所控制, 而無需驅動的明確支持。但有些情況下驅動也需要直接處理類。

幾乎所有的類都顯示在 /sys/class 目錄中。出於歷史的原因,有一個例外:塊設備顯示在 /sys/block目錄中。在許多情況, 類子系統是向用戶空間導出信息的最好方法。當類子系統創建一個類時, 它將完全擁有這個類,根本不用擔心哪個模塊擁有那些屬性,而且信息的表示也比較友好。

爲了管理類,驅動程序核心導出了一些接口,其目的之一是提供包含設備號的屬性以便自動創建設備節點,所以udev的使用離不開類。 類函數和結構與設備模型的其他部分遵循相同的模式,所以真正嶄新的概念是很少的。

注意:class_simple 是老接口,在2.6.13中已被刪除,這裏不再研究。

管理類的接口

類由 struct class 的結構體來定義:

/*
 * device classes
 */
struct class {
    const char        * name;/*每個類需要一個唯一的名字, 它將顯示在 /sys/class 中*/
    struct module        * owner;

    struct kset        subsys;
    struct list_head    children;
    struct list_head    devices;
    struct list_head    interfaces;
    struct kset        class_dirs;
    struct semaphore    sem;    /* locks both the children and interfaces lists */

    struct class_attribute        * class_attrs;/* 指向類屬性的指針(以NULL結尾) */
    struct class_device_attribute    * class_dev_attrs;/* 指向類中每個設備的一組默認屬性的指針 */
    struct device_attribute        * dev_attrs;

    int    (*uevent)(struct class_device *dev, char **envp,
             int num_envp, char *buffer, int buffer_size);/* 類熱插拔產生時添加環境變量的函數 */
    int    (*dev_uevent)(struct device *dev, char **envp, int num_envp,
                char *buffer, int buffer_size);/* 類中的設備熱插拔時添加環境變量的函數 */

    void    (*release)(struct class_device *dev);/* 把設備從類中刪除的函數 */
    void    (*class_release)(struct class *class);/* 刪除類本身的函數 */
    void    (*dev_release)(struct device *dev);

    int    (*suspend)(struct device *, pm_message_t state);
    int    (*resume)(struct device *);
};


/*類註冊函數:*/
int class_register(struct class *cls);
void class_unregister(struct class *cls);

/*類屬性的接口:*/
struct class_attribute {
 struct attribute attr;
 ssize_t (*show)(struct class *cls, char *buf);
 ssize_t (*store)(struct class *cls, const char *buf, size_t count); 
}; 
CLASS_ATTR(_name,_mode,_show,_store); 
int class_create_file(struct class *cls, const struct class_attribute *attr);void class_remove_file(struct class *cls, const struct class_attribute *attr);

在更新的內核裏,這個結構體變得簡潔了,刪除了一些成員:

/*in Linux 2.6.26.5*/

/*
 * device classes
 */
struct class {
    const char        *name;
    struct module        *owner;

    struct kset        subsys;
    struct list_head    devices;
    struct list_head    interfaces;
    struct kset        class_dirs;
    struct semaphore    sem; /* locks children, devices, interfaces */
    struct class_attribute        *class_attrs;
    struct device_attribute        *dev_attrs;

    int (*dev_uevent)(struct device *dev, struct kobj_uevent_env *env);

    void (*class_release)(struct class *class);
    void (*dev_release)(struct device *dev);

    int (*suspend)(struct device *dev, pm_message_t state);
    int (*resume)(struct device *dev);
};

類設備(在新內核中已被刪除)

類存在的真正目的是給作爲類成員的各個設備提供一個容器,成員由 struct class_device 來表示:

struct class_device {
    struct list_head    node;/*for internal use by the driver core only*/
    struct kobject        kobj;/*for internal use by the driver core only*/
    struct class        * class;    /* 指向該設備所屬的類,必須*/
    dev_t            devt;        /* dev_t, creates the sysfs "dev" ,for internal use by the driver core only*/
    struct class_device_attribute *devt_attr;/*for internal use by the driver core only*/
    struct class_device_attribute uevent_attr;
    struct device        * dev;        /* 指向此設備相關的 device 結構體,可選。若不爲NULL,應是一個從類入口到/sys/devices 下相應入口的符號連接,以便用戶空間查找設備入口*/
    void            * class_data;    /* 私有數據指針 */
    struct class_device    *parent;    /* parent of this child device, if there is one */
    struct attribute_group ** groups;    /* optional groups */

    void    (*release)(struct class_device *dev);
    int    (*uevent)(struct class_device *dev, char **envp,
             int num_envp, char *buffer, int buffer_size);
    char    class_id[BUS_ID_SIZE];    /* 此類中的唯一的名字 */
};

/*類設備註冊函數:*/
int class_device_register(struct class_device *cd);
void class_device_unregister(struct class_device *cd);

/*重命名一個已經註冊的類設備入口:*/
int class_device_rename(struct class_device *cd, char *new_name); 

/*類設備入口屬性:*/
struct class_device_attribute {
 struct attribute attr;
 ssize_t (*show)(struct class_device *cls, char *buf);
 ssize_t (*store)(struct class_device *cls, const char *buf,
 size_t count);
};

CLASS_DEVICE_ATTR(_name, _mode, _show, _store); 

/*創建和刪除除struct class中設備默認屬性外的屬性*/
int class_device_create_file(struct class_device *cls, const struct class_device_attribute *attr);
void class_device_remove_file(struct class_device *cls, const struct class_device_attribute *attr);

類接口

類子系統有一個 Linux 設備模型的其他部分找不到的附加概念,稱爲“接口”, 可將它理解爲一種設備加入或離開類時獲得信息的觸發機制,結構體如下:
struct class_interface {
    struct list_head    node;
    struct class        *class;/* 指向該接口所屬的類*/

    int (*add) (struct class_device *, struct class_interface *);

 /*當一個類設備被加入到在 class_interface 結構中指定的類時, 將調用接口的 add 函數,進行一些設備需要的額外設置,通常是添加更多屬性或其他的一些工作*/
    void (*remove)    (struct class_device *, struct class_interface *);/*一個接口的功能是簡單明瞭的. 當設備從類中刪除, 將調用remove 方法來進行必要的清理*/
    int (*add_dev)     (struct device *, struct class_interface *);
    void (*remove_dev) (struct device *, struct class_interface *);
};

/*註冊或註銷接口的函數:*/
int class_interface_register(struct class_interface *class_intf);
void class_interface_unregister(struct class_interface *class_intf);
/*一個類可註冊多個接口*/

Linux設備驅動模型之platform總線

1 平臺設備和驅動初識

platform是一個虛擬的地址總線,相比pci,usb,它主要用於描述SOC上的片上資源,比如s3c2410上集成的控制器(lcd,watchdog,rtc等),platform所描述的資源有一個共同點,就是在cpu的總線上直接取址。

平臺設備會分到一個名稱(用在驅動綁定中)以及一系列諸如地址和中斷請求號(IRQ)之類的資源.
struct platform_device {
const char * name;
int id;
struct device dev;
u32 num_resources;
struct resource * resource;
};

平臺驅動遵循標準驅動模型的規範, 也就是說發現/列舉(discovery/enumeration)在驅動之外處理, 而
由驅動提供probe()和remove方法. 平臺驅動按標準規範對電源管理和關機通告提供支持
struct platform_driver {
int (*probe)(struct platform_device *);
int (*remove)(struct platform_device *);
void (*shutdown)(struct platform_device *);
int (*suspend)(struct platform_device *, pm_message_t state);
int (*suspend_late)(struct platform_device *, pm_message_t state);
int (*resume_early)(struct platform_device *);
int (*resume)(struct platform_device *);
struct device_driver driver;
};
probe()總應該覈實指定的設備硬件確實存在;平臺設置代碼有時不能確定這一點. 枚舉(probing)可以使用的設備資源包括時鐘及設備的platform_data.(譯註: platform_data定義在device.txt中的"基本設備結構體"中.)

平臺驅動通過普通的方法註冊自身
int platform_driver_register(struct platform_driver *drv);

或者, 更常見的情況是已知設備不可熱插拔, probe()過程便可以駐留在一個初始化區域(init section)
中,以便減少驅動的運行時內存佔用(memory footprint)
int platform_driver_probe(struct platform_driver *drv, int (*probe)(struct platform_device *));

設備列舉
按規定, 應由針對平臺(也適用於針對板)的設置代碼來註冊平臺設備
int platform_device_register(struct platform_device *pdev);
int platform_add_devices(struct platform_device **pdevs, int ndev)

一般的規則是隻註冊那些實際存在的設備, 但也有例外. 例如, 某外部網卡未必會裝配在所有的板子上,
或者某集成控制器所在的板上可能沒掛任何外設, 而內核卻需要被配置來支持這些網卡和控制器

有些情況下, 啓動固件(boot firmware)會導出一張裝配到板上的設備的描述表. 如果沒有這張表, 通常
就只能通過編譯針對目標板的內核來讓系統設置代碼安裝正確的設備了. 這種針對板的內核在嵌入式和自定
義的系統開發中是比較常見的.

多數情況下, 分給平臺設備的內存和中斷請求號資源是不足以讓設備正常工作的. 板設置代碼通常會用設備
的platform_data域來存放附加信息, 並向外提供它們.

嵌入式系統時常需要爲平臺設備提供一個或多個時鐘信號. 除非被用到, 這些時鐘一般處於靜息狀態以節電.
系統設置代碼也負責爲設備提供這些時鐘, 以便設備能在它們需要是調用

clk_get(&pdev->dev, clock_name).

也可以用如下函數來一次性完成分配空間和註冊設備的任務
struct platform_device *platform_device_register_simple( const char *name, int id, struct resource *res, unsigned int nres)

設備命名和驅動綁定
platform_device.dev.bus_id是設備的真名. 它由兩部分組成:

*platform_device.name … 這也被用來匹配驅動

*platform_device.id … 設備實例號, 或者用"-1"表示只有一個設備.

連接這兩項, 像"serial"/0就表示bus_id爲"serial.0", “serial”/3表示bus_id爲"serial.3";
上面二例都將使用名叫"serial"的平臺驅動. 而"my_rtc"/-1的bus_id爲"my_rtc"(無實例號), 它的
平臺驅動爲"my_rtc".

2 平臺總線
下面我們看看與platform相關的操作
平臺總線的初始化

int __init platform_bus_init(void)
{
    int error;

    error = device_register(&platform_bus);
    if (error)
        return error;
    error =  bus_register(&platform_bus_type);
    if (error)
        device_unregister(&platform_bus);
    return error;
}

這段初始化代碼創建了一個platform設備,以後屬於platform類型的設備就會以此爲parent,增加的設備會出現在/sys/devices/platform目錄下

[root@wangping platform]# pwd
/sys/devices/platform
[root@wangping platform]# ls
bluetooth floppy.0 i8042 pcspkr power serial8250 uevent vesafb.0

緊接着註冊名爲platform的平臺總線
struct bus_type platform_bus_type = {
.name = “platform”,
.dev_attrs = platform_dev_attrs,
.match = platform_match,
.uevent = platform_uevent,
.suspend = platform_suspend,
.suspend_late = platform_suspend_late,
.resume_early = platform_resume_early,
.resume = platform_resume,
};

int platform_device_add(struct platform_device *pdev)
{…
pdev->dev.parent = &platform_bus; //增加的platform設備以後都以platform_bus(platform設備)爲父節點
pdev->dev.bus = &platform_bus_type; //platform類型設備都掛接在platform總線上 /sys/bus/platform/

}

3 platform device的註冊

struct platform_device {
const char * name;
int id;
struct device dev;
u32 num_resources;
struct resource * resource;
};

1)動態分配一個名爲name的platform設備

struct platform_object {
    struct platform_device pdev;
    char name[1];
};

struct platform_device *platform_device_alloc(const char *name, int id)
{
    struct platform_object *pa;

    pa = kzalloc(sizeof(struct platform_object) + strlen(name), GFP_KERNEL);//由於 platform_object內name只有一個字節,所以需要多分配strlen(name)長度
    if (pa) {
        strcpy(pa->name, name);
        pa->pdev.name = pa->name;
        pa->pdev.id = id;
        device_initialize(&pa->pdev.dev);
        pa->pdev.dev.release = platform_device_release;
    }

    return pa ? &pa->pdev : NULL;
}

實際上就是分配一個platform_object 結構體(包含了一個platform device結構體)並初始化內部成員platform driver,然後返回platform driver結構體以完成動態分配一個platform設備
然後調用platform_add_devices()以追加一個platform 設備到platform bus上

int platform_device_add(struct platform_device *pdev)
{
    int i, ret = 0;

    if (!pdev)
        return -EINVAL;

    if (!pdev->dev.parent)
        pdev->dev.parent = &platform_bus; //初始化設備的父節點所屬類型爲platform device(platform_bus)

    pdev->dev.bus = &platform_bus_type;  //初始化設備的總線爲platform bus

    if (pdev->id != -1)
        snprintf(pdev->dev.bus_id, BUS_ID_SIZE, "%s.%d", pdev->name,
             pdev->id);
    else
        strlcpy(pdev->dev.bus_id, pdev->name, BUS_ID_SIZE);

    for (i = 0; i < pdev->num_resources; i++) {
        struct resource *p, *r = &pdev->resource[i];

        if (r->name == NULL)
            r->name = pdev->dev.bus_id;

        p = r->parent;
        if (!p) {
            if (r->flags & IORESOURCE_MEM)
                p = &iomem_resource;
            else if (r->flags & IORESOURCE_IO)
                p = &ioport_resource;
        }

        if (p && insert_resource(p, r)) {                //插入資源到資源樹上
            printk(KERN_ERR
                   "%s: failed to claim resource %d\n",
                   pdev->dev.bus_id, i);
            ret = -EBUSY;
            goto failed;
        }
    }

    pr_debug("Registering platform device '%s'. Parent at %s\n",
         pdev->dev.bus_id, pdev->dev.parent->bus_id);

    ret = device_add(&pdev->dev);       //註冊特定的設備到platform bus上
    if (ret == 0)
        return ret;

 failed:
    while (--i >= 0)
        if (pdev->resource[i].flags & (IORESOURCE_MEM|IORESOURCE_IO))
            release_resource(&pdev->resource[i]);
    return ret;
}

上面的操作我們看到另外一個陌生的結構 設備資源(struct resource)
關於資源的操作(從上面已經瞭解,平臺設備會分到一系列諸如地址和中斷請求號(IRQ)之類的資源.
struct resource {
resource_size_t start;
resource_size_t end;
const char *name;
unsigned long flags;// IORESOURCE_IO IORESOURCE_MEM IORESOURCE_IRQ IORESOURCE_DMA
struct resource *parent, *sibling, *child;
};

基於資源的分類(flags)有I/O端口、IRQ、DMA等等,而I/O端口又分爲2種類型, IORESOURCE_IO(I/O映射) IORESOURCE_MEM(內存映射)

這裏說一下關於I/O端口:
CPU對外設IO端口物理地址的編址方式有2種:一種是IO映射方式(IO-mapped), 另一種是內存映射方式(Memory-mapped)。具體採用哪一種方式則取決於CPU的體系結構。 像X86體系對外設就專門實現了一個單獨地址空間,並且有專門的I/O指令來訪問I/O端口,像ARM體系結構通常只是實現一個物理地址空間,I/O端口就被映射到CPU的單一物理地址空間中,而成爲內存的一部分,所以一般資源都採用(IORESOURCE_MEM)。

linux中對設備的資源按照資源樹的結構來組織(其實就是一個鏈表結構的插入、刪除、查找等操作),上面再添加設備(platform_device_add)的同時對相應的資源在資源樹上進行插入操作int insert_resource(struct resource *parent, struct resource *new)

關於platform resource有相關的函數進行對資源的操作。

struct resource *platform_get_resource(struct platform_device *, unsigned int, unsigned int);
int platform_get_irq(struct platform_device *, unsigned int);
struct resource *platform_get_resource_byname(struct platform_device *, unsigned int, char *);
int platform_get_irq_byname(struct platform_device *, char *);

例如s3c24210的watchdog資源分配實例:
watchdog寄存器的基地址爲0x5300000
#define S3C2410_PA_WATCHDOG (0x53000000)
#define S3C24XX_SZ_WATCHDOG SZ_1M

static struct resource s3c_wdt_resource[] = {
[0] = {
.start = S3C24XX_PA_WATCHDOG,
.end = S3C24XX_PA_WATCHDOG + S3C24XX_SZ_WATCHDOG - 1,
.flags = IORESOURCE_MEM, //內存映射
},
[1] = {
.start = IRQ_WDT,
.end = IRQ_WDT,
.flags = IORESOURCE_IRQ, //IRQ
}

};

動態註冊platform device例:
/linux/drivers/serial/8250.c

static int __init serial8250_init(void)
{
    int ret, i;

    if (nr_uarts > UART_NR)
        nr_uarts = UART_NR;

    printk(KERN_INFO "Serial: 8250/16550 driver $Revision: 1.90 $ "
        "%d ports, IRQ sharing %sabled\n", nr_uarts,
        share_irqs ? "en" : "dis");

    for (i = 0; i < NR_IRQS; i++)
        spin_lock_init(&irq_lists[i].lock);

    ret = uart_register_driver(&serial8250_reg);
    if (ret)
        goto out;

    serial8250_isa_devs = platform_device_alloc("serial8250",
                            PLAT8250_DEV_LEGACY);
    if (!serial8250_isa_devs) {
        ret = -ENOMEM;
        goto unreg_uart_drv;
    }

    ret = platform_device_add(serial8250_isa_devs);
    if (ret)
        goto put_dev;

    serial8250_register_ports(&serial8250_reg, &serial8250_isa_devs->dev);

    ret = platform_driver_register(&serial8250_isa_driver);
    if (ret == 0)
        goto out;

    platform_device_del(serial8250_isa_devs);
 put_dev:
    platform_device_put(serial8250_isa_devs);
 unreg_uart_drv:
    uart_unregister_driver(&serial8250_reg);
 out:
    return ret;
}

也可以在編譯的時候就確定設備的相關信息,調用 int platform_device_register(struct platform_device *);

/linux/arch/arm/mach-smdk2410/mach-smdk2410.c

static struct platform_device *smdk2410_devices[] __initdata = {
    &s3c_device_usb,
    &s3c_device_lcd,
    &s3c_device_wdt,
    &s3c_device_i2c,
    &s3c_device_iis,
};

static void __init smdk2410_init(void)
{
    platform_add_devices(smdk2410_devices, ARRAY_SIZE(smdk2410_devices)); //靜態增加一組soc設備,以便在加載驅動的時候匹配相關驅動
    smdk_machine_init();
}

int platform_add_devices(struct platform_device **devs, int num)
{
    int i, ret = 0;

    for (i = 0; i < num; i++) {
        ret = platform_device_register(devs[i]);  //實際上是調用 platform_device_register追加platform device
        if (ret) {
            while (--i >= 0)
                platform_device_unregister(devs[i]);
            break;
        }
    }

    return ret;
}

int platform_device_register(struct platform_device * pdev)
{
    device_initialize(&pdev->dev);
    return platform_device_add(pdev);
}

從上面看出這和動態增加一個platform device所做的動作基本上是一樣的(device_initialize,platform_device_add)

例 watchdog設備定義:
struct platform_device s3c_device_wdt = {
.name = “s3c2410-wdt”,
.id = -1,
.num_resources = ARRAY_SIZE(s3c_wdt_resource),
.resource = s3c_wdt_resource,
};

4 platform driver的註冊
先看結構體,裏面內嵌了一個
struct platform_driver {
int (*probe)(struct platform_device *);
int (*remove)(struct platform_device *);
void (*shutdown)(struct platform_device *);
int (*suspend)(struct platform_device *, pm_message_t state);
int (*suspend_late)(struct platform_device *, pm_message_t state);
int (*resume_early)(struct platform_device *);
int (*resume)(struct platform_device *);
struct device_driver driver;
};

int platform_driver_register(struct platform_driver *drv)
{
drv->driver.bus = &platform_bus_type;
if (drv->probe)
drv->driver.probe = platform_drv_probe;
if (drv->remove)
drv->driver.remove = platform_drv_remove;
if (drv->shutdown)
drv->driver.shutdown = platform_drv_shutdown;
if (drv->suspend)
drv->driver.suspend = platform_drv_suspend;
if (drv->resume)
drv->driver.resume = platform_drv_resume;
return driver_register(&drv->driver);
}

指定platform device所屬總線,同時如果爲platform_driver中各項指定了接口,則爲struct device_driver中相應的接口賦值。
那麼是如何賦值的呢?

#define to_platform_driver(drv)    (container_of((drv), struct platform_driver, driver))

static int platform_drv_probe(struct device *_dev)
{
    struct platform_driver *drv = to_platform_driver(_dev->driver);
    struct platform_device *dev = to_platform_device(_dev);

    return drv->probe(dev);
}

從上面可以看出,是將struct device轉換爲struct platform_device和struct platform_driver.然後調用platform_driver中的相應接口函數來實現,
最後調用 driver_register()將platform driver註冊到總線上。

/linux/drivers/serial/8250.c
static int __init serial8250_init(void)
{
    ......
    serial8250_isa_devs = platform_device_alloc("serial8250",
                            PLAT8250_DEV_LEGACY);
    if (!serial8250_isa_devs) {
        ret = -ENOMEM;
        goto unreg_uart_drv;
    }

    ret = platform_device_add(serial8250_isa_devs);
    if (ret)
        goto put_dev;

    serial8250_register_ports(&serial8250_reg, &serial8250_isa_devs->dev);

    ret = platform_driver_register(&serial8250_isa_driver);
    if (ret == 0)
        goto out;

    ......
}

在設備成功進行了註冊後,調用platform_driver_register()進行驅動註冊。

最後,總線上註冊有設備和相應的驅動,就會進行設備和驅動的匹配。
在找到一個設備和驅動的配對後, 驅動綁定是通過調用probe()由驅動核心自動完成的. 如果probe()成功,
驅動和設備就正常綁定了. 有三種不同的方法來進行配對:

-設備一被註冊, 就檢查對應總線下的各驅動, 看是否匹配. 平臺設備應在系統啓動過程的早期被註冊

-當驅動通過platform_driver_register()被註冊時, 就檢查對應總線上所有未綁定的設備.驅動通常在啓動過程的後期被註冊或通過裝載模塊來註冊.

-用platform_driver_probe()來註冊驅動的效果跟用platform_driver_register()幾乎相同, 不同點僅在於,如果再有設備註冊, 驅動就不會再被枚舉了. (這無關緊要, 因爲這種接口只用在不可熱插拔的設備上.)
驅動和設備的匹配僅僅是通過名稱來匹配的

static int platform_match(struct device * dev, struct device_driver * drv)
{
    struct platform_device *pdev = container_of(dev, struct platform_device, dev);

    return (strncmp(pdev->name, drv->name, BUS_ID_SIZE) == 0);
}

小結:本節總結平臺設備和驅動的模型,這部份知識可以作爲我們深入瞭解具體平臺設備驅動的基礎。

BUS

在設備模型中,所有的device都是通過總線bus 連接,這裏的bus包括通常意義的總線如usb,pci,也包括虛擬的platform總線。

[root@wangp bus]# pwd

/sys/bus

[root@wangp bus]# ls

ac97  acpi  bluetooth  gameport  i2c  ide  pci  pci_express  pcmcia  platform  pnp  scsi  serio  usb

[root@wangp platform]# pwd

/sys/bus/platform

[root@wangp platform]# ls

devices  drivers

 

struct bus_type {

       const char              * name;

       struct module         * owner;

 

       struct kset             subsys;

       struct kset             drivers;

       struct kset             devices;

       struct klist              klist_devices;

       struct klist              klist_drivers;

 

       struct blocking_notifier_head bus_notifier;

 

       struct bus_attribute * bus_attrs;

       struct device_attribute    * dev_attrs;

       struct driver_attribute     * drv_attrs;

 

       int           (*match)(struct device * dev, struct device_driver * drv);

       int           (*uevent)(struct device *dev, struct kobj_uevent_env *env);

       int           (*probe)(struct device * dev);

       int           (*remove)(struct device * dev);

       void         (*shutdown)(struct device * dev);

 

       int (*suspend)(struct device * dev, pm_message_t state);

       int (*suspend_late)(struct device * dev, pm_message_t state);

       int (*resume_early)(struct device * dev);

       int (*resume)(struct device * dev);

 

       unsigned int drivers_autoprobe:1;

};

name是總線的名字,每個總線下都有自己的子系統,其中包含2個kset,deviece和driver,分別代表已知總線的驅動和插入總線的設備

如platform總線的聲明如下:

struct bus_type platform_bus_type = {

       .name             = "platform",

       .dev_attrs       = platform_dev_attrs,

       .match            = platform_match,

       .uevent          = platform_uevent,

       .suspend  = platform_suspend,

       .suspend_late  = platform_suspend_late,

       .resume_early  = platform_resume_early,

       .resume          = platform_resume,

};

 

只有很少的bus_type成員需要初始化,大部分交給kernel來處理

關於總線的操作常用的如下:

int bus_register(struct bus_type * bus);

void bus_unregister(struct bus_type * bus);

/* iterator helpers for buses */

列舉總線上從start之後的每個設備,並進行fn操作,通常用途是對bus上的設備和驅動進行綁定

int bus_for_each_dev(struct bus_type * bus, struct device * start, void * data, int (*fn)(struct device *, void *));

 

int driver_attach(struct device_driver * drv)

{

       return bus_for_each_dev(drv->bus, NULL, drv, __driver_attach);

}

 

static int __driver_attach(struct device * dev, void * data)

{

       struct device_driver * drv = data;

 

       /*

        * Lock device and try to bind to it. We drop the error

        * here and always return 0, because we need to keep trying

        * to bind to devices and some drivers will return an error

        * simply if it didn't support the device.

        *

        * driver_probe_device() will spit a warning if there

        * is an error.

        */

 

       if (dev->parent)      /* Needed for USB */

              down(&dev->parent->sem);

       down(&dev->sem);

       if (!dev->driver)

              driver_probe_device(drv, dev);

       up(&dev->sem);

       if (dev->parent)

              up(&dev->parent->sem);

 

       return 0;

}

幾乎linux設備模型的每一層都提供了添加屬性的函數,總線也不例外

struct bus_attribute {

       struct attribute       attr;

       ssize_t (*show)(struct bus_type *, char * buf); //顯示屬性

       ssize_t (*store)(struct bus_type *, const char * buf, size_t count); //設置屬性

};

創建屬於一個總線的屬性使用(在模塊的加載時間完成)

int bus_create_file(struct bus_type *,struct bus_attribute *);

void bus_remove_file(struct bus_type *, struct bus_attribute *);

在說明下bus在sysfs裏面的結構,剛纔已經講過,bus_type中有2個kset結構對應於device和driver,也就是說每個bus下面都會有device何driver2個文件夾。

首先在總線上註冊的驅動會得到一個文件夾driver,如platform驅動

[root@wangp platform]# pwd

/sys/bus/platform

[root@wangp platform]# ls

devices drivers

[root@wangp drivers]# pwd

/sys/bus/platform/drivers

[root@wangp drivers]# ls

i8042 pcspkr serial8250 vesafb

而任何在總線/sys/bus/xxx/上發現的設備會得到一個symlink(符號鏈接)即/sys/bus/xxx/device指向/sys/device/xxx下面的文件夾

[root@wangp devices]# pwd

/sys/bus/platform/devices

[root@wangp devices]# ls -l

total 0

lrwxrwxrwx 1 root root 0 Jun  6 10:37 bluetooth -> ../../../devices/platform/bluetooth

lrwxrwxrwx 1 root root 0 Jun  6 10:37 floppy.0 -> ../../../devices/platform/floppy.0

lrwxrwxrwx 1 root root 0 Jun  6 10:37 i8042 -> ../../../devices/platform/i8042

lrwxrwxrwx 1 root root 0 Jun  6 10:37 pcspkr -> ../../../devices/platform/pcspkr

lrwxrwxrwx 1 root root 0 Jun  6 10:37 serial8250 -> ../../../devices/platform/serial8250

lrwxrwxrwx 1 root root 0 Jun  6 10:37 vesafb.0 -> ../../../devices/platform/vesafb.0

 

 

DEVICE

struct device {

       struct klist              klist_children;

       struct klist_node     knode_parent;        /* node in sibling list */

       struct klist_node     knode_driver;

       struct klist_node     knode_bus;

       struct device          *parent;  //該設備所屬的設備

 

       struct kobject kobj;

       char bus_id[BUS_ID_SIZE];     /* position on parent bus */

       struct device_type  *type;

       unsigned         is_registered:1;

       unsigned         uevent_suppress:1;

 

       struct semaphore    sem; /* semaphore to synchronize calls to

                                    * its driver.

                                    */

 

       struct bus_type      * bus;             /* type of bus device is on */

       struct device_driver *driver;    /* which driver has allocated this  device */

       void         *driver_data;   /* data private to the driver */

       void         *platform_data;      /* Platform specific data, device  core doesn't touch it */

       struct dev_pm_info power;

 

#ifdef CONFIG_NUMA

       int           numa_node;    /* NUMA node this device is close to */

#endif

       u64         *dma_mask;    /* dma mask (if dma'able device) */

       u64         coherent_dma_mask;/* Like dma_mask, but for

                                        alloc_coherent mappings as

                                        not all hardware supports

                                        64 bit addresses for consistent

                                        allocations such descriptors. */

 

       struct list_head       dma_pools;      /* dma pools (if dma'ble) */

 

       struct dma_coherent_mem     *dma_mem; /* internal for coherent mem   override */

       /* arch specific additions */

       struct dev_archdata archdata;

 

       spinlock_t        devres_lock;

       struct list_head       devres_head;

 

       /* class_device migration path */

       struct list_head       node;

       struct class             *class;

       dev_t                    devt;              /* dev_t, creates the sysfs "dev" */

       struct attribute_group    **groups;       /* optional groups */

 

       void  (*release)(struct device * dev);

};

這個結構相當於一個基類,對於基於特定總線的設備,會派生出特定的device結構(linux的驅動模型有很多結構都可以基於類來看待)

struct platform_device {

       const char       * name;

       int           id;

       struct device   dev;

       u32         num_resources;

       struct resource       * resource;

};

 

一個總線設備用如下函數註冊(註冊總線類型)

int device_register(struct device *dev)

{

       device_initialize(dev);

       return device_add(dev);

}

以完成parent name bus_id bus幾個成員的初始化,註冊後可以在/sys/devices下面看到

void device_unregister(struct device *dev);

同時和其相關的屬性爲

struct device_attribute {

       struct attribute       attr;

       ssize_t (*show)(struct device *dev, struct device_attribute *attr,char *buf);

       ssize_t (*store)(struct device *dev, struct device_attribute *attr, const char *buf, size_t count);

};

int device_create_file(struct device *device,struct device_attribute * entry);

device_remove_file(struct device * dev, struct device_attribute * attr);

以下完成一個總線設備的註冊:

static void simple_bus_release(struct device *dev)

{

       printk("simple bus release\n");

}

struct device simple_bus = {

       .bus_id ="simple_bus",

       .release = simple_bus_release

}

 

ret = device_register(&simple_bus);

if(ret)

pritnk("unable to register simple_bus\n");

完成註冊後,simple_bus就可以再sysfs中/sys/devices下面看見,任何掛載這個bus上的device都會在/sys/devices/simple_bus下看到

DEVICE_DRIVER

 

struct device_driver {

       const char              * name;//在sysfs下顯示

       struct bus_type             * bus;

 

       struct kobject         kobj;

       struct klist              klist_devices;

       struct klist_node     knode_bus;

 

       struct module         * owner;

       const char             * mod_name;  /* used for built-in modules */

       struct module_kobject    * mkobj;

 

       int    (*probe)  (struct device * dev);

       int    (*remove)       (struct device * dev);

       void  (*shutdown)   (struct device * dev);

       int    (*suspend)      (struct device * dev, pm_message_t state);

       int    (*resume)       (struct device * dev);

};

由於大多數驅動都會帶有特有的針對某種特定總線的信息,因此一般都是基於device_driver來派生出自己

特有的驅動,如

struct platform_driver {

       int (*probe)(struct platform_device *);

       int (*remove)(struct platform_device *);

       void (*shutdown)(struct platform_device *);

       int (*suspend)(struct platform_device *, pm_message_t state);

       int (*suspend_late)(struct platform_device *, pm_message_t state);

       int (*resume_early)(struct platform_device *);

       int (*resume)(struct platform_device *);

       struct device_driver driver;

};

比較xxx_driver 和device_driver我們可以發現,結構體所帶的方法基本相同,在具體應該用的時候是可以轉換的。

驅動的註冊

int driver_register(struct device_driver * drv);

void driver_unregister(struct device_driver * drv);

而大多數驅動會調用針對特定總線的諸如platform_driver_register,pci_driver_register之類的函數去註冊

總線(bus)可以掛接一類設備(device)

驅動(driver)可以驅動一類設備(device)

因此和bus一樣,device_driver也有一個函數爲某個驅動來遍歷所有設備

int driver_for_each_dev(struct device_driver *drv, void *data,int (*callback)(struct device *dev,void *data);

所有device_driver完成註冊後,會在/sys/bus/xxx/driver目錄下看到驅動信息

同時相應的屬性內容

struct driver_attribute {

       struct attribute       attr;

       ssize_t (*show)(struct device_driver *, char * buf);

       ssize_t (*store)(struct device_driver *, const char * buf, size_t count);

};

int  driver_create_file(struct device_driver *,struct driver_attribute *);

void driver_remove_file(struct device_driver *, struct driver_attribute *);

說了這麼多,現在來理一理kobject kset,subsys,sysfs,bus之間的關係

< XMLNAMESPACE PREFIX =“V” />

上圖反映了繼承體系的一個基本結構,kset是一組相同的kobject的集合,kernel可以通過跟蹤kset來跟蹤所用的特定類型設備,platform、pci、i2c等,kset起到連接作用將設備模型和sysfs聯繫在一起。每個kset自身都包含一個kobject,這個kobject將作爲很多其他的kobject的父類,從sys上看,某個kobject的父類是某個目錄,那麼它就是那個目錄的子目錄,parent指針可以代表目錄層次,這樣典型的設備模型層次就建立起來了,從面向對象的觀點看,kset是頂層的容器類,kset繼承他自己的kobject,並且可以當做kobject來處理

linux設備模型深探(2) - 784192422 - 嵌入式
如圖:kset把它的子類kobject放在鏈表裏面,kset子類鏈表裏面那些kobject的kset指針指向上面的kset,parent指向父類。

struct kobject {

       const char              * k_name;

       struct kref              kref;

       struct list_head       entry;

       struct kobject         * parent;

       struct kset             * kset;

       struct kobj_type     * ktype;

       struct sysfs_dirent   * sd;

};

 

struct kset {

       struct kobj_type     *ktype;

       struct list_head       list;

       spinlock_t        list_lock;

       struct kobject         kobj;

       struct kset_uevent_ops  *uevent_ops;

};

4、kmalloc和vmalloc的區別

kmalloc()
用於申請較小的、連續的物理內存

  1. 以字節爲單位進行分配,在<linux/slab.h>中
  2. void *kmalloc(size_t size, int flags) 分配的內存物理地址上連續,虛擬地址上自然連續
  3. gfp_mask標誌:什麼時候使用哪種標誌?如下:
    ———————————————————————————————-
    情形 相應標誌
    ———————————————————————————————-
    進程上下文,可以睡眠 GFP_KERNEL
    進程上下文,不可以睡眠 GFP_ATOMIC
    中斷處理程序 GFP_ATOMIC
    軟中斷 GFP_ATOMIC
    Tasklet GFP_ATOMIC
    用於DMA的內存,可以睡眠 GFP_DMA | GFP_KERNEL
    用於DMA的內存,不可以睡眠 GFP_DMA | GFP_ATOMIC
    ———————————————————————————————-
  4. void kfree(const void *ptr)
    釋放由kmalloc()分配出來的內存塊

vmalloc()
用於申請較大的內存空間,虛擬內存是連續的

  1. 以字節爲單位進行分配,在<linux/vmalloc.h>中
  2. void *vmalloc(unsigned long size) 分配的內存虛擬地址上連續,物理地址不連續
  3. 一般情況下,只有硬件設備才需要物理地址連續的內存,因爲硬件設備往往存在於MMU之外,根本不瞭解虛擬地址;但爲了性能上的考慮,內核中一般使用 kmalloc(),而只有在需要獲得大塊內存時才使用vmalloc(),例如當模塊被動態加載到內核當中時,就把模塊裝載到由vmalloc()分配 的內存上。
    4.void vfree(void *addr),這個函數可以睡眠,因此不能從中斷上下文調用。

malloc(), vmalloc()和kmalloc()區別

[*]kmalloc和vmalloc是分配的是內核的內存,malloc分配的是用戶的內存
[*]kmalloc保證分配的內存在物理上是連續的,vmalloc保證的是在虛擬地址空間上的連續,malloc不保證任何東西(這點是自己猜測的,不一定正確)
[*]kmalloc能分配的大小有限,vmalloc和malloc能分配的大小相對較大
[*]內存只有在要被DMA訪問的時候才需要物理上連續
[*]vmalloc比kmalloc要慢

5、module_init的級別

在Linux底下寫過driver模塊的對這個宏一定不會陌生。module_init宏在MODULE宏有沒有定義的情況下展開的內容是不同的,如果這個宏沒有定義,基本上表明閣下的模塊是要編譯進內核的(obj-y)。
1.在MODULE沒有定義這種情況下,module_init定義如下:

#define module_init(x) __initcall(x);
因爲
#define __initcall(fn)                            device_initcall(fn)
#define device_initcall(fn) __define_initcall("6",fn,6)
#define __define_initcall(level,fn,id) \
static initcall_t __initcall_##fn##id __used \
__attribute__((__section__(".initcall" level ".init"))) = fn

所以,module_init(x)最終展開爲:

static initcall_t __initcall_##fn##id __used \
__attribute__((__section__(".initcall" level ".init"))) = fn

更直白點,假設閣下driver所對應的模塊的初始化函數爲int gpio_init(void),那麼module_init(gpio_init)實際上等於:

static initcall_t  __initcall_gpio_init_6 __used __attribute__((__section__(".initcall6.init"))) = gpio_init;

就是聲明一類型爲initcall_t(typedef int (*initcall_t)(void))函數指針類型的變量__initcall_gpio_init_6並將gpio_init賦值與它。
這裏的函數指針變量聲明比較特殊的地方在於,將這個變量放在了一名爲".initcall6.init"節中。接下來結合vmlinux.lds中的

.initcall.init : AT(ADDR(.initcall.init) - (0xc0000000 -0x00000000)) {
   __initcall_start = .;
   *(.initcallearly.init) __early_initcall_end = .; *(.initcall0.init) *(.initcall0s.init) *(.initcall1.init) *(.initcall1s.init) *(.initcall2.init) *(.initcall2s.init) *(.initcall3.init) *(.initcall3s.init) *(.initcall4.init) *(.initcall4s.init) *(.initcall5.init) *(.initcall5s.init) *(.initcallrootfs.init) *(.initcall6.init) *(.initcall6s.init) *(.initcall7.init) *(.initcall7s.init)
   __initcall_end = .;
   }

以及do_initcalls:

static void __init do_initcalls(void)
{
initcall_t *call;
for (call = __initcall_start; call < __initcall_end; call++)
do_one_initcall(*call);
/* Make sure there is no pending stuff from the initcall sequence */
flush_scheduled_work();
}

那麼就不難理解閣下模塊中的module_init中的初始化函數何時被調用了:在系統啓動過程中start_kernel()->rest_init()->kernel_init()->do_basic_setup()->do_initcalls()。

在MODULE被定義的情況下(大部分可動態加載的driver模塊都屬於此, obj-m),module_init定義如下:

#define module_init(initfn) \
static inline initcall_t __inittest(void) \
{ return initfn; } \
int init_module(void) __attribute__((alias(#initfn)));

這段宏定義關鍵點是後面一句,通過alias將initfn變名爲init_module。前面那個__inittest的定義其實是種技巧,用來對initfn進行某種靜態的類型檢查,如果閣下將模塊初始化函數定義成,比如,void gpio_init(void)或者是int gpio_init(int),那麼在編譯時都會有類似下面的warning:

GPIO/fsl-gpio.c: In function '__inittest':
GPIO/fsl-gpio.c:46: warning: return from incompatible pointer type

通過module_init將模塊初始化函數統一別名爲init_module,這樣以後insmod時候,在系統內部會調用sys_init_module()去找到init_module函數的入口地址。
如果objdump -t gpio.ko,就會發現init_module和gpio_init位於相同的地址偏移處。簡言之,這種情況下模塊的初始化函數在insmod時候被調用。

6、添加驅動

靜態加載和動態加載:
靜態加載是系統啓動的時候由內核自動加載的,這個要事先將驅動編譯進內核才行;
動態加載,也就是模塊加載方式,這種方式下驅動以模塊的形式存放在文件系統中,需要時動態載入內核,這種主要用在調試的時候,比較方便靈活。insmod module.ko

7、IIC原理,總線框架,設備編寫方法,i2c_msg

首先來看一下原理圖:
IIC總線驅動基礎知識 - 小白 - 小白的博客

我們看到它有兩根線:數據線和時鐘線,他們用於數據傳輸。
A0、A1、A2是設備地址,它是已經固化在硬件層的。

再來看看AT24c02與2440連接圖:
IIC總線驅動基礎知識 - 小白 - 小白的博客
我們要知道2440與at24c02之間屬於主從連接,也就是說所有的會話必須由主設備發起,這裏的主設備自然是2440。那麼當2440的iic總線上掛載了多個存儲設備時,2440如何去找到自己想要訪問的存儲設備呢?這就需要設備地址,那麼當訪問到具體的設備後,想要讀寫特定的存儲空間,還需要知道存儲地址!那麼這些地址是如何組織的呢?我們可以看看下面這幅圖:
IIC總線驅動基礎知識 - 小白 - 小白的博客

由於我們用到的是at24c02,也就是2k的存儲設備,所以只需要看第一行:
這裏就是設備地址了,高4位是固化的,不用管,A2、A1、A0可以根據硬件連接來設置。fl2440開發板原理圖可知A2、A1、A0都是0,所以從設備地址是:1010000
我們再來看看4k,8k,16k的情況:我們知道設備地址發出之後在發出的是存儲地址,而存儲地址都是8位的,那麼對4k,8k,16k的情況怎麼辦呢?他們可不是8位地址就可以尋址的。8位地址只能尋址一頁數據,4k是2頁,8k是4頁,16k是8頁,我們看到上面圖片p0可以代表2頁,p1、p0可以代表4頁,p2、p1、p0可以代表8頁,這下明白了!
至於存儲地址,當然可以根據自己的需要來設置了。

那麼通過iic總線與2440連接的存儲設備如何讀寫呢?我們來看一看時序圖,不過因爲讀寫都分爲好幾種,我們分別只選其中一種來分析:
首先我們來分析一下隨機讀時序:
IIC總線驅動基礎知識 - 小白 - 小白的博客
一開始SDA、SCL信號都是高電平,然後SDA出現一個下降沿,表示開始發送數據。然後會發出設備地址,就是我們上面提到過的,它是7位的,第8位是讀寫標識位。由於只有一根數據線,顯然數據是一位一位發送的。當SCL是低電平的時候,SDA上的數據可以變化,當SCL爲高電平的時候,SDA上的數據被讀走。那麼要發送完這8位的數據,需要8個時鐘週期。在第九個時鐘週期,2440會交出總線的控制權,由從機來驅動,從機會向2440發送ack應答信號。
好下面我們略去比較細節的東西來分析這個隨機讀時序:
首先發送設備地址並附帶寫信號,這樣就找到了設備並告訴設備下一次是2440向設備發送
2440向設備發送要讀的地址
發送設備地址並附帶讀信號,表示下一次是設備向2440發送數據
然後是設備向2440發送數據

再來看字節寫時序:
IIC總線驅動基礎知識 - 小白 - 小白的博客
首先發出地址信號並附加些標識,這樣就找到了要寫的設備並告訴設備下一次是2440向設備發送
然後發出要寫的存儲地址
之後開始寫入數據

Linux設備驅動之I2C架構分析
一、前言
I2c是philips提出的外設總線.I2C只有兩條線,一條串行數據線:SDA,一條是時鐘線SCL.正因爲這樣,它方便了工程人員的佈線.另外,I2C是一種多主機控制總線.它和USB總線不同,USB是基於master-slave機制,任何設備的通信必須由主機發起纔可以.而 I2C 是基於multi master機制.一同總線上可允許多個master.關於I2C協議的知識,這裏不再贅述.可自行下載spec閱讀即可.
二、I2C架構概述
在linux中,I2C驅動架構如下所示:

Linux設備驅動之I2C架構分析【轉】 - 喧鬧的寂寞 - 喧鬧的寂寞

如上圖所示,每一條I2C對應一個adapter.在kernel中,每一個adapter提供了一個描述的結構(struct i2c_adapter),也定義了adapter支持的操作(struct i2c_adapter).再通過i2c core層將i2c設備與i2c adapter關聯起來.
這個圖只是提供了一個大概的框架.在下面的代碼分析中,從下至上的來分析這個框架圖.以下的代碼分析是基於linux 2.6.26.分析的代碼基本位於: linux-2.6.26.3/drivers/i2c/位置.

三、adapter註冊
在 kernel中提供了兩個adapter註冊接口,分別爲

int i2c_add_adapter(struct i2c_adapter *adapter);

int i2c_add_numbered_adapter(struct i2c_adapter *adapter);

由於在系統中可能存在多個adapter,因此將每一條I2C總線對應一個編號(下文中稱爲 I2C總線號)。這個總線號的PCI中的總線號不同,它和硬件無關,只是軟件上便於區分而已。 (這句很重要)
對於i2c_add_adapter()而言,它使用的是動態總線號,即由系統給其分配一個總線號,而i2c_add_numbered_adapter()則是自己指定總線號,如果這個總線號非法或者是被佔用,就會註冊失敗.

分別來看一下這兩個函數的代碼:

int i2c_add_adapter(struct i2c_adapter *adapter)
{
    int id, res = 0;

retry:
    if (idr_pre_get(&i2c_adapter_idr, GFP_KERNEL) == 0)
        return -ENOMEM;

    mutex_lock(&core_lock);
    /* "above" here means "above or equal to", sigh */
    res = idr_get_new_above(&i2c_adapter_idr, adapter, __i2c_first_dynamic_bus_num, &id);
    mutex_unlock(&core_lock);

    if (res < 0) {
        if (res == -EAGAIN)
            goto retry;
        return res;
    }

    adapter->nr = id;
    return i2c_register_adapter(adapter);
}

在這裏涉及到一個idr結構.idr結構本來是爲了配合page cache中的radix tree而設計的.在這裏我們只需要知道,它是一種高效的搜索樹,且這個樹預先存放了一些內存.避免在內存不夠的時候出現問題.所在,在往idr中插入結構的時候,首先要調用idr_pre_get()爲它預留足夠的空閒內存,然後再調用idr_get_new_above()將結構插入idr中,該函數以參數的形式返回一個id.以後憑這個id就可以在idr中找到相對應的結構了.對這個數據結構操作不太理解的可以查閱本站<< linux文件系統之文件的讀寫>>中有關radix tree的分析.
注意一下 idr_get_new_above(&i2c_adapter_idr, adapter,__i2c_first_dynamic_bus_num, &id)的參數的含義,它是將adapter結構插入到i2c_adapter_idr中,存放位置的id必須要大於或者等於 __i2c_first_dynamic_bus_num,然後將對應的id號存放在adapter->nr中.調用i2c_register_adapter(adapter)對這個adapter進行進一步註冊.

看一下另外一人註冊函數: i2c_add_numbered_adapter( ),如下所示:

int i2c_add_numbered_adapter(struct i2c_adapter *adap)
{
    int id;
    int status;

    if (adap->nr & ~MAX_ID_MASK)
        return -EINVAL;

retry:
    if (idr_pre_get(&i2c_adapter_idr, GFP_KERNEL) == 0)
        return -ENOMEM;

    mutex_lock(&core_lock);
    /* "above" here means "above or equal to", sigh;
    * we need the "equal to" result to force the result
    */
    status = idr_get_new_above(&i2c_adapter_idr, adap, adap->nr, &id);
    if (status == 0 && id != adap->nr) {
        status = -EBUSY;
        idr_remove(&i2c_adapter_idr, id);
    }
    mutex_unlock(&core_lock);
    if (status == -EAGAIN)
        goto retry;

    if (status == 0)
        status = i2c_register_adapter(adap);
    return status;
}

對比一下就知道差別了,在這裏它已經指定好了adapter->nr了.如果分配的id不和指定的相等,便返回錯誤.

過一步跟蹤i2c_register_adapter().代碼如下:

static int i2c_register_adapter(struct i2c_adapter *adap)
{
    int res = 0, dummy;

    mutex_init(&adap->bus_lock);
    mutex_init(&adap->clist_lock);
    INIT_LIST_HEAD(&adap->clients);

    mutex_lock(&core_lock);

    /* Add the adapter to the driver core.
    * If the parent pointer is not set up,
    * we add this adapter to the host bus.
    */
    if (adap->dev.parent == NULL) {
        adap->dev.parent = &platform_bus;
        pr_debug("I2C adapter driver [%s] forgot to specify "
            "physical device/n", adap->name);
    }
    sprintf(adap->dev.bus_id, "i2c-%d", adap->nr);
    adap->dev.release = &i2c_adapter_dev_release;
    adap->dev.class = &i2c_adapter_class;
    res = device_register(&adap->dev);
    if (res)
        goto out_list;

    dev_dbg(&adap->dev, "adapter [%s] registered/n", adap->name);

    /* create pre-declared device nodes for new-style drivers */
    if (adap->nr < __i2c_first_dynamic_bus_num)
        i2c_scan_static_board_info(adap);

    /* let legacy drivers scan this bus for matching devices */
    dummy = bus_for_each_drv(&i2c_bus_type, NULL, adap,
                i2c_do_add_adapter);

out_unlock:
    mutex_unlock(&core_lock);
    return res;

out_list:
    idr_remove(&i2c_adapter_idr, adap->nr);
    goto out_unlock;
}

首先對adapter和adapter中內嵌的struct device結構進行必須的初始化.之後將adapter內嵌的struct device註冊.

在這裏注意一下adapter->dev的初始化.它的類別爲i2c_adapter_class,如果沒有父結點,則將其父結點設爲platform_bus.

adapter->dev的名字爲i2c + 總線號.

測試一下:

[eric@mochow i2c]$ cd /sys/class/i2c-adapter/
[eric@mochow i2c-adapter]$ ls
i2c-0

可以看到,在我的PC上,有一個I2C adapter,看下詳細信息:

[eric@mochow i2c-adapter]$ tree
.
`-- i2c-0
    |-- device -> ../../../devices/pci0000:00/0000:00:1f.3/i2c-0
    |-- name
    |-- subsystem -> ../../../class/i2c-adapter
    `-- uevent

3 directories, 2 files
可以看到,該adapter是一個PCI設備.
繼續往下看:
之後,在註釋中看到,有兩種類型的driver,一種是new-style drivers,另外一種是legacy drivers
New-style drivers是在2.6近版的kernel加入的.它們最主要的區別是在adapter和i2c driver的匹配上.

3.1、new-style 形式的adapter註冊
對於第一種,也就是new-style drivers,將相關代碼再次列出如下:
if (adap->nr < __i2c_first_dynamic_bus_num)
i2c_scan_static_board_info(adap);
如果adap->nr 小於__i2c_first_dynamic_bus_num的話,就會進入到i2c_scan_static_board_info().
結合我們之前分析的adapter的兩種註冊分式: i2c_add_adapter()所分得的總線號會不會小於__i2c_first_dynamic_bus_num.只有i2c_add_numbered_adapter()纔有可能滿足:(adap->nr < __i2c_first_dynamic_bus_num)。
而且必須要調用i2c_register_board_info()將板子上的I2C設備信息預先註冊時纔會更改 __i2c_first_dynamic_bus_num的值.在x86上沒有使用i2c_register_board_info()。因此,x86平臺上的分析可以忽略掉new-style driver的方式。不過,還是詳細分析這種情況下.
首先看一下i2c_register_board_info(),如下:

int __init i2c_register_board_info(int busnum,struct i2c_board_info const *info, unsigned len)
{
    int status;

    mutex_lock(&__i2c_board_lock);

    /* dynamic bus numbers will be assigned after the last static one */
    if (busnum >= __i2c_first_dynamic_bus_num)
        __i2c_first_dynamic_bus_num = busnum + 1;

    for (status = 0; len; len--, info++) {
        struct i2c_devinfo  *devinfo;

        devinfo = kzalloc(sizeof(*devinfo), GFP_KERNEL);
        if (!devinfo) {
            pr_debug("i2c-core: can't register boardinfo!/n");
            status = -ENOMEM;
            break;
        }

        devinfo->busnum = busnum;
        devinfo->board_info = *info;
        list_add_tail(&devinfo->list, &__i2c_board_list);
    }

    mutex_unlock(&__i2c_board_lock);

    return status;
}

這個函數比較簡單, struct i2c_board_info用來表示I2C設備的一些情況,比如所在的總線.名稱,地址,中斷號等.最後,這些信息會被存放到__i2c_board_list鏈表.

跟蹤i2c_scan_static_board_info():代碼如下:

static void i2c_scan_static_board_info(struct i2c_adapter *adapter)
{
    struct i2c_devinfo  *devinfo;

    mutex_lock(&__i2c_board_lock);
    list_for_each_entry(devinfo, &__i2c_board_list, list) {
        if (devinfo->busnum == adapter->nr
                && !i2c_new_device(adapter,
                        &devinfo->board_info))
            printk(KERN_ERR "i2c-core: can't create i2c%d-%04x/n",
                i2c_adapter_id(adapter),
                devinfo->board_info.addr);
    }
    mutex_unlock(&__i2c_board_lock);
}

該函數遍歷掛在__i2c_board_list鏈表上面的i2c設備的信息,也就是我們在啓動的時候指出的i2c設備的信息.

如果指定設備是位於adapter所在的I2C總線上,那麼,就調用i2c_new_device().代碼如下:

struct i2c_client  *i2c_new_device(struct i2c_adapter *adap, struct i2c_board_info const *info)
{
    struct i2c_client  *client;
    int        status;

    client = kzalloc(sizeof *client, GFP_KERNEL);
    if (!client)
        return NULL;

    client->adapter = adap;

    client->dev.platform_data = info->platform_data;
    device_init_wakeup(&client->dev, info->flags & I2C_CLIENT_WAKE);

    client->flags = info->flags & ~I2C_CLIENT_WAKE;
    client->addr = info->addr;
    client->irq = info->irq;

    strlcpy(client->name, info->type, sizeof(client->name));

    /* a new style driver may be bound to this device when we
    * return from this function, or any later moment (e.g. maybe
    * hotplugging will load the driver module).  and the device
    * refcount model is the standard driver model one.
    */
    status = i2c_attach_client(client);
    if (status < 0) {
        kfree(client);
        client = NULL;
    }
    return client;
}

我們又遇到了一個新的結構:struct i2c_client,不要被這個結構嚇倒了,其實它就是一個嵌入struct device的I2C設備的封裝.它和我們之前遇到的struct usb_device結構的作用是一樣的.
首先,在clinet裏保存該設備的相關消息.特別的, client->adapter指向了它所在的adapter.特別的,clinet->name爲info->name.也是指定好了的.
一切初始化完成之後,便會調用i2c_attach_client( ).看這個函數的字面意思,是將clinet關聯起來.到底怎麼樣關聯呢?繼續往下看:

int i2c_attach_client(struct i2c_client *client)
{
    struct i2c_adapter *adapter = client->adapter;
    int res = 0;

    //初始化client內嵌的dev結構
    //父結點爲所在的adapter,所在bus爲i2c_bus_type
    client->dev.parent = &client->adapter->dev;
    client->dev.bus = &i2c_bus_type;

    //如果client已經指定了driver,將driver和內嵌的dev關聯起來  
    if (client->driver)
        client->dev.driver = &client->driver->driver;
    //指定了driver, 但不是newstyle的
    if (client->driver && !is_newstyle_driver(client->driver)) {
        client->dev.release = i2c_client_release;
        client->dev.uevent_suppress = 1;
    } else
        client->dev.release = i2c_client_dev_release;

    //clinet->dev的名稱
    snprintf(&client->dev.bus_id[0], sizeof(client->dev.bus_id),
        "%d-%04x", i2c_adapter_id(adapter), client->addr);
    //將內嵌的dev註冊
    res = device_register(&client->dev);
    if (res)
        goto out_err;

    //將clinet鏈到adapter->clients中
    mutex_lock(&adapter->clist_lock);
    list_add_tail(&client->list, &adapter->clients);
    mutex_unlock(&adapter->clist_lock);

    dev_dbg(&adapter->dev, "client [%s] registered with bus id %s/n",
        client->name, client->dev.bus_id);
    //如果adapter->cleinet_reqister存在,就調用它
    if (adapter->client_register)  {
        if (adapter->client_register(client)) {
            dev_dbg(&adapter->dev, "client_register "
                "failed for client [%s] at 0x%02x/n",
                client->name, client->addr);
        }
    }

    return 0;

out_err:
    dev_err(&adapter->dev, "Failed to attach i2c client %s at 0x%02x "
        "(%d)/n", client->name, client->addr, res);
    return res;
} 

參考上面添加的註釋,應該很容易理解這段代碼了,就不加詳細分析了.這個函數的名字不是i2c_attach_client()麼?怎麼沒看到它的關係過程呢?
這是因爲:在代碼中設置了client->dev所在的bus爲i2c_bus_type .以爲只需要有bus爲i2c_bus_type的driver註冊,就會產生probe了.這個過程呆後面分析i2c driver的時候再來詳細分析.

3.2、legacy形式的adapter註冊
Legacy形式的adapter註冊代碼片段如下:
dummy = bus_for_each_drv(&i2c_bus_type, NULL, adap,
i2c_do_add_adapter);
這段代碼遍歷掛在i2c_bus_type上的驅動,然後對每一個驅動和adapter調用i2c_do_add_adapter().
代碼如下:

static int i2c_do_add_adapter(struct device_driver *d, void *data)
{
    struct i2c_driver *driver = to_i2c_driver(d);
    struct i2c_adapter *adap = data;

    if (driver->attach_adapter) {
        /* We ignore the return code; if it fails, too bad */
        driver->attach_adapter(adap);
    }
    return 0;
} 

該函數很簡單,就是調用driver的attach_adapter()接口.
到此爲止,adapter的註冊已經分析完了.

四、i2c driver註冊
在分析i2c driver的時候,有必要先分析一下i2c架構的初始化
代碼如下:
static int __init i2c_init(void)
{
int retval;

retval = bus_register(&i2c_bus_type);//註冊總線
if (retval)
return retval;
retval = class_register(&i2c_adapter_class);//註冊類
if (retval)
goto bus_err;
retval = i2c_add_driver(&dummy_driver);//添加驅動
if (retval)
goto class_err;
return 0;

class_err:
class_unregister(&i2c_adapter_class);
bus_err:
bus_unregister(&i2c_bus_type);
return retval;
}
subsys_initcall(i2c_init);
很明顯,i2c_init()會在系統初始化的時候被調用.
在i2c_init中,先註冊了i2c_bus_type的bus,i2c_adapter_class的class.然後再調用i2c_add_driver()註冊了一個i2c driver.
I2c_bus_type結構如下:
static struct bus_type i2c_bus_type = {
.name = “i2c”,
.dev_attrs = i2c_dev_attrs,
.match = i2c_device_match,
.uevent = i2c_device_uevent,
.probe = i2c_device_probe,
.remove = i2c_device_remove,
.shutdown = i2c_device_shutdown,
.suspend = i2c_device_suspend,
.resume = i2c_device_resume,
};

這個結構先放在這裏吧,以後還會用到裏面的信息的.
從上面的初始化函數裏也看到了,註冊i2c driver的接口爲i2c_add_driver().代碼如下:
static inline int i2c_add_driver(struct i2c_driver *driver)
{
return i2c_register_driver(THIS_MODULE, driver);
}
繼續跟蹤:

int i2c_register_driver(struct module *owner, struct i2c_driver *driver)
{
    int res;

    /* new style driver methods can't mix with legacy ones */
    //如果是一個newstyle的driver.但又定義了attach_adapter/detach_adapter.非法
    if (is_newstyle_driver(driver)) {
        if (driver->attach_adapter || driver->detach_adapter
                || driver->detach_client) {
            printk(KERN_WARNING
                    "i2c-core: driver [%s] is confused/n",
                    driver->driver.name);
            return -EINVAL;
        }
    }

    /* add the driver to the list of i2c drivers in the driver core */
    //關聯到i2c_bus_types
    driver->driver.owner = owner;
    driver->driver.bus = &i2c_bus_type;

    /* for new style drivers, when registration returns the driver core
    * will have called probe() for all matching-but-unbound devices.
    */
    //註冊內嵌的driver
    res = driver_register(&driver->driver);
    if (res)
        return res;

    mutex_lock(&core_lock);

    pr_debug("i2c-core: driver [%s] registered/n", driver->driver.name);

    /* legacy drivers scan i2c busses directly */
    //遍歷所有的adapter,對其都調用driver->attach_adapter
    if (driver->attach_adapter) {
        struct i2c_adapter *adapter;

        down(&i2c_adapter_class.sem);
        list_for_each_entry(adapter, &i2c_adapter_class.devices,
                    dev.node) {
            driver->attach_adapter(adapter);
        }
        up(&i2c_adapter_class.sem);
    }

    mutex_unlock(&core_lock);
    return 0;
} 

這裏也有兩種形式的區分,對於第一種,只需要將內嵌的driver註冊就可以了。對於legacy的情況,對每一個adapter都調用driver->attach_adapter().

現在,我們可以將adapter和i2c driver關聯起來考慮一下了:
1:如果是news style形式的,在註冊adapter的時候,將它上面的i2c 設備轉換成了struct client。struct client->dev->bus又指定了和i2c driver同一個bus.因爲,它們可以發生probe.
2:如果是legacy形式,就直接找到對應的對象,調用driver->attach_adapter().

五、i2c_bus_type的相關操作
I2c_bus_type的操作主要存在於new-style形式的驅動中.接下來分析一下對應的probe過程:
5.1:match過程分析
Match對應的操作函數爲i2c_device_match().代碼如下

static int i2c_device_match(struct device *dev, struct device_driver *drv)
{
    struct i2c_client  *client = to_i2c_client(dev);
    struct i2c_driver  *driver = to_i2c_driver(drv);

    /* make legacy i2c drivers bypass driver model probing entirely;
    * such drivers scan each i2c adapter/bus themselves.
    */
    if (!is_newstyle_driver(driver))
        return 0;

    /* match on an id table if there is one */
    if (driver->id_table)
        return i2c_match_id(driver->id_table, client) != NULL;

    return 0;
}
如果該驅動不是一個new-style形式的.或者driver沒有定義匹配的id_table.都會匹配失敗.
繼續跟蹤進i2c_match_id():
static const struct i2c_device_id *i2c_match_id(const struct i2c_device_id *id,
                        const struct i2c_client *client)
{
    while (id->name[0]) {
        if (strcmp(client->name, id->name) == 0)
            return id;
        id++;
    }
    return NULL;
}

由此可見.如果client的名字和driver->id_table[]中的名稱匹配即爲成功.

5.2:probe過程分析
Probe對應的函數爲: i2c_device_probe()

static int i2c_device_probe(struct device *dev)
{
    struct i2c_client  *client = to_i2c_client(dev);
    struct i2c_driver  *driver = to_i2c_driver(dev->driver);
    const struct i2c_device_id *id;
    int status;

    if (!driver->probe)
        return -ENODEV;
    client->driver = driver;
    dev_dbg(dev, "probe/n");

    if (driver->id_table)
        id = i2c_match_id(driver->id_table, client);
    else
        id = NULL;
    status = driver->probe(client, id);
    if (status)
        client->driver = NULL;
    return status;
}

這個函數也很簡單,就是將probe流程回溯到i2c driver的probe()

六、其它的擴展
分析完adapter和i2c driver的註冊之後,好像整個架構也差不多了,其它,擴展的東西還有很多.
我們舉一個legacy形式的例子,這個例子是在kernel中隨便搜索出來的:
在linux-2.6.26.3/drivers/hwmon/ad7418.c中,初始化函數爲:
static int __init ad7418_init(void)
{
return i2c_add_driver(&ad7418_driver);
}
i2c_driver ad7418_driver結構如下:
static struct i2c_driver ad7418_driver = {
.driver = {
.name = “ad7418”,
},
.attach_adapter = ad7418_attach_adapter,
.detach_client = ad7418_detach_client,
};
該結構中沒有probe()函數,可以斷定是一個legacy形式的驅動.這類驅動註冊的時候,會調用driver的attach_adapter函數.在這裏也就是ad7418_attach_adapter.
這個函數代碼如下:
static int ad7418_attach_adapter(struct i2c_adapter *adapter)
{
if (!(adapter->class & I2C_CLASS_HWMON))
return 0;
return i2c_probe(adapter, &addr_data, ad7418_detect);
}
在這裏我們又遇到了一個i2c-core中的函數,i2c_probe().在分析這個函數之前,先來看下addr_data是什麼?

#define I2C_CLIENT_MODULE_PARM(var,desc) /
  static unsigned short var[I2C_CLIENT_MAX_OPTS] = I2C_CLIENT_DEFAULTS; /
  static unsigned int var##_num; /
  module_param_array(var, short, &var##_num, 0); /
  MODULE_PARM_DESC(var,desc)

#define I2C_CLIENT_MODULE_PARM_FORCE(name)              /
I2C_CLIENT_MODULE_PARM(force_##name,                    /
              "List of adapter,address pairs which are "    /
              "unquestionably assumed to contain a `"      /
              # name "' chip")


#define I2C_CLIENT_INSMOD_COMMON                    /
I2C_CLIENT_MODULE_PARM(probe, "List of adapter,address pairs to scan "  /
              "additionally");                  /
I2C_CLIENT_MODULE_PARM(ignore, "List of adapter,address pairs not to "  /
              "scan");                      /
static const struct i2c_client_address_data addr_data = {      /
    .normal_i2c = normal_i2c,                  /
    .probe      = probe,                    /
    .ignore    = ignore,                  /
    .forces    = forces,                  /
}

#define I2C_CLIENT_FORCE_TEXT /
    "List of adapter,address pairs to boldly assume to be present"

由此可知道,addr_data中的三個成員都是模塊參數.在加載模塊的時候可以用參數的方式對其賦值.三個模塊參數爲別爲probe,ignore,force.另外需要指出的是normal_i2c不能以模塊參數的方式對其賦值,只能在驅動內部靜態指定.
從模塊參數的模述看來, probe是指"List of adapter,address pairs to scan additionally"
Ignore是指"List of adapter,address pairs not to scan "
Force是指"List of adapter,address pairs to boldly assume to be present"
事實上,它們裏面的數據都是成對出現的.前面一部份表示所在的總線號,ANY_I2C_BUS表示任一總線.後一部份表示設備的地址.

addr_data是在 include/linux/i2c.h 中定義的或自己在自己驅動程序中定義的(注意,此處addr_data有兩種定義方式)一個i2c_client_address_data結構:
若自己不定義,則用i2c.h中的默認定義。
/* i2c_client_address_data is the struct for holding default client addresses for a driver and for the parameters supplied on the command line */
struct i2c_client_address_data {
unsigned short *normal_i2c;
unsigned short *probe;
unsigned short *ignore;
unsigned short *forces;
};
根據作者自行定義設備地址與否,有兩種情形:
a. 採用默認定義,一般是不會work,畢竟大多數i2c-core中是不可能提前知道所接設備地址的,這樣通過i2c_probe()探測肯定不可能找到,也不可能建立兩者之間的聯繫;況且,i2c_probe()屬於i2c-core中的函數,i2c-core中管理着所有註冊過的設備和驅動列表,i2c_probe()中也不能隨意傳入地址,否則容易導致系統混亂或有潛在的風險,所以i2c-core也不允許這麼做!
b. 作者自行定義地址結構
典型例子如下:
若自行定義,則參考如下:
/
Addresses to scan */
static unsigned short normal_i2c[] = {I2C_KS0127_ADDON>>1,
I2C_KS0127_ONBOARD>>1, I2C_CLIENT_END};/// 實際設備的地址List
static unsigned short probe[2] = {I2C_CLIENT_END, I2C_CLIENT_END};
static unsigned short ignore[2] = {I2C_CLIENT_END, I2C_CLIENT_END};
static struct i2c_client_address_data addr_data = {
normal_i2c,
probe,
ignore,
};
或者根本就不定義完整的i2c_client_address_data結構,只根據需要定義normal_i2c[],probe[],ignore[],forces[][],然後調用i2c_probe(adapter,&addr_data, &my_probe) 即可。
在my_probe()中把實際的地址賦於i2c_client,調用i2c_set_clientdata()設置i2c_client->dev->drv_data,並調用i2c_attach_client(client)向系統註冊設備。
最後,i2c_probe()中探測時的地址優先級:forces[X][ X], probe[X ], normal_i2c X
I2c設備在實際使用中比較廣泛,sensor,rtc,audio, codec,etc. 因設備複雜性不同,Linux中有些驅動中對地址的定義不在同一文件,這時多數情況都在arch中對設備作爲platform_device進行初始化並註冊的代碼中。

現在可以來跟蹤i2c_probe()的代碼了.如下:

int i2c_probe(struct i2c_adapter *adapter, const struct i2c_client_address_data *address_data, int (*found_proc) (struct  i2c_adapter *, int, int))
{
    int i, err;
    int adap_id = i2c_adapter_id(adapter);

    /* Force entries are done first, and are not affected by ignore
      entries */
      //先掃描force裏面的信息,注意它是一個二級指針.ignore裏的信息對它是無效的
    if (address_data->forces) {
        const unsigned short * const *forces = address_data->forces;//forces爲常量指針,所指向的內容不能改變。
        int kind;

        for (kind = 0; forces[kind]; kind++) {
            for (i = 0; forces[kind] != I2C_CLIENT_END;
                i += 2) {
                if (forces[kind] == adap_id
                || forces[kind] == ANY_I2C_BUS) {
                    dev_dbg(&adapter->dev, "found force "
                        "parameter for adapter %d, "
                        "addr 0x%02x, kind %d/n",
                        adap_id, forces[kind][i + 1],
                        kind);
                    err = i2c_probe_address(adapter,
                        forces[kind][i + 1],
                        kind, found_proc);
                    if (err)
                        return err;
                }
            }
        }
    }

    /* Stop here if we can't use SMBUS_QUICK */
    //如果adapter不支持quick.不能夠遍歷這個adapter上面的設備
    if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_QUICK)) {
        if (address_data->probe[0] == I2C_CLIENT_END
        && address_data->normal_i2c[0] == I2C_CLIENT_END)
            return 0;

        dev_warn(&adapter->dev, "SMBus Quick command not supported, "
            "can't probe for chips/n");
        return -1;
    }

    /* Probe entries are done second, and are not affected by ignore
      entries either */
      //遍歷probe上面的信息.ignore上的信息也對它是沒有影響的 
    for (i = 0; address_data->probe != I2C_CLIENT_END; i += 2) {
        if (address_data->probe == adap_id
        || address_data->probe == ANY_I2C_BUS) {
            dev_dbg(&adapter->dev, "found probe parameter for "
                "adapter %d, addr 0x%02x/n", adap_id,
                address_data->probe[i + 1]);
            err = i2c_probe_address(adapter,
                        address_data->probe[i + 1],
                        -1, found_proc);
            if (err)
                return err;
        }
    }

    /* Normal entries are done last, unless shadowed by an ignore entry */
    //最後遍歷normal_i2c上面的信息.它上面的信息不能在ignore中.
    for (i = 0; address_data->normal_i2c != I2C_CLIENT_END; i += 1) {
        int j, ignore;

        ignore = 0;
        for (j = 0; address_data->ignore[j] != I2C_CLIENT_END;
            j += 2) {
            if ((address_data->ignore[j] == adap_id ||
                address_data->ignore[j] == ANY_I2C_BUS)
            && address_data->ignore[j + 1]
                == address_data->normal_i2c) {
                dev_dbg(&adapter->dev, "found ignore "
                    "parameter for adapter %d, "
                    "addr 0x%02x/n", adap_id,
                    address_data->ignore[j + 1]);
                ignore = 1;
                break;
            }
        }
        if (ignore)
            continue;

        dev_dbg(&adapter->dev, "found normal entry for adapter %d, "
            "addr 0x%02x/n", adap_id,
            address_data->normal_i2c);
        err = i2c_probe_address(adapter, address_data->normal_i2c,
                    -1, found_proc);
        if (err)
            return err;
    }

    return 0;
} 

這段代碼很簡單,結合代碼上面添加的註釋應該很好理解.如果匹配成功,則會調用i2c_probe_address ().這個函數代碼如下:

static int i2c_probe_address(struct i2c_adapter *adapter, int addr, int kind,
                int (*found_proc) (struct i2c_adapter *, int, int))
{
    int err;

    /* Make sure the address is valid */
    //地址小於0x03或者大於0x77都是不合法的
    if (addr < 0x03 || addr > 0x77) {
        dev_warn(&adapter->dev, "Invalid probe address 0x%02x/n",
            addr);
        return -EINVAL;
    }

    /* Skip if already in use */
    //adapter上已經有這個設備了
    if (i2c_check_addr(adapter, addr))
        return 0;

    /* Make sure there is something at this address, unless forced */
    //如果kind小於0.檢查adapter上是否有這個設備
    if (kind < 0) {
        if (i2c_smbus_xfer(adapter, addr, 0, 0, 0,
                  I2C_SMBUS_QUICK, NULL) < 0)
            return 0;

        /* prevent 24RF08 corruption */
        if ((addr & ~0x0f) == 0x50)
            i2c_smbus_xfer(adapter, addr, 0, 0, 0,
                      I2C_SMBUS_QUICK, NULL);
    }

    /* Finally call the custom detection function */
    //調用回調函數
    err = found_proc(adapter, addr, kind);
    /* -ENODEV can be returned if there is a chip at the given address
      but it isn't supported by this chip driver. We catch it here as
      this isn't an error. */
    if (err == -ENODEV)
        err = 0;

    if (err)
        dev_warn(&adapter->dev, "Client creation failed at 0x%x (%d)/n",
            addr, err);
    return err;
}

首先,對傳入的參數進行一系列的合法性檢查.另外,如果該adapter上已經有了這個地址的設備了.也會返回失敗.所有adapter下面的設備都是以 adapter->dev爲父結點的.因此只需要遍歷adapter->dev下面的子設備就可以得到當前地址是不是被佔用了.
如果kind < 0.還得要adapter檢查該總線是否有這個地址的設備.方法是向這個地址發送一個Read的Quick請求.如果該地址有應答,則說明這個地址上有這個設備.另外還有一種情況是在24RF08設備的特例.
如果adapter上確實有這個設備,就會調用驅動調用時的回調函數.

在上面涉及到了IIC的傳輸方式,有疑問的可以參考intel ICH5手冊的有關smbus部份.
跟蹤i2c_smbus_xfer().代碼如下:

s32 i2c_smbus_xfer(struct i2c_adapter * adapter, u16 addr, unsigned short flags,
                  char read_write, u8 command, int size,
                  union i2c_smbus_data * data)
{
    s32 res;

    flags &= I2C_M_TEN | I2C_CLIENT_PEC;

    if (adapter->algo->smbus_xfer) {
        mutex_lock(&adapter->bus_lock);
        res = adapter->algo->smbus_xfer(adapter,addr,flags,read_write,
                                        command,size,data);
        mutex_unlock(&adapter->bus_lock);
    } else
        res = i2c_smbus_xfer_emulated(adapter,addr,flags,read_write,
                                          command,size,data);

    return res;
}

如果adapter有smbus_xfer()函數,則直接調用它發送,否則,也就是在adapter不支持smbus協議的情況下,調用i2c_smbus_xfer_emulated()繼續處理.
跟進i2c_smbus_xfer_emulated().代碼如下:

static s32 i2c_smbus_xfer_emulated(struct i2c_adapter * adapter, u16 addr,
                                  unsigned short flags,
                                  char read_write, u8 command, int size,
                                  union i2c_smbus_data * data)
{
    /* So we need to generate a series of msgs. In the case of writing, we
      need to use only one message; when reading, we need two. We initialize
      most things with sane defaults, to keep the code below somewhat
      simpler. */
    //寫操作只會進行一次交互,而讀操作,有時會有兩次操作.
    //因爲有時候讀操作要先寫command,再從總線上讀數據
    //在這裏爲了代碼的簡潔.使用了兩個緩存區,將兩種情況統一起來.
    unsigned char msgbuf0[I2C_SMBUS_BLOCK_MAX+3];
    unsigned char msgbuf1[I2C_SMBUS_BLOCK_MAX+2];
    //一般來說,讀操作要交互兩次.例外的情況我們在下面會接着分析
    int num = read_write == I2C_SMBUS_READ?2:1;
    //與設備交互的數據,一般在msg[0]存放寫入設備的信息,在msb[1]裏存放接收到的
    //信息.不過也有例外的
    //msg[2]的初始化,默認發送緩存區佔一個字節,無接收緩存
    struct i2c_msg msg[2] = { { addr, flags, 1, msgbuf0 },
                              { addr, flags | I2C_M_RD, 0, msgbuf1 }
                            };
    int i;
    u8 partial_pec = 0;

    //將要發送的信息copy到發送緩存區的第一字節
    msgbuf0[0] = command;
    switch(size) {
        //quick類型的,其它並不傳輸有效數據,只是將地址寫到總線上,等待應答即可
        //所以將發送緩存區長度置爲0 .再根據讀/寫操作,調整msg[0]的標誌位
        //這類傳輸只需要一次總線交互
    case I2C_SMBUS_QUICK:
        msg[0].len = 0;
        /* Special case: The read/write field is used as data */
        msg[0].flags = flags | (read_write==I2C_SMBUS_READ)?I2C_M_RD:0;
        num = 1;
        break;
    case I2C_SMBUS_BYTE:
        //BYTE類型指一次寫和讀只有一個字節.這種情況下,讀和寫都只會交互一次
        //這種類型的讀有例外,它讀取出來的數據不是放在msg[1]中的,而是存放在msg[0]
        if (read_write == I2C_SMBUS_READ) {
            /* Special case: only a read! */
            msg[0].flags = I2C_M_RD | flags;
            num = 1;
        }
        break;
    case I2C_SMBUS_BYTE_DATA:
        //Byte_Data是指命令+數據的傳輸形式.在這種情況下,寫只需要一次交互,讀卻要兩次
        //第一次將command寫到總線上,第二次要轉換方向.要將設備地址和read標誌寫入總線.
        //應回答之後再進行read操作
        //寫操作佔兩字節,分別是command+data.讀操作的有效數據只有一個字節
        //交互次數用初始化值就可以了
        if (read_write == I2C_SMBUS_READ)
            msg[1].len = 1;
        else {
            msg[0].len = 2;
            msgbuf0[1] = data->byte;
        }
        break;
    case I2C_SMBUS_WORD_DATA:
        //Word_Data是指命令+雙字節的形式.這種情況跟Byte_Data的情況類似
        //兩者相比只是交互的數據大小不同
        if (read_write == I2C_SMBUS_READ)
            msg[1].len = 2;
        else {
            msg[0].len=3;
            msgbuf0[1] = data->word & 0xff;
            msgbuf0[2] = data->word >> 8;
        }
        break;
    case I2C_SMBUS_PROC_CALL:
        //Proc_Call的方式與write 的Word_Data相似,只不過寫完Word_Data之後,要等待它的應答
        //應該它需要交互兩次,一次寫一次讀
        num = 2; /* Special case */
        read_write = I2C_SMBUS_READ;
        msg[0].len = 3;
        msg[1].len = 2;
        msgbuf0[1] = data->word & 0xff;
        msgbuf0[2] = data->word >> 8;
        break;
    case I2C_SMBUS_BLOCK_DATA:
        //Block_Data:指command+N段數據的情況.
        //如果是讀操作,它首先要寫command到總線,然後再讀N段數據.要寫的command已經
        //放在msg[0]了.現在只需要將msg[1]的標誌置I2C_M_RECV_LEN位,msg[1]有效長度爲1字節.因爲
        //adapter驅動會處理好的.現在現在還不知道要傳多少段數據.

        //對於寫的情況:msg[1]照例不需要.將要寫的數據全部都放到msb[0]中.相應的也要更新
        //msg[0]中的緩存區長度
        if (read_write == I2C_SMBUS_READ) {
            msg[1].flags |= I2C_M_RECV_LEN;
            msg[1].len = 1; /* block length will be added by
                      the underlying bus driver */
        } else {
            //data->block[0]表示後面有多少段數據.總長度要加2是因爲command+count+N段數據
            msg[0].len = data->block[0] + 2;
            if (msg[0].len > I2C_SMBUS_BLOCK_MAX + 2) {
                dev_err(&adapter->dev, "smbus_access called with "
                      "invalid block write size (%d)/n",
                      data->block[0]);
                return -1;
            }
            for (i = 1; i < msg[0].len; i++)
                msgbuf0 = data->block[i-1];
        }
        break;
    case I2C_SMBUS_BLOCK_PROC_CALL:
        //Proc_Call:表示寫完Block_Data之後,要等它的應答消息它和Block_Data相比,只是多了一部份應答而已
        num = 2; /* Another special case */
        read_write = I2C_SMBUS_READ;
        if (data->block[0] > I2C_SMBUS_BLOCK_MAX) {
            dev_err(&adapter->dev, "%s called with invalid "
                "block proc call size (%d)/n", __func__,
                data->block[0]);
            return -1;
        }
        msg[0].len = data->block[0] + 2;
        for (i = 1; i < msg[0].len; i++)
            msgbuf0 = data->block[i-1];
        msg[1].flags |= I2C_M_RECV_LEN;
        msg[1].len = 1; /* block length will be added by
                  the underlying bus driver */
        break;
    case I2C_SMBUS_I2C_BLOCK_DATA:
        //I2c Block_Data與Block_Data相似,只不過read的時候,數據長度是預先定義好了的.另外
        //與Block_Data相比,中間不需要傳輸Count字段.(Count表示數據段數目)
        if (read_write == I2C_SMBUS_READ) {
            msg[1].len = data->block[0];
        } else {
            msg[0].len = data->block[0] + 1;
            if (msg[0].len > I2C_SMBUS_BLOCK_MAX + 1) {
                dev_err(&adapter->dev, "i2c_smbus_xfer_emulated called with "
                      "invalid block write size (%d)/n",
                      data->block[0]);
                return -1;
            }
            for (i = 1; i <= data->block[0]; i++)
                msgbuf0 = data->block;
        }
        break;
    default:
        dev_err(&adapter->dev, "smbus_access called with invalid size (%d)/n",
              size);
        return -1;
    }

    //如果啓用了PEC.Quick和I2c Block_Data是不支持PEC的
    i = ((flags & I2C_CLIENT_PEC) && size != I2C_SMBUS_QUICK
                      && size != I2C_SMBUS_I2C_BLOCK_DATA);
    if (i) {
        /* Compute PEC if first message is a write */
        //如果第一個操作是寫操作
        if (!(msg[0].flags & I2C_M_RD)) {
            //如果只是寫操作
            if (num == 1) /* Write only */
                //如果只有寫操作,寫緩存區要擴充一個字節,用來存放計算出來的PEC
                i2c_smbus_add_pec(&msg[0]);
            else /* Write followed by read */
                //如果後面還有讀操作,先計算前面寫部份的PEC(注意這種情況下不需要
                //擴充寫緩存區,因爲不需要發送PEC.只會接收到PEC)
                partial_pec = i2c_smbus_msg_pec(0, &msg[0]);
        }
        /* Ask for PEC if last message is a read */
        //如果最後一次是讀消息.還要接收到來自slave的PEC.所以接收緩存區要擴充一個字節
        if (msg[num-1].flags & I2C_M_RD)
            msg[num-1].len++;
    }

    if (i2c_transfer(adapter, msg, num) < 0)
        return -1;

    /* Check PEC if last message is a read */
    //操作完了之後,如果最後一個操作是PEC的讀操作.檢驗後面的PEC是否正確
    if (i && (msg[num-1].flags & I2C_M_RD)) {
        if (i2c_smbus_check_pec(partial_pec, &msg[num-1]) < 0)
            return -1;
    }

    //操作完了,現在可以將數據放到data部份返回了.
    if (read_write == I2C_SMBUS_READ)
        switch(size) {
            case I2C_SMBUS_BYTE:
                data->byte = msgbuf0[0];
                break;
            case I2C_SMBUS_BYTE_DATA:
                data->byte = msgbuf1[0];
                break;
            case I2C_SMBUS_WORD_DATA:
            case I2C_SMBUS_PROC_CALL:
                data->word = msgbuf1[0] | (msgbuf1[1] << 8);
                break;
            case I2C_SMBUS_I2C_BLOCK_DATA:
                for (i = 0; i < data->block[0]; i++)
                    data->block[i+1] = msgbuf1;
                break;
            case I2C_SMBUS_BLOCK_DATA:
            case I2C_SMBUS_BLOCK_PROC_CALL:
                for (i = 0; i < msgbuf1[0] + 1; i++)
                    data->block = msgbuf1;
                break;
        }
    return 0;
}

在這個函數添上了很詳細的註釋,配和intel的datasheet,應該很容易看懂.在上面的交互過程中,調用了子函數i2c_transfer().它的代碼如下所示:

int i2c_transfer(struct i2c_adapter * adap, struct i2c_msg *msgs, int num)
{
    int ret;

    if (adap->algo->master_xfer) {
#ifdef DEBUG
        for (ret = 0; ret < num; ret++) {
            dev_dbg(&adap->dev, "master_xfer[%d] %c, addr=0x%02x, "
                "len=%d%s/n", ret, (msgs[ret].flags & I2C_M_RD)
                ? 'R' : 'W', msgs[ret].addr, msgs[ret].len,
                (msgs[ret].flags & I2C_M_RECV_LEN) ? "+" : "");
        }
#endif

        if (in_atomic() || irqs_disabled()) {
            ret = mutex_trylock(&adap->bus_lock);
            if (!ret)
                /* I2C activity is ongoing. */
                return -EAGAIN;
        } else {
            mutex_lock_nested(&adap->bus_lock, adap->level);
        }

        ret = adap->algo->master_xfer(adap,msgs,num);
        mutex_unlock(&adap->bus_lock);

        return ret;
    } else {
        dev_dbg(&adap->dev, "I2C level transfers not supported/n");
        return -ENOSYS;
    }
}

因爲在這裏的同步用的是mutex.首先判斷判斷是否充許睡眠,如果不允許,嘗試獲鎖.如果獲鎖失敗,則返回,這樣的操作是避免進入睡眠,我們在後面也可以看到,實際的傳輸工作交給了adap->algo->master_xfer()完成.

在這裏,我們終於把i2c_probe_address()的執行分析完了,經過這個分析,我們也知道了數據是怎麼樣傳輸的.我們接着 i2c_probe()往下看.如果i2c_probe_address()成功.說明總線上確實有這樣的設備.那麼就會調用驅動中的回調函數.在 ad7148的驅動中,如下所示:
return i2c_probe(adapter, &addr_data, ad7418_detect);
也就是說,要調用的回調函數是ad7418_detect().這個函數中我們只分析和i2c框架相關的部份.代碼片段如下所示:

static int ad7418_detect(struct i2c_adapter *adapter, int address, int kind)
{
    struct i2c_client *client;
    ……
    ……
client->addr = address;
    client->adapter = adapter;
    client->driver = &ad7418_driver;

    i2c_set_clientdata(client, data);
    ……
    ……
if ((err = i2c_attach_client(client)))
        goto exit_free;
    ……
    ……
}

結合上面關於new-style形式的驅動分析.發現這裏走的是同一個套路,即初始化了client.然後調用 i2c_attach_client().後面的流程就跟上面分析的一樣了.只不過,不相同的是,這裏clinet已經指定了驅動爲 ad7418_driver.應該在註冊clinet->dev之後,就不會走bus->match和bus->probe的流程了.

七:i2c dev節點操作
現在來分析上面架構圖中的i2c-dev.c中的部份.這個部份爲用戶空間提供了操作adapter的接口.這部份代碼其實對應就晃一個模塊.它的初始化函數爲:
module_init(i2c_dev_init);
i2c_dev_init()代碼如下:

static int __init i2c_dev_init(void)
{
    int res;

    printk(KERN_INFO "i2c /dev entries driver/n");

    res = register_chrdev(I2C_MAJOR, "i2c", &i2cdev_fops);
    if (res)
        goto out;

    i2c_dev_class = class_create(THIS_MODULE, "i2c-dev");
    if (IS_ERR(i2c_dev_class))
        goto out_unreg_chrdev;

    res = i2c_add_driver(&i2cdev_driver);
    if (res)
        goto out_unreg_class;

    return 0;

out_unreg_class:
    class_destroy(i2c_dev_class);
out_unreg_chrdev:
    unregister_chrdev(I2C_MAJOR, "i2c");
out:
    printk(KERN_ERR "%s: Driver Initialisation failed/n", __FILE__);
    return res;
}

首先爲主冊了一個主設備號爲I2C_MAJOR(89),操作集爲i2cdev_fops的字符設備.

然後註冊了一個名爲”i2c-dev”的class.之後再註冊了一個i2c的driver.如下所示:

res = i2c_add_driver(&i2cdev_driver);
if (res)
goto out_unreg_class;
i2cdev_driver定義如下:
static struct i2c_driver i2cdev_driver = {
.driver = {
.name = “dev_driver”,
},
.id = I2C_DRIVERID_I2CDEV,
.attach_adapter = i2cdev_attach_adapter,
.detach_adapter = i2cdev_detach_adapter,
.detach_client = i2cdev_detach_client,
};
也就是說,當它註冊或者有新的adapter註冊後,就會它的attach_adapter()函數.該函數代碼如下:

static int i2cdev_attach_adapter(struct i2c_adapter *adap)
{
    struct i2c_dev *i2c_dev;
    int res;

    i2c_dev = get_free_i2c_dev(adap);
    if (IS_ERR(i2c_dev))
        return PTR_ERR(i2c_dev);

    /* register this i2c device with the driver core */
    i2c_dev->dev = device_create(i2c_dev_class, &adap->dev,
                    MKDEV(I2C_MAJOR, adap->nr),
                    "i2c-%d", adap->nr);
    if (IS_ERR(i2c_dev->dev)) {
        res = PTR_ERR(i2c_dev->dev);
        goto error;
    }
    res = device_create_file(i2c_dev->dev, &dev_attr_name);
    if (res)
        goto error_destroy;

    pr_debug("i2c-dev: adapter [%s] registered as minor %d/n",
        adap->name, adap->nr);
    return 0;
error_destroy:
    device_destroy(i2c_dev_class, MKDEV(I2C_MAJOR, adap->nr));
error:
    return_i2c_dev(i2c_dev);
    return res;
}

這個函數也很簡單,首先調用get_free_i2c_dev()分配並初始化了一個struct i2c_dev結構,使i2c_dev->adap指向操作的adapter.之後,該i2c_dev會被鏈入鏈表i2c_dev_list中。再分別以I2C_MAJOR, adap->nr爲主次設備號創建了一個device.如果此時系統配置了udev或者是hotplug,那麼就麼在/dev下自動創建相關的設備節點了.
剛纔我們說過,所有主設備號爲I2C_MAJOR的設備節點的操作函數是i2cdev_fops.它的定義如下所示:
static const struct file_operations i2cdev_fops = {
.owner = THIS_MODULE,
.llseek = no_llseek,
.read = i2cdev_read,
.write = i2cdev_write,
.ioctl = i2cdev_ioctl,
.open = i2cdev_open,
.release = i2cdev_release,
};

7.1:i2c dev的open操作
Open操作對應的函數爲i2cdev_open().代碼如下:

static int i2cdev_open(struct inode *inode, struct file *file)
{
    unsigned int minor = iminor(inode);
    struct i2c_client *client;
    struct i2c_adapter *adap;
    struct i2c_dev *i2c_dev;

    //以次設備號從i2c_dev_list鏈表中取得i2c_dev
    i2c_dev = i2c_dev_get_by_minor(minor);
    if (!i2c_dev)
        return -ENODEV;

    //以apapter的總線號從i2c_adapter_idr中找到adapter
    adap = i2c_get_adapter(i2c_dev->adap->nr);
    if (!adap)
        return -ENODEV;

    /* This creates an anonymous i2c_client, which may later be
    * pointed to some address using I2C_SLAVE or I2C_SLAVE_FORCE.
    *
    * This client is ** NEVER REGISTERED ** with the driver model
    * or I2C core code!!  It just holds private copies of addressing
    * information and maybe a PEC flag.
    */
    //分配並初始化一個i2c_client結構
    client = kzalloc(sizeof(*client), GFP_KERNEL);
    if (!client) {
        i2c_put_adapter(adap);
        return -ENOMEM;
    }
    snprintf(client->name, I2C_NAME_SIZE, "i2c-dev %d", adap->nr);
    client->driver = &i2cdev_driver;

    //clinet->adapter指向操作的adapter
    client->adapter = adap;
    //關聯到file
    file->private_data = client;

    return 0;
}

注意這裏分配並初始化了一個struct i2c_client結構.但是沒有註冊這個clinet.此外,這個函數中還有一個比較奇怪的操作.不是在前面已經將i2c_dev->adap 指向要操作的adapter麼?爲什麼還要以adapter->nr爲關鍵字從i2c_adapter_idr去找這個操作的adapter呢?注意了,調用i2c_get_adapter()從總線號nr找到操作的adapter的時候,還會增加module的引用計數.這樣可以防止模塊意外被釋放掉.也許有人會有這樣的疑問,那 i2c_dev->adap->nr操作,如果i2c_dev->adap被釋放掉的話,不是一樣會引起系統崩潰麼?這裏因爲,在 i2cdev_attach_adapter()間接的增加了一次adapter的一次引用計數.如下:
tatic int i2cdev_attach_adapter(struct i2c_adapter *adap)
{
......
i2c_dev->dev = device_create(i2c_dev_class, &adap->dev,
MKDEV(I2C_MAJOR, adap->nr),
“i2c-%d”, adap->nr);
......
}
看到了麼,i2c_dev內嵌的device是以adap->dev爲父結點,在device_create()中會增次adap->dev的一次引用計數.
好了,open()操作到此就完成了.

7.2:read操作
Read操作對應的操作函數如下示:

static ssize_t i2cdev_read (struct file *file, char __user *buf, size_t count,
                            loff_t *offset)
{
    char *tmp;
    int ret;

    struct i2c_client *client = (struct i2c_client *)file->private_data;

    if (count > 8192)
        count = 8192;

    tmp = kmalloc(count,GFP_KERNEL);
    if (tmp==NULL)
        return -ENOMEM;

    pr_debug("i2c-dev: i2c-%d reading %zd bytes./n",
        iminor(file->f_path.dentry->d_inode), count);

    ret = i2c_master_recv(client,tmp,count);
    if (ret >= 0)
        ret = copy_to_user(buf,tmp,count)?-EFAULT:ret;
    kfree(tmp);
    return ret;
}

首先從file結構中取得struct i2c_clinet.然後在kernel同分配相同長度的緩存區,隨之調用i2c_master_recv()從設備中讀取數據.再將讀取出來的數據copy到用戶空間中.
I2c_master_recv()代碼如下:

int i2c_master_recv(struct i2c_client *client, char *buf ,int count)
{
    struct i2c_adapter *adap=client->adapter;
    struct i2c_msg msg;
    int ret;

    msg.addr = client->addr;
    msg.flags = client->flags & I2C_M_TEN;
    msg.flags |= I2C_M_RD;
    msg.len = count;
    msg.buf = buf;

    ret = i2c_transfer(adap, &msg, 1);

    /* If everything went ok (i.e. 1 msg transmitted), return #bytes
      transmitted, else error code. */
    return (ret == 1) ? count : ret;
}

看完前面的代碼之後,這個函數應該很簡單了,就是爲讀操作初始化了一個i2c_msg.然後調用i2c_tanster().代碼中的 client->flags & I2C_M_TEN表示adapter是否採用10位尋址的方式.在這裏就不再詳細分析了.
另外,有人可能看出了一個問題.這裏clinet->addr是從哪來的呢?對,在read之前應該還要有一步操作來設置 clinet->addr的值.這個過程是ioctl的操作.ioctl可以設置PEC標誌,重試次數,超時時間,和發送接收數據等,我們在這裏只看一下clinet->addr的設置.代碼片段如下示:
static int i2cdev_ioctl(struct inode *inode, struct file file,
unsigned int cmd, unsigned long arg)
{
......
......
switch ( cmd ) {
case I2C_SLAVE:
case I2C_SLAVE_FORCE:
/
NOTE: devices set up to work with “new style” drivers
* can’t use I2C_SLAVE, even when the device node is not
* bound to a driver. Only I2C_SLAVE_FORCE will work.
*
* Setting the PEC flag here won’t affect kernel drivers,
* which will be using the i2c_client node registered with
* the driver model core. Likewise, when that client has
* the PEC flag already set, the i2c-dev driver won’t see
* (or use) this setting.
/
if ((arg > 0x3ff) ||
(((client->flags & I2C_M_TEN) == 0) && arg > 0x7f))
return -EINVAL;
if (cmd == I2C_SLAVE && i2cdev_check_addr(client->adapter, arg))
return -EBUSY;
/
REVISIT: address could become busy later */
client->addr = arg;
return 0;
......
......
}
由此可見,調用I2C_SLAVE或者I2C_SLAVE_FORCE的Ioctl就會設置clinet->addr.另外,註釋中也說得很清楚了. 如果是I2C_SLAVE的話,還會調用其所長i2cdev_check_addr().進行地址檢查,如果adapter已經關聯到這個地址的設備,就會檢查失敗.

7.2:write操作
Write操作如下所示:

static ssize_t i2cdev_write (struct file *file, const char __user *buf, size_t count,
                            loff_t *offset)
{
    int ret;
    char *tmp;
    struct i2c_client *client = (struct i2c_client *)file->private_data;

    if (count > 8192)
        count = 8192;

    tmp = kmalloc(count,GFP_KERNEL);
    if (tmp==NULL)
        return -ENOMEM;
    if (copy_from_user(tmp,buf,count)) {
        kfree(tmp);
        return -EFAULT;
    }

    pr_debug("i2c-dev: i2c-%d writing %zd bytes./n",
        iminor(file->f_path.dentry->d_inode), count);

    ret = i2c_master_send(client,tmp,count);
    kfree(tmp);
    return ret;
}

該操作比較簡單,就是將用戶空間的數據發送到i2c 設備.

八:小結
在本節中,分析了i2c的框架設計.這個框架大體上沿用了Linux的設備驅動框架,不過之中又做了很多變通.在之後的分析中,會分別舉一個adapter和i2c device的例子來詳細描述一下有關i2c driver的設計.

IIC驅動程序分析

根據上一節課的分析,我們來解讀這段代碼:

#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/jiffies.h>
#include <linux/i2c.h>
#include <linux/mutex.h>

static unsigned short ignore[]      = { I2C_CLIENT_END };
static unsigned short normal_addr[] = { 0x50, I2C_CLIENT_END }; /* 地址值是7位 */

static struct i2c_client_address_data addr_data = {
 .normal_i2c = normal_addr,  /* 要發出S信號和設備地址並得到ACK信號,才能確定存在這個設備 */
 .probe  = ignore,
 .ignore  = ignore,
};

static int at24cxx_detect(struct i2c_adapter *adapter, int address, int kind)
{
 printk("at24cxx_detect\n");
 return 0;
}

/*  i2c_add_driver之後會調用到這個函數 */

static int at24cxx_attach(struct i2c_adapter *adapter)
{
 return i2c_probe(adapter, &addr_data, at24cxx_detect);//這個函數最終會調用適配器的發送函數來發送設備地址
}

/* i2c_del_driver時會調用這個函數,但前提是調用在at24cxx_detect函數中調用了i2c_attach_client函數將 設備、驅動、適配器三者聯繫起來了*/

static int at24cxx_detach(struct i2c_client *client)
{
 printk("at24cxx_detach\n");
 return 0;
}


/* 1. 分配一個i2c_driver結構體 */
/* 2. 設置i2c_driver結構體 */
static struct i2c_driver at24cxx_driver = {
 .driver = {
  .name = "at24cxx",
 },
 .attach_adapter = at24cxx_attach,
 .detach_client  = at24cxx_detach,
};

static int at24cxx_init(void)
{
 i2c_add_driver(&at24cxx_driver);
 return 0;
}

static void at24cxx_exit(void)
{
 i2c_del_driver(&at24cxx_driver);
}

module_init(at24cxx_init);
module_exit(at24cxx_exit);

MODULE_LICENSE("GPL");

我們來分析一下這個程序的流程:

首先分配一個i2c_driver結構體,然後設置並註冊這個結構體。設置的時候有來個函數比較重要:attach_adapter 和.detach_client ,前一個在註冊的時候調用,後一個在卸載的時候調用。註冊之後,這個結構體就被加入到總線驅動列表裏面,然後它會調用at24cxx_attach函數,at24cxx_attach函數又會調用i2c_probe(adapter, &addr_data, at24cxx_detect);函數,其中addr_data,代表設備地址,at24cxx_detect是找到設備後要執行的函數。i2c_probe函數最終會調用適配器裏面的真正發送信號的函數把設備地址發送給從設備,一旦發現了相符合的從設備,就會調用at24cxx_detect函數,這個函數裏面需要調用i2c_attach_client建立聯繫。這裏建立聯繫的工作沒有做,後來再來完善。

當調用 i2c_del_driver卸載i2c_driver這個結構體的時候,就會調用對應的驅動程序的 .detach_client = at24cxx_detach,函數來解除連接,但是前提是已經通過i2c_attach_client函數建立了聯繫。由於這裏並沒有建立聯繫,所以。。。。。。

我們可以總結出編寫IIC總線驅動的大致流程:

  1. 分配一個i2c_driver結構體

  2. 設置
    attach_adapter // 它直接調用 i2c_probe(adap, 設備地址, 發現這個設備後要調用的函數);
    detach_client // 卸載這個驅動後,如果之前發現能夠支持的設備,則調用它來清理

  3. 註冊:i2c_add_driver

IIC驅動程序分析(二)
在上一節的實驗中,我們採用的是normal_i2c 的方式,即:要發出S信號和設備地址並得到ACK信號,才能確定存在這個設備。那麼如果本身不存在這個設備當然啊不會給出應答信號,這是就不會調用i2c_probe(adapter, &addr_data, at24cxx_detect)函數中的at24cxx_detect函數。如果我們目前沒有接上這個設備,但是我們今後打算把它安裝上去,所以我們想要調用i2c_probe(adapter, &addr_data, at24cxx_detect)函數中的at24cxx_detect函數,那怎麼辦呢?這時就不能用normal_i2c方式,而應該採用forces方式。

#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/jiffies.h>
#include <linux/i2c.h>
#include <linux/mutex.h>

static unsigned short ignore[]      = { I2C_CLIENT_END };
static unsigned short normal_addr[] = { 0x50, I2C_CLIENT_END }; /* 地址值是7位 */
                                        /* 改爲0x60的話, 由於不存在設備地址爲0x60的設備, 所以at24cxx_detect不被調用 */

/* 這裏ANY_I2C_BUS表示在任意一條總線上查找該設備,0x60表示要發出的設備地址 */

static unsigned short force_addr[] = {ANY_I2C_BUS, 0x60, I2C_CLIENT_END};
static unsigned short * forces[] = {force_addr, NULL};
          
static struct i2c_client_address_data addr_data = {
 .normal_i2c = ignore,  /* 要發出S信號和設備地址並得到ACK信號,才能確定存在這個設備 */
 .probe  = ignore,
 .ignore  = ignore,
 .forces     = forces, /* 強制認爲存在這個設備 */
};

static int at24cxx_detect(struct i2c_adapter *adapter, int address, int kind)
{
 printk("at24cxx_detect\n");
 return 0;
}

static int at24cxx_attach(struct i2c_adapter *adapter)
{
 return i2c_probe(adapter, &addr_data, at24cxx_detect);
}

static int at24cxx_detach(struct i2c_client *client)
{
 printk("at24cxx_detach\n");
 return 0;
}


static struct i2c_driver at24cxx_driver = {
 .driver = {
  .name = "at24cxx",
 },
 .attach_adapter = at24cxx_attach,
 .detach_client  = at24cxx_detach,
};

static int at24cxx_init(void)
{
 i2c_add_driver(&at24cxx_driver);
 return 0;
}

static void at24cxx_exit(void)
{
 i2c_del_driver(&at24cxx_driver);
}

module_init(at24cxx_init);
module_exit(at24cxx_exit);

MODULE_LICENSE("GPL");

在這個程序裏面,即便不存在設備地址爲0x60的設備,也會調用i2c_probe(adapter, &addr_data, at24cxx_detect)函數中的at24cxx_detect函數,因爲這裏採用的是強制的方式。具體原因等到以後在來研究,目前只是先有個輪廓!

IIC驅動程序分析(三)
上面兩個程序我們主要實現了設備的識別,但是我們發現當卸載驅動的時候並沒有相關的打印信息,這時怎麼回事兒呢?其實原因我們之前已經提到過了,那是因爲我們在i2c_probe(adapter, &addr_data, at24cxx_detect);的功能函數at24cxx_detect裏面並沒有建立設備、驅動、適配器的聯繫,因爲沒有建立聯繫,所以卸載的時候當然不會解除聯繫了!那麼具體應該怎麼做呢?我們來看代碼:

#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/jiffies.h>
#include <linux/i2c.h>
#include <linux/mutex.h>

static unsigned short ignore[]      = { I2C_CLIENT_END };
static unsigned short normal_addr[] = { 0x50, I2C_CLIENT_END };

static unsigned short force_addr[] = {ANY_I2C_BUS, 0x60, I2C_CLIENT_END};
static unsigned short * forces[] = {force_addr, NULL};
          
static struct i2c_client_address_data addr_data = {
 .normal_i2c = normal_addr, 
 .probe  = ignore,
 .ignore  = ignore,
};

static struct i2c_driver at24cxx_driver;

static int at24cxx_detect(struct i2c_adapter *adapter, int address, int kind)
{
 struct i2c_client *new_client;
 
 printk("at24cxx_detect\n");

 /* 構構一個i2c_client結構體: 以後收改數據時會用到它 */
 new_client = kzalloc(sizeof(struct i2c_client), GFP_KERNEL);//分配
 new_client->addr    = address;//這是設備地址
 new_client->adapter = adapter;這時適配器
 new_client->driver  = &at24cxx_driver;//這是驅動
 strcpy(new_client->name, "at24cxx");//這是名字
 i2c_attach_client(new_client);//添加聯繫
 
 return 0;
}

static int at24cxx_attach(struct i2c_adapter *adapter)
{
 return i2c_probe(adapter, &addr_data, at24cxx_detect);
}

static int at24cxx_detach(struct i2c_client *client)
{
 printk("at24cxx_detach\n");
 i2c_detach_client(client);
 kfree(i2c_get_clientdata(client));

 return 0;
}
static struct i2c_driver at24cxx_driver = {
 .driver = {
  .name = "at24cxx",
 },
 .attach_adapter = at24cxx_attach,
 .detach_client  = at24cxx_detach,
};

static int at24cxx_init(void)
{
 i2c_add_driver(&at24cxx_driver);
 return 0;
}

static void at24cxx_exit(void)
{
 i2c_del_driver(&at24cxx_driver);
}

module_init(at24cxx_init);
module_exit(at24cxx_exit);

MODULE_LICENSE("GPL");

這樣的話卸載的時候就會有打印信息了!

IIC驅動程序之完善篇
下面我們來分析一個比較完整的IIC驅動程序:

#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/jiffies.h>
#include <linux/i2c.h>
#include <linux/mutex.h>
#include <linux/fs.h>
#include <asm/uaccess.h>

static unsigned short ignore[] = { I2C_CLIENT_END };
static unsigned short normal_addr[] = { 0x50, I2C_CLIENT_END }; /* 地址值是7位 */
/* 改爲0x60的話, 由於不存在設備地址爲0x60的設備, 所以at24cxx_detect不被調用 */

static unsigned short force_addr[] = {ANY_I2C_BUS, 0x60, I2C_CLIENT_END};
static unsigned short * forces[] = {force_addr, NULL};

static struct i2c_client_address_data addr_data = {
.normal_i2c = normal_addr, /* 要發出S信號和設備地址並得到ACK信號,才能確定存在這個設備 */
.probe = ignore,
.ignore = ignore,
//.forces = forces, /* 強制認爲存在這個設備 */
};

static struct i2c_driver at24cxx_driver;


static int major;
static struct class *cls;
struct i2c_client *at24cxx_client;

static ssize_t at24cxx_read(struct file *file, char __user *buf, size_t size, loff_t * offset)
{
unsigned char address;
unsigned char data;
struct i2c_msg msg[2];
int ret; /* address = buf[0] * data = buf[1] */ if (size != 1) return -EINVAL; copy_from_user(&address, buf, 1);/ *將內核用戶空間的數據(在buf中)拷貝到address裏面 */


/* 數據傳輸三要素: 源,目的,長度 */

/* 讀AT24CXX時,要先把要讀的存儲空間的地址發給它 */
msg[0].addr = at24cxx_client->addr; /* 目的 */
msg[0].buf = &address; /* 源 */
msg[0].len = 1; /* 地址=1 byte */
msg[0].flags = 0; /* 表示寫 */

/* 然後啓動讀操作 */
msg[1].addr = at24cxx_client->addr; /* 源 */
msg[1].buf = &data; /* 目的 */
msg[1].len = 1; /* 數據=1 byte */
msg[1].flags = I2C_M_RD; /* 表示讀 */


ret = i2c_transfer(at24cxx_client->adapter, msg, 2);
if (ret == 2)
{
copy_to_user(buf, &data, 1);
return 1;
}
else
return -EIO;
}

static ssize_t at24cxx_write(struct file *file, const char __user *buf, size_t size, loff_t *offset)
{
unsigned char val[2];
struct i2c_msg msg[1];
int ret;

/* address = buf[0] 
* data = buf[1]
*/
if (size != 2)
return -EINVAL;

copy_from_user(val, buf, 2);

/* 數據傳輸三要素: 源,目的,長度 */
msg[0].addr = at24cxx_client->addr; /* 目的 */
msg[0].buf = val; /* 源 */
msg[0].len = 2; /* 地址+數據=2 byte */
msg[0].flags = 0; /* 表示寫 */

ret = i2c_transfer(at24cxx_client->adapter, msg, 1);
if (ret == 1)
return 2;
else
return -EIO;
}


static struct file_operations at24cxx_fops = {
.owner = THIS_MODULE,
.read = at24cxx_read,
.write = at24cxx_write,
};

static int at24cxx_detect(struct i2c_adapter *adapter, int address, int kind)
{ 
printk("at24cxx_detect\n");

/* 構構一個i2c_client結構體: 以後收改數據時會用到它 */
at24cxx_client = kzalloc(sizeof(struct i2c_client), GFP_KERNEL);
at24cxx_client->addr = address;
at24cxx_client->adapter = adapter;
at24cxx_client->driver = &at24cxx_driver;
strcpy(at24cxx_client->name, "at24cxx"); i2c_attach_client(at24cxx_client); 
major = register_chrdev(0, "at24cxx", &at24cxx_fops); 
cls = class_create(THIS_MODULE, "at24cxx"); 
class_device_create(cls, NULL, MKDEV(major, 0), NULL, "at24cxx"); /* /dev/at24cxx */ return 0;
}
static int at24cxx_attach(struct i2c_adapter *adapter)
{ 
return i2c_probe(adapter, &addr_data, at24cxx_detect);
}
static int at24cxx_detach(struct i2c_client *client)
{
printk("at24cxx_detach\n"); 
class_device_destroy(cls, MKDEV(major, 0)); 
class_destroy(cls); 
unregister_chrdev(major, "at24cxx"); 
i2c_detach_client(client);
kfree(i2c_get_clientdata(client)); 
return 0;
}
/* 1. 分配一個i2c_driver結構體 */
/* 2. 設置i2c_driver結構體 */
static struct i2c_driver at24cxx_driver = { 
.driver = { .name = "at24cxx", }, 
.attach_adapter = at24cxx_attach, 
.detach_client = at24cxx_detach,
};
static int at24cxx_init(void)
{
i2c_add_driver(&at24cxx_driver);
return 0;
}
static void at24cxx_exit(void)
{ i2c_del_driver(&at24cxx_driver);
}
module_init(at24cxx_init);
module_exit(at24cxx_exit);
MODULE_LICENSE("GPL");

之前我們分析的IIC驅動程序主要是通過IIC總線機制完成了存儲設備的識別,但是我們想對存儲設備進行操作怎麼辦呢?那就要用到字符設備驅動程序的概念了!其實IIC總線機制就和平臺總線機制是一個性質的,要完成具體的操作,還需要字符設備的支持。下面我們來詳細分析一下:
首先通過IIC總線機制完成了存儲設備的識別,接着註冊字符設備以及創建設備節點,同時定義了操作函數,那麼我們這裏詳細要分析的就是操作函數了,我們先來看看iic的消息傳輸函數:
i2c_transfer(struct i2c_adapter * adap, struct i2c_msg *msgs, int num)
adap:適配器
msgs:消息,其格式如下:

struct i2c_msg {
__u16 addr; /* 設備地址 /
__u16 flags; / * 讀寫標誌位
/

__u16 len; /* 消息的長度,字節爲單位 */
__u8 buf; / 指向消息的指針 */
};
我們重點要做的就是來設置這個消息,設置好之後只要用i2c_transfer發送就可以了。
num:要傳輸的消息的數目

讀函數:
讀數據的話,首先把地址寫進去,然後把數據讀出來
些函數:
些數據的話,首先把地址寫進去,然後把數據寫進去

測試程序如下:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>


/* i2c_test r addr
* i2c_test w addr val
*/

void print_usage(char *file)
{
printf("%s r addr\n", file);
printf("%s w addr val\n", file);
}

int main(int argc, char **argv)
{
int fd;
unsigned char buf[2];

if ((argc != 3) && (argc != 4))
{
print_usage(argv[0]);
return -1;
}

fd = open("/dev/at24cxx", O_RDWR);
if (fd < 0)
{
printf("can't open /dev/at24cxx\n");
return -1;
}

if (strcmp(argv[1], "r") == 0)
{
buf[0] = strtoul(argv[2], NULL, 0);
read(fd, buf, 1);
printf("data: %c, %d, 0x%2x\n", buf[0], buf[0], buf[0]);
} 
else if (strcmp(argv[1], "w") == 0) 
{
buf[0] = strtoul(argv[2], NULL, 0); 
buf[1] = strtoul(argv[3], NULL, 0); 
write(fd, buf, 2); 
}
else 
{
print_usage(argv[0]); 
return -1; 
}
return 0;
}

8、kernel panic

有兩種主要類型kernel panic:

1.hard panic(也就是Aieee信息輸出)
2.soft panic (也就是Oops信息輸出)

9、USB總線,USB傳輸種類,urb等

USB總線:

USB總線屬於一種輪詢式總線,主機控制端口初始化所有的數據傳輸。每一總線動作最多傳送三個數據包,包括令牌(Token)、數據(Data)、聯絡(HandShake)。按照傳輸前制定好的原則,在每次傳送開始時,主機送一個描述傳輸動作的種類、方向、USB設備地址和終端號的USB數據包,這個數據包通常被稱爲令牌包(TokenPacket)。USB設備從解碼後的數據包的適當位置取出屬於自己的數據。數據傳輸方向不是從主機到設備就是從設備到主機。在傳輸開始時,由標誌包來標誌數據的傳輸方向,然後發送端開始發送包含信息的數據包或表明沒有數據傳送。接收端也要相應發送一個握手的數據包表明是否傳送成功。發送端和接收端之間的USB數據傳輸,在主機和設備的端口之間,可視爲一個通道。USB中有一個特殊的通道一缺省控制通道,它屬於消息通道,設備一啓動即存在,從而爲設備的設置、狀態查詢和輸入控制信息提供一個入口。

USB總線的四種傳輸類型:

1、中斷傳輸:由OUT事務和IN事務構成,用於鍵盤、鼠標等HID設備的數據傳輸中 2、批量傳輸:由OUT事務和IN事務構成,用於大容量數據傳輸,沒有固定的傳輸速率,也不佔用帶寬,當總線忙時,USB會優先進行其他類型的數據傳輸,而暫時停止批量轉輸。 3、同步傳輸:由OUT事務和IN事務構成,有兩個特別地方,第一,在同步傳輸的IN和OUT事務中是沒有返回包階段的;第二,在數據包階段任何的數據包都爲DATA0 4、控制傳輸:最重要的也是最複雜的傳輸,控制傳輸由三個階段構成(初始配置階段、可選數據階段、狀態信息步驟),每一個階段能夠看成一個的傳輸,也就是說控制傳輸其實是由三個傳輸構成的,用來於USB設備初次加接到主機之後,主機通過控制傳輸來交換信息,設備地址和讀取設備的描述符,使得主機識別設備,並安裝相應的驅動程式,這是每一個USB研發者都要關心的問題。
URB:
USB請求塊(USB request block,urb)是USB設備驅動中用來描述與USB設備通信所用的基本載體和核心數據結構,非常類似於網絡設備驅動中的sk_buff結構體,是USB主機與設備通信的“電波”。

20.3.2 USB請求塊(URB)
20.3.2 USB請求塊(URB)

1.urb結構體

USB請求塊(USB request block,urb)是USB設備驅動中用來描述與USB設備通信所用的基本載體和核心數據結構,非常類似於網絡設備驅動中的sk_buff結構體,是USB主機與設備通信的“電波”。

代碼清單20.13 urb結構體

1 struct urb
2 {
3 /* 私有的:只能由USB核心和主機控制器訪問的字段 /
4 struct kref kref; /urb引用計數 /
5 spinlock_t lock; /
urb鎖 /
6 void hcpriv; / 主機控制器私有數據 /
7 int bandwidth; /
INT/ISO請求的帶寬 /
8 atomic_t use_count; /
併發傳輸計數 /
9 u8 reject; /
傳輸將失敗
/
10
11 /
公共的: 可以被驅動使用的字段 /
12 struct list_head urb_list; /
鏈表頭
/
13 struct usb_device dev; / 關聯的USB設備 /
14 unsigned int pipe; /
管道信息 /
15 int status; /
URB的當前狀態 /
16 unsigned int transfer_flags; /
URB_SHORT_NOT_OK | …*/
17 void transfer_buffer; / 發送數據到設備或從設備接收數據的緩衝區 */
18 dma_addr_t transfer_dma; /*用來以DMA方式向設備傳輸數據的緩衝區 */
19 int transfer_buffer_length;/*transfer_buffer或transfer_dma 指向緩衝區的大小 /
20
21 int actual_length; /
URB結束後,發送或接收數據的實際長度 /
22 unsigned char setup_packet; / 指向控制URB的設置數據包的指針
/
23 dma_addr_t setup_dma; /控制URB的設置數據包的DMA緩衝區/
24 int start_frame; /等時傳輸中用於設置或返回初始幀/
25 int number_of_packets; /*等時傳輸中等時緩衝區數據 /
26 int interval; /
URB被輪詢到的時間間隔(對中斷和等時urb有效) /
27 int error_count; /
等時傳輸錯誤數量 */
28 void context; / completion函數上下文 /
29 usb_complete_t complete; /
當URB被完全傳輸或發生錯誤時,被調用 */
30 struct usb_iso_packet_descriptor iso_frame_desc[0];
31 /*單個URB一次可定義多個等時傳輸時,描述各個等時傳輸 */
32 };
當transfer_flags標誌中的URB_NO_TRANSFER_DMA_MAP被置位時,USB核心將使用transfer_dma指向的緩衝區而非transfer_buffer指向的緩衝區,意味着即將傳輸DMA緩衝區。

當transfer_flags標誌中的URB_NO_SETUP_DMA_MAP被置位時,對於有DMA緩衝區的控制urb而言,USB核心將使用setup_dma指向的緩衝區而非setup_packet指向的緩衝區。

2.urb處理流程

USB設備中的每個端點都處理一個urb隊列,在隊列被清空之前,一個urb的典型生命週期如下:

(1)被一個 USB 設備驅動創建。

創建urb結構體的函數爲:

struct urb *usb_alloc_urb(int iso_packets, int mem_flags);
iso_packets是這個urb應當包含的等時數據包的數目,若爲0表示不創建等時數據包。 mem_flags參數是分配內存的標誌,和kmalloc()函數的分配標誌參數含義相同。如果分配成功,該函數返回一個urb結構體指針,否則返回0。

urb結構體在驅動中不能靜態創建,因爲這可能破壞USB核心給urb使用的引用計數方法。

usb_alloc_urb()的“反函數”爲:

void usb_free_urb(struct urb *urb);
該函數用於釋放由usb_alloc_urb()分配的urb結構體。

(2)初始化,被安排給一個特定USB設備的特定端點。

對於中斷urb,使用usb_fill_int_urb()函數來初始化urb,如下所示:

void usb_fill_int_urb(struct urb *urb, struct usb_device *dev,
unsigned int pipe, void *transfer_buffer,
int buffer_length, usb_complete_t complete,
void *context, int interval);
urb參數指向要被初始化的urb的指針;dev指向這個urb要被髮送到的USB設備;pipe是這個urb要被髮送到的USB設備的特定端點;transfer_buffer是指向發送數據或接收數據的緩衝區的指針,和urb一樣,它也不能是靜態緩衝區,必須使用kmalloc()來分配;buffer_length是transfer_buffer指針所指向緩衝區的大小;complete指針指向當這個 urb完成時被調用的完成處理函數;context是完成處理函數的“上下文”;interval是這個urb應當被調度的間隔。

上述函數參數中的pipe使用usb_sndintpipe()或usb_rcvintpipe()創建。

對於批量urb,使用usb_fill_bulk_urb()函數來初始化urb,如下所示:

void usb_fill_bulk_urb(struct urb *urb, struct usb_device *dev,
unsigned int pipe, void *transfer_buffer,
int buffer_length, usb_complete_t complete,
void *context);
除了沒有對應於調度間隔的interval參數以外,該函數的參數和usb_fill_int_urb()函數的參數含義相同。

上述函數參數中的pipe使用usb_sndbulkpipe()或者usb_rcvbulkpipe()函數來創建。

對於控制 urb,使用usb_fill_control_urb()函數來初始化urb,如下所示:

void usb_fill_control_urb(struct urb *urb, struct usb_device *dev,
unsigned int pipe, unsigned char *setup_packet,
void *transfer_buffer, int buffer_length,
usb_complete_t complete, void *context);
除了增加了新的setup_packet參數以外,該函數的參數和usb_fill_bulk_urb()函數的參數含義相同。setup_packet參數指向即將被髮送到端點的設置數據包。

上述函數參數中的pipe使用usb_sndctrlpipe()或usb_rcvictrlpipe()函數來創建。

等時urb沒有像中斷、控制和批量urb的初始化函數,我們只能手動地初始化urb,而後才能提交給USB核心。代碼清單20.14給出了初始化等時urb的例子,它來自drivers/usb/media/usbvideo.c文件。

代碼清單20.14 初始化等時urb

1 for (i = 0; i < USBVIDEO_NUMSBUF; i++)
2 {
3 int j, k;
4 struct urb urb = uvd->sbuf[i].urb;
5 urb->dev = dev;
6 urb->context = uvd;
7 urb->pipe = usb_rcvisocpipe(dev, uvd->video_endp);/端口/
8 urb->interval = 1;
9 urb->transfer_flags = URB_ISO_ASAP; /urb被調度/
10 urb->transfer_buffer = uvd->sbuf[i].data;/傳輸buffer/
11 urb->complete = usbvideo_IsocIrq; /
完成函數 */
12 urb->number_of_packets = FRAMES_PER_DESC; /urb中的等時傳輸數量/
13 urb->transfer_buffer_length = uvd->iso_packet_len *FRAMES_PER_DESC;
14 for (j = k = 0; j < FRAMES_PER_DESC; j++, k += uvd->iso_packet_len)
15 {
16 urb->iso_frame_desc[j].offset = k;
17 urb->iso_frame_desc[j].length = uvd->iso_packet_len;
18 }
19 }
(3)被USB設備驅動提交給USB 核心。

在完成第(1)、(2)步的創建和初始化urb後,urb便可以提交給USB核心,通過usb_submit_urb()函數來完成,如下所示:

int usb_submit_urb(struct urb *urb, int mem_flags);
urb參數是指向urb的指針,mem_flags參數與傳遞給kmalloc()函數參數的意義相同,它用於告知USB核心如何在此時分配內存緩衝區。

在提交urb到USB核心後,直到完成函數被調用之前,不要訪問urb中的任何成員。

usb_submit_urb()在原子上下文和進程上下文中都可以被調用,mem_flags變量需根據調用環境進行相應的設置,如下所示。

l GFP_ATOMIC:在中斷處理函數、底半部、tasklet、定時器處理函數以及urb完成函數中,在調用者持有自旋鎖或者讀寫鎖時以及當驅動將current->state修改爲非 TASK_ RUNNING時,應使用此標誌。

l GFP_NOIO:在存儲設備的塊I/O和錯誤處理路徑中,應使用此標誌;

l GFP_KERNEL:如果沒有任何理由使用GFP_ATOMIC和GFP_NOIO,就使用GFP_ KERNEL。

如果usb_submit_urb()調用成功,即urb的控制權被移交給USB核心,該函數返回0;否則,返回錯誤號。

(4)提交由USB核心指定的USB主機控制器驅動。

(5)被USB主機控制器處理,進行一次到USB設備的傳送。

第(4)~(5)步由USB核心和主機控制器完成,不受USB設備驅動的控制。

(6)當urb完成,USB主機控制器驅動通知USB設備驅動。

在如下3種情況下,urb將結束,urb完成函數將被調用。

l urb 被成功發送給設備,並且設備返回正確的確認。如果urb->status爲0,意味着對於一個輸出urb,數據被成功發送;對於一個輸入urb,請求的數據被成功收到。

l 如果發送數據到設備或從設備接收數據時發生了錯誤,urb->status將記錄錯誤值。

l urb 被從USB 核心“去除連接”,這發生在驅動通過usb_unlink_urb()或usb_kill_urb()函數取消urb,或urb雖已提交,而USB設備被拔出的情況下。

usb_unlink_urb()和usb_kill_urb()這兩個函數用於取消已提交的urb,其參數爲要被取消的urb指針。對usb_unlink_urb()而言,如果urb結構體中的URB_ASYNC_UNLINK(即異步unlink)的標誌被置位,則對該urb的usb_unlink_urb()調用將立即返回,具體的unlink動作將在後臺進行。否則,此函數一直等到urb被解開鏈接或結束時才返回。usb_kill_urb()會徹底終止urb的生命週期,它通常在設備的disconnect()函數中被調用。

當urb生命結束時(處理完成或被解除鏈接),通過urb結構體的status成員可以獲知其原因,如0表示傳輸成功,-ENOENT表示被usb_kill_urb()殺死,-ECONNRESET表示被usb_unlink_urb()殺死,-EPROTO表示傳輸中發生了bitstuff錯誤或者硬件未能及時收到響應數據包,-ENODEV表示USB設備已被移除,-EXDEV表示等時傳輸僅完成了一部分等。

對以上urb的處理步驟進行一個總結,圖20.5給出了一個urb的整個處理流程,虛線框的usb_unlink_urb()和usb_kill_urb()並非一定會發生,它只是在urb正在被USB核心和主機控制器處理時,被驅動程序取消的情況下才發生。

3.簡單的批量與控制URB

有時USB驅動程序只是從USB設備上接收或向USB設備發送一些簡單的數據,這時候,沒有必要將urb創建、初始化、提交、完成處理的整個流程走一遍,而可以使用兩個更簡單的函數,如下所示。

(1)usb_bulk_msg()。

usb_bulk_msg()函數創建一個USB批量urb 並將它發送到特定設備,這個函數是同步的,它一直等待urb完成後才返回。usb_bulk_msg()函數的原型爲:

int usb_bulk_msg(struct usb_device *usb_dev, unsigned int pipe,
void *data, int len, int *actual_length,
int timeout);

圖20.5 urb處理流程
usb_dev參數爲批量消息要發送的USB 設備的指針,pipe爲批量消息要發送到的USB設備的端點,data參數爲指向要發送或接收的數據緩衝區的指針,len參數爲data參數所指向的緩衝區的長度,actual_length用於返回實際發送或接收的字節數,timeout是發送超時,以jiffies爲單位,0意味着永遠等待。

如果函數調用成功,返回0;否則,返回1個負的錯誤值。

(2)usb_control_msg()函數。

usb_control_msg()函數與usb_bulk_msg()函數類似,不過它提供驅動發送和結束USB控制信息而非批量信息的能力,該函數的原型爲:

int usb_control_msg(struct usb_device *dev, unsigned int pipe, _ _u8 request,
_ _u8 requesttype, _ _u16 value, _ _u16 index, void *data, _ _u16 size, int timeout);
dev指向控制消息發往的USB設備,pipe是控制消息要發往的USB設備的端點,request是這個控制消息的USB請求值,requesttype是這個控制消息的USB請求類型,value是這個控制消息的USB消息值,index是這個控制消息的USB消息索引值,data指向要發送或接收的數據緩衝區,size是data參數所指向的緩衝區的大小,timeout是發送超時,以jiffies爲單位,0意味着永遠等待。

參數request、requesttype、value和index與USB規範中定義的USB控制消息直接對應。

如果函數調用成功,該函數返回發送到設備或從設備接收到的字節數;否則,返回一個負的錯誤值。

對usb_bulk_msg()和usb_control_msg()函數的使用要特別慎重,由於它們是同步的,因此不能在中斷上下文和持有自旋鎖的情況下使用。而且,該函數也不能被任何其他函數取消,因此,務必要使得驅動程序的disconnect()函數掌握足夠的信息,以判斷和等待該調用的結束。

10、android boot 流程

android boot process from power on

  1. Power on and boot ROM code execution
    開機並執行 boot ROM 代碼

At power on the CPU will be in a state where no initializations have been done. Internal clocks are not set up and the only memory available is the internal RAM.
When power supplies are stable the execution will start with the Boot ROM code. This is a small piece of code that is hardwired in the CPU ASIC.

纔開機時,CPU 處於未初始化狀態,還沒有設定內部時鐘,僅僅只有內部 RAM 可用。當電源穩定後會開始執行 BOOT ROM 代碼,這是一小塊代碼被硬編碼在 CPU ASIC 中。

A. The Boot ROM code will detect the boot media using a system register that maps to some physical balls on the asic.
This is to determine where to find the first stage of the boot loader.
Boot ROM 代碼會引導 boot 媒介使用系統寄存器映射到 ASIC 中一些物理區域,這是爲了確定哪裏能找到 boot loader 的第一階段

B. Once the boot media sequence is established the boot ROM will try to load the first stage boot loader to internal RAM.
Once the boot loader is in place the boot ROM code will perform a jump and execution continues in the boot loader.
一旦 boot 媒介順序確定,boot ROM 會試着裝載 boot loader 的第一階段到內部 RAM 中,一旦 boot loader 就位,boot ROM 代碼會跳到並執行 boot loader

  1. The boot loader

The boot loader is a special program separate from the Linux kernel that is used to set up initial memories and load the kernel to RAM.
On desktop systems the boot loaders are programs like GRUB and in embedded Linux uBoot is often the boot loader of choice.
Device manufacturers often use their own proprietary boot loaders.

boot loader 是一個特殊的獨立於 Linux 內核的程序,它用來初始化內存和裝載內核到 RAM 中,桌面系統的 boot loader 程序有 GRUB,嵌入式系統常用 uBoot,
設備製造商常常使用自己專有的 boot loader 程序。

A. The first boot loader stage will detect and set up external RAM.
boot loader 第一階段會檢測和設置外部 RAM

B. Once external RAM is available and the system is ready the to run something more significant the first stage will load the main boot loader and place it in external RAM.
一旦外部 RAM 可用,系統會準備裝載主 boot loader,把它放到外部 RAM 中

C. The second stage of the boot loader is the first major program that will run. This may contain code to set up file systems, additional memory,
network support and other things.On a mobile phone it may also be responsible for loading code for the modem CPU and setting up low level memory
protections and security options.

boot loader 第二階段是運行的第一個重要程序,它包含了設置文件系統,內存,網絡支持和其他的代碼。在一個移動電話上,
也可能是負責加載調制解調器的CPU代碼和設定低級別的內存保護和安全選項

D. Once the boot loader is done with any special tasks it will look for a Linux kernel to boot. It will load this from the boot media
(or some other source depending on system configuration) and place it in the RAM.
It will also place some boot parameters in memory for the kernel to read when it starts up.
一旦 boot loader 完成這些特殊任務,開始尋找 linux 內核,它會從 boot 媒介上裝載 linux 內核(或者其他地方,這取決於系統配置),把它放到 RAM 中,
它也會在內存中爲內核替換一些在內核啓動時讀取的啓動參數

E. Once the boot loader is done it will perform a jump to the Linux kernel, usually some decompression routine, and the kernel assumes system responsibility.
一旦 boot loader 完成會跳到 linux 內核,通常通過解壓程序解壓內核文件,內核將取得系統權限

  1. The Linux kernel
      linux 內核

The Linux kernel starts up in a similar way on Android as on other systems. It will set up everything that is needed for the system to run. Initialize interrupt controllers,
set up memory protections, caches and scheduling.
linux 內核在 android 上跟在其他系統上的啓動方式一樣,它將設置系統運行需要的一切,初始化中斷控制器,設定內存保護,高速緩存和調度

A. Once the memory management units and caches have been initialized the system will be able to use virtual memory and launch user space processes.
  一旦內存管理單元和高速緩存初始化完成,系統將可以使用虛擬內存和啓動用戶空間進程

B. The kernel will look in the root file system for the init process (found under system/core/init in the Android open source tree) and launch it as the initial user space process.
  內核在根目錄尋找初始化程序(代碼對應 android source tree: /system/core/init ),啓動它作爲初始化用戶空間進程

  1. The init process
      初始化進程

The init process is the “grandmother” of all system processes. Every other process in the system will be launched from this process or one of its descendants.
初始化進程是所有其他系統進程的 “祖母 ”,系統的每一個其他進程將從該進程中或者該進程的子進程中啓動

A. The init process in Android will look for a file called init.rc. This is a script that describes the system services, file system and other parameters that need to be set up.
The init.rc script is placed in system/core/rootdir in the Android open source project.
初始化進程會尋找 init.rc 文件,init.rc 腳本文件描述了系統服務,文件系統和其他需要設定的參數,該文件在代碼:system/core/rootdir

B. The init process will parse the init script and launch the system service processes.
初始化進程解析 init 腳本,啓動系統服務進程

  1. Zygote and Dalvik

The Zygote is launched by the init process and will basically just start executing and and initialize the Dalvik VM.
Zygote 被初始化進程啓動,開始運行和初始化 dalvik 虛擬機

  1. The system server
    系統服務
    The system server is the first java component to run in the system. It will start all the Android services such as telephony manager and bluetooth.
    Start up of each service is currently written directly into the run method of the system server. The system server source can be found in the file :
    frameworks/base/services/java/com/android/server/SystemServer.java in the open source project.

系統服務是在系統中運行的第一個 java 組件,它會啓動所有的 android 服務,比如:電話服務,藍牙服務,每個服務的啓動被直接寫在 SystemServer.java 這個類的 run 方法裏面
代碼: frameworks/base/services/java/com/android/server/SystemServer.java

  1. Boot completed
      啓動完成

Once the System Server is up and running and the system boot has completed there is a standard broadcast action called ACTION_BOOT_COMPLETED.
一旦系統服務啓動並運行,android 系統啓動就完成了,同時發出 ACTION_BOOT_COMPLETED 廣播

android boot 代碼流程 1
之前這篇,從整體展示了 android 的整個啓動流程,爲了搞清楚 android 啓動到底在代碼層面上是如何調用的,將從源代碼角度去分析,另所有代碼基於 android 4.0 source tree

all story begin with the init process startup
故事從 init 進程啓動開始

init 運行,代碼:system/core/init ,入口:system/core/init/init.c main 函數:

複製代碼
複製代碼
1 int main(int argc, char **argv){
2
3 …
4 // 初始化文件系統
5 mkdir("/dev", 0755);
6 mkdir("/proc", 0755);
7 mkdir("/sys", 0755);
8
9 mount(“tmpfs”, “/dev”, “tmpfs”, MS_NOSUID, “mode=0755”);
10 mkdir("/dev/pts", 0755);
11 mkdir("/dev/socket", 0755);
12 mount(“devpts”, “/dev/pts”, “devpts”, 0, NULL);
13 mount(“proc”, “/proc”, “proc”, 0, NULL);
14 mount(“sysfs”, “/sys”, “sysfs”, 0, NULL);
15
16 …

17 // 解析 /init.rc 和 /init.$hardware.rc 腳本,其中 $hardware 參數從 /proc/cpuinfo 中讀取,模擬器默認是 goldfish
18 INFO(“reading config file\n”);
19 init_parse_config_file("/init.rc");
20
21 /* pull the kernel commandline and ramdisk properties file in /
22 import_kernel_cmdline(0, import_kernel_nv);
23 /
don’t expose the raw commandline to nonpriv processes */
24 chmod("/proc/cmdline", 0440);
25 get_hardware_name(hardware, &revision);
26 snprintf(tmp, sizeof(tmp), “/init.%s.rc”, hardware);
27 init_parse_config_file(tmp);
28
29 …
30 }
複製代碼
複製代碼
解析 init.rc 文件,主要是由 /system/core/init/init_parser.c 來完成,截取 init.rc 的部分內容如下:(具體 init.rc 文件規範,可參考:/system/core/init/readme.txt)

複製代碼
複製代碼
on early-init
start ueventd

# create mountpoints
mkdir /mnt 0775 root system

# setup the global environment
export PATH /sbin:/vendor/bin:/system/sbin:/system/bin:/system/xbin

# Create cgroup mount points for process groups
mkdir /dev/cpuctl
mount cgroup none /dev/cpuctl cpu
chown system system /dev/cpuctl
chown system system /dev/cpuctl/tasks
chmod 0777 /dev/cpuctl/tasks
write /dev/cpuctl/cpu.shares 1024

service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server
class main
socket zygote stream 666
onrestart write /sys/android_power/request_state wake
onrestart write /sys/power/state on
onrestart restart media
onrestart restart netd
複製代碼
複製代碼
init.rc 使用的是 android init language 規範,它支持4種語句,Actions,Commands,Services,Options:

Action 定義方式:
on #以 on 開頭,後面是觸發器名字,觸發器有3種方式:
#1.只是一個名字,如: on early-init; 
                  #2.name=value 對,如:on property:vold.decrypt=trigger_reset_main;
#3.系統自帶的,如:device-added-,device-removed-,service-exited-
#在觸發器下一行就是在觸發器觸發的時候需要執行的命令,如:start…,mkdir,…
Command 就是系統支持的一系列命令,如:export,hostname,mkdir,mount,等等,其中一部分是 linux 命令,還有一些是 android 添加的,如:class_start : 啓動服務,
class_stop :關閉服務,等等。
Service 定義方式:
複製代碼
複製代碼
service [ ]*

...

#如:啓動 android 最重要的服務 zygote
service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server
class main
socket zygote stream 666
onrestart write /sys/android_power/request_state wake
onrestart write /sys/power/state on
onrestart restart media
onrestart restart netd
複製代碼
複製代碼
Option 是針對 Service 的選項,如:
複製代碼
複製代碼
setenv <name> <value>                   在啓動服務時設置環境變量
user <username>                   運行服務之前切換用戶
oneshot                   如果服務已經存在,將不再啓動
class                   爲服務設置名字,具有相同名字的服務將一起啓動或者關閉
socket <name> <type>\ [ <user> [ <group> ] ]      創建以命名的 socket,並將該 socket 的文件描述符返回給啓動的服務
onrestart <command>                        在服務重新啓動的時候執行<command>
複製代碼
複製代碼
在對 init.rc 和 init.$hardware.rc 2個文件按照 android init language 規範進行解析完成以後,會將所有將要執行的命令放到一個大的 action_list 當中,然後緊跟上面解析 init 文件下面:

複製代碼
複製代碼

 // 尋找 early-init 觸發器,加到 action_queue 中
action_for_each_trigger("early-init", action_add_queue_tail);

// 添加系統的一些觸發器
   queue_builtin_action(wait_for_coldboot_done_action, "wait_for_coldboot_done");
queue_builtin_action(property_init_action, "property_init");
queue_builtin_action(keychord_init_action, "keychord_init");
queue_builtin_action(console_init_action, "console_init");
queue_builtin_action(set_init_properties_action, "set_init_properties");

/* execute all the boot actions to get us started */
action_for_each_trigger("init", action_add_queue_tail);

/* skip mounting filesystems in charger mode */
if (strcmp(bootmode, "charger") != 0) {
    action_for_each_trigger("early-fs", action_add_queue_tail);
    action_for_each_trigger("fs", action_add_queue_tail);
    action_for_each_trigger("post-fs", action_add_queue_tail);
    action_for_each_trigger("post-fs-data", action_add_queue_tail);
}

queue_builtin_action(property_service_init_action, "property_service_init");
queue_builtin_action(signal_init_action, "signal_init");
queue_builtin_action(check_startup_action, "check_startup");

if (!strcmp(bootmode, "charger")) {
    action_for_each_trigger("charger", action_add_queue_tail);
} else {
    action_for_each_trigger("early-boot", action_add_queue_tail);
    action_for_each_trigger("boot", action_add_queue_tail);
}

    /* run all property triggers based on current state of the properties */
queue_builtin_action(queue_property_triggers_action, "queue_propety_triggers");
  
  ...
  
  // 按順序執行 action_queue 中的命令
for(;;) {
    int nr, i, timeout = -1;

    execute_one_command();
    restart_processes();

    ...
}

複製代碼
複製代碼
從 action_list 中找到制定觸發器,將觸發器需要執行的命令添加到 action_queue 中,最後按順序執行 action_queue 中的命令來完成初始化,初始化除了設置一些環境變量和創建文件夾以外,

更多的是關心 Service 的啓動,init 文件裏面的服務有2種,1種是 class core,還有1種是 class main,對 init.rc 的 service 按照 class 分類如下:

複製代碼
複製代碼
class core:

service ueventd /sbin/ueventd
service console /system/bin/sh
service adbd /sbin/adbd
service servicemanager /system/bin/servicemanager
service vold /system/bin/vold

class main:

service netd /system/bin/netd
service debuggerd /system/bin/debuggerd
service ril-daemon /system/bin/rild
service surfaceflinger /system/bin/surfaceflinger
service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server
service drm /system/bin/drmserver
service media /system/bin/mediaserver
service bootanim /system/bin/bootanimation
service dbus /system/bin/dbus-daemon --system --nofork
service bluetoothd /system/bin/bluetoothd -n
service installd /system/bin/installd
service flash_recovery /system/etc/install-recovery.sh
service racoon /system/bin/racoon
service mtpd /system/bin/mtpd
service keystore /system/bin/keystore /data/misc/keystore
service dumpstate /system/bin/dumpstate -s
複製代碼
複製代碼
service ueventd:會讀取 /ueventd.rc 和 /ueventd.$hadrware.rc 文件,解析跟 init.rc 解析類似,主要是對文件系統的權限和用戶進行設置:(代碼:system/core/init/ueventd.c)

#目錄 權限 user group

/dev/null 0666 root root
/dev/zero 0666 root root
在 class core 服務啓動以後, class main 開始啓動,service zygote 是標誌進入 android 最重要的一個服務,服務名字 zygote,實際上啓動的是 app_process,(代碼:frameworks/base/cmds/app_process/app_main.cpp)

複製代碼
複製代碼

int main(int argc, const char* const argv[])
{
    // These are global variables in ProcessState.cpp
    mArgC = argc;
    mArgV = argv;

    mArgLen = 0;
     
    //讀取參數,傳遞的參數就是 init.rc 中啓動時傳入的: -Xzygote /system/bin --zygote --start-system-server
    for (int i=0; i<argc; i++) {
        mArgLen += strlen(argv[i]) + 1;
    }
    mArgLen--;

    AppRuntime runtime;
    const char* argv0 = argv[0];   //-Xzygote

    // Process command line arguments
    // ignore argv[0]
    argc--;           //之前是 4, 現在是 3
    argv++;           //argv 指向 argv[1]

    // Everything up to '--' or first non '-' arg goes to the vm
  
   // i = 0,代碼:frameworks/base/core/jni/AndroidRuntime.cpp
    int i = runtime.addVmArguments(argc, argv);

    // Parse runtime arguments.  Stop at first unrecognized option.
    bool zygote = false;
    bool startSystemServer = false;
    bool application = false;
    const char* parentDir = NULL;
    const char* niceName = NULL;
    const char* className = NULL;
    while (i < argc) {
        const char* arg = argv[i++];
        if (!parentDir) {
            parentDir = arg;                 //parentDir = /system/bin
        } else if (strcmp(arg, "--zygote") == 0) {     //當 i = 2,arg = argv[1] 時,即:--zygote
            zygote = true;                
            niceName = "zygote";
        } else if (strcmp(arg, "--start-system-server") == 0) {
            startSystemServer = true;
        } else if (strcmp(arg, "--application") == 0) {
            application = true;
        } else if (strncmp(arg, "--nice-name=", 12) == 0) {
            niceName = arg + 12;
        } else {
            className = arg;
            break;
        }
    }

    if (niceName && *niceName) {
        setArgv0(argv0, niceName);
        set_process_name(niceName);
    }

    runtime.mParentDir = parentDir;

    if (zygote) {
     // zygote = true,啓動 com.android.internal.os.ZygoteInit,參數:startSystemServer
        runtime.start("com.android.internal.os.ZygoteInit",startSystemServer ? "start-system-server" : "");
    } else if (className) {
        // Remainder of args get passed to startup class main()
        runtime.mClassName = className;
        runtime.mArgC = argc - i;
        runtime.mArgV = argv + i;
        runtime.start("com.android.internal.os.RuntimeInit",
                application ? "application" : "tool");
    } else {
        fprintf(stderr, "Error: no class name or --zygote supplied.\n");
        app_usage();
        LOG_ALWAYS_FATAL("app_process: no class name or --zygote supplied.");
        return 10;
    }
}

複製代碼
複製代碼
檢測傳入參數,將調用 :

runtime.start(“com.android.internal.os.ZygoteInit”,startSystemServer ? “start-system-server” : “”);
runtime 的代碼:frameworks/base/core/jni/AndroidRuntime.cpp,start 函數會啓動虛擬機, 執行 com.android.internal.os.ZygoteInit 該類的 main 函數,並傳入參數: start-system-server:

複製代碼
複製代碼

void AndroidRuntime::start(const char* className, const char* options)
{
    LOGD("\n>>>>>> AndroidRuntime START %s <<<<<<\n",
            className != NULL ? className : "(unknown)");

    ...

    /* start the virtual machine */
    //設置 dalvik 虛擬機參數,創建並啓動虛擬機 
    JNIEnv* env;
    if (startVm(&mJavaVM, &env) != 0) {
        return;
    }
    onVmCreated(env);

    /*
     * Register android functions.
     */
    if (startReg(env) < 0) {
        LOGE("Unable to register all android natives\n");
        return;
    }

    ...

    /*
     * Start VM.  This thread becomes the main thread of the VM, and will
     * not return until the VM exits.
     */
    //將類名 com.xxx.xxx 轉換成 com/xxx/xxx
    char* slashClassName = toSlashClassName(className);
    jclass startClass = env->FindClass(slashClassName);
    if (startClass == NULL) {
        LOGE("JavaVM unable to locate class '%s'\n", slashClassName);
        /* keep going */
    } else {
     // jni 調用 java 方法,獲取對應類名的 class,然後調用靜態 main 方法
        jmethodID startMeth = env->GetStaticMethodID(startClass, "main",
            "([Ljava/lang/String;)V");
        if (startMeth == NULL) {
            LOGE("JavaVM unable to find main() in '%s'\n", className);
            /* keep going */
        } else {
            env->CallStaticVoidMethod(startClass, startMeth, strArray);
        }
    }
    free(slashClassName);

    LOGD("Shutting down VM\n");
    if (mJavaVM->DetachCurrentThread() != JNI_OK)
        LOGW("Warning: unable to detach main thread\n");
    if (mJavaVM->DestroyJavaVM() != 0)
        LOGW("Warning: VM did not shut down cleanly\n");
}

複製代碼
複製代碼
ZygoteInit 類 main 函數:

複製代碼
複製代碼

public static void main(String argv[]) {
        try {
            // Start profiling the zygote initialization.
            SamplingProfilerIntegration.start();
       
            //註冊 socket server
            registerZygoteSocket();
            EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_START,
                SystemClock.uptimeMillis());

            //預加載資源,有 preloadClasses() 和 preloadResources(),加載的開始和結束會被記錄在 /system/etc/event-log-tags 文件中
            preload();
            EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_END,
                SystemClock.uptimeMillis());
       
             
            // Finish profiling the zygote initialization.
            SamplingProfilerIntegration.writeZygoteSnapshot();

            // Do an initial gc to clean up after startup
            gc();

            // If requested, start system server directly from Zygote
            if (argv.length != 2) {
                throw new RuntimeException(argv[0] + USAGE_STRING);
            }
  
            if (argv[1].equals("start-system-server")) {
          //在調用 Zygote 的 main 函數時,已經傳入 start-system-server,調用 startSystemServer() 
                startSystemServer();
            } else if (!argv[1].equals("")) {
                throw new RuntimeException(argv[0] + USAGE_STRING);
            }

            Log.i(TAG, "Accepting command socket connections");

            if (ZYGOTE_FORK_MODE) {
                runForkMode();
            } else {
         //上面通過 registerZygoteSocket() 函數調用註冊的 server scocket,會啓動,開始監聽 Zygote 連接
                runSelectLoopMode();
            }
        
            closeServerSocket();
        } catch (MethodAndArgsCaller caller) {
            caller.run();
        } catch (RuntimeException ex) {
            Log.e(TAG, "Zygote died with exception", ex);
            closeServerSocket();
            throw ex;
        }
    }

複製代碼
複製代碼

那 startSystemServer 到底又做了什麼東東呢,以及最後系統如何發出 ACTION_BOOT_COMPLETED 廣播的呢,且聽下回分解。 -):

android boot 代碼流程 2
上回 說到,開始調用 ZygoteInit main 函數,main 函數:

registerZygoteServer:註冊一個 zygote server socket,所有來自客戶端的連接都通過 socket 方式連接;
preload:預加載系統的類庫和資源,這樣其他程序啓動將不再加載系統資源,只需加載自己程序的資源,這樣就達到系統資源在程序之間共享;
startSystemServer:
複製代碼
複製代碼

private static boolean startSystemServer()
            throws MethodAndArgsCaller, RuntimeException {
        /* Hardcoded command line to start the system server */
     //命令行參數,包括:uid,gid,group,process_name,process class
        String args[] = {
            "--setuid=1000",
            "--setgid=1000",
            "--setgroups=1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,1018,3001,3002,3003,3006,3007",
            "--capabilities=130104352,130104352",
            "--runtime-init",
            "--nice-name=system_server",
            "com.android.server.SystemServer",
        };
        ZygoteConnection.Arguments parsedArgs = null;

        int pid;

        try {
       //解析命令行參數
            parsedArgs = new ZygoteConnection.Arguments(args);
            ZygoteConnection.applyDebuggerSystemProperty(parsedArgs);
            ZygoteConnection.applyInvokeWithSystemProperty(parsedArgs);

            /* Request to fork the system server process */
       //從 zygote 進程派生一個新的進程,fork 可參考:http://linux.die.net/man/2/fork ,不同的是該進程結束時,也會讓 zygote 進程結束
      //所以這裏,會返回2次,一次返回的是 zygote 進程的 pid ,值大於0;一次返回的是子進程 pid,值等於0
            // fork 返回在 zygote 進程返回的子進程 pid,非0,在子進程中返回0
            pid = Zygote.forkSystemServer(
                    parsedArgs.uid, parsedArgs.gid,
                    parsedArgs.gids,
                    parsedArgs.debugFlags,
                    null,
                    parsedArgs.permittedCapabilities,
                    parsedArgs.effectiveCapabilities);
        } catch (IllegalArgumentException ex) {
            throw new RuntimeException(ex);
        }

        /* For child process */
    //zygote 進程 pid 非0,直接返回,而子進程 pid = 0,對子進程進行設置
        if (pid == 0) {
            handleSystemServerProcess(parsedArgs);
        }

        return true;
    }

複製代碼
複製代碼
而 handleSystemServerProcess 將啓動 com.android.server.SystemServer:

複製代碼
複製代碼

private static void handleSystemServerProcess(
            ZygoteConnection.Arguments parsedArgs)
            throws ZygoteInit.MethodAndArgsCaller {

        //因爲有 zygote 監聽 socket,所以 system server 不監聽 socket 連接,此處關閉 
        closeServerSocket();

        // set umask to 0077 so new files and directories will default to owner-only permissions.
        FileUtils.setUMask(FileUtils.S_IRWXG | FileUtils.S_IRWXO);
     
     //設置進程名字,即從命令行參數獲取的:system_server 
        if (parsedArgs.niceName != null) {
            Process.setArgV0(parsedArgs.niceName);
        }

        if (parsedArgs.invokeWith != null) {
            WrapperInit.execApplication(parsedArgs.invokeWith,
                    parsedArgs.niceName, parsedArgs.targetSdkVersion,
                    null, parsedArgs.remainingArgs);
        } else {
            /*
             * Pass the remaining arguments to SystemServer.
             */
       /* zygoteInit -> applicationInit:設置 sdktarget 版本 -> invokeStaticMain:得到 com.android.server.SystemServer main 方法 -> ZygoteInit.MethodAndArgsCaller
        * ZygoteInit.MethodAndArgsCaller 方法拋出異常 MethodAndArgsCaller,跳過了在 startSystemServer 下面的代碼:
             * if (ZYGOTE_FORK_MODE) {
        *     runForkMode();
        *   } else {
        *     runSelectLoopMode();
        *   }
        */
       RuntimeInit.zygoteInit(parsedArgs.targetSdkVersion, parsedArgs.remainingArgs); } /* should never reach here */
     }

複製代碼
複製代碼
在對 MethodAndArgsCaller 異常 catch 語句裏,直接調用了 com.android.server.SystemServer main 方法,而 zygote 進程因爲 pid 不爲0,執行 runSelectLoopMode 方法:

複製代碼
複製代碼

private static void runSelectLoopMode() throws MethodAndArgsCaller {
        ArrayList<FileDescriptor> fds = new ArrayList();
        ArrayList<ZygoteConnection> peers = new ArrayList();
        FileDescriptor[] fdArray = new FileDescriptor[4];

        fds.add(sServerSocket.getFileDescriptor());
        peers.add(null);

        int loopCount = GC_LOOP_COUNT;
     //一直循環
        while (true) {
            int index;

            /*
             * Call gc() before we block in select().
             * It's work that has to be done anyway, and it's better
             * to avoid making every child do it.  It will also
             * madvise() any free memory as a side-effect.
             *
             * Don't call it every time, because walking the entire
             * heap is a lot of overhead to free a few hundred bytes.
             */
            if (loopCount <= 0) {
                gc();
                loopCount = GC_LOOP_COUNT;
            } else {
                loopCount--;
            }

       //採用非阻塞方式,等待並取出 zygote 連接
            try {
                fdArray = fds.toArray(fdArray);
                index = selectReadable(fdArray);
            } catch (IOException ex) {
                throw new RuntimeException("Error in select()", ex);
            }
            
            //selectReadable 返回值小於0 ,有錯誤發生;值等於0,有新的連接,加到 list 中;值大於0,處理當前連接
            if (index < 0) {
                throw new RuntimeException("Error in select()");
            } else if (index == 0) {
                ZygoteConnection newPeer = acceptCommandPeer();
                peers.add(newPeer);
                fds.add(newPeer.getFileDesciptor());
            } else {
                boolean done;
                done = peers.get(index).runOnce();

                if (done) {
                    peers.remove(index);
                    fds.remove(index);
                }
            }
        }
    }

複製代碼
複製代碼
在 zygote 進程等待連接的同時,com.android.server.SystemServer 已經啓動:

複製代碼
複製代碼

native public static void init1(String[] args);

public static void main(String[] args) {
     …
     //加載 jni ,init1 是本地方法
System.loadLibrary(“android_servers”);
     // init1 -> frameworks/base/services/jni/com_android_server_SystemServer.cpp :: android_server_SystemServer_init1 ->
    // frameworks/base/cmds/system_server/library/system_init.cpp :: system_init
init1(args);
}

// init1 將回調 init2 方法
public static final void init2() {
Slog.i(TAG, “Entered the Android system server!”);
Thread thr = new ServerThread();
thr.setName(“android.server.ServerThread”);
thr.start();
}

複製代碼
複製代碼
init1 方法最終調用的是 system_init 方法(代碼:frameworks/base/cmds/system_server/library/system_init.cpp)

複製代碼
複製代碼

extern "C" status_t system_init()
{
    LOGI("Entered system_init()");

    sp<ProcessState> proc(ProcessState::self());
  
    sp<IServiceManager> sm = defaultServiceManager();
    LOGI("ServiceManager: %p\n", sm.get());

    sp<GrimReaper> grim = new GrimReaper();
    sm->asBinder()->linkToDeath(grim, grim.get(), 0);
  
   //初始化 SurfaceFlinger 和傳感器
    char propBuf[PROPERTY_VALUE_MAX];
    property_get("system_init.startsurfaceflinger", propBuf, "1");
    if (strcmp(propBuf, "1") == 0) {
        // Start the SurfaceFlinger
        SurfaceFlinger::instantiate();
    }

    property_get("system_init.startsensorservice", propBuf, "1");
    if (strcmp(propBuf, "1") == 0) {
        // Start the sensor service
        SensorService::instantiate();
    }

    // And now start the Android runtime.  We have to do this bit
    // of nastiness because the Android runtime initialization requires
    // some of the core system services to already be started.
    // All other servers should just start the Android runtime at
    // the beginning of their processes's main(), before calling
    // the init function.
    LOGI("System server: starting Android runtime.\n");
    AndroidRuntime* runtime = AndroidRuntime::getRuntime();
  
   //回調 com.android.server.SystemServer init2 方法
    LOGI("System server: starting Android services.\n");
    JNIEnv* env = runtime->getJNIEnv();
    if (env == NULL) {
        return UNKNOWN_ERROR;
    }
    jclass clazz = env->FindClass("com/android/server/SystemServer");
    if (clazz == NULL) {
        return UNKNOWN_ERROR;
    }
    jmethodID methodId = env->GetStaticMethodID(clazz, "init2", "()V");
    if (methodId == NULL) {
        return UNKNOWN_ERROR;
    }
    env->CallStaticVoidMethod(clazz, methodId);
  
  //啓動線程池,爲 binder 服務
    LOGI("System server: entering thread pool.\n");
    ProcessState::self()->startThreadPool();
    IPCThreadState::self()->joinThreadPool();
    LOGI("System server: exiting thread pool.\n");

    return NO_ERROR;
}

複製代碼
複製代碼
init2 啓動 ServerThread 線程,它會啓動 android 系統所有的服務:

複製代碼
複製代碼

public void run() {
        EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_SYSTEM_RUN,
            SystemClock.uptimeMillis());
    
        Looper.prepare();

        android.os.Process.setThreadPriority(
                android.os.Process.THREAD_PRIORITY_FOREGROUND);

        BinderInternal.disableBackgroundScheduling(true);
        android.os.Process.setCanSelfBackground(false);

        String factoryTestStr = SystemProperties.get("ro.factorytest");
        int factoryTest = "".equals(factoryTestStr) ? SystemServer.FACTORY_TEST_OFF
                : Integer.parseInt(factoryTestStr);
      
     //初始化服務,如:網絡服務,Wifi服務,藍牙,電源,等等,初始化完成以後,加到 ServiceManager 中,
       //所以我們用 Context.getSystemService (String name) 才獲取到相應的服務
        LightsService lights = null;
        PowerManagerService power = null;
        BatteryService battery = null;
        AlarmManagerService alarm = null;
        NetworkManagementService networkManagement = null;
        NetworkStatsService networkStats = null;
        NetworkPolicyManagerService networkPolicy = null;
        ConnectivityService connectivity = null;
        WifiP2pService wifiP2p = null;
        WifiService wifi = null;
        IPackageManager pm = null;
        Context context = null;
        WindowManagerService wm = null;
        BluetoothService bluetooth = null;
        BluetoothA2dpService bluetoothA2dp = null;
        DockObserver dock = null;
        UsbService usb = null;
        UiModeManagerService uiMode = null;
        RecognitionManagerService recognition = null;
        ThrottleService throttle = null;
        NetworkTimeUpdateService networkTimeUpdater = null;

        // Critical services...
        try {
            Slog.i(TAG, "Entropy Service");
            ServiceManager.addService("entropy", new EntropyService());

            Slog.i(TAG, "Package Manager");
            // Only run "core" apps if we're encrypting the device.

            ......
      
      //ActivityManagerService 是 android 系統最核心的服務之一 
//1.系統 context 的初始化,設置默認主題 android.R.style.Theme_Holo
//2.設置進程名字爲 system_process
//3.初始化 ActivityStack

context = ActivityManagerService.main(factoryTest);

 

//往 service manager 裏面添加一些服務,如:activity,meminfo,cupinfo,permission
ActivityManagerService.setSystemProcess();
 

//安裝系統 content provider
Slog.i(TAG, "System Content Providers");
ActivityManagerService.installSystemProviders();

 

//設置 windows manager

ActivityManagerService.self().setWindowManager(wm);                    

            ......

        // We now tell the activity manager it is okay to run third party
        // code.  It will call back into us once it has gotten to the state
        // where third party code can really run (but before it has actually
        // started launching the initial applications), for us to complete our
        // initialization.
     
     //代碼到這裏,表明系統已經就緒,可以運行第3方代碼
        ActivityManagerService.self().systemReady(new Runnable() {
            public void run() {
                Slog.i(TAG, "Making services ready");
         // systemui 是 3.0 以後添加的,因爲沒有物理鍵,提供虛擬鍵 
                startSystemUi(contextF);

          //諸多服務開始啓動
                try {
                    if (batteryF != null) batteryF.systemReady();
                } catch (Throwable e) {
                    reportWtf("making Battery Service ready", e);
                }
                try {
                    if (networkManagementF != null) networkManagementF.systemReady();
                } catch (Throwable e) {
                    reportWtf("making Network Managment Service ready", e);
                }
                ......
            }
        });

        // For debug builds, log event loop stalls to dropbox for analysis.
        if (StrictMode.conditionallyEnableDebugLogging()) {
            Slog.i(TAG, "Enabled StrictMode for system server main thread.");
        }

        Looper.loop();
        Slog.d(TAG, "System ServerThread is exiting!");
    }

複製代碼
複製代碼
而要執行 ActivityManagerService.self().systemReady(new Runnable() …) 參數裏面 Runnable 的 run 方法,還必須等到 ActivityManagerService systemReady:

複製代碼
複製代碼

public void systemReady(final Runnable goingCallback) {
        synchronized(this) {
        //mSystemReady = false
            if (mSystemReady) {
                if (goingCallback != null) goingCallback.run();
                return;
            }
            
            // Check to see if there are any update receivers to run.
            if (!mDidUpdate) {
                if (mWaitingUpdate) {
                    return;
                }
         //檢測是否有 ACTION_PRE_BOOT_COMPLETED register,該廣播在 ACTION_BOOT_COMPLETED 前發出
                Intent intent = new Intent(Intent.ACTION_PRE_BOOT_COMPLETED);
                List<ResolveInfo> ris = null;
                try {
                    ris = AppGlobals.getPackageManager().queryIntentReceivers(
                                intent, null, 0);
                } catch (RemoteException e) {
                }
                if (ris != null) {
                    for (int i=ris.size()-1; i>=0; i--) {
              //檢測廣播註冊是否是系統程序
                        if ((ris.get(i).activityInfo.applicationInfo.flags
                                &ApplicationInfo.FLAG_SYSTEM) == 0) {
                            ris.remove(i);
                        }
                    }
                    intent.addFlags(Intent.FLAG_RECEIVER_BOOT_UPGRADE);
                    
                    ArrayList<ComponentName> lastDoneReceivers = readLastDonePreBootReceivers();
                    
                    final ArrayList<ComponentName> doneReceivers = new ArrayList<ComponentName>();
                    for (int i=0; i<ris.size(); i++) {
                        ActivityInfo ai = ris.get(i).activityInfo;
                        ComponentName comp = new ComponentName(ai.packageName, ai.name);
                        if (lastDoneReceivers.contains(comp)) {
                            ris.remove(i);
                            i--;
                        }
                    }

                    for (int i=0; i<ris.size(); i++) {
                        ActivityInfo ai = ris.get(i).activityInfo;
                        ComponentName comp = new ComponentName(ai.packageName, ai.name);
                        doneReceivers.add(comp);
                        intent.setComponent(comp);
                        IIntentReceiver finisher = null;
                        if (i == ris.size()-1) {
                            finisher = new IIntentReceiver.Stub() {
                                public void performReceive(Intent intent, int resultCode,
                                        String data, Bundle extras, boolean ordered,
                                        boolean sticky) {
                                    // The raw IIntentReceiver interface is called
                                    // with the AM lock held, so redispatch to
                                    // execute our code without the lock.
                                    mHandler.post(new Runnable() {
                                        public void run() {
                                            synchronized (ActivityManagerService.this) {
                                                mDidUpdate = true;
                                            }
                                            writeLastDonePreBootReceivers(doneReceivers);
                                            showBootMessage(mContext.getText(
                                                    R.string.android_upgrading_complete),
                                                    false);
                          //如果有 ACTION_PRE_BOOT_COMPLETED,在處理完廣播 receive 以後 ,還會再次走 systemRead(goingCallback)
                                            systemReady(goingCallback);
                                        }
                                    });
                                }
                            };
                        }
                        Slog.i(TAG, "Sending system update to: " + intent.getComponent());
                        broadcastIntentLocked(null, null, intent, null, finisher,
                                0, null, null, null, true, false, MY_PID, Process.SYSTEM_UID);
                        if (finisher != null) {
                            mWaitingUpdate = true;
                        }
                    }
                }
                if (mWaitingUpdate) {
                    return;
                }
                mDidUpdate = true;
            }
            
            mSystemReady = true;
        //mStartRunning 已經在 ActivityManagerService.main(int factoryTest) 設置成 true
            if (!mStartRunning) {
                return;
            }
        }

        
     ......

        retrieveSettings();
      
  
      //開始執行 runnable 的 run 方法,執行完成以後,系統就緒
        if (goingCallback != null) goingCallback.run();
        
        synchronized (this) {
            if (mFactoryTest != SystemServer.FACTORY_TEST_LOW_LEVEL) {
                try {
                    List apps = AppGlobals.getPackageManager().
                        getPersistentApplications(STOCK_PM_FLAGS);
                    if (apps != null) {
                        int N = apps.size();
                        int i;
                        for (i=0; i<N; i++) {
                            ApplicationInfo info
                                = (ApplicationInfo)apps.get(i);
                            if (info != null &&
                                    !info.packageName.equals("android")) {
                                addAppLocked(info);
                            }
                        }
                    }
                } catch (RemoteException ex) {
                    // pm is in same process, this will never happen.
                }
            }

            // Start up initial activity.
            mBooting = true;
            
            try {
                if (AppGlobals.getPackageManager().hasSystemUidErrors()) {
                    Message msg = Message.obtain();
                    msg.what = SHOW_UID_ERROR_MSG;
                    mHandler.sendMessage(msg);
                }
            } catch (RemoteException e) {
            }
       
        //恢復 top activity,因爲現在沒有任何啓動的 activity, 將會啓動 startHomeActivityLocked,啓動 HOME  
            mMainStack.resumeTopActivityLocked(null);
        }
    }

複製代碼
複製代碼
HOME 啓動以後,ActivityManagerService 中 finishBooting 方法會發出 Intent.ACTION_BOOT_COMPLETED 廣播,調用該方法的地方有很多,resume activity 的時候或者出錯的時候,

調用一次以後就不再調用。

至此 android 就完成了整個啓動工作,整個流程可以用下圖簡潔表示:

11、android init解析init.rc

Android初始化語言(Android Init Language

Android初始化腳本語言包含四種類型的語句:

動作(Actions)
指令(Commands)
服務(Services)
選項(Options)
該語言的語法包括下列約定:

所有類型的語句都是基於行(line-oriented)的, 一個語句包含若干個tokens,token之間通過空格字符分隔. 如果一個token中需要包含空格字符,則需要通過C語言風格的反斜線(’’)來轉義,或者使用雙引號把整個token引起來。反斜線還可以出現在一行的末尾,表示下一行的內容仍然屬於當前語句。
以’#'開始的行是註釋行。
動作(Actions)和服務(Services)語句隱含表示一個新的段落(section)的開始。 所有的指令(commands)和選項(options)歸屬於上方最近的一個段落。在第一個段落之前的指令(commands)和選項(options)是無效的。
動作(Actions)和服務(Services)擁有唯一性的名字。如果出現重名,那麼後出現的定義將被作爲錯誤忽略掉。
動作(Actions)

動作(Actions)是一個有名字的指令(commands)序列。每個動作(Actions)都定義一個觸發條件(trigger),用於指示什麼時候執行這個動作。當與動作的觸發器匹配的事件發生時,該動作將被添加到一個即將被執行的隊列的隊尾(除非它已經在隊列中)。

隊列中的每一個動作被依次取出執行,動作中的每一個指令也將依次執行。初始化程序(Init)在執行一個動作的各項指令的期間,還需要處理其它操作(比如,設備創建/銷燬,屬性設置,進程重啓)。

一個動作定義的形式如下:

on <trigger>
  <command>
  <command>
  <command>

服務(Services)

服務是初始化程序需要啓動的一些程序,初始化程序還有可能會在這些程序退出之後重啓它們。Services take 一個服務定義的形式如下:

  service <name> <pathname> [ <argument> ]*
  <option>
  <option>
  ...

選項(Options)

選項將影響控制初始化程序運行服務的時機和方法。可能的選項如下表。

選項 說明

disabled	This service will not automatically start with its class. It must be explicitly started by name.
socket <name> <type>  <perm> [ <user> [ <group> ] ]	Create a unix domain socket named /dev/socket/<name> and pass its fd to the launched process. Valid <type> values include dgram and stream. user and groupdefault to 0.
user <username>	Change to username before exec'ing this service. Currently defaults to root.
group <groupname> [ <groupname> ]*	Change to groupname before exec'ing this service.  Additional  groupnames beyond the first, which is required, are used to set additional groups of the process (withsetgroups()). Currently defaults to root.
capability [ <capability> ]+	Set linux capability before exec'ing this service
oneshot	Do not restart the service when it exits.
class <name>	Specify a class name for the service.  All services in a named class must start and stop together. A service is considered of class "default" if one is not specified via the class option.

觸發器(Triggers)

觸發器是一個字符串,用於匹配特定的事件,這些事件將觸發觸發器所屬動作(Actions)的執行。

觸發器 說明

boot	This is the first trigger that occurs when init starts (after /init.conf is loaded).
<name>=<value>	Triggers of this form occur when the property <name> is set to the specific value<value>.
device-added-<path>
device-removed-<path>	Triggers of these forms occur when a device node is added or removed.
service-exited-<name>	Triggers of this form occur when the specified service exits.

指令(Commands)

Command	Description
exec <path> [ <argument> ]*	Fork and execute a program (<path>). This will block until the program completes execution. Try to avoid exec. Unlike the builtin commands, it runs the risk of getting init "stuck".
export <name> <value>	Set the environment variable <name> equal to <value> in the global environment (which will be inherited by all processes started after this command is executed).
ifup <interface>	Bring the network interface <interface> online.
import <filename>	Parse an init config file, extending the current configuration.
hostname <name>	Set the host name.
class_start <serviceclass>	Start all services of the specified class if they are not already running.
class_stop <serviceclass>	Stop all services of the specified class if they are currently running.
domainname <name>	Set the domain name.
insmod <path>	Install the module at <path>.
mkdir <path>	Make a directory at <path>.
mount <type> <device> <dir> [ <mountoption> ]*	Attempt to mount the named device at the directory <dir> <device>. This may be of the form mtd@name to specify a mtd block device by name.
setkey	- currenlty undefined -
setprop <name> <value>	Set system property <name> to <value>.
setrlimit <resource> <cur> <max>	Set the rlimit for a resource.
start <service>	Start a service running if it is not already running.
stop <service>	Stop a service from running if it is currently running.
symlink <target> <path>	Create a symbolic link at <path> with the value <target>.
write <path> <string> [ <string> ]*	Open the file at <path> and write one or more strings to it with write(2).
 

屬性(Properties)

初始化程序(Init)可以根據需要修改一些系統的屬性。

屬性 說明

init.action	Equal to the name of the action currently being executed or "" if none.
init.command	Equal to the command being executed or "" if none.
init.svc.<name>	State of a named service ("stopped", "running", or "restarting").

init.rc文件示例

on boot
  export PATH /sbin:/system/sbin:/system/bin
  export LD_LIBRARY_PATH /system/lib

  mkdir /dev
  mkdir /proc
  mkdir /sys


  mount tmpfs tmpfs /dev
  mkdir /dev/pts
  mkdir /dev/socket
  mount devpts devpts /dev/pts
  mount proc proc /proc
  mount sysfs sysfs /sys


  write /proc/cpu/alignment 4


  ifup lo


  hostname localhost
  domainname localhost


  mount yaffs2 mtd@system /system
  mount yaffs2 mtd@userdata /data


  import /system/etc/init.conf


  class_start default


service adbd /sbin/adbd
  user adb
  group adb


service usbd /system/bin/usbd -r
  user usbd
  group usbd
  socket usbd 666


service zygote /system/bin/app_process -Xzygote /system/bin --zygote
  socket zygote 666


service runtime /system/bin/runtime
  user system
  group system


on device-added-/dev/compass
  start akmd


on device-removed-/dev/compass
  stop akmd


service akmd /sbin/akmd
  disabled
  user akmd
  group akmd

12、同步和互斥

同步和互斥

相交進程之間的關係主要有兩種,同步與互斥。所謂互斥,是指散步在不同進程之間的若干程序片斷,當某個進程運行其中一個程序片段時,其它進程就不能運行它們之中的任一程序片段,只能等到該進程運行完這個程序片段後纔可以運行。所謂同步,是指散步在不同進程之間的若干程序片斷,它們的運行必須嚴格按照規定的某種先後次序來運行,這種先後次序依賴於要完成的特定的任務。

顯然,同步是一種更爲複雜的互斥,而互斥是一種特殊的同步。

也就是說互斥是兩個線程之間不可以同時運行,他們會相互排斥,必須等待一個線程運行完畢,另一個才能運行,而同步也是不能同時運行,但他是必須要安照某種次序來運行相應的線程(也是一種互斥)!

總結:

互斥:是指某一資源同時只允許一個訪問者對其進行訪問,具有唯一性和排它性。但互斥無法限制訪問者對資源的訪問順序,即訪問是無序的。

同步:是指在互斥的基礎上(大多數情況),通過其它機制實現訪問者對資源的有序訪問。在大多數情況下,同步已經實現了互斥,特別是所有寫入資源的情況必定是互斥的。少數情況是指可以允許多個訪問者同時訪問資源

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章