新聞中心

EEPW首頁(yè) > 嵌入式系統(tǒng) > 設(shè)計(jì)應(yīng)用 > 嵌入式Linux應(yīng)用程序訪問(wèn)物理地址的實(shí)例

嵌入式Linux應(yīng)用程序訪問(wèn)物理地址的實(shí)例

作者: 時(shí)間:2013-05-28 來(lái)源:網(wǎng)絡(luò) 收藏

前言
  按照分層思想,外設(shè)與主機(jī)控制器的不相關(guān),主機(jī)控制器的驅(qū)動(dòng)不關(guān)心外設(shè),而外設(shè)驅(qū)動(dòng)也不關(guān)心主機(jī),外設(shè)訪問(wèn)核心層的通用應(yīng)用程序進(jìn)行數(shù)據(jù)傳輸,主機(jī)和外設(shè)之間可以進(jìn)行任意的組合。這樣思想要求應(yīng)用程序不應(yīng)當(dāng)直接訪問(wèn)物理地址,而是應(yīng)當(dāng)通過(guò)驅(qū)動(dòng)程序的調(diào)用來(lái)實(shí)現(xiàn),以便保持應(yīng)用程序的性,操作訪問(wèn)的統(tǒng)一性,應(yīng)用程序利用系統(tǒng)的統(tǒng)一調(diào)用訪問(wèn)外設(shè),如使用write(),read()等函數(shù)進(jìn)行實(shí)際的外設(shè)讀寫控制。應(yīng)用程序通過(guò)調(diào)用進(jìn)入內(nèi)核函數(shù)后,內(nèi)核利用copy_from_user()獲得應(yīng)用層數(shù)據(jù),內(nèi)核驅(qū)動(dòng)程序也通過(guò)分層最終執(zhí)行物理訪問(wèn),之后把獲得的數(shù)據(jù)用copy_to_user()回傳給應(yīng)用程序的調(diào)用者。由于驅(qū)動(dòng)對(duì)外需要有個(gè)統(tǒng)一接口,所以定義了一些結(jié)構(gòu)體,鏈表等機(jī)制,以便讓應(yīng)用程序操作簡(jiǎn)單化,數(shù)據(jù)在內(nèi)核一應(yīng)用之間的復(fù)制,填充結(jié)構(gòu)體等都需要時(shí)間開銷,有時(shí)按這種標(biāo)準(zhǔn)調(diào)用方式,因?yàn)椴僮鲿r(shí)間過(guò)長(zhǎng),無(wú)法完成設(shè)計(jì)目的。

操作效率評(píng)估
  我們的一個(gè)項(xiàng)目中,系統(tǒng)由FPGA和ARM11結(jié)合為核心控制器,其中FPGA連接外部高速ADC、DAC和RF器件在ARM11的控制下,實(shí)現(xiàn)GB18000-6C標(biāo)準(zhǔn)的UHF RFID讀寫控制狀態(tài)機(jī)。FPGA與ARM11的接口采用SPI,其中ARM11選用三星S3C6410,作為SPI的主機(jī),F(xiàn)PGA作為SPI的從機(jī),受S3C6410的控制。在本系統(tǒng)中,SPI接口充當(dāng)ARM11和FPGA交互的橋梁,ARM11的命令和動(dòng)作參數(shù)傳給FPGA并啟動(dòng)FPGA處理狀態(tài)機(jī),F(xiàn)PGA動(dòng)作的結(jié)果也通過(guò)SPI回傳給ARM11,兩者之間的通訊效率在系統(tǒng)中需要重點(diǎn)關(guān)注。

  評(píng)估通訊接口時(shí),利用三星提供的SPI驅(qū)動(dòng)函數(shù),系統(tǒng)運(yùn)行在533MHz,SPI時(shí)鐘配置為16MHz,程序在linux3.0環(huán)境下通過(guò)read/write進(jìn)行操作,為了評(píng)估效率,另外采用一個(gè)GPIO輸出脈沖指示操作過(guò)程,試驗(yàn)結(jié)果顯示效率非常低下,從應(yīng)用層執(zhí)行write代碼開始到SPI端口輸出時(shí)鐘,延時(shí)長(zhǎng)達(dá)72μs,SPI操作之后,再回到應(yīng)用層的下一個(gè)語(yǔ)句也延時(shí)42μs,對(duì)于比較少的數(shù)據(jù)傳輸情況,附加的額外等待時(shí)間遠(yuǎn)遠(yuǎn)長(zhǎng)于實(shí)際傳輸有效時(shí)間,從前面數(shù)據(jù)看出,通過(guò)標(biāo)準(zhǔn)庫(kù)調(diào)用嚴(yán)重影響系統(tǒng)性能,沒(méi)法滿足系統(tǒng)需求。通過(guò)查看驅(qū)動(dòng)程序的源代碼,可以發(fā)現(xiàn)因?yàn)轵?qū)動(dòng)程序?qū)訉臃庋b,并且包含應(yīng)用層到內(nèi)核的copy_from_user()和內(nèi)核到應(yīng)用層的copy_to_user()兩次數(shù)據(jù)搬移,導(dǎo)致執(zhí)行效率很低。為了提高數(shù)據(jù)交互效率,就要設(shè)法繞開數(shù)據(jù)搬移等時(shí)間開銷,最好能直接操作寄存器,雖然這種想法與分層驅(qū)動(dòng)思想不相符合,但是在嵌入式系統(tǒng)中,有時(shí)需要高的執(zhí)行效率,如果利用系統(tǒng)一些特定函數(shù),實(shí)現(xiàn)高效率的數(shù)據(jù)交互從而完成設(shè)計(jì)目標(biāo)是有必要和可能的。

  linux存在名為mmap的函數(shù),能把物理地址映射為虛擬地址,并且這個(gè)函數(shù)能直接在應(yīng)用程序中直接調(diào)用而不是僅僅屬于內(nèi)核調(diào)用的函數(shù),這樣在應(yīng)用層直接操作S3C6410的物理外設(shè)成為可能??紤]到在特定的嵌入式系統(tǒng)中,特定外設(shè)的使用可以由程序控制,這樣可以簡(jiǎn)化共享設(shè)備的互斥保護(hù),進(jìn)一步減少代碼量,提高了訪問(wèn)效率。

