5.4. Interaction Between Devices and Kernel 裝置與核心的互動

來源:互聯網
上載者:User

目錄:http://www.cnblogs.com/WuCountry/archive/2008/11/15/1333960.html
 
[不提供插圖,讀者最好從網上下載源書]
    
5.4. Interaction Between Devices and Kernel 裝置與核心的互動
Nearly all devices (including NICs) interact with the kernel in one of two ways:
幾乎所有的裝置(包括NIC)有2種方式與核心互動:

Polling 輪詢

Driven on the kernel side. The kernel checks the device status at regular intervals to see if it has anything to say.
核心驅動模式,由核心有規則的主動式偵測裝置的狀態,看裝置是否有內容要輸出。

Interrupt

Driven on the device side. The device sends a hardware signal (by generating an interrupt) to the kernel when it needs the kernel's attention.
裝置驅動模式,在裝置須要核心關注時,由裝置主動發送一個硬體訊號(通常是通過一個中斷)給核心。

In Chapter 9, you can find a detailed discussion of NIC driver design alternatives as well as software interrupts. You will also see how Linux can use a combination of polling and interrupts to increase performance. In this chapter, we will look only at the interrupt-based case.
在第9章,你可以看到詳細的討論,關於NIC驅動可以選擇性的設計成很好的軟體中斷模式。你同樣可以看到Linux是如何合并使用輪詢和中斷來提高效能的。在這一章,我只講基於中斷的情況。

I won't go into detail on how interrupts are reported by the hardware, the difference between hardware exceptions and device interrupts, how the driver and bus kernel infrastructures are designed, etc.; you can refer to Linux Device Drivers and Understanding the Linux Kernel for those topics. But I'll give a brief overview on interrupts to help you understand how device drivers initialize and register the devices they are responsible for, with special attention to the networking aspect.
我不會詳細的說明中斷是如何從硬體上報給核心的,硬體異常和裝置中斷的區別,驅動和核心匯流排的底層是怎樣設計的,等等。關於這些內容你可以參考:Linux Device Drivers 和 Understanding the Linux Kernel。但我會簡單的說明一下中斷,以協助你理解裝置驅動的初始化和讓裝置回應的註冊,而這些都只關注於網路特性。

5.4.1. Hardware Interrupts 使用中斷
You do not need to know the low-level background about how hardware interrupts are handled. However, there are details worth mentioning because they can make it easier to understand how NIC device drivers are written, and therefore how they interact with the upper networking layers.
你須要知道一些關於硬體底層中斷處理的背景知識。顯然,它們的細節是值得被關注的,因為這會讓你更加容易的理解NIC裝置驅動應該怎樣寫,而且知道它們是怎樣與網路層互動的。

Every interrupt runs a function called an interrupt handler, which must be tailored to the device and therefore is installed by the device driver. Typically, when a device driver registers an NIC, it requests and assigns an IRQ. It then registers and (if the driver is unloaded) unregisters a handler for a given IRQ with the following two architecture-dependent functions. They are defined in kernel/irq/manage.c and are overridden by architecture-specific functions in arch/XXX/kernel/irq.c, where XXX is the architecture-specific directory:
每一個中斷是以一個被稱為中斷處理的函數調用來啟動並執行,這些必須可以通過安裝的裝置驅動程式,根據裝置被裁剪。代表性的,當一個裝置驅動註冊成一個NIC,它要請求分配一個IRQ。然後它就通過以下兩個基於系統體繫結構的函數來註冊和反註冊(如果裝置在卸載時)IRQ。這兩個函數定義在kernel/irq/manage.c中,而且它們被不同的體繫結構在arch/XXX/kernel/irq.c中重載,其中的XXX是指定的體繫結構目錄:

int request_irq(unsigned int irq, void (*handler)(int, void*, struct pt_regs*), unsigned long irqflags, const char * devname, void *dev_id)

This function registers a handler, first making sure that the requested interrupt is a valid one, and that it is not already allocated to another device unless both devices understand shared IRQs (see the later section "Interrupt sharing").
這個函數註冊一個控制代碼,首先確保請求的中斷是一個有效中斷號,而且它還沒有分配給另一個裝置,除非兩個裝置都明確的使用共用IRQ(參見後面的一節“Interrupt sharing”)。

void free_irq(unsigned_int irq, void *dev_id)

