0 引言
隨著嵌入式微處理器技術和嵌入式操作系統(tǒng)的快速發(fā)展,嵌入式系統(tǒng)已廣泛滲透到儀器儀表、工業(yè)控制等各個領域。uClinux以其源代碼開放和功能齊備的特性在嵌入式操作系統(tǒng)中獨樹一幟,在嵌入式系統(tǒng)中得到了廣泛應用,各種硬件也需要應用在嵌入式系統(tǒng)中以實現(xiàn)系統(tǒng)功能,這樣就必須有相應的驅動程序支持這些硬件,以滿足系統(tǒng)擴展功能的要求。在uClinux版本2.0以前實現(xiàn)驅動程序傳統(tǒng)的做法是把驅動程序寫成靜態(tài)加載模塊,每次修改源代碼后調試都需要編譯一次內核,這樣導致驅動程序的調試效率十分低下。為了解決這個問題,本文采用了穩(wěn)定的內核版本2.4.26的uClinux源代碼進行驅動程序的開發(fā),并采用可加載內核模塊的方式進行設備驅動程序的調試,在內核運行時可以
動態(tài)的加載和卸載。本文主要介紹在uClinux操作系統(tǒng)下如何實現(xiàn)可加載的設備驅動程序的配置、編寫和調試步驟。
uClinux是針對嵌入式控制領域的嵌入式Linux操作系統(tǒng),系統(tǒng)結構上繼承了Linux內核的絕大部分特性,在uClinux分發(fā)包中還包括很多成
熟的應用層程序,包括busybox、telnet、ftp等。由于現(xiàn)有的uClinux是為了沒有內存管理單元MMU(memory manager unit)的嵌入式微處理器而設計的,所以有無MMU支持是Linux與uClinux的基本差異。由于沒有內存管理單元,uClinux上的應用程序可直接訪問物理存儲器和外圍設備端口,這使原有在嵌入式微處理器編寫的程序可很容易移植到uClinux上使用。
1 可加載的設備驅動程序
設備驅動程序是操作系統(tǒng)內核和機器硬件間的接口。設備驅動程序為應用程序屏蔽了硬件的細節(jié),這樣在應用程序看來,硬件設備只是一個文件,應用程序可像操作普通文件一樣對硬件設備進行操作。
在uClinux操作系統(tǒng)下有字符設備和塊設備兩類主要的設備文件類型。字符設備和塊設備的主要區(qū)別是:在對字符設備發(fā)出讀/寫請求時,實際的硬件I/O 一般就緊接著發(fā)生了;塊設備利用一塊系統(tǒng)內存作為緩沖區(qū),當用戶進程對設備請求滿足用戶要求時,就返回請求的數(shù)據(jù),它是主要針對磁盤等慢速設備設計的,以免耗費過多的CPU時間來等待。為說明此原理,本文探討把一個8盞LED和8個普通按鈕作為字符設備在uClinux中使用。
Linux是一個高度模塊化的操作系統(tǒng),uClinux下驅動程序也是以模塊形式存在的。在uClinux下對驅動程序加載有兩種方式:靜態(tài)編譯到內核和動態(tài)加載到內核。在uClinux V2.4之前的版本只支持靜態(tài)編譯到內核,V2.4以后,uClinux支持了動態(tài)加載到內核的功能,使驅動程序的調試變得與調試應用程序一樣方便快捷??杉虞d內核驅動程序的開發(fā)步驟一般分為3步:
①采用uClinux V2.4.26以后的穩(wěn)定版本進行內核編譯,在編譯時讓內核支持動態(tài)加載;
②可加載內核模塊驅動程序的編寫;
③把驅動程序通過FTP傳到目標板中用命令行工具安裝,通過應用層測試程序進行測試,改進。
下面說明可加載的內核驅動程序的開發(fā)步驟。
1.1 uClinux內核的配置
首先是配置并編譯內核,使內核支持串行接口,F(xiàn)TP,支持內核的命令,如:insmod,lsmod,rmmod。接下來運行make dep和make all用于生成目標板的內核映像文件image.ram和image.rom,這里把image.rom寫入Flash。寫入完成后,通過連接到 uClinux目標板的串口終端運行并配置uClinux,正確設置網(wǎng)絡IP地址,使uClinux的目標板和開發(fā)用主機能夠互相用FTP訪問,這里把主機IP設為210.46.109.26,uClinux的目標板IP地址設為210.46.109.25,通過ping命令確認網(wǎng)絡通暢,在串口終端上運行“FTP 210.46.109.26”能夠正常登陸主機并下載文件。
不代參數(shù)執(zhí)行insmod,lsmod,rmmod命令能夠得到正常的錯誤提示。至此第一步的準備工作完成。
1.2 可加載內核模塊驅動程序的編寫
在uClinux中,為了讓應用程序對底層硬件有一個統(tǒng)一的訪問標準,必須對每個在uClinux中應用的硬件編寫驅動程序,驅動程序加載成功后,應用程序就可以像讀寫普通文件一樣來通過驅動程序訪問硬件設備了,所以驅動程序必須處理一些標準的文件操作,常見的包括“open”,“read”, “write”等。
編寫驅動程序主要是編寫當“Open”,“Read”,“Write”等操作產生時的相應函數(shù),這些函數(shù)的入口地址在程序中被定位在uClinux的一個“file-operations”結構體中,例如:
struct file-operations lcd-fileops={
open:lcd-open,
release:lcd-release,
read:led-read,
write:led-write,
};
lcd-fileops為此結構體的名稱,均為led-open,lcd-release等均為相應的函數(shù)名稱,當此驅動程序加載后,若應用程序要打開此設備,操作系統(tǒng)會向驅動程序發(fā)出“open”請求,驅動程序會按lcd-fileops中的定義調用open對應的處理函數(shù)lcd-open實現(xiàn)應用程序要的打開功能,例如:
int lcd-open(struct inode *inode,struct file *file)
{
MOD-INC-USE-COUNT;
printk("charlcd device opened.\n");
return 0;
}
struct inode和struct file是操作系統(tǒng)傳進來的指針,一般情況不需修改,MOD-INC-USE-COUNT是使此設備計數(shù)器+1,在調用release操作時會-1(對應操作為MOD-DEC-USE-COUNT),uClinux通過這種簡單的機制以防止文件在關閉前驅動程序模塊被卸載出內核。每次成功的設備打開操作后都應該執(zhí)行MOD-INC-USE-COUNT。為簡單起見,沒有進行設備有效性檢查,直接調用了,printk是調試驅動程序時一個內核輸出函數(shù)(在內核中不能用printf函數(shù)),它執(zhí)行時會向默認的輸出設備輸出相應信息,如,本例執(zhí)行時,會向串口終端輸出字符串“charlcd device opened.”并換行。
文件操作函數(shù)led-read和led-write,分別對應“read”和“write”操作,其中l(wèi)ed-read的函數(shù)例子如下:
ssize-t lcd-read(struct file* file,char *userBur,size-t sz-buf,loff-t *lf)
{
IOMODE= INPUTMODE;
userBuf[0]=IOPDATA;
printk("Reading data from kernel process\n");
return 0;
}
userBuf是驅動程序與應用程序的數(shù)據(jù)接口,其長度為sz-buf。在執(zhí)行讀操作時應該把硬件“讀”到的數(shù)據(jù)放在userBuf中。應用程序在對此硬件執(zhí)行“read”操作后會得到userBuf中的數(shù)據(jù)。本例是讀取外部8個開關的狀態(tài),把輸入輸出端口模式寄存器IOPMODE定義為輸入 (INPUTMODE),并把輸入輸出端口數(shù)據(jù)寄存器的值(當前開關狀態(tài))賦給userBuf[0]。Lcd-write的函數(shù)例子如下:
ssize-t lcd-write(struct file*file,const char *buf,size-t sz-buf,loff-t *lf)
{
int count;
if(bur==NULL)return -1;
IOPMODE=OUTPUTMODE;
IOPDATA=buf[0];
printk("Writing user data to kernel process\n");
return 0;
}
buf是應用程序與驅動程序的接口,其長度為sz-buf。在執(zhí)行操作時,由上層應用程序來決定buf的內容,由此函數(shù)內的代碼來決定如何把buf的內容 “寫”到硬件設備中。本例中,把Samsung4510b的輸入輸出端口模式寄存器(IOPMODE)置為輸出狀態(tài)來控制8個發(fā)光二極管,通過把buf [0]的數(shù)據(jù)寫入輸入輸出端口寄存器(IOPDATA)來決定外部8個發(fā)光二極管的亮滅。
以上操作對于動態(tài)和靜態(tài)兩種加載方式的驅動程序都是通用的,對可加載內核模塊驅動程序,還必須在程序中定位以下兩個操作的位置:module-init和module-exit。在程序中如下表示:
module-init(lcd-init);
module-exit(lcd-clearup);
說明module-init操作的實現(xiàn)函數(shù)是lcd-init,module-exit操作的實現(xiàn)函數(shù)是(lcd-clearup),由于lcd- init是模塊注冊和初始化函數(shù),使用完后就可以釋放占有的資源,在執(zhí)行時必須放到內核內存區(qū)的.text.init區(qū),所以,必須在lcd-init函數(shù)定義前加入static int --init聲明,一個實際的例子如下:
int lcd-devNo;
static int --init lcd-init(void)
{
int iResult==0;
printk("Registering charlcd device into /dev \n");
iResult = register-chrdev(254,"charlcd",&lcd-fileops);
if(iResult==254)
{
printk("Charlcd device is registered.\n");
led-devNo = iResult;
return iResult;
}
else
{
printk("Error occurred while Registering charlcd device.\n");
return iResult;
}
}
若register-chrdev執(zhí)行成功,會返回注冊成功的主設備號(major number),把此主設備號保存到lcd-devNo是將來為動態(tài)注銷此驅動程序時使用的,因為動態(tài)注銷此驅動程序需要主設備號,動態(tài)注銷函數(shù)是 module-exit()中指定的函數(shù),本例中是“lcd-clearup”,例如:
int led-clearup(void)
{
int iResult=0;
if((iResult= unregister-chrdev(1ed-devNo,"charlcd"))==0)
printk("Remove the charlcd device from kernel.\n");
else
printk("Error occurred while Removing the charlcd device.\n");
return iResult;
}
lcd-devNo是當register-chrdev執(zhí)行成功時賦的值,若unregister-chrdev函數(shù)執(zhí)行成功,返回0,否則返回出錯代碼,交由操作系統(tǒng)處理。
綜上所述,一個典型的可動態(tài)加載的內核驅動程序模塊的代碼包括兩大部分:
①file-operations結構體的填充和函數(shù)實現(xiàn);
②module-init()和module-exit()函數(shù)參數(shù)指針的填充和函數(shù)實現(xiàn)。
對于靜態(tài)內核驅動程序而言不需要第二部分,但是需要手動修改uClinux的配置源代碼,加入模塊注冊信息,比較煩瑣,容易出錯。而可動態(tài)加載的內核驅動程序模塊則不會出現(xiàn)此問題。
把編輯完的代碼通過arm-elf-gcc編譯成目標文件(.o文件)就完成了可加載內核驅動程序的生成,常用的編譯指令文件Makefile的內容如下:
CC=arm-elf-gcc//指明使用的交叉編譯器
OBJS=led.o//指明目標文件的名稱
SOCS=led.c//指明源程序文件的名稱
LD= arm-elf-gcc//指明使用的連接器
LDFLAGS = --DMODULE -D --KERNEL -- D --linux --C
all:
$(CC) -o $(OBJS) $(LDFLAGS)
$(SOCS)
cp $(OBJS)/root/led.o
clean:
-rm -f $(EXEC) *.elf *.gdb *.o
1.3 驅動程序的安裝與測試
把驅動程序通過FTP傳到目標板中安裝,通過應用程序對例子進行測試,改進。
首先通過串口終端輸入指令把上一步生成的目標文件(這里假設為led.o)以二進制形式(命令為bina)用FTP下載到uClinux的/var目錄下(因為此
目錄是可寫的),而后用insmod命令加載led.o驅動程序,此時操作系統(tǒng)會調用驅動程序的module-init操作,對本例是執(zhí)行l(wèi)cd.init 函數(shù)完成驅動程序的注冊。insmod命令執(zhí)行正常,會在串口終端輸出“Charlcd device is registered.”字符串,表明此字符設備已經(jīng)正確加載,為了確認此設備是否真的已經(jīng)在操作系統(tǒng)中存在,可用lsmod命令或查看 /proc/devices文件的內容來確認。/proc/devices文件是uClinux保存當前系統(tǒng)設備的一個特殊文件,以純文本形式存儲。例如,執(zhí)行“cat/proc/devices”,如正常,系統(tǒng)會有以下輸出:
Character devices:
1 mem
2 pty
254 charlcd -這是驅動程序中申請的設備主編號和設備名稱。
此步說明驅動程序已經(jīng)成功地動態(tài)加載到內核,如何用應用程序訪問驅動程序呢?就是采用操作系統(tǒng)的文件操作就可以訪問驅動程序,進而訪問硬件設備,下面給出一個簡單的應用程序例程片斷,用來訪問之前動態(tài)加載的驅動程序:
int main(void)
{
int i;
char buf[20];
int iResult=open("/dev/charlcd",O-RDWR);
if(iResult==0)
{
printf("Open/dev/charlcd ok!\n");
read(iResult,buf,1);//讀取8個開關狀態(tài)
printf("Key Status:%.2x \n",buf[O]);
buf[0]=0x55;
printf("LED Status:%.2x \n",buf[O]);
write(iResult,buf,1);//設置8個發(fā)光二極管亮滅
close(iResult);
}
else
{
printf("Error on open/dev/charlcd!\n");
}
return 1;
}
read(iResult,bur,i)函數(shù)執(zhí)行時會調用驅動程序的read操作對應的函數(shù)lcd-read(struct file *file,char *userBuf,size-t sz-buf,loff-t *lf)進行操作,把iResult傳給file、buf傳給userBuf、i傳給sz-buf;write(iResult,buf,1)函數(shù)執(zhí)行時會調用驅動程序的write操作對應的函數(shù)led write(struct file*file,const char* buf,size-t sz.buf,loff-t *lf)進行操作,把iResult傳給file、buf傳遞給buf、i傳遞給sz-buf。
通過以上方法可像調試應用程序一樣來迅速測試驅動程序,通過調試輸出語句printk可調試驅動程序,當然驅動程序調試完畢后,發(fā)布時還應該用靜態(tài)編譯到內核的方式把驅動程序固化到內核中,uClinux下把驅動程序靜態(tài)編譯到內核已有文獻介紹 ,本文不再贅述。
2 結語
介紹了uClinux下可加載內核驅動程序模塊的實現(xiàn)過程,這種方式與原有靜態(tài)編譯到內核的方式比較,在驅動程序測試,調試階段節(jié)省了大量的時間,極大地提高了驅動程序的調試效率,使具體硬件設備盡早在uClinux的產品中使用成為可能。