mmap函數(shù)調(diào)用實(shí)例
  mmap函數(shù)作用是將物理地址映射至用戶空間。下面是函數(shù)的參數(shù)簡(jiǎn)單說(shuō)明
void* mmap(void * addr, size_t len, int prot, int flags, int fd, off_t offset);
addr: 指定文件應(yīng)被映射到進(jìn)程空間的起始地址
len: 映射到用戶空間的字節(jié)數(shù)
prot: 指定被映射空間的訪問(wèn)權(quán)限,
flags: 由以下幾個(gè)常值指定:
fd: 映射到用戶空間的文件的描述符
offset: 被映射內(nèi)存區(qū)在文件中的偏移值該函數(shù)映射文件描述符

  通過(guò)這個(gè)函數(shù),我們可以在應(yīng)用層訪問(wèn)對(duì)應(yīng)物理地址正確映射后的虛擬地址,這個(gè)函數(shù)使我們?cè)趹?yīng)用層也具有對(duì)任意物理地址的操作權(quán)限,下面代碼配置S3C6410的SPI0,因?yàn)槭褂胢map映射,所以不論內(nèi)核是否帶有SPI驅(qū)動(dòng)都不影響我們使用SPI0,但是因?yàn)楸境绦蛐枰獙?duì)比研究標(biāo)準(zhǔn)驅(qū)動(dòng)方式與直接存儲(chǔ)器訪問(wèn)方式的執(zhí)行差異,所以在內(nèi)核中編譯了標(biāo)準(zhǔn)SPI的驅(qū)動(dòng)程序。由于S3C6410多數(shù)腳都有復(fù)用功能,為了使SPI0正確工作,還需要配置相關(guān)對(duì)應(yīng)的GPIO為SPI功能(實(shí)際上因?yàn)槲覀兙幾g的內(nèi)核帶有SPI0的驅(qū)動(dòng),內(nèi)核程序已經(jīng)完成了SPI的初始化,有的內(nèi)核沒(méi)有編譯SPI,所以下面還是完整配置了SPI,供參考),同時(shí)為了觀察研究SPI的執(zhí)行效率,我們程序還對(duì)其他GPIO做了配置以便輸出脈沖,通過(guò)示波器來(lái)評(píng)估觀察。另外我們還使用若干時(shí)間標(biāo)志來(lái)記錄操作過(guò)程時(shí)間,對(duì)于在沒(méi)有示波器的情況下也能評(píng)估執(zhí)行時(shí)間。

  下面是測(cè)試程序代碼以及測(cè)試過(guò)程的示波器記錄抓圖。