Given the device identified by dev_id, this function removes the handler and disables the IRQ line if no more devices are registered for that IRQ. Note that to identify the handler, the kernel needs both the IRQ number and the device identifier. This is especially important with shared IRQs, as explained in the later section "Interrupt sharing."
dev_id給出了裝置唯一標識,如果一個IRQ上已經沒有了註冊的裝置,這個函數就用於刪除處理控制代碼並禁用IRQ匯流排。注意,為了標識處理控制代碼,核心同時須要IRQ號和裝置ID。對於共用IRQ來說,這更加重要,在後面的章節“共用中斷”中有解釋。

When the kernel receives an interrupt notification, it uses the IRQ number to find out the driver's handler and then executes this handler. To find handlers, the kernel stores the associations between IRQ numbers and function handlers in a global table. The association can be either one-to-one or one-to-many, because the Linux kernel allows multiple devices to use the same IRQ, a feature described in the later section "Interrupt sharing."
當核心收到一個中斷通知時,它就使用IRQ號來找到裝置的控制代碼,然後執行它。為了找到處理控制代碼,核心將IRQ號和函數控制代碼以關聯的形式儲存在一個全域表中。這個關聯可以是一對一的,也可以是一對多的,因為Linux核心充許多裝置使用同一個IRQ,相關特性在後面的“共用中斷”中有描述

In the following sections, you will see common examples of the information exchanged between devices and drivers by means of interrupts, and how an IRQ can be shared by multiple devices under some conditions.
在下面的章節中,就會看到一些在名義上稱為中斷的,關於裝置與驅動之間資訊交換的通用樣本,以及一個IRQ是如何在一些條件下,在多個裝置之間共用的。

5.4.1.1. Interrupt types 中斷類型
With an interrupt, an NIC can tell its driver several different things. Among them are:
在一個中斷上,一個NIC可以特性它的驅動一些不同的事。這些有:

Reception of a frame 收到一個幀

This is the most common and standard situation. 這是最常見和標準的情況。

Transmission failure 轉送失敗

This kind of notification is generated on Ethernet devices only after a feature called exponential binary backoff has failed (this feature is implemented at the hardware level by the NIC). Note that the driver will not relay this notification to higher network layers; they will come to know about the failure by other means (timer timeouts, negative ACKs, etc.).
這種通知只在以太裝置上,當一個稱為(exponential binary backoff)失敗以後才發生。(這一特性是在NIC的硬體層上實現的)。注意,驅動在更高層的網路層上將不再對這個通知轉寄;它們以另一種形式來獲知這個失敗(計時器逾時,拒絕的確認幀等)。

DMA transfer has completed successfully DMA傳送已經成功的完成

Given a frame to send, the buffer that holds it is released by the driver once the frame has been uploaded into the NIC's memory for transmission on the medium. With synchronous transmissions (no DMA), the driver knows right away when the frame has been uploaded on the NIC. But with DMA, which uses asynchronous transmissions, the device driver needs to wait for an explicit interrupt from the NIC. You can find an example of each case at points where dev_kfree_skb[*] is called within the driver code drivers/net/3c59x.c (DMA) and drivers/net/3c509.c (non-DMA).
給一個發送幀,在介質上傳送該幀時,一但幀已經上傳到NIC的記憶體以後,該幀的緩衝會被驅動釋放。在同步傳輸時(非DMA),裝置可以正確的知道在什麼時候幀已經上傳到NIC裝置中。但在DMA中,也就是使用者使用非同步傳送模式,裝置驅動須要等待NIC一個清楚的中斷。你可以在drivers/net/3c59x.c和drivers/net/3c509.c中分別找到調用dev_kfree_skb的樣本。

[*] Chapter 11 describes this function in detail. 該函數在第11章中有詳細說明。

Device has enough memory to handle a new transmission 裝置已經有足夠的記憶體來處理新的傳輸

It is common for an NIC device driver to disable transmissions by stopping the egress queue when that queue does not have sufficient free space to hold a frame of maximum size (e.g., 1,536 bytes for an Ethernet NIC). The queue is then re-enabled when memory becomes available. The rest of this section goes into this case in more detail.
對於NIC裝置驅動來說,當隊列沒有足夠的空閑空間來儲存一個最大位元組的幀(例如,在以太NIC上是1536位元組)時,通過停止出口隊列來禁止傳輸是很常見的情況。而當隊列上有了足夠的記憶體時就會重新使能該裝置。這一節後面的內容會詳細討論這一問題。

