摘要:首先介紹了嵌入式Linux環(huán)境下設(shè)備驅(qū)動程序的開發(fā)流程,詳細描述了Linux設(shè)備驅(qū)動程序的體系結(jié)構(gòu)和Linux設(shè)備文件的概念。然后通過一個例子描述了如何設(shè)計和編寫Linux設(shè)備驅(qū)動程序,并解釋了其中的核心代碼。最后構(gòu)建一個Glade工程來調(diào)用這個設(shè)備驅(qū)動程序,完成測試工作。
關(guān)鍵詞:嵌入式Linux;設(shè)備文件;設(shè)備驅(qū)動;Glade
0 引言
Linux是開放源代碼的操作系統(tǒng),由于其高效穩(wěn)定、執(zhí)行速度快、實現(xiàn)了真正的多任務(wù)、多用戶環(huán)境、強大的網(wǎng)絡(luò)功能、較好的可裁減性與移植性等特點,在嵌入式系統(tǒng)領(lǐng)域獲得了飛速發(fā)展。針對ARM體系結(jié)構(gòu)CPU開發(fā)的,具有MMU功能的嵌入式Linux操作系統(tǒng)無疑是ARM平臺上操作系統(tǒng)的最佳選擇。本文主要研究了在Linux下開發(fā)驅(qū)動程序并構(gòu)建一個GUI程序來使用這個驅(qū)動程序的一般流程。
1 Linux設(shè)備驅(qū)動程序
Linux支持三類硬件設(shè)備:字符設(shè)備、塊設(shè)備和網(wǎng)絡(luò)設(shè)備。字符設(shè)備是指無須緩存直接按字節(jié)讀寫的設(shè)備。塊設(shè)備以塊為單位進行讀寫,能夠進行隨機訪問。網(wǎng)絡(luò)設(shè)備在Linux里有專門的處理,它沒有被映射到文件系統(tǒng)的設(shè)備節(jié)點,對它的訪問采用socket機制。字符設(shè)備與塊設(shè)備的主要區(qū)別是:在對字符設(shè)備發(fā)出讀/寫請求時,實際的硬件I/O一般緊接著發(fā)生;塊設(shè)備是利用一塊系統(tǒng)內(nèi)存作緩沖區(qū)來進行實際的I/O操作。
圖1 設(shè)備驅(qū)動流程
在Linux中,幾乎所有的內(nèi)容都是文件,對設(shè)備驅(qū)動的訪問也是以文件操作的方式實現(xiàn)。無論是字符設(shè)備還是塊設(shè)備,用戶對沒備的操作都是通過虛擬文件系統(tǒng)(VFS)轉(zhuǎn)化為設(shè)備驅(qū)動與硬件操作例程的交互(見圖1)。即使是訪問網(wǎng)絡(luò)設(shè)備的socket接口,也是通過VFS實現(xiàn)的。Linux通過VFS為用戶提供了一個統(tǒng)一的設(shè)備訪問接口,使用戶能夠透明地訪問設(shè)備驅(qū)動程序。所有的硬件設(shè)備都可以使用和操作系統(tǒng)調(diào)用接口來打開、關(guān)閉、讀寫和I/O控制,而驅(qū)動程序的主要任務(wù)就是實現(xiàn)這些系統(tǒng)調(diào)用函數(shù)。Linux系統(tǒng)中的所有硬件設(shè)備都使用一種特殊的設(shè)備文件來表示。每個設(shè)備文件都有兩個設(shè)備號:一個是主設(shè)備號,它用來標(biāo)識該設(shè)備的種類,也標(biāo)識該設(shè)備使用的驅(qū)動程序;另一個是次設(shè)備號,用來標(biāo)識使用同一設(shè)備驅(qū)動程序的不同硬件設(shè)備。
實現(xiàn)一個嵌入式Linux設(shè)備驅(qū)動的大致流程如下:
(1)定義主、次設(shè)備號;也可以動態(tài)獲取。
(2)實現(xiàn)驅(qū)動初始化和清除函數(shù),如果驅(qū)動程序采用模塊方式,則要實現(xiàn)模塊初始化和清除函數(shù)。
(3)設(shè)計所要實現(xiàn)的文件操作,定義file_operations結(jié)構(gòu)。
(4)實現(xiàn)所需的文件操作調(diào)用,如read、write等。
(5)實現(xiàn)中斷服務(wù)函數(shù),并用request_irq向內(nèi)核注冊。中斷并不是每個設(shè)備驅(qū)動所需要。
(6)將驅(qū)動編譯到內(nèi)核或編譯成模塊,用insmod命令加載。
(7)生成設(shè)備節(jié)點文件。
下面以一個簡單的例子來說明設(shè)備驅(qū)動程序的結(jié)構(gòu),這個驅(qū)動程序用來控制目標(biāo)板上的一組LED燈。led_fops結(jié)構(gòu)體定義了該設(shè)備需要的操作接口。它的成員全部是函數(shù)指針,所以實質(zhì)上就是函數(shù)跳轉(zhuǎn)表。
struct file_operations led_fops={
open:led_open,
/*打開設(shè)備操作*/
read:led_read,
/*讀設(shè)備操作*/
write:led_write,
/*寫設(shè)備操作*/
ioctl:led_ioctl,
/*控制模塊的設(shè)置*/
release:led_release /*釋放設(shè)備操作*/
}:
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);/*獲取當(dāng)前8個按鍵的狀態(tài)*/
for(i=0:i<8:++i)
{if(((BottonStatus>>i)&1)==0)
Bottontmp=i+1;
}
copy_to user(Putbuf,Bottontmp,length);
/*將內(nèi)核空間的數(shù)據(jù)復(fù)制到用戶空間*/
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);
/*將用戶空間的寫入到設(shè)備文件的內(nèi)容傳送到內(nèi)核空間*/
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參數(shù)可以完成有些復(fù)雜的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.“l(fā)ed”,led_fops);
/*這個函數(shù)是向內(nèi)核注冊設(shè)備,Led_MAJOR是驅(qū)動的主設(shè)備
號,led是驅(qū)動名,led_fops是驅(qū)動所執(zhí)行的操作*/
printk(“%s%sinitialized.\n”,KEYPAD_NAME,KEYPAD_VERSION);
return 0;
}
static void_exit keypad_exit(void)
{unregister_chrdev(Led_MAJOR,“l(fā)ed”);/*向內(nèi)核注銷設(shè)備*/
led_off_on();
}
module_init(keypad_init);
module_exit(keypad_exit);
module_init()和module_exit()這對宏是對程序模塊的初始化和退出函數(shù)名稱進行記錄。它能顯式地命名模塊的注冊和注銷函數(shù)并保證內(nèi)核中驅(qū)動名的惟一性。
2 嵌入式LInux的GUI
Tiny-X是由XFree86核心小組的成員Keith Packard一手設(shè)計的。它能夠在配有IMB以下內(nèi)存的系統(tǒng)上,建立起標(biāo)準(zhǔn)的X系統(tǒng),是一個性能相當(dāng)出色并且免費的嵌入式圖形界面GUI。在嵌入式系統(tǒng)中,使用Tiny-X圖形界面開發(fā)產(chǎn)品,上層的應(yīng)用程序編寫將會很方便。可以通過GTK的集成開發(fā)環(huán)境——Glade完成界面布局并生成原始代碼(界面圖如圖2),再通過文本編輯工具添加事件響應(yīng)程序。 在程序控制硬件設(shè)備之前要打開設(shè)備文件,所以要在執(zhí)行g(shù)tk_main()前調(diào)用設(shè)備打開函數(shù)。這個函數(shù)主要用于打開設(shè)備文件并獲得主設(shè)備號。關(guān)鍵代碼如下: int fd_keypad;
static char*dev_keypad=“/dev/keypad”;
fd_keypad=open(dev_keypad,O_RDWR);
return fd_keypad;
圖2 Glade工程界面
然后對每個button添加click響應(yīng)事件。對buttonl的click響應(yīng)事件編寫控制函數(shù)如下:
write(fd_keypad,(const char*)lednum,sizeof((const char)
lednum));
進入該工程目錄,使用命令:#./autogen.sh配置相應(yīng)的程序以及生成所需的Makefiles文件,然后修改Makefile文件,把自行編寫的C文件添加到編譯表中,主要是在參數(shù)Led_SOURCE和Led_OBJECTS中。然后執(zhí)行下面的命令:
#exportCC=arm-linux-gcc
/*通過設(shè)置環(huán)境變量.指定編譯工具為arm-linux-gcc*/
#./configure host=arm build=i686
target=armwith-gtk-exec-prefix=usr/Iocal/arm-linux
/*設(shè)置生成二進制文件的工作環(huán)境為arm,并指定Linux內(nèi)核所
在的目錄*/
#make
/*在/src編譯生成二進制代碼,下載到開發(fā)平臺上即可運行*/
用戶程序的開發(fā)調(diào)試主要有兩種方式:一是在主機上編寫用戶程序,將其直接編譯入內(nèi)核,整體下載入目標(biāo)板,再進行調(diào)試;二是在主機上通過交叉編譯器編譯用戶程序,生成能在目標(biāo)板上執(zhí)行的二進制文件,通過串口或網(wǎng)絡(luò)將用戶程序下載到目標(biāo)板上,進行調(diào)試。第一種方式由于每次更改程序都需要編譯入內(nèi)核、下載到目標(biāo)板,顯得靈活性較低,且十分繁瑣。而第二種開發(fā)方式每次只用下載用戶程序即可調(diào)試,非常方便,因此筆者在驅(qū)動程序開發(fā)中使用了第二種方式。
3 結(jié)束語
在Linux中,系統(tǒng)調(diào)用是操作系統(tǒng)內(nèi)核和應(yīng)用程序之間的接口,而設(shè)備驅(qū)動程序就是操作系統(tǒng)內(nèi)核和機器硬件之間的接口。內(nèi)核利用驅(qū)動程序的接口完成對設(shè)備的初始化和釋放,在系統(tǒng)內(nèi)核和硬件、設(shè)備文件和應(yīng)用程序之間傳送數(shù)據(jù),并時刻檢測和處理設(shè)備出現(xiàn)的錯誤。當(dāng)操作系統(tǒng)對設(shè)備進行操作時,會調(diào)用驅(qū)動程序注冊的file_operations結(jié)構(gòu)中的函數(shù)指針,找到相應(yīng)的功能函數(shù)。
注:本文中所涉及到的圖表、注解、公式等內(nèi)容請以PDF格式閱讀原文。