摘要:首先介紹了嵌入式Linux環境下設備驅動程序的開發流程,詳細描述了Linux設備驅動程序的體系結構和Linux設備文件的概念。然后通過一個例子描述了如何設計和編寫Linux設備驅動程序,并解釋了其中的核心代碼。最后構建一個Glade工程來調用這個設備驅動程序,完成測試工作。
關鍵詞:嵌入式Linux;設備文件;設備驅動;Glade
0 引言
Linux是開放源代碼的操作系統,由于其高效穩定、執行速度快、實現了真正的多任務、多用戶環境、強大的網絡功能、較好的可裁減性與移植性等特點,在嵌入式系統領域獲得了飛速發展。針對ARM體系結構CPU開發的,具有MMU功能的嵌入式Linux操作系統無疑是ARM平臺上操作系統的最佳選擇。本文主要研究了在Linux下開發驅動程序并構建一個GUI程序來使用這個驅動程序的一般流程。
1 Linux設備驅動程序
Linux支持三類硬件設備:字符設備、塊設備和網絡設備。字符設備是指無須緩存直接按字節讀寫的設備。塊設備以塊為單位進行讀寫,能夠進行隨機訪問。網絡設備在Linux里有專門的處理,它沒有被映射到文件系統的設備節點,對它的訪問采用socket機制。字符設備與塊設備的主要區別是:在對字符設備發出讀/寫請求時,實際的硬件I/O一般緊接著發生;塊設備是利用一塊系統內存作緩沖區來進行實際的I/O操作。

圖1 設備驅動流程
在Linux中,幾乎所有的內容都是文件,對設備驅動的訪問也是以文件操作的方式實現。無論是字符設備還是塊設備,用戶對沒備的操作都是通過虛擬文件系統(VFS)轉化為設備驅動與硬件操作例程的交互(見圖1)。即使是訪問網絡設備的socket接口,也是通過VFS實現的。Linux通過VFS為用戶提供了一個統一的設備訪問接口,使用戶能夠透明地訪問設備驅動程序。所有的硬件設備都可以使用和操作系統調用接口來打開、關閉、讀寫和I/O控制,而驅動程序的主要任務就是實現這些系統調用函數。Linux系統中的所有硬件設備都使用一種特殊的設備文件來表示。每個設備文件都有兩個設備號:一個是主設備號,它用來標識該設備的種類,也標識該設備使用的驅動程序;另一個是次設備號,用來標識使用同一設備驅動程序的不同硬件設備。
實現一個嵌入式Linux設備驅動的大致流程如下:
(1)定義主、次設備號;也可以動態獲取。
(2)實現驅動初始化和清除函數,如果驅動程序采用模塊方式,則要實現模塊初始化和清除函數。
(3)設計所要實現的文件操作,定義file_operations結構。
(4)實現所需的文件操作調用,如read、write等。
(5)實現中斷服務函數,并用request_irq向內核注冊。中斷并不是每個設備驅動所需要。
(6)將驅動編譯到內核或編譯成模塊,用insmod命令加載。
(7)生成設備節點文件。
下面以一個簡單的例子來說明設備驅動程序的結構,這個驅動程序用來控制目標板上的一組LED燈。led_fops結構體定義了該設備需要的操作接口。它的成員全部是函數指針,所以實質上就是函數跳轉表。
struct file_operations led_fops={
open:led_open,
/*打開設備操作*/
read:led_read,
/*讀設備操作*/
write:led_write,
/*寫設備操作*/
ioctl:led_ioctl,
/*控制模塊的設置*/
release:led_release /*釋放設備操作*/
}:
ssize_t led_read(struct nIe*flip,char*Putbuf,size_t length,
loff__t*f_pos)
{unsigned short BottonStatus;
unsigned char Bottontmp=0;
int i;
BottonStatus=(KEY_CSOxff);/*獲取當前8個按鍵的狀態*/
for(i=0:i<8:++i)
{if(((BottonStatus>>i)&1)==0)
Bottontmp=i+1;
}
copy_to user(Putbuf,Bottontmp,length);
/*將內核空間的數據復制到用戶空間*/
return length;
}
ssize_t led_write(struct file *filp,const char *Getbuf,
size_t length,loff-t *f_pos)
{int num;
unsigned char UsrWantLed;
copy_from_user(UsrWantLed,Getbuf,length);
/*將用戶空間的寫入到設備文件的內容傳送到內核空間*/
num=((UsrWantLed)0xff);
LED_CS=-(1<<(num-1));
/*通過對LED_CS地址賦值來控制LED的亮滅*/
return(0);
}
int led_ioctl(struct inode *inode,struct file*filp,
unsigned int cmd,unsigned long arg)
{switch(cmd)/*利用cmd和arg參數可以完成有些復雜的I/O
操作*/
{ case LED_SHOW:
{if(arg)
led_off_on();
break;
}
}return 0:
}
static int inn keypad_init(void)
{int result;
result=register_chrdev(Led_MAJOR.“led”,led_fops);
/*這個函數是向內核注冊設備,Led_MAJOR是驅動的主設備
號,led是驅動名,led_fops是驅動所執行的操作*/
printk(“%s%sinitialized.\n”,KEYPAD_NAME,KEYPAD_VERSION);
return 0;
}
static void_exit keypad_exit(void)
{unregister_chrdev(Led_MAJOR,“led”);/*向內核注銷設備*/
led_off_on();
}
module_init(keypad_init);
module_exit(keypad_exit);
module_init()和module_exit()這對宏是對程序模塊的初始化和退出函數名稱進行記錄。它能顯式地命名模塊的注冊和注銷函數并保證內核中驅動名的惟一性。
2 嵌入式LInux的GUI
Tiny-X是由XFree86核心小組的成員Keith Packard一手設計的。它能夠在配有IMB以下內存的系統上,建立起標準的X系統,是一個性能相當出色并且免費的嵌入式圖形界面GUI。在嵌入式系統中,使用Tiny-X圖形界面開發產品,上層的應用程序編寫將會很方便。可以通過GTK的集成開發環境——Glade完成界面布局并生成原始代碼(界面圖如圖2),再通過文本編輯工具添加事件響應程序。 在程序控制硬件設備之前要打開設備文件,所以要在執行gtk_main()前調用設備打開函數。這個函數主要用于打開設備文件并獲得主設備號。關鍵代碼如下: int fd_keypad;
static char*dev_keypad=“/dev/keypad”;
fd_keypad=open(dev_keypad,O_RDWR);
return fd_keypad;