#include "test.h"
void Init_FPGA_SPI(){//配置SPI端口
int fbb;
fbb=open("/dev/mem",O_RDWR | O_SYNC);
map_base=(char *)mmap(0,4096,PROT_READ|PROT_WRITE,MAP_SHARED,fbb,0x7f00b000);
*(volatile unsigned int *)(map_base+0x04)=0x00000101;//CLK=16.625MHz
*(volatile unsigned int *)(map_base+0x08)=0x00000000;
*(volatile unsigned int *)(map_base+0x0c)=0x00000002;
*(volatile unsigned int *)(map_base)=0x00000003;
FPGA_RUN=map_base+0x18;
map_base=(char *)mmap(0,4096,PROT_READ|PROT_WRITE,MAP_SHARED,fbb,0x7f008000);
GPC=map_base+0x40;//配置端口復(fù)用功能為SPI
map_GPC=*(volatile unsigned int *)(GPC+4);
*(volatile unsigned int *)(GPC)=0x12201222;
GPC+=4;
virt_addr2=map_base+0x824;//配置觀察IO
GLEDstate=*(volatile unsigned int *)(virt_addr2);
}
void Init_Timer(){//添加加配置1微秒時(shí)基定時(shí)器
int fbb;
unsigned int temp;
fbb=open("/dev/mem",O_RDWR | O_SYNC);
map_base=(char *)mmap(0,4096,PROT_READ|PROT_WRITE,MAP_SHARED,fbb,0x7f006000);
…………………… 篇幅原因略去部分次要代碼
MYSYSTICK=map_base+0x14;
}
void SPI_init(){
bits=8;
speed = 16625000;
trr.len =20;
trr.delay_usecs = 0;
trr.speed_hz = speed;
trr.bits_per_word = bits;
fspi = open("/dev/spidev0.0", O_RDWR);
ioctl(fspi, SPI_IOC_RD_MODE, mode);
ioctl(fspi, SPI_IOC_WR_MODE, mode);
}
__inline unsigned int GETSYSCLK(){
return(*(volatile unsigned int *)(MYSYSTICK));
}
__inline void CSFPGAL(){
map_GPC=0xfffffff7;
*(volatile unsigned int *)(GPC)=map_GPC;
}
__inline void CSFPGAH(){
map_GPC|=0x00000008;
*(volatile unsigned int *)(GPC)=map_GPC;
}
void test(){
GLEDstate=0xfffffffe;
*(volatile unsigned int *)(virt_addr2)=GLEDstate;//產(chǎn)生GPIO負(fù)跳變
starttime2=GETSYSCLK();
*(volatile unsigned int *)(FPGA_RUN-0x0c)=0x00;
*(volatile unsigned int *)(FPGA_RUN-0x18)=0x23;
*(volatile unsigned int *)(FPGA_RUN-0x18)=0x03;
CSFPGAL();
*(volatile unsigned int *)(FPGA_RUN)=tx[0];
*(volatile unsigned int *)(FPGA_RUN)=tx[1];
*(volatile unsigned int *)(FPGA_RUN)=tx[2];
*(volatile unsigned int *)(FPGA_RUN)=tx[3];
*(volatile unsigned int *)(FPGA_RUN)=tx[4];
while (((*(volatile unsigned int *)(FPGA_RUN-4)0xfe000)>>13)5){};
CSFPGAH();
stoptime2=GETSYSCLK();
GLEDstate|=0x00000001;
*(volatile unsigned int *)(virt_addr2)=GLEDstate;
GLEDstate=0xfffffffe;
*(volatile unsigned int *)(virt_addr2)=GLEDstate;
starttime1=GETSYSCLK();//產(chǎn)生GPIO一個(gè)正脈沖
write(fspi,tx,5);
stoptime1=GETSYSCLK();
GLEDstate|=0x00000001;
*(volatile unsigned int *)(virt_addr2)=GLEDstate; //產(chǎn)生GPIO正跳變
printf("DRVtime=%d REGtime=%d ",starttime1-stoptime1,starttime2-stoptime2);
}
int main(void){
SPI_init();Init_FPGA_SPI();Init_Timer();
waittime=GETSYSCLK();
while(1){
if ((waittime-GETSYSCLK())>2000000){//2000ms測(cè)試一次
waittime=GETSYSCLK();
test();
}
}
}

  圖1示波器截圖添加了一些時(shí)間信息以便對(duì)應(yīng)代碼注釋說(shuō)明,對(duì)應(yīng)于代碼mmap方式和標(biāo)準(zhǔn)驅(qū)動(dòng)調(diào)用方式產(chǎn)生了兩組SCK時(shí)鐘,GPIO觀察腳顯示第一次SPI訪問(wèn)消耗5μs,第二次訪問(wèn)消耗114μs,其中真正操作SPI的時(shí)間也就4μs不到,其它時(shí)間消耗在系統(tǒng)應(yīng)用層到內(nèi)核兩次雙向的數(shù)據(jù)拷貝以及為了統(tǒng)一對(duì)外接口所做的數(shù)據(jù)結(jié)構(gòu)配置等方面,由此對(duì)比可以看出兩種方式訪問(wèn)效率上的巨大差異。

圖 1


結(jié)語(yǔ)
  通過(guò)mmap方式應(yīng)用程序在下操作硬件寄存器,適合于關(guān)注高效率的訪問(wèn)場(chǎng)合,在嵌入式應(yīng)用中,我們既能夠獲得使用操作系統(tǒng)管理任務(wù)和豐富開源驅(qū)動(dòng)庫(kù)的好處,同時(shí)又能在局部提升處理效率,提高處理數(shù)據(jù)的實(shí)時(shí)性。

linux操作系統(tǒng)文章專題:linux操作系統(tǒng)詳解(linux不再難懂)

linux相關(guān)文章:linux教程




評(píng)論


相關(guān)推薦

技術(shù)專區(qū)

關(guān)閉