天天看點

《Essential Linux Device Drivers》第4章

第4章 打下基礎

裝置和驅動介紹

由于對硬體的操作要求擁有執行特殊指令和進行中斷等處理器特權,是以使用者應用程式一般不能直接和硬體通信。裝置驅動則承擔了硬體互動的工作,它也向應用程式和核心中其他的部分引出接口這些接口。應用程式通過/dev目錄中的裝置結點可對裝置進行操作,通過/sys目錄中的結點可以收集裝置資訊[1]。

[1]以後你将學習到,網絡應用程式通過不同的機制将請求發給底層驅動。

圖4.1是一個典型的PC相容的系統的硬體塊結構圖。從圖中可以看出,系統支援各種各樣的裝置和接口,如記憶體、視訊、音頻、USB、PCI、WiFi、I2C、IDE、以太網、序列槽、鍵盤、滑鼠、軟驅、并口和紅外等。核心控制器和圖形控制器在PC體系結構中位于北橋晶片組中,然後外設總線則源自南橋晶片組。

圖4.2給出了一個假想的嵌入式裝置的類似于圖4.1的塊圖。該圖中包含了數個PC中通常不存在的接口,如閃存、LCD、觸摸屏和無線數據機。

中斷處理

中斷上下文

下面給出了中斷上下文可為和不可為事項的清單:

2. 為了在中斷處理函數中保護臨界區,你不能使用互斥體,因為它們也許導緻睡眠。應該使用自旋鎖代替互斥體,但是一定要記住的是隻有真正需要的時候才采用它。

3. 中斷處理函數不能與使用者空間直接互動資料,因為它們經由程序上下文與使用者空間建立連接配接。這也是為什麼中斷處理函數不能睡眠的第2個理由:排程器工作于程序之間,如果中斷處理函數睡眠并被排程出去,它們怎麼傳回到運作隊列呢?

4. 中斷處理函數一方面需要快速地出來,另一方面又需要完成它的工作。為了規避這種沖突,中斷處理函數通常被分成2個部分。瘦小的頂半部标志一個響應以宣稱它已經服務了該中斷,而重大的工作負載都被丢給了肥胖的底半部。底半部的執行被延後,在其執行環境中,所有的中斷都是使能的。在讨論softirq和tasklet的時候,你将學習到真也難怪開發底半部。

5. 中斷處理函數不必是可重用的。當某中斷被執行的時候,在它傳回之前,相應的IRQ都被禁止了。是以,與程序上下文代碼不同的是,同一中斷處理函數的不同執行個體不可能同時運作在多個處理器上。

6. 中斷處理函數可以被更高優先級IRQ的中斷處理函數打斷。如果你請求核心将你的中斷處理函數作為快中斷處理的話,此類中斷嵌套将被禁止。快中斷服務函數運作的時候,本處理器上的所有中斷都會被禁止。在禁止中斷或将你的中斷辨別為快中斷之前,請意識到中斷屏蔽對系統性能的壞處。中斷屏蔽的時間越長,中斷延遲就會更長,或者說已經被産生的中斷得到服務的延遲就會越久。中斷延遲與系統真實的響應時間成反比。

與外部硬體産生的異步中斷不一樣,也存在同步到達的中斷。同步中斷意味着它們不會不期而遇,它們由處理器本身執行某指令而産生。外部中斷和同步中斷在核心中使用相同的機制處理。

(1)異常,被用于報告嚴重的運作時錯誤;

(2)軟中斷,如int 0x80指令,被使用者實作x86體系結構上的系統調用。

裝置驅動必須将它們的IRQ号與一個中斷處理函數連接配接。是以,它們需要知道它們正在驅動的裝置的IRQ号。IRQ的配置設定可以很直接,也可能需要複雜的探測過程。在PC體系結構中,例如,定時器中斷被配置設定了IRQ 0,RTC中斷也是IRQ 8。現代的中斷技術(如PCI)足夠強大,它能夠響應對IRQ的查詢(系統啟動過程中由BIOS配置設定),PCI驅動能夠通路裝置配置空間的相應區域并獲得IRQ。對于較老的裝置,如基于工業标準體系結構(ISA)的卡而言,驅動也許不得不利用特定硬體的知識以探測和解析IRQ。

