目錄
1.摘要………………………………………………………2
2.UBOOT,LINUX內核,文件系統的介紹及相互關係..3
2.1嵌入式系統簡介………………………………………3
2.2嵌入式Linux概述……………………………………3
2.3 UBOOT簡介………………………………………….4
3.UBOOT的啟動過程……………………………………..6
4.內核的主要功能和裁剪……………………………………7
4.1Linux的編譯…………………………………………………7
4.2嵌入式Linux的配置和剪裁…………………………………..8
5.文件系統的製作過程………………………………………8
6.交叉編譯器的搭建和環境變量的設置…………………..9
7.驅動程序的編寫過程與關鍵點…………………………11
7.1Linux網絡驅動程序的結構………………………………11
7.2 網絡驅動程序的基本方法………………………………12
7.3 網絡驅動程序中用到的數據結構……………………….12
7.4 常用的系統支持………………………………………….14
7.5編寫Linux網絡驅動程序中需要注意的問題…………18
8.參考文獻………………………………………………..20
方法/步驟
摘要
嵌入式系統是以應用為中心,以計算機為基礎,並且軟硬件可裁剪,適用於應用系統對功能、可靠性、成本、體積、功耗有嚴格要求的專用計算機系統。嵌入式系統一般由嵌入式微處理器、外圍硬件設備、嵌入式操作系統以及用戶的應用程序4部分組成,用於實現對其它設備的控制、監視或管理等功能。其廣泛應用於控制領域、消費電子產品等行業,已成為現代電子領域的重要研究方向之一。嵌入式Linux的研究已經成為當前信息技術研究的熱點,它的應用蘊含著巨大的商業價值,並且己經廣泛的應用於各種信息家電、通訊產品、工業控制中。論文首先介紹了ARM和嵌入式Linux操作系統的特點和當前的發展概況。然後闡述了嵌入式Linux開發流程以及移植到具體硬件平臺需要完成的工作,如U-Boot的移植、Linux內核的編譯與裁剪、文件系統的製作、驅動程序的編寫等。
關鍵字:嵌入式;ARM;嵌入式Linux; Linux內核;驅動程序
LINUX,UBOOT,文件系統的介紹及相互關係
2.1嵌入式系統簡介
嵌入式系統是以應用為中心,以計算機為基礎,並且軟硬件可裁剪,適用於應用系統對功能、可靠性、成本、體積、功耗有嚴格要求的專用計算機系統。單片機、單板機控制系統以及一些專用的工業控制計算機都可以稱作嵌入式系統,它是嵌入式系統領域的重要組成部分,只是更著重於對各自硬件系統的研究。嵌入式系統更著重於對軟件系統進行研究,順應了軟硬件協同設計以及應用需求的發展。目前,嵌入式系統己逐步發展成為一門學科,朝著系統化和規範化的方面發展。嵌入式系統學科和產業的發展使得設計人員能夠從容地面對越來越複雜的應用需求,通過軟件和硬件的模塊化設計大大地簡化和加快應用系統的開發.嵌入式系統主要包括硬件和軟件兩部分。硬件包括處理器、存儲器及外部設備、I/0端口和圖形控制器等;軟件部分包括操作系統(OS)和用戶應用程序。嵌入式系統硬件的核心是嵌入式微處理器。它的功能、外設集成度、速度、功耗、體積、成本、可靠性和電磁兼容性等方面均受到應用要求的制約,是各個半導體廠商之間競爭的熱點。嵌入式系統的軟件是實現嵌入式系統功能的關鍵,軟件要求固化存儲,代碼的高質量、高可靠性和高實時性在許多場合也是基本要求。多任務嵌入式操作系統是知識集成的平臺,是嵌入式系統走向工業標準化道路的基礎,是嵌入式系統研究的重要方向。現在人們講嵌入式系統時,某種程度上是指近些年來比較熱門的具有操作系統的嵌入式系統。
2.2嵌入式Linux概述
Linux操作系統源於芬蘭一位大學生—LinusTorvalds的課餘作品,隨著Intemet的發展,Linux操作系統在全球計算機愛好者的關懷下,不斷地發展和成長,己成為當前最流行的免費操作系統,任何人都可以自由的使用Linux源程序。嵌入式Linux操作系統的組成,我們可以和PC機相對應來理解,在PC機上,Windows的啟動大致有BIOS、內核、文件系統和初始化程序幾個部分;那麼相對而言,嵌入式Linux的移植有Bootloader、Linux內核、文件系統、初始化和用戶的應用程序幾部分,Bootloader完成系統的初始化和引導。
Linux操作系統具有以下幾大特點:
(1)開放源碼,豐富的軟件資源
Linux遵循GPL(GNU通用許可證),用法律保障了用戶免費獲得內核源代碼的權利。由於嵌入式系統千差萬別,往往需要針對某一具體應用去修改和優化系統,這樣,能否獲得源代碼就至關重要。Linux是自由的操作系統,它的開放源代碼使用戶獲得了最大的自由度。Linux上的軟件資源十分豐富,每種通用程序在Linux上都可以找到,並且每天都在增加。在Linux上往往不需要從頭做起,而是先選擇一個類似的自由軟件,進行二次開發。這就大大節省了開發工作量,縮短了開發時間。
(2)功能強大的內核,性能高效、穩定、多任務
Linux的內核非常穩定。它的高效和穩定性已經在各個領域,尤其在網絡服務器領域得到了事實的驗證,而且Linux內核小巧靈活,易於裁剪。這使Linux能很適合嵌入式系統的應用。
(3)支持多種體系結構
Linux能支持X86,ARM,MIPS,POWERP,ALPHA,SPARC等多種體系結構。目前,Linux己被移植到數十種硬件平臺上,幾乎所有流行的CPU,Linux都支持。
(4)完善的網絡通信、圖形和文件管理機制
Linux自產生之日起就與網絡密不可分,網絡是Linux的強項。另外,它支持ext2,fatl6,fat32,romfs等多種文件操作系統。在圖形系統方面,Linux上既有成熟的xWindow,也有embedcd QT,MiniGUI等嵌入式圖形用戶界面GUI,還有ysvgalib,framebuffer等優秀工具,可以適合不同的用途。
(5)支持大量的周邊硬件設備,驅動豐富
Linux上的驅動己經非常豐富了,支持各種主流硬件設備和最新硬件技術,而且隨著Linux的廣泛應用,許多芯片廠家也已經開始提供Linux上的驅動。這一步促進了Linux各種硬件平臺上的應用。
(6)大小功能都可定製
Linux繼承了Unix的優秀設計思想,內核與用戶界面是完全獨立的。它非常靈活,各部分的可定製性都很強,能適合多種需求。
2.3 UBOOT簡介
U—Boot是一個非常複雜的東酉,它也體現了嵌入式系統的一個非常重要的特徵:自己定製。它脫胎於PC機的Linux,可從網站上直接下載,U.Boot和其它任何BOOTLOADER都是一樣的,主要實現對系統進行初始化、系統引導、FLASH操作等功能。開發平臺的U.Boot主要是對板子的硬件進行初始化,包括:時鐘和PLL、定時器、調試串D(DebugDART)等等。有了U.B00t我們可以在主機的超級終端通過調試串口和目標機進行通信和設置。
2.3.1編譯U-Boot
在Linux系統下,用下面的命令對U.Boot進行編譯
$cdU.BOOT;進入目錄
Smakeat91rm9200dk_eonfig;編譯
Smakeall
$gzip_cu-boot.bin>u-boot.gz;壓縮為gz文件
編譯boot.bin
$cdBoot
Smake
編譯loader.bin
$cdLoader
Smake
2.3.2 U.Boot命令
在本系統中,採用的是U-Boot,U-Boot在嵌入式系統中相當於Pc機的BIOS加上操作系統引導頭部的內容,並且引導操作系統進行裝載和運行,U.Boot啟動後有一系列的命令,使得我們能夠方便的對FLASH、RAM進行操作,U.Boot已經對系統的頻率、定時器進行了設置,初始化了一個調試串口,我們可以通過串口或者以太網進行數據的下載。下面是U.Boot常用的命令:
go 一在地址’addr’處開始程序執行
run一運行命令
bootm 一從內存中進行應用程序影像運行
bootp 一通過網絡用BootP/TFTP協議來啟動影像
tf砸boot 一通過網絡用TFTP協議、設置服務器和客戶機的IP地址進行影像文件傳送
loadb 一通過串口線(kermitmode)來裝載二進制文件
pfintcnv 一打印環境變量 .
setcnv 一設置環境變量
saveenv 一保存環境變量到內存
下面是U.Boot的簡單環境變量
baudrate 一波特率
bootdelay--Boot延遲
bootcmd --Boot命令
bootargs —Boot參數
ipaddr 一客戶機IP地址
servefip 一服務器地址
loadaddr 一裝載地址
etlladdr 一網卡MAC地址
UBOOT的啟動過程
嵌入式Linux系統一般沒有自舉程序,必須通過啟動程序來引導硬件系統進入操作系統。啟動程序的工作包括:改變系統時鐘、關閉WATCHDOG、初始化存儲控制器等。本文針對本嵌入式控制系統所需的硬件方案,植入Uboot1-3.4啟動程序。U-Boot是一種功能強大的引導轉載程序。它不僅支持Linux、Vxworks等操作系統,還支持PowerPC、ARM等多種系列處理器。 Uboot啟動過程分為兩個階段。第一階段由彙編來實現,用於完成依賴於CPU體系結構的初始化,並調用第二階段的代碼。第二階段由C語言實現,完成相關初始化後,進入命令循環以等待用戶命令,或將參數傳給內核,引導Linux內核啟動。圖中給出了Uboot的啟動流程。
Uboot-1.3.4中對at91rm9200dk系列的開發板有很好的支持,只需做少量修改即可使用。但是在目前U-Boot-1.3.4引導系統中,不能識別本論文中採用的8MB NOR Flash(SPANSION公司的S29GL064N90TFI04)和1GB NAND Flash(SAMSUNG公司的K9K8G08U0A)兩款芯片,需要自行移植。
Linux的編譯,內核配置和裁剪
4.1Linux的編譯
在配置內核前的須做必要的設置,主要在內核原碼中設置文件Makefile,用下列指令打開Makefile文件:$viMakefile
在Makefile中主要設置兩個地方:ARCH CROSSCOMPILE。
ARCH:=arm;表示目標板為arm。CROSS COMPILE=交叉編譯工具的地址 ;設置交叉編譯工具的地址,例如CRoSSCOMPILE=lusr/10cal/arm/2.95.3、birdarm.1inux。還要在腳本文件mkimage中把路徑改為9200/bootldr/u-boot-1.0.O/tools。(具體的路徑和你的u-boot放的位置有關)然後按如下命令順序進行內核編譯即可:
內核配置:
Smake menuconfig 或makcxeon!ig
內核編譯:
Smaketiean $make dep $make $./mkimage;運行mkimage腳本文件
4.2嵌入式Linux的配置和剪裁
在Linux下,用makemenuconfig或makexeontig進入配置界面。在內核配置中,一般有四種選擇:Y(選擇)、N(不選)、M(模塊)和數字,用戶可以根據剪裁需要進行設置,最後配置完畢,選擇是否對配置結果進行保存?保存為.eonfig文件。
嵌入式Linux文件系統的製作過程
嵌入式系統可以使用硬盤和光盤,但是這與嵌入式系統的便攜式特性相違背,所以一般採用Flash作為存儲介質。和硬盤相比,Flash有自己獨特的物理特性,所以必須使用專門的文件系統。嵌入式系統對文件的操作是通過層次結構實現的。對於用戶程序來說,文件是有結構的文件,用戶程序通過文件I/O函數操作文件。嵌入式文件系統是嵌入式系統的一部分,它的任務是對邏輯文件進行管理,其工作包括提供對邏輯文件的操作(複製、刪除、修改等)接口,方便用戶操作文件和目錄。在文件系統的內部,又根據存儲設備的特點,適用不同的文件組織模式來實現文件的邏輯結構,比如磁帶終使用的順序文件以及大多數操作系統適用的樹狀文件。此外,文件系統要對管理文件的安全性負責。目前支持閃存的文件系統技術有以下幾種:
eJFFS2和Yaffs。這些文件系統可以使用在沒有初始化的NANDFlash和有CFI接口的NORFlash中。eTrueFFS,該文件系統相當於Linux中的MTD層,必須配合其他文件系統。eFTL/NTFL,它是一種中間層解決方案的統稱,為上層文件系統提供接口。eRAMFS、CRAMFS和ROMFS,這些文件系統用於早期的小容量閃存設備,系統
功能比較簡單,僅提供基本接口,只屬於只讀的閃存文件系統。適合存儲空間小的系統。可按照如下步驟製作自己的文件系統:
解開壓縮的文件系統:
$gamzipramdisk.gz
文件系統掛載:
$mount_oloopramdisk/mnt/nweramdisk(注:newramdisk是新建的目錄)。
進入newramdisk目錄進行操作,隨意的增減文件:
Sod/nmt/newramdisk(注:進入目錄後敲入命令隨意的增減文件)
退出newramdisk目錄後御載文件系統:
Sumount/mnt/newramdisic
壓縮文件系統,生成新的文件系統映象:
$gzip-c-、,9ramdisk>./newramdisk
Linux中交叉編譯器的搭建和環境變量的設置
1. 安裝標準的C開發環境,由於Linux安裝默認是不安裝的,所以需要先安裝一下(如果已經安裝好的話,就可以免去這一步了): #sudo apt-get install gcc g++ libgcc1 libg++ make gdb 2. 下載arm-linux-gcc-3.4.1.tar.bz2到任意的目錄下。 3. 解壓 arm-linux-gcc-3.4.1.tar.bz2 #tar -jxvf arm-linux-gcc-3.4.1.tar.bz2 解壓過程需要一段時間,解壓後的文件形成了 usr/local/ 文件夾,進入該文件夾,將arm文件夾拷貝到/usr/local/下 # cd usr/local/ #cp -rv arm /usr/local/ 現在交叉編譯程序集都在/usr/local/arm/3.4.1/bin下面。 4. 修改環境變量,把交叉編譯器的路徑加入到PATH。
方法一:修改/etc/bash.bashrc文件 #vim /etc/bash.bashrc 在最後加上: export PATH=$PATH:/usr/local/arm/3.4.1/bin export PATH 方法二:修改/etc/profile文件: # vim /etc/profile 增加路徑設置,在末尾添加如下,保存/etc/profile文件: export PATH=$PATH:/usr/local/arm/3.4.1/bin 方法三:#export PATH=$PATH:/usr/local/arm/3.4.1/bin 注:(這隻能在當前的終端下才是有效的!) 5. 立即使新的環境變量生效,不用重啟電腦: 對應方法一:#source /root/.bashrc 對應方法二:# source /etc/profile 6. 檢查是否將路徑加入到PATH: # echo $PATH 顯示的內容中有/usr/local/arm/bin,說明已經將交叉編譯器的路徑加入PATH。至此,交叉編譯環境安裝完成。 7. 測試是否安裝成功 # arm-linux-gcc -v 上面的命令會顯示arm-linux-gcc信息和版本: Reading specs from /usr/local/arm/3.4.1/lib/gcc/arm-linux/3.4.1/specs Configured with
:/work/crosstool-0.27/build/arm-linux/gcc-3.4.1-glibc-2.3.2/gcc- 3.4.1/configure --target=arm-linux --host=i686-host_pc-linux-gnu
--prefix=/usr/local/arm/3.4.1 --with-headers=/usr/local/arm/3.4.1/arm
-linux/include --with-local-prefix=/usr/local/arm/3.4.1/arm-linux --disable
-nls --enable-threads=posix --enable-symvers=gnu --enable-__cxa_atexit --enable- languages=c,c++ --enable-shared --enable-c99 --enable-long-long Thread model: posix gcc version 3.4.1 8.編譯Hello World程序,測試交叉工具鏈 寫下下面的Hello World程序,保存為hello.c #include
-------------------------------------------------------------
修改環境變量這一步修改/etc/profile文件
在path中添加arm-linux-gcc路徑
if [ "`id -u`" -eq 0 ]; then PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/arm/3.4.1/bin" else PATH="/usr/local/bin:/usr/bin:/bin:/usr/games" fi
再source /etc/profile就可以刷新環境變量
驅動程序的編寫過程與關鍵點
7.1Linux網絡驅動程序的結構
所有的Linux網絡驅動程序遵循通用的接口。設計時採用的是面向對象的方法。一個設備就是一個對象(device 結構),它內部有自己的數據和方法。每一個設備的方法被調用時的第一個參數都是這個設備對象本身。這樣這個方法就可以存取自身的數據(類似面向對象程序設計時的this引用)。 一個網絡設備最基本的方法有初始化、發送和接收。
deliver packets receive packets queue
(dev_queue_xmit()) them(netif_rx())
------------------- ---------------------
methods and variables(initialize,open,close,hard_xmit,
interrupt handler,config,resources,status...)
-------------------------------------------------------
----------------------
send to hardware receivce from hardware
-----------------------------------------------------
hardware media
-----------------------------------------------------
初始化程序完成硬件的初始化、device中變量的初始化和系統資源的申請。發送程序是在驅動程序的上層協議層有數據要發送時自動調用的。一般驅動程序中不對發 送數據進行緩存,而是直接使用硬件的發送功能把數據發送出去。接收數據一般是通過硬件中斷來通知的。在中斷處理程序裡,把硬件幀信息填入一個skbuff結構中,然後調用netif_rx()傳遞給上層處理。
7.2 網絡驅動程序的基本方法
網絡設備做為一個對象,提供一些方法供系統訪問。正是這些有統一接口的方法,掩蔽了硬件的具體細節,讓系統對各種網絡設備的訪問都採用統一的形式,做到硬件無關性。下面是最基本的方法。
(1) 初始化(initialize)(2)打開(open)(3)關閉(stop)
(4) 發送(hard_start_xmit)(5) 接收(reception)(6)硬件幀頭(hard_header)(7) 地址解析(xarp)
(8) 參數設置和統計數據
7.3 網絡驅動程序中用到的數據結構
最重要的是網絡設備的數據結構。定義在include/linux/netdevice.h裡。
7.4 常用的系統支持
7.4.1 內存申請和釋放
include/linux/kernel.h裡聲明瞭kmalloc()和kfree()。用於在內核模式下申請和釋放內存。
Void *kmalloc(unsigned int len,int priority);
void kfree(void *__ptr);
與用戶模式下的malloc()不同,kmalloc()申請空間有大小限制。長度是2的整次方。可以申請的最大長度也有限制。另外kmalloc()有priority參數,通常使用時可以為GFP_KERNEL,如果在中斷裡調用用GFP_ATOMIC參數,因為使用GFP_KERNEL則調用者可能進入sleep狀態,在處理中斷時是不允許的。 Kfree()釋放的內存必須是kmalloc()申請的。如果知道內存的大小,也可以用kfree_s()釋放。
7.4.2 request_irq()、free_irq()
這是驅動程序申請中斷和釋放中斷的調用。在include/linux/sched.h裡聲明。
Request_irq()調用的定義:
int request_irq(unsigned int irq,
void (*handler)(int irq, void *dev_id, struct pt_regs *regs),
unsigned long irqflags,
const char * devname,
void *dev_id);
irq是要申請的硬件中斷號。在Intel平臺,範圍0--15。Handler是向系統登記的中斷處理函數。這是一個回調函數,中斷髮生時,系統調用這個函數,傳入的參數包括硬件中斷號,device id,寄存器值。Dev_id就是下面的request_irq時傳遞給系統的參數dev_id。Irqflags是中斷處理的一些屬性。比較重要的有SA_INTERRUPT,標明中斷處理程序是快速處理程序(設置SA_INTERRUPT)還是慢速處理程序(不設置SA_INTERRUPT)。快速處理程序被調用時屏蔽所有中斷。慢速處理程序不屏蔽。還有一個SA_SHIRQ屬性,設置了以後運行多個設備共享中斷。Dev_id在中斷共享時會用到。一般設置為這個設備的device結構本身或者NULL。中斷處理程序可以用dev_id找到相應的控制這個中斷的設備,或者用irq2dev_map找到中斷對應的設備。
Void free_irq(unsigned int irq,void *dev_id);
7.4.3 時鐘
時鐘的處理類似中斷,也是登記一個時間處理函數,在預定的時間過後,系統會調用這個函數。在include/linux/timer.h裡聲明。
Struct timer_list {
struct timer_list *next;
struct timer_list *prev;
unsigned long expires;
unsigned long data;
void (*function)(unsigned long);
};
void add_timer(struct timer_list * timer);
int del_timer(struct timer_list * timer);
void init_timer(struct timer_list * timer);
使用時鐘,先聲明一個timer_list結構,調用init_timer對它進行初始化。 Time_list結構裡expires是標明這個時鐘的週期,單位採用jiffies的單位。 Jiffies是Linux一個全局變量,代表時間。它的單位隨硬件平臺的不同而不同。 系統裡定義了一個常數HZ,代表每秒種最小時間間隔的數目。這樣jiffies的單位 就是1/HZ。Intel平臺jiffies的單位是1/100秒,這就是系統所能分辨的最小時間 間隔了。所以expires/HZ就是以秒為單位的這個時鐘的週期。 Function就是時間到了以後的回調函數,它的參數就是timer_list中的data。 Data這個參數在初始化時鐘的時候賦值,一般賦給它設備的device結構指針。 在預置時間到系統調用function,同時系統把這個time_list從定時隊列裡清除。所以如果需要一直使用定時函數,要在function裡再次調用add_timer()把這個timer_list加進定時隊列。
7.4.4 I/O
I/O端口的存取使用:
inline unsigned int inb(unsigned short port);
inline unsigned int inb_p(unsigned short port);
inline void outb(char value, unsigned short port);
inline void outb_p(char value, unsigned short port);
在include/adm/io.h裡定義。Inb_p()、outb_p()與inb()、outb_p()的不同在於前者在存取I/O時有等待 (pause)一適應慢速的I/O設備。為了防止存取I/O時發生衝突,Linux提供對端口使用情況的控制。在使用端口 之前,可以檢查需要的I/O是否正在被使用,如果沒有,則把端口標記為正在使用,使用完後再釋放。系統提供以下幾個函數做這些工作。
Int check_region(unsigned int from, unsigned int extent);
void request_region(unsigned int from, unsigned int extent,const char *name);
void release_region(unsigned int from, unsigned int extent);
其中的參數from表示用到的I/O端口的起始地址,extent標明從from開始的端口數目。Name為設備名稱。
7.4.5 中斷打開關閉
系統提供給驅動程序開放和關閉響應中斷的能力。是在include/asm/system.h 中的兩個定義。
#define cli() __asm__ __volatile__ ("cli"::)
#define sti() __asm__ __volatile__ ("sti"::)
7.4.6 打印信息
類似普通程序裡的printf(),驅動程序要輸出信息使用printk()。在include /linux/kernel.h裡聲明。
Int printk(const char* fmt, ...);
其中fmt是格式化字符串。...是參數。都是和printf()格式一樣的。
7.4.7 註冊驅動程序
如果使用模塊(module)方式加載驅動程序,需要在模塊初始化時把設備註冊到系統設備表裡去。不再使用時,把設備從系統中卸除。定義在drivers/net/net_init.h裡的兩個函數完成這個工作。
Int register_netdev(struct device *dev);
void unregister_netdev(struct device *dev);
dev就是要註冊進系統的設備結構指針。在register_netdev()時,dev結構一般填寫前面11項,即到init,後面的暫時可以不用初始化。最重要的是name指針和 init方法。Name指針空(NULL)或者內容為'\0'或者name[0]為空格(space),則系統把你的設備做為以太網設備處理。以太網設備有統一的命名格式,ethX。對以太網這麼特別對待大概和Linux的歷史有關。 Init方法一定要提供,register_netdev()會調用這個方法讓你對硬件檢測和設置。Register_netdev()返回0表示成功,非0不成功。
7.4.8 sk_buff
Linux網絡各層之間的數據傳送都是通過sk_buff。Sk_buff提供一套管理緩衝區的方法,是Linux系統網絡高效運行的關鍵。每個sk_buff包括一些控制方法和一塊數據緩衝區。控制方法按功能分為兩種類型。一種是控制整個buffer鏈的方法,另一種是控制數據緩衝區的方法。Sk_buff組織成雙向鏈表的形式,根據網絡應用的特點,對鏈表的操作主要是刪除鏈表頭的元素和添加到鏈表尾。Sk_buff的控制
方法都很短小以儘量減少系統負荷。(translated from article written by Alan Cox)
常用的方法包括:
.alloc_skb() 申請一個sk_buff並對它初始化。返回就是申請到的sk_buff。
.dev_alloc_skb()類似alloc_skb,在申請好緩衝區後,保留16字節的幀頭空間。主要用在Ethernet驅動程序。
.kfree_skb() 釋放一個sk_buff。
.skb_clone() 複製一個sk_buff,但不復制數據部分。
.skb_copy()完全複製一個sk_buff。
.skb_dequeue() 從一個sk_buff鏈表裡取出第一個元素。返回取出的sk_buff,如果鏈表空則返回NULL。這是常用的一個操作。
.skb_queue_head() 在一個sk_buff鏈表頭放入一個元素。
.skb_queue_tail() 在一個sk_buff鏈表尾放入一個元素。這也是常用的一個操作。網絡數據的處理主要是對一個先進先出隊列的管理,skb_queue_tail()和skb_dequeue()完成這個工作。
.skb_insert() 在鏈表的某個元素前插入一個元素。
.skb_append() 在鏈表的某個元素後插入一個元素。一些協議(如TCP)對沒按順序到達的數據進行重組時用到skb_insert()和skb_append()。
.skb_reserve() 在一個申請好的sk_buff的緩衝區裡保留一塊空間。這個空間一般是用做下一層協議的頭空間的。
.skb_put() 在一個申請好的sk_buff的緩衝區裡為數據保留一塊空間。在alloc_skb以後,申請到的sk_buff的緩衝區都是處於空(free)狀態,有一個tail指針指向free空間,實際上開始時tail就指向緩衝區頭。Skb_reserve()在free空間裡申請協議頭空間,skb_put()申請數據空間。見下面的圖。
.skb_push() 把sk_buff緩衝區裡數據空間往前移。即把Head room中的空間移一部分到Data area。
.skb_pull() 把sk_buff緩衝區裡Data area中的空間移一部分到Head room中。
--------------------------------------------------
Tail room(free)
--------------------------------------------------
After alloc_skb()
--------------------------------------------------
Head room Tail room(free)
--------------------------------------------------
After skb_reserve()
--------------------------------------------------
Head room Data area Tail room(free)
--------------------------------------------------
After skb_put()
--------------------------------------------------
Head skb_ Data Tail room(free)
room push
Data area
--------------------------------------------------
After skb_push()
--------------------------------------------------
Head skb_ Data area Tail room(free)
pull
Head room
After skb_pull()
7.5.編寫Linux網絡驅動程序中需要注意的問題
7.5.1 中斷共享
Linux系統運行幾個設備共享同一個中斷。需要共享的話,在申請的時候指明共享方式。系統提供的request_irq()調用的定義:
int request_irq(unsigned int irq,
void (*handler)(int irq, void *dev_id, struct pt_regs *regs),
unsigned long irqflags,
const char * devname,
void *dev_id);
如果共享中斷,irqflags設置SA_SHIRQ屬性,這樣就允許別的設備申請同一個 中斷。需要注意所有用到這個中斷的設備在調用request_irq()都必須設置這個屬 性。系統在回調每個中斷處理程序時,可以用dev_id這個參數找到相應的設備。一 般dev_id就設為device結構本身。系統處理共享中斷是用各自的dev_id參數依次調用每一箇中斷處理程序。
7.5.2 硬件發送忙時的處理
主CPU的處理能力一般比網絡發送要快,所以經常會遇到系統有數據要發,但 上一包數據網絡設備還沒發送完。因為在Linux裡網絡設備驅動程序一般不做數據 緩存,不能發送的數據都是通知系統發送不成功,所以必須要有一個機制在硬件不 忙時及時通知系統接著發送下面的數據。一般對發送忙的處理在前面設備的發送方法(hard_start_xmit)裡已經描述過, 即如果發送忙,置tbusy為1。處理完髮送數據後,在發送結束中斷裡清tbusy,同時用mark_bh()調用通知系統繼續發送。但在具體實現我的驅動程序時發現,這樣的處理系統好象並不能及時地知道硬件已經空閒了,即在mark_bh()以後,系統要等一段時間才會接著發送。造成發送效率很低。2M線路只有10%不到的使用率。內核版本為2.0.35。我最後的實現是不把tbusy置1,讓系統始終認為硬件空閒,但是報告發送不成功。系統會一直嘗試重發。這樣處理就運行正常了。但是遍循內核源碼中的網絡驅動程序,似乎沒有這樣處理的。不知道癥結在哪裡。
7.5.3 流量控制(flow control)
網絡數據的發送和接收都需要流量控制。這些控制是在系統裡實現的,不需要驅動程序做工作。每個設備數據結構裡都有一個參數dev->tx_queue_len,這個參數標明發送時最多緩存的數據包。在Linux系統裡以太網設備(10/100Mbps)tx_queue_len一般設置為100,串行線路(異步串口)為10。實際上如果看源碼可以知道,設置了dev->tx_queue_len並不是為緩存這些數據申請了空間。這個參數只是在收到協議層的數據包時判斷髮送隊列裡的數據是不是到了tx_queue_len的限度,以決定這一包數據加不加進發送隊列。發送時另一個方面的流控是更高層協議的發送窗口(TCP協議裡就有發送窗口)。達到了窗口大小,高層協議就不會再發送數據。接收流控也分兩個層次。Netif_rx()緩存的數據包有限制。另外高層協議也會有一個最大的等待處理的數據量。發送和接收流控處理在net/core/dev.c的do_dev_queue_xmit()和netif_rx()中。
7.5.4 調試
很多Linux的驅動程序都是編譯進內核的,形成一個大的內核文件。但對調試 來說,這是相當麻煩的。調試驅動程序可以用module方式加載。支持模塊方式的 驅動程序必須提供兩個函數:int init_module(void)和void cleanup_module(void)。 Init_module()在加載此模塊時調用,在這個函數裡可以register_netdev()註冊 設備。Init_module()返回0表示成功,返回負表示失敗。Cleanup_module()在驅動程序被卸載時調用,清除佔用的資源,調用unregister_netdev()。模塊可以動態地加載、卸載。在2.0.xx版本里,還有kerneld自動加載模塊,但是2.2.xx中已經取消了kerneld。手工加載使用insmod命令,卸載用rmmod命令,看內核中的模塊用lsmod命令。編譯驅動程序用gcc,主要命令行參數-DKERNEL -DMODULE。並且作為模塊加載的驅動程序,只編譯成obj形式(加-c參數)。編譯好的目標文件放在/lib/modules/2.x.xx/misc下,在啟動文件裡用insmod加載。
7.6.進一步的閱讀
Linux程序設計資料可以從網上獲得。這就是開放源代碼的好處。並且沒有什麼“未公開的祕密”。我編寫驅動程序時參閱的主要資料包括:Linux內核源代碼
<> by Michael K. Johnson
<> by Ori Pomerantz
<> by olly in BBS
可以選擇一個模板作為開始,內核源代碼裡有一個網絡驅動程序的模板,drivers/net/skeleton.c。裡面包含了驅動程序的基本內容。這個模板是以以太網設備為對象的,以太網的處理在Linux系統裡有特殊“待遇”,所以如果不是以太網設備,有些細節上要注意,主要在初始化程序裡。
參考文獻
【1】許海熱等編著.嵌入式系統技術與應用.
【2】王田苗編著.嵌入式系統設計與實例開發.
【3】魏忠等編著.嵌入式開發詳解。北京:電子工業出版
【4】馬忠梅馬廣雲徐英慧天澤編著.ARM嵌入式處理器結構與應用基礎.北京航空航天大學出版社,
【5】杜春雷編著.ARM體系結構與編程.清華大學出版社,【6】6陳文智等編著.嵌入式系統開發原理與實踐.清華大學出版社,
【7】胡曉軍張愛成編著.USB接口開發技術.西安電子科技大學出版社,2005.5,l~74
【8】皺思軼編著.嵌入式Linux設計與應用.清華大學出版社,2002.1,241—282
【9】T_學龍,嵌入式Linux系統設計與應用.清華大學出版社,2001.8,243~358
【10】孫天澤袁文菊張海峰編著.嵌入式設計及Linux驅動開發指南.基於ARM9處理器,電子工業出版社