圖2 Glade工程界面
然后對每個button添加click響應事件。對buttonl的click響應事件編寫控制函數如下:
write(fd_keypad,(const char*)lednum,sizeof((const char)
lednum));
進入該工程目錄,使用命令:#./autogen.sh配置相應的程序以及生成所需的Makefiles文件,然后修改Makefile文件,把自行編寫的C文件添加到編譯表中,主要是在參數Led_SOURCE和Led_OBJECTS中。然后執行下面的命令:
#exportCC=arm-linux-gcc
/*通過設置環境變量.指定編譯工具為arm-linux-gcc*/
#./configure host=arm build=i686
target=armwith-gtk-exec-prefix=usr/Iocal/arm-linux
/*設置生成二進制文件的工作環境為arm,并指定Linux內核所
在的目錄*/
#make
/*在/src編譯生成二進制代碼,下載到開發平臺上即可運行*/
用戶程序的開發調試主要有兩種方式:一是在主機上編寫用戶程序,將其直接編譯入內核,整體下載入目標板,再進行調試;二是在主機上通過交叉編譯器編譯用戶程序,生成能在目標板上執行的二進制文件,通過串口或網絡將用戶程序下載到目標板上,進行調試。第一種方式由于每次更改程序都需要編譯入內核、下載到目標板,顯得靈活性較低,且十分繁瑣。而第二種開發方式每次只用下載用戶程序即可調試,非常方便,因此筆者在驅動程序開發中使用了第二種方式。
3 結束語
在Linux中,系統調用是操作系統內核和應用程序之間的接口,而設備驅動程序就是操作系統內核和機器硬件之間的接口。內核利用驅動程序的接口完成對設備的初始化和釋放,在系統內核和硬件、設備文件和應用程序之間傳送數據,并時刻檢測和處理設備出現的錯誤。當操作系統對設備進行操作時,會調用驅動程序注冊的file_operations結構中的函數指針,找到相應的功能函數。
注:本文中所涉及到的圖表、注解、公式等內容請以PDF格式閱讀原文。