通過/proc/interrupts可以檢視系統中活動的IRQ的清單。

現在你已經學習了中斷處理的基本知識,現在我們來實作一個輥輪裝置執行個體的中斷處理。在一些手機和PDA上能找到輥輪,它支援3種動作(順時針旋轉,逆時針旋轉和按鍵),可便利菜單導航。本例輥輪中的任何運作都會向處理器産生IRQ 7。通用目的I/O(GPIO)端口D的低3位與輥輪裝置連接配接。這些引腳上産生的波形與圖4.3中不同的輥輪運動一緻。中斷處理函數的工作是通過檢視端口D的GPIO資料寄存器解析出輥輪的運動。

驅動必須首先請求IRQ并将一個中斷處理函數與其綁定:

#define ROLLER_IRQ  7

static irqreturn_t roller_interrupt(int irq, void *dev_id);

if (request_irq(ROLLER_IRQ, roller_interrupt, IRQF_DISABLED |

                IRQF_TRIGGER_RISING, "roll", NULL)) {

  printk(KERN_ERR "Roll: Can't register IRQ %d\n", ROLLER_IRQ);

  return -EIO;

}

我們看一下傳遞給request_irq()的參數,本例中沒有查詢或探測IRQ号,而是直接寫死為ROLLER_IRQ。第2個參數roller_interrupt()是中斷處理函數。中斷處理函數的原型的傳回值類型為irqreturn_t,如果中斷處理成功,則傳回IRQ_HANDLED,否則,傳回IRQ_NONE。對于PCI等I/O而言,該傳回值的意義更重要,因為多個裝置可能共享同一IRQ。

IRQF_DISABLED标志意味着這個中斷處理為快中斷,是以,在調用該處理函數的時候,核心将禁止所有的中斷。IRQF_TRIGGER_RISING暗示輥輪将在中斷線上産生一個上升沿以發出中斷。換句話說,輥輪是一個邊沿觸發的裝置。有一些裝置是電平觸發的,在CPU服務其中斷之前,它一直将中斷線保持在一個電平上。使用IRQF_TRIGGER_HIGH或IRQF_TRIGGER_LOW可以辨別一個中斷為高/低電平觸發。該參數其他的可能值包括IRQF_SAMPLE_RANDOM(第5章《字元裝置驅動》的《僞字元裝置驅動》一節會用到)、IRQF_SHARED(定義這個IRQ被多個裝置共享)。

驅動初始化的時候申請IRQ并不是太好,因為這樣會導緻甚至裝置未被使用的時候,有價值的資源也被占用。是以,裝置驅動通常在裝置被應用打開的時候申請IRQ。類似地,IRQ也在應用關閉裝置的時候釋放IRQ,而不是在退出驅動子產品的時候進行。使用下面的方法可以釋放一個IRQ:

清單4.1給出了輥輪中斷處理的實作。

roller_interrupt()有2個參數,IRQ和裝置辨別符(傳遞給request_irq()的最後一個參數)。請對照圖4.3檢視清單4.1。

spinlock_t roller_lock = SPIN_LOCK_UNLOCKED;

static DECLARE_WAIT_QUEUE_HEAD(roller_poll);

static irqreturn_t

roller_interrupt(int irq, void *dev_id)