The final case in the previous list covers a sophisticated way of throttling transmissions in a manner that can improve efficiency if done properly. In this system, a device driver disables transmissions for lack of queuing space, asks the NIC to issue an interrupt when the available memory is bigger than a given amount (typically the device's Maximum Transmission Unit, or MTU), and then re-enables transmissions when the interrupt comes.
前面列出的最後一個情況,包括了一個如果處理正確的話,可以提高效率的成熟的方法;在這個系統裡,一個裝置驅動在缺少空間的隊列上禁用資料轉送,詢問該NIC以確認一個中斷,當有大於給定數量的記憶體空間(通常就是裝置的傳輸單元最大值,或者MTU),然後當中斷到來時重新使能資料轉送。

A device driver can also disable the egress queue before a transmission (to prevent the kernel from generating another transmission request on the device), and re-enable it only if there is enough free memory on the NIC; if not, the device asks for an interrupt that allows it to resume transmission at a later time. Here is an example of this logic, taken from the el3_start_xmit routine, which the drivers/net/3c509.c driver installs as its hard_start_xmit function in its net_device structure:
一個裝置驅動同樣可以禁用出口隊列的資料轉送(用於防止核心在該裝置上產生另一個傳輸請示),而只在NIC有足夠的記憶體時才重新使能;否則,該裝置請求一個中斷,該中斷充許它在後面某個時刻恢複資料轉送。關於這一邏輯,這裡有一個從drivers/net/3c509.c裡取得的一個el3_start_xmit的樣本,裝置驅動用類似hard_start_mit的函數放在它的裝置資料結構裡:

 The hard_start_xmit virtual function is described in Chapter 11.
hard_start_xmit虛函數在第11章裡討論

static int
el3_start_xmit(struct sk_buff *skb, struct net_device *dev)
{
    ... ... ...
    netif_stop_queue (dev);
    ... ... ...
    if (inw(ioaddr + TX_FREE) > 1536)
        netif_start_queue(dev);
    else
        outw(SetTxThreshold + 1536, ioaddr + EL3_CMD);
    ... ... ...
}

The driver stops the device queue with netif_stop_queue, thus inhibiting the kernel from submitting further transmission requests. The driver then checks whether the device's memory has enough free space for a packet of 1,536 bytes. If so, the driver starts the queue to allow the kernel once again to submit transmission requests; otherwise, it instructs the device (by writing to a configuration register with an outw call) to generate an interrupt when that condition will be met. An interrupt handler will then re-enable the device queue with netif_start_queue so that the kernel can restart transmissions.
該驅動用netif_stop_queue來停止裝置隊列,這樣就抑制核心後期的傳輸請求。然後驅動檢測裝置是否有足夠的記憶體空間來存放一個1536位元組的資料包。如果是這樣的,裝置啟動隊列,用以再次讓核心可以提交資料轉送請求。否則,它就指示裝置(通過寫一個outw調用來寫一個配置寄存器)在這個條件滿足時產生一個中斷。然後一個中斷控制代碼用netif_start_queue來重新使能裝置隊列,這樣核心就可以重新啟動資料轉送。

The netif_xxx_queue routines are described in the section "Enabling and Disabling Transmissions" in Chapter 11.
netif_xxx_queue函數會在第11章的“傳輸的使能與去使能”中描述。

5.4.1.2. Interrupt sharing 共用中斷
IRQ lines are a limited resource. A simple way to increase the number of devices a system can host is to allow multiple devices to share a common IRQ. Normally, each driver registers its own handler to the kernel for that IRQ. Instead of having the kernel receive the interrupt notification, find the right device, and invoke its handler, the kernel simply invokes all the handlers of those devices that registered for the same shared IRQ. It is up to the handlers to filter spurious invocations, such as by reading a registry on their devices.
IRQ線是有限的資源。一個簡單的方法可以增加該系統可以提供的裝置數目,就是充許多個裝置共用一個通用的IRQ。原來核心是收到一個中斷通知以後,尋找到正確的裝置,然後調用它的控制代碼;共用IRQ以後,核心取而代之的就是簡單的調用這些裝置註冊在同一個IRQ上的所有控制代碼。這就等於讓控制代碼去過濾欺騙的行為,例如在他們的裝置上讀一個寄存器。

For a group of devices to share an IRQ line, all of them must have device drivers capable of handling shared IRQs. In other words, each time a device registers for an IRQ line, it needs to explicitly say whether it supports interrupt sharing. For example, the first device that registers for one IRQ, saying something like "assign me IRQ n and use this routine fn as the handler," must also specify whether it is willing to share the IRQ with other devices. When another device driver tries to register the same IRQ number, it is refused if either it, or the driver to which the IRQ is currently assigned, is incapable of sharing IRQs.
為了讓一組裝置可以共用一個IRQ線,所有裝置都必須有一個在共用IRQ上可以處理的控制代碼。也就是說,每次在註冊一個IRQ時,它必須顯示的說明它是否支援共用中斷。例如,第一個裝置在註冊一個IRQ時,像這樣申明:“分配給我一個IRQ n,而且用這個控制代碼來做為我的調用函數”,同時還必須指定它是否要與其它裝置共用這個中斷號。當另一個裝置來註冊同一個中斷號是時,如果該裝置不使用共用,那麼它會被拒絕,或者說,已經註冊了該中斷號的裝置也是無法共用該中斷號的。

5.4.1.3. Organization of IRQs to handler mappings IRQ控制代碼的映射組織
The mapping of IRQs to handlers is stored in a vector of lists, one list of handlers for each IRQ (see Figure 5-2). A list includes more than one element only when multiple devices share the same IRQ. The size of the vector (i.e., the number of possible IRQ numbers) is architecture dependent and can vary from 15 (on an x86) to more than 200. With the introduction of interrupt sharing, even more devices can be supported on a system at once.
映射IRQ的控制代碼是儲存在一個向量鏈表中,每一個IRQ(參見圖5-2)有一個控制代碼鏈表。一個鏈表只有在多個裝置共用同一個IRQ時才會包含更多的元素。向量的大小(例如:IRQ的可用數量)是與體繫結構相關的,而且是可以從15(在x86的體繫結構上)到多於200的範圍上變化的。和中斷共用所介紹的一樣,可能更多的裝置可以一次在一個系統是被支援。

The section "Hardware Interrupts" already introduced the two functions provided by the kernel to register and unregister a handler, respectively. Let's now see the data structure used to store the mappings.
在硬體中斷一節中已經介紹了核心所提供的兩個函數,用於註冊和反註冊一個控制代碼。接下來讓我們看看關於映射的資料結構的儲存。

Mappings are defined with irqaction data structures. The request_irq function introduced in the earlier section "Hardware Interrupts" is a wrapper around setup_irq, which takes an irqaction structure as input and inserts it into the global irq_desc vector. irq_desc is defined in kernel/irq/handler.c and can be overridden in the per-architecture files arch/XXX/kernel/irq.c. setup_irq is defined in kernel/irq/manage.c and can be overridden in the per-architecture files arch/XXX/kernel/irq.c.
映射和中斷動作(irqaction)資料結構定義在一起。request_irq函數已經在前面的“硬體中斷”一節中介紹過,它被封裝成setup_irq,這個函數用一個中斷動作資料結構做為一個輸入參數,而且將它插入到一個全域的irq_desc向量中。irq_des在kernel/irq/handler.c中有定義,而且它可在以每個不同的體繫結構中被重載,這些體繫結構檔案在arch/xxx/kernel/irq.c中。setup_irq被定義在kernel/irq/manage.c中,而且可以被arch/xxx/kernle/irq.c中不同的體繫結構所重載。

The kernel function that handles interrupts and passes them to drivers is architecture dependent. It is called handle_IRQ_event on most architectures.
核心用於處理控制代碼並將它們傳給裝置的函數是與體繫結構相關的。它們在很多體繫結構中都被稱為handle_IRQ_event。

Figure 5-2 shows how irqaction instances are stored: there is an instance of irq_desc for each possible IRQ and an instance of irqaction for each successfully registered IRQ handler. The vector of irq_desc instances is called irq_desc as well, and its size is given by the architecture-dependent symbol NR_IRQS.
圖5-2展示了中斷動作執行個體是如何儲存的:這裡,在每一個可能的IRQ上有一個irq_desc的執行個體,而且每中斷動作的執行個體已經成功的註冊了IRQ處理控制代碼。irq_desc向量的執行個體可以就稱為irq_desc,而且它的大小是由給定的與體繫結構相關的NR_IRQS所決定的。

Note that when you have more than one irqaction instance for a given IRQ number (that is, for a given element of the irq_desc vector), interrupt sharing is required (each structure must have the SA_SHIRQ flag set).
應該注意,當你在一個給定的IRQ數上使多個IRQ動作時(也就是在一個給定的irq_desc向量元素上),中斷共用是必須的(每個結構必須同時擁有SA_SHIRQ標誌位)。

Figure 5-2. Organization of IRQ handlers
圖5-2,IRQ控制代碼組織

Let's see now what information is stored about IRQ handlers in the fields of an irqaction data structure:
現在讓我們來看看關於IRQ控制代碼的資訊在一個中斷動作的資料結構中是如何儲存的:

void (*handler)(int irq, void *dev_id, struct pt_regs *regs)

Function provided by the device driver to handle notifications of interrupts: whenever the kernel receives an interrupt on line irq, it invokes handler. Here are the function's input parameters:
該函數由裝置驅動提供,用於處理中斷通知:不管什麼時候,只要核心在IRQ線上收到一個中斷,它就調用這個控制代碼。這裡是函數的輸入參數:

int irq

IRQ number that generated the notification. Most of the time it is not used by the NICs' device drivers to accomplish their job; the device ID is sufficient.
IRQ號,用於合成(中斷)通知。多數時候,它只並不被NIC裝置驅動所使用去完成它們的任務,裝置ID已經足夠了。

void *dev_id

Device identifier. The same driver can be responsible for different devices at the same time, so it needs the device ID to process the notification correctly.
裝置ID,同一個裝置驅動可以同時對不同的裝置負責,所以它須要裝置ID用於處理正確的中斷通知。

struct pt_regs *regs

Structure used to save the content of the processor's registers at the moment the interrupt interrupted the current process. It is normally not used by the interrupt handler.
該結構用於在中斷正中斷了當前進程時,用於儲存當前處理器的寄存器內容。通常它不被中斷控制代碼所使用。

unsigned long flags

Set of flags. The possible values SA_XXX are defined in include/asm-XXX/signal.h. Here are the main ones from the x86 architecture file:
標誌位,它的可能值在include/asm-XXX/signal.h中以SA_XXX的形式定義,這裡是幾個x86體繫結構的主要值:

SA_SHIRQ

When set, the device driver can handle shared IRQs.
設定它時,裝置驅動可以處理共用IRQ

SA_SAMPLE_RANDOM

When set, the device is making itself available as a source of random events. This can be useful to help the kernel generate random numbers for internal use, and is called contributing to system entropy. This is further described in the later section "Initializing the Device Handling Layer: net_dev_init."
設定它時,裝置就讓它自己可成為一個隨機事件的源。這可以用於協助核心產生一些內部使用的隨機數,而且它被稱為給核心貢獻的熵值。這個會在後面的“初始化裝置控制代碼層:net_dev_init”章節中描述。

SA_INTERRUPT

When set, the handler runs with interrupts disabled on the local processor. This should be specified only for handlers that can get done very quickly. See one of the handle_IRQ_event instances for an example (for instance, /kernel/irq/handle.c).
當它被設定時,控制代碼就在本地處理器上以可中斷的形式運行。這隻有在控制代碼可以很快的處理完時指定。可以參見一個handle_IRQ_event的執行個體(/kernel/irq/handle.c)。

There are other values, but they are either obsolete or used only by particular architectures.
這裡有另一個值,但它們並沒有被丟棄,也沒有被特殊的體繫結構所使用。

void *dev_id

Pointer to the net_device data structure associated with the device. The reason it is declared void * is that NICs are not the only devices to use IRQs. Because various device types use different data structures to identify and represent device instances, a generic type declaration is used.
用於指向一個與裝置相關的資料結構net_device,使用void *的理由是NIC並不是唯一使用IRQ的裝置。

struct irqaction *next

All the devices sharing the same IRQ number are linked together in a list with this pointer.
所有的共用同一個IRQ數的裝置被一個鏈表所關聯在一起,這個指標指向這個鏈表。

const char *name

Device name. You can read it by dumping the contents of /proc/interrupts.
裝置名稱,你可以通過匯出/proc/interrupts中的資訊來讀取它。

聯繫我們

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

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

A Free Trial That Lets You Build Big!

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

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

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

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