{

  int i, PA_t, PA_delta_t, movement = 0;

  /* Get the waveforms from bits 0, 1 and 2

     of Port D as shown in Figure 4.3 */

  PA_t = PORTD & 0x07;

  /* Wait until the state of the pins change.

     (Add some timeout to the loop) */

  for (i=0; (PA_t==PA_delta_t); i++){

    PA_delta_t = PORTD & 0x07;

  }

  movement = determine_movement(PA_t, PA_delta_t); /* See below */

  spin_lock(&roller_lock);

  /* Store the wheel movement in a buffer for

     later access by the read()/poll() entry points */

  store_movements(movement);

  spin_unlock(&roller_lock);

  /* Wake up the poll entry point that might have

     gone to sleep, waiting for a wheel movement */

  wake_up_interruptible(&roller_poll);

  return IRQ_HANDLED;

int

determine_movement(int PA_t, int PA_delta_t)

  switch (PA_t){

    case 0:

      switch (PA_delta_t){

      case 1:

        movement = ANTICLOCKWISE;

        break;

      case 2:

        movement = CLOCKWISE;

      case 4:

        movement = KEYPRESSED;

      }

      break;

    case 1:

      case 3:

      case 0:

    case 2:

    case 3:

    case 4:

      movement = KEYPRESSED;

第7章《輸入裝置驅動》的清單7.3利用了核心的輸入接口,将輥輪轉化為輥滑鼠。

在本節結束前,我們介紹一下使能和禁止特定IRQ的函數。enable_irq(ROLLER_IRQ)用于使能輥輪運動的中斷發生,disable_irq(ROLLER_IRQ)則進行相反的工作。disable_irq_nosync(ROLLER_IRQ)禁止輥輪中斷,并且不等待任何正在執行的roller_interrupt()執行個體的傳回。disable_irq()的非同步變體執行地更快,但是可能導緻潛在的競态。隻有在你确認沒有競争的盡快下,才可以這樣使用。drivers/ide/ide-io.c由一個使用disable_irq_nosync()的例子,在初始化過程中,它阻止了一些中斷,因為一些系統中可能在此方面存在問題。

正如以前讨論的那樣,中斷處理有2個沖突的要求:它們需要完成大量的裝置資料處理,但是又不得不盡可能快地退出。為了擺脫這一困境,中斷處理過程被分成2部分:一個急切的且搶占的與硬體互動的頂半部,和一個在所有中斷都使能情況下并非十分急切的處理大量工作的底半部。如頂半部不一樣,底半部是同步的,因為核心決定了它什麼時候會執行它們。如下機制都可用于核心中延後一個工作到底半部執行:softirq、tasklet和工作隊列(work queue)。

Softirq是一種基本的底半部機制,有較強的加鎖需求。僅僅在一些對性能敏感的子系統(如網絡層、SCSI層和核心定時器)中才會使用softirq。Tasklet建立在softirq之上,使用起來更簡單。除非有嚴格的可擴充性和速度要求,都建議使用Tasklet。Softirq和Tasklet的主要不同是前者是可重用的而後者則不需要。Softirq的不同執行個體可運作在不同的處理器上,而tasklet則不允許。

為了論證Softirq和Tasklet的用法,假定前例中的輥輪由存在由于運動部件導緻的潛在問題(如旋輪偶爾被卡住)進而導緻不同于spec的波形。一個被卡住的旋輪會不停地産生假的中斷,并可能使系統當機。為了解決這個問題,可以捕獲波形,進行一些分析,并在發現卡住的情況下動态地從中斷模式切換到輪詢模式,如果旋輪恢複正常,軟體也恢複到正常模式。我們在中斷處理函數中捕獲波形,并在底半部分析它。清單4.2和4.3分别用Softirq和Tasklet對此進行了實作。

它們都是清單4.1的簡化的變體,它們将中斷處理簡化為2個函數:從GPIO端口D捕獲波形的roller_capture()和對波形進行算術分析并按需切換到輪詢模式的roller_analyze()。

void __init

roller_init()

  /* ... */

  /* Open the softirq. Add an entry for ROLLER_SOFT_IRQ in

     the enum list in include/linux/interrupt.h */

  open_softirq(ROLLER_SOFT_IRQ, roller_analyze, NULL);

/* The bottom half */

void

roller_analyze()

  /* Analyze the waveforms and switch to polled mode if required */

/* The interrupt handler */

  /* Capture the wave stream */

  roller_capture();

  /* Mark softirq as pending */

  raise_softirq(ROLLER_SOFT_IRQ);

struct roller_device_struct { /* Device-specific structure */

  struct tasklet_struct tsklt;

void __init roller_init()

  struct roller_device_struct *dev_struct;

  /* Initialize tasklet */

  tasklet_init(&dev_struct->tsklt, roller_analyze, dev);

/* Analyze the waveforms and switch to

   polled mode if required */

  /* Mark tasklet as pending */

  tasklet_schedule(&dev_struct->tsklt);

(1)tasklet_enable()使能tasklet;

(2)tasklet_disable()禁止tasklet,并等待正在執行的tasklet退出;

<a>(3)tasklet_disable_nosync()的語義和disable_irq_nosync()相似,它并不等待正在執行的tasklet退出。</a>

你已經看到了中斷處理函數和底半部的不同,但是,也有幾個相似點。中斷處理函數和tasklet都不需要可重用。而且,二者都不能睡眠。另外,中斷處理函數、tasklet和softirq都不能被搶占。

工作隊列是中斷處理延後執行的第3種方式。它們在程序上下文執行,允許睡眠,是以可以使用mutex這類可能導緻睡眠的函數。在前一章分析核心輔助接口的時候,我們已經讨論了工作隊列。表4.1對softirq、tasklet和工作隊列進行了對比分析。

Tasklet

Work Queue

執行上下文

延後的工作,運作于中斷上下文

延後的工作,運作于程序上下文

可重用

可以在不同的CPU上同時運作

不能在不同的CPU上同時運作,但是不同的CPU可以運作不同的tasklet

睡眠

不能睡眠

可以睡眠

搶占

不能搶占/排程

可以搶占/排程

易用性

不容易使用

容易使用

何時使用

如果延後的工作不會睡眠,而且有嚴格的可擴充性或速度要求

第2章讨論的–rt更新檔集将中斷處理移到了核心線程執行,以實作更廣泛的搶占支援。

Linux裝置模型

新的Linux裝置模型引入了類似于C++的抽象機制,它總結出裝置驅動的共性,并提取出了總線和核心層。接下來,我們分析一下裝置模型中的udev、sysfs、kobject和裝置類(device class)等元件,以及這些元件對/dev結點管理、熱插拔、固件下載下傳和子產品自動加載等關鍵核心子系統的影響。Udev是分析裝置模型優點的最佳入口點,我們先從它開始講解。

幾年前,Linux作業系統還很年輕,管理裝置節點的工作一點都不好玩。所有需要的結點(達到數千個)都不得不在/dev目錄下靜态建立。該問題實際起源于原始的UNIX系統。在2.4核心中,引入了devfs,它支援裝置結點的動态建立。Devfs提供在了位于記憶體的檔案系統中建立裝置結點的能力,而命名結點的負擔還是落在了裝置驅動頭上。但是,裝置命名政策是可管理的,不應與核心混在一起。政策可位于頭檔案、子產品參數或使用者空間中。而Udev将成功地裝置管理的任務徹底推向了使用者空間。

1.核心中的sysfs支援,sysfs是Linux裝置模型的一個重要組成部分。Sysfs位于記憶體中,在啟動時被挂載在了/sys目錄(見/etc/fstab)。下一節我們會分析sysfs,你可以認為通路sysfs是理所當然的。

首先,從sysfs相應的檔案中提取産品資訊。假定Targus DVD驅動器被配置設定的裝置結點為/dev/sr0,Addonics CD-RW驅動器為/dev/sr1,使用udevinfo可收集裝置資訊:

bash&gt; udevinfo -a -p /sys/block/sr0

...

looking at the device chain at

'/sys/devices/pci0000:00/0000:00:1d.7/usb1/1-4':

 BUS=»usb»

 ID=»1-4»

 SYSFS{bConfigurationValue}=»1»

 ...

 SYSFS{idProduct}=»0701»

 SYSFS{idVendor}=»05e3»

 SYSFS{manufacturer}=»Genesyslogic»

 SYSFS{maxchild}=»0»

 SYSFS{product}=»USB Mass Storage Device»

bash&gt; udevinfo -a -p /sys/block/sr1

 looking at the device chain at

 '/sys/devices/pci0000:00/0000:00:1d.7/usb1/1-3':

 ID=»1-3»

 SYSFS{bConfigurationValue}=»2»

  ...

 SYSFS{idProduct}=»0302»

 SYSFS{idVendor}=»0dbf»

 SYSFS{manufacturer}=»Addonics»

 SYSFS{product}=»USB to IDE Cable»

BUS="usb", SYSFS{idProduct}="0701", SYSFS{idVendor}="05e3",

KERNEL="sr[0-9]*", NAME="%k", SYMLINK="usbdvd"

BUS="usb", SYSFS{idProduct}="0302", SYSFS{idVendor}="0dbf",

KERNEL="sr[0-9]*", NAME="%k", SYMLINK="usbcdrw"

第1條規則告訴udev,一旦它發現一個USB裝置的産品ID為0x0701,廠商ID為0x05e3,就增加一個以sr開始的名稱,udev将在/dev建立一個同名的結點并為之建立一個名為usbdvd的符号連結。類似地,第2條規則為CD-RW驅動器建立一個名為usbcdrw的符号連結。

為了測試新建立規則的文法錯誤,可以對/sys/block/sr*運作udevtest。為了打開/var/log/messages中的相關提示資訊,可以将/etc/udev/udev.conf檔案中的udev_log設定為“yes”。為了在運作過程中對/dev目錄應用新增規則,可以運作udevstart重新開機udev。此後,你的DVD驅動器在系統中将始終為/dev/usbdvd,而你的CD-RW驅動器将總是為/dev/usbcdrw。

你可以自主地通過在shell的腳本中通過如下指令來挂載裝置:

為裝置結點(以及網絡接口)進行恒定的命名并非udev的唯一功能。實際上,udev已經演變為Linux熱插拔管理器。Udev也承擔了按需自動加載子產品和為裝置下載下傳微碼的任務。在挖掘這些能力之前,讓我們首先對裝置模型的内在機理有一個基本的認識。

Sysfs、Kobject和裝置類(Device Class)

Sysfs、Kobjects和裝置類(Device Classes)是裝置模型的組成子產品,但是它們羞于在公衆面前露面,一直深藏于幕後。它們主要在總線和核心層的實作中使用,隐藏在為裝置驅動提供服務的API中。

Sysfs是核心中結構化的裝置模型在使用者空間的印證。它與procfs類型,二者都是位于記憶體的檔案系統,而且包含核心資料結構的資訊。但是,procfs是檢視核心内部的一個通用視窗,而sysfs則特定地對應于裝置模型。因而,Sysfs并非procfs的替代品。程序描述符、sysctl參數等資訊屬于procfs而非sysfs。很快,我們會發現udev的大多數功能都依賴于sysfs。

Kobject封裝了一些公用的對象屬性,如引用計數。它們通常被嵌在更大的資料結構中。Kobject的主要字段如下(定義于include/linux/kobject.h檔案):

1. kref對象,用于引用計數管理。kref_init()接口用于初始化kref接口,kref_get()使用者增加引用計數,而kref_put()則用于減少引用計數,當沒有剩下的引用後,對象會被釋放。例如,URB結構體(見第11章《通用串行總線》)就包含一個kref,用于跟蹤對它的引用的數量[2]。

2. kset的指針,表征kobject歸屬的對象集(object set)。

3. kobj_type,用于描述對象的類型。

Kobject與sysfs緊密關聯。核心中的每個對象執行個體對有一個sysfs的代表。

裝置類概念的引入是裝置模型的另一個特點,在驅動中,我們更有可能用到此接口。類接口抽象了這一理念,即每個裝置都屬于某一個總括性的分類。例如,USB滑鼠、PS/2鍵盤和操縱杆對屬于輸入裝置類,對在/sys/class/input/擁有入口。

圖4.4顯示了一個連接配接了外部USB滑鼠的筆記本電腦的sysfs結構。頂層的bus、class和device目錄被展開,以便顯示sysfs是怎樣基于裝置類型和實體連接配接提供USB滑鼠的視圖的。滑鼠是一個輸入類裝置,但是在實體上是一個USB裝置,包含2個端點:1個控制端點ep00和一個中斷斷點ep81。上述B端口位于BUS 2上的USB主機控制器,而USB主機控制器自身則通過PCI總線被橋接給CPU。如果到目前為止,你還是理不清的話,不必擔心。在讀完講解輸入裝置驅動的第7章、PCI驅動的第10章和USB驅動的第11章後,請回過頭來閱讀本節。

[/sys]

     +[block]

     -[bus]—[usb]—[devices]—[usb2]—[2-2]—[2-2:1.0]-[usbendpoint:usbdev2.2-ep81]

     -[class]-[input]—[mouse2]—[device]—[bus]—[usbendpoint:usbdev2.2-ep81]

             -[usb_device]—[usbdev2.2]—[device]—[bus]

             -[usb_endpoint]—[usbdev2.2-ep00]—[device]

                            —[usbdev2.2-ep81]—[device]

     -[devices]—[pci0000:00]—[0000:00:1d:1]—[usb2]—[2-2]—[2-2:1.0]

     +[firmware]

     +[fs]

     +[kernel]

     +[module]

     +[power]

加載RTC驅動子產品,檢視/sys和/dev下載下傳的結點:

bash&gt; ls -lR /sys/class/misc

drwr-xr-x 2 root root 0 Jan 15 01:23 rtc

/sys/class/misc/rtc:

total 0

-r--r--r-- 1 root root 4096 Jan 15 01:23 dev

--w------- 1 root root 4096 Jan 15 01:23 uevent

bash&gt; ls -l /dev/rtc

crw-r--r-- 1 root root 10, 135 Jan 15 01:23 /dev/rtc

/sys/class/misc/rtc/dev包含了配置設定給該裝置的主次裝置号(下一章讨論),/sys/class/misc/rtc/uevent用于冷插拔(下一節讨論),/dev/rtc是應用程式通路RTC驅動的入口。

下面分析一下裝置模型的代碼流程。在初始化過程中,Misc驅動利用了misc_register()服務,抽取一些代碼片段:

dev = MKDEV(MISC_MAJOR, misc-&gt;minor);

misc-&gt;class = class_device_create(misc_class, NULL, dev,

                                  misc-&gt;dev,

                                  "%s", misc-&gt;name);

if (IS_ERR(misc-&gt;class)) {

  err = PTR_ERR(misc-&gt;class);

  goto out;

/* ... */

圖4.5繼續剝離了更多層以便深入到裝置迷信的底層。它論證了class、kobject、sysfs以及udev之間的互動,這些互動會在/sys和/dev中産生相應的檔案。

并口LED驅動(第5章《Talking to the Parallel Port》一節)和虛拟滑鼠輸入裝置驅動(第7章《Device Example: Virtual Mouse》一節)可以作為建立sysfs中裝置控制檔案的例子。

以核心的I2C子系統(第8章《The Inter-Integrated Circuit Protocol》)為例。I2C層包含一個核心設施、總線擴充卡的裝置驅動以及client裝置驅動。I2C核心層使用bus_register()注冊每個被偵測到的I2C總線擴充卡。當一個I2C client裝置(例如,一個電可擦除的可程式設計隻讀存儲EEPROM晶片)被探測到後,device_register()将表征它的存在。最後,I2C EEPROM client驅動通過driver_register()注冊自身。實際上,這些注冊是由I2C核心提供的API間接執行的。

bus_register()會在/sys/bus/增加一個相應的入口,而device_register()會在/sys/devices/增加相應的入口。bus_type、device、device_driver這3個結構體分别是與總線、裝置和驅動對應的主要資料結構。include/linux/device.h中包含了它們的定義。

在運作過程中往系統中插入裝置稱為熱插拔,而在系統啟動前就連接配接裝置則稱為冷插拔。以前,核心通過調用由/proc檔案系統注冊的輔助程式來向使用者空間通知熱插拔事件。而在目前的核心裡,偵測到熱插拔事件後,它們會通過netlink套接字向使用者空間派生uevent。Netlink一種在核心空間和使用者空間透過套接字API進行通信的足夠強大的機制。使用者空間的udevd(管理裝置結點建立和移除的守護程式)會接收uevent并管理熱插拔。

為了檢視熱插拔處理機制最新的進展,檢視一下2.6核心各個不同版本情況下udev的逐漸變遷:

bash&gt; ls -l /etc/hotplug.d/default/

lrwcrwxrwx 1 root root 14 May 11 2005 10-udev.hotplug -&gt; /sbin/udevsend

當/sbin/udevsend執行後,它将熱插拔裝置的資訊傳遞給udevd。

2. udev-058和2.6.11核心,情況發生了一些變化。Udevsend程式代替了/sbin/hotplug:

bash&gt; cat /proc/sys/kernel/hotplug

/sbin/udevsend

bash&gt;

微碼下載下傳

一些裝置在開始運作前,必須下載下傳微碼。微碼會在片上的微控制器中執行,過去,裝置驅動通常将微碼存放于頭檔案的靜态數組中。但是,這種途徑并不安全,這是因為微碼通常是裝置制造商的具有産權的映像檔案,它們不宜進入GPL的核心。另一個原因導緻不宜将二者混在一起的原因是核心和固件釋出的時間線不同。顯而易見,更好的解決方法是在使用者空間維護微碼,并在核心需要的時候将其下載下傳進去。Sysfs和udev為此提供了支援。

request_firmware(..,"ipw2100-1.3.fw",..);

2.步驟1将向使用者空間分發一個熱插拔uevent,并提供所請求微碼映像的辨別資訊。

3.udevd接收該uevent,并調用/sbin/firmware_helper進行響應。為此,它使用了/etc/udev/rules.d/目錄下某一規則檔案中類似的規則進行處理:

ACTION=="add", SUBSYSTEM=="firmware", RUN="/sbin/firmware_helper"

4./sbin/firmware_helper在/lib/firmware/目錄中找到對應的微碼映像ipw2100-1.3.fw,并将該映像存儲到/sys/class/0000:02:02.0/data。0000:02:02是本例中無線網卡的“總線:裝置:功能”辨別。

5.驅動接收微碼并将其下載下傳到裝置中。下載下傳完成後,它調用release_firmware()釋放相應的資料結構。

6.驅動完成剩餘的初始化工作,無線網卡進入工作狀态。

子產品自動加載

按需自動加載核心子產品是Linux支援的一種實用功能。為了了解核心怎樣發起“子產品缺失”事件以及udev怎麼處理它,下面以向筆記本電腦的PC卡插槽插入Xircom CardBus以太網擴充卡為例進行說明:

1. 在編譯的時候,驅動包含了一個它所支援的裝置的辨別清單。檢視Xircom CardBus網卡的驅動(drivers/net/tulip/xircom_cb.c),可以發現如下的代碼片段:

static struct pci_device_id xircom_pci_table[] = {

    {0x115D, 0x0003, PCI_ANY_ID, PCI_ANY_ID,},

    {0,},

};

/* Mark the device table */

MODULE_DEVICE_TABLE(pci, xircom_pci_table);

這表明該驅動支援PCI制造商ID為0x115D和裝置ID(詳見第10章)為0x0003的任何卡。當安裝該驅動子產品的時候,depmod将分析子產品映像并提取其中的裝置表以獲得ID,并将如下的入口添加到/lib/modules/kernel-version/modules.alias:

alias pci:v0000115Dd00000003sv*sd*bc*sc*i* xircom_cb

其中,v代表制造商ID,d代表裝置ID,sv代表子制造商ID,*為通配符。

2. 當使用者将該Xircom卡熱插入CardBus插槽後,核心将産生一個uevent,在其中包含新插入裝置的ID。可以使用udevmonitor檢視産生的uevent:

bash&gt; udevmonitor --env

   ...

   MODALIAS=pci:v0000115Dd00000003sv0000115Dsd00001181bc02sc00i00

3. Udevd通過netlink套接字接收uevent,并用核心傳給它的上述别名調用modprobe:

modprobe pci:v0000115Dd00000003sv0000115Dsd00001181bc02sc00i00

4. modprobe在第1步建立的/lib/modules/kernel-version/modules.alias檔案中尋找比對的入口,接着插入xircom_cb:

bash&gt; lsmod

Module      Size   Used by

xircom_cb   10433  0

在讀完第10章以後,讀者可以回過頭來閱讀本節。

1. udev在每次重新開機時建立/dev結點,而靜态結點隻是在軟體安裝的時候才建立。如果嵌入式裝置使用Flash存儲器,存放/dev結點的Flash頁面在每次重新開機的時候都需要進行擦除——寫操作,這會減少Flash的壽命。(第17章《存儲技術裝置》将對FlashFlash存儲器進行讨論。)當然,為了避免此問題,/dev可以挂載在基于RAM的檔案系統中。

2. udev增加了啟動時間。

記憶體屏障

除了前文提到的CPU與硬體的互動以外,記憶體屏障也關系到SMP系統中CPU與CPU之間的互動。如果CPU的資料高速緩沖區正在使用寫回模式(在該模式下,隻有當資料确實需要被寫進記憶體的時候,它才會被從高速緩沖區拷貝到記憶體),某些情況下,程式可能需要停止指令流以等待高速緩沖區寫回記憶體的操作全部結束。這一點關系重大,到遇到需要擷取和釋放鎖的指令序列時,使用屏障可以在保證CPU獲得一緻的視圖。

在讨論完第10章的PCI驅動和第17章的Flash映射驅動後,讀者可以重新回顧記憶體屏障的知識。與此同時,Documentation/memory-barriers.txt包含了各種記憶體屏障的解釋。

電源管理

電源管理對于使用電池的裝置(如筆記本電腦、手持裝置)而言非常關鍵。Linux驅動需要意識到電源狀态,并對待機、睡眠和電池電壓低等事件作出反應,并在不同的狀态間進行轉換。在切換到低功耗模式的時候,驅動能夠利用底層硬體支援的節能功能。例如,存儲器驅動減速運作,視訊驅動顯示空白。

核心的中斷處理代碼是通用的,位于kernel/irq/目錄,而體系結構相關的部分在位于arch/your-arch/kernel/irq.c。該檔案中定義的do_IRQ()函數是開始分析核心中斷處理機制的良好起點。

核心軟中斷和tasklet的實作位于kernel/softirq.c,該檔案也包含了提供更細粒度對軟中斷和tasklet進行控制的函數。檢視include/linux/interrupt.h可以獲得軟中斷向量的枚舉以及實作自己的中斷處理函數的原型。drivers/net/lib8390.c可以作為編寫中斷處理函數和底半部的執行個體,讀者可以追索其從中斷處理到網絡協定棧的過程。

為了更好地了解APM在基于x86體系結構Linux上的實作,可以檢視核心中的arch/x86/kernel/apm_32.c、include/linux/apm_bios.h和include/asm-x86/mach-default/apm.h檔案。如果讀者對無BIOS體系結構(如ARM)的APM實作感興趣的話,可以檢視include/linux/apm-emulation.h和它的使用。核心的ACPI實作位于drivers/acpi/。

表4.2 資料結構總結

資料結構

位置

描述

tasklet_struct

include/linux/interrupt.h

管理tasklet,實作底半部的一種方法

kobject

include/linux/kobject.h

封裝一個核心對象的公共屬性

kset

kobject所屬于的對象集

kobj_type

描述一個kobject的對象類型

class

include/linux/device.h

bus

device

device_driver

表4.3 核心程式設計接口總結

request_irq()

kernel/irq/manage.c

free_irq()

disable_irq()

禁止與某IRQ關聯的中斷

disable_irq_nosync()

enable_irq()

重新使能已經被disable_irq()或disable_irq_nosync()禁止的中斷

open_softirq()

kernel/softirq.c

打開一個軟中斷

raise_softirq()

tasklet_init()

tasklet_schedule()

include/linux/interrupt.hkernel/softirq.c

辨別tasklet需要被執行

tasklet_enable()

使能一個tasklet

tasklet_disable()

禁止一個tasklet

tasklet_disable_nosync()

class_device_register()

drivers/base/class.c

kobject_add()

lib/kobject.c

sysfs_create_dir()

lib/kobject_uevent.c

class_device_create()

fs/sysfs/dir.c

fs/sysfs/file.c

 本文轉自 21cnbao 51CTO部落格,原文連結:http://blog.51cto.com/21cnbao/119924,如需轉載請自行聯系原作者

繼續閱讀