嵌入式Linux的ARM移植實(shí)例研究
應用實(shí)例的編寫(xiě)實(shí)際上已經(jīng)不屬于Linux操作系統移植的范疇,但是為了保證本系列文章的完整性,這里提供一系列針對嵌入式Linux開(kāi)發(fā)應用程序的實(shí)例。
編寫(xiě)Linux應用程序要用到如下工具:
(1)編譯器:GCC
GCC是Linux平臺下最重要的開(kāi)發(fā)工具,它是GNU的C和C++編譯器,其基本用法為:gcc[options][filenames]。
我們應該使用arm-linux-gcc。
(2)調試器:GDB
gdb是一個(gè)用來(lái)調試C和C++程序的強力調試器,我們能通過(guò)它進(jìn)行一系列調試工作,包括設置斷點(diǎn)、觀(guān)查變量、單步等。
我們應該使用arm-linux-gdb。
(3)Make
GNUMake的主要工作是讀進(jìn)一個(gè)文本文件,稱(chēng)為makefile。這個(gè)文件記錄了哪些文件由哪些文件產(chǎn)生,用什么命令來(lái)產(chǎn)生。Make依靠此makefile中的信息檢查磁盤(pán)上的文件,如果目的文件的創(chuàng )建或修改時(shí)間比它的一個(gè)依靠文件舊的話(huà),make就執行相應的命令,以便更新目的文件。
Makefile中的編譯規則要相應地使用arm-linux-版本。
(4)代碼編輯
可以使用傳統的vi編輯器,但最好采用emacs軟件,它具備語(yǔ)法高亮、版本控制等附帶功能。
在宿主機上用上述工具完成應用程序的開(kāi)發(fā)后,可以通過(guò)如下途徑將程序下載到目標板上運行:
(1)通過(guò)串口通信協(xié)議rz將程序下載到目標板的文件系統中(感謝Linux提供了rz這樣的一個(gè)命令);
(2)通過(guò)ftp通信協(xié)議從宿主機上的ftp目錄里將程序下載到目標板的文件系統中;
(3)將程序拷入U盤(pán),在目標機上mountU盤(pán),運行U盤(pán)中的程序;
(4)如果目標機Linux使用NFS文件系統,則可以直接將程序拷入到宿主機相應的目錄內,在目標機Linux中可以直接使用。
1.文件編程
Linux的文件操作API涉及到創(chuàng )建、打開(kāi)、讀寫(xiě)和關(guān)閉文件。
創(chuàng )建
intcreat(constchar*filename,mode_tmode);
參數mode指定新建文件的存取權限,它同umask一起決定文件的最終權限(modeumask),其中umask代表了文件在創(chuàng )建時(shí)需要去掉的一些存取權限。umask可通過(guò)系統調用umask()來(lái)改變:
intumask(intnewmask);
該調用將umask設置為newmask,然后返回舊的umask,它只影響讀、寫(xiě)和執行權限。
打開(kāi)
intopen(constchar*pathname,intflags);
intopen(constchar*pathname,intflags,mode_tmode);
讀寫(xiě)
在文件打開(kāi)以后,我們才可對文件進(jìn)行讀寫(xiě)了,Linux中提供文件讀寫(xiě)的系統調用是read、write函數:
intread(intfd,constvoid*buf,size_tlength);
intwrite(intfd,constvoid*buf,size_tlength);
其中參數buf為指向緩沖區的指針,length為緩沖區的大?。ㄒ宰止潪閱挝唬?。函數read()實(shí)現從文件描述符fd所指定的文件中讀取length個(gè)字節到buf所指向的緩沖區中,返回值為實(shí)際讀取的字節數。函數write實(shí)現將把length個(gè)字節從buf指向的緩沖區中寫(xiě)到文件描述符fd所指向的文件中,返回值為實(shí)際寫(xiě)入的字節數。
以O_CREAT為標志的open實(shí)際上實(shí)現了文件創(chuàng )建的功能,因此,下面的函數等同creat()函數:
intopen(pathname,O_CREAT|O_WRONLY|O_TRUNC,mode);
定位
對于隨機文件,我們可以隨機的指定位置讀寫(xiě),使用如下函數進(jìn)行定位:
intlseek(intfd,offset_toffset,intwhence);
lseek()將文件讀寫(xiě)指針相對whence移動(dòng)offset個(gè)字節。操作成功時(shí),返回文件指針相對于文件頭的位置。參數whence可使用下述值:
SEEK_SET:相對文件開(kāi)頭
SEEK_CUR:相對文件讀寫(xiě)指針的當前位置
SEEK_END:相對文件末尾
offset可取負值,例如下述調用可將文件指針相對當前位置向前移動(dòng)5個(gè)字節:
lseek(fd,-5,SEEK_CUR);
由于lseek函數的返回值為文件指針相對于文件頭的位置,因此下列調用的返回值就是文件的長(cháng)度:
lseek(fd,0,SEEK_END);
關(guān)閉
只要調用close就可以了,其中fd是我們要關(guān)閉的文件描述符:
intclose(intfd);
下面我們來(lái)編寫(xiě)一個(gè)應用程序,在當前目錄下創(chuàng )建用戶(hù)可讀寫(xiě)文件example.txt,在其中寫(xiě)入HelloWorld,關(guān)閉文件,再次打開(kāi)它,讀取其中的內容并輸出在屏幕上:
#include
#include
#include
#include
#defineLENGTH100
main()
{
intfd,len;
charstr[LENGTH];
fd=open(hello.txt,O_CREAT|O_RDWR,S_IRUSR|S_IWUSR);/*創(chuàng )建并打開(kāi)文件*/
if(fd)
{
write(fd,Hello,SoftwareWeekly,strlen(Hello,softwareweekly));
/*寫(xiě)入Hello,softwareweekly字符串*/
close(fd);
}
fd=open(hello.txt,O_RDWR);
len=read(fd,str,LENGTH);/*讀取文件內容*/
str[len]='';
printf(%sn,str);
close(fd);
}
2.進(jìn)程控制/通信編程
進(jìn)程控制中主要涉及到進(jìn)程的創(chuàng )建、睡眠和退出等,在Linux中主要提供了fork、exec、clone的進(jìn)程創(chuàng )建方法,sleep的進(jìn)程睡眠和exit的進(jìn)程退出調用,另外Linux還提供了父進(jìn)程等待子進(jìn)程結束的系統調用wait。
fork
對于沒(méi)有接觸過(guò)Unix/Linux操作系統的人來(lái)說(shuō),fork是最難理解的概念之一,因為它執行一次卻返回兩個(gè)值,以前聞所未聞。先看下面的程序:
intmain()
{
inti;
if(fork()==0)
{
for(i=1;i3;i++)
printf(Thisischildprocessn);
}
else
{
for(i=1;i3;i++)
printf(Thisisparentprocessn);
}
}
執行結果為:
Thisischildprocess
Thisischildprocess
Thisisparentprocess
Thisisparentprocess
fork在英文中是分叉的意思,一個(gè)進(jìn)程在運行中,如果使用了fork,就產(chǎn)生了另一個(gè)進(jìn)程,于是進(jìn)程就分叉了。當前進(jìn)程為父進(jìn)程,通過(guò)fork()會(huì )產(chǎn)生一個(gè)子進(jìn)程。對于父進(jìn)程,fork函數返回子程序的進(jìn)程號而對于子程序,fork函數則返回零,這就是一個(gè)函數返回兩次的本質(zhì)。
exec
在Linux中可使用exec函數族,包含多個(gè)函數(execl、execlp、execle、execv、execve和execvp),被用于啟動(dòng)一個(gè)指定路徑和文件名的進(jìn)程。exec函數族的特點(diǎn)體現在:某進(jìn)程一旦調用了exec類(lèi)函數,正在執行的程序就被干掉了,系統把代碼段替換成新的程序(由exec類(lèi)函數執行)的代碼,并且原有的數據段和堆棧段也被廢棄,新的數據段與堆棧段被分配,但是進(jìn)程號卻被保留。也就是說(shuō),exec執行的結果為:系統認為正在執行的還是原先的進(jìn)程,但是進(jìn)程對應的程序被替換了。
fork函數可以創(chuàng )建一個(gè)子進(jìn)程而當前進(jìn)程不死,如果我們在fork的子進(jìn)程中調用exec函數族就可以實(shí)現既讓父進(jìn)程的代碼執行又啟動(dòng)一個(gè)新的指定進(jìn)程,這很好。fork和exec的搭配巧妙地解決了程序啟動(dòng)另一程序的執行但自己仍繼續運行的問(wèn)題,請看下面的例子:
charcommand[MAX_CMD_LEN];
voidmain()
{
intrtn;/*子進(jìn)程的返回數值*/
while(1)
{
/*從終端讀取要執行的命令*/
printf(>);
fgets(command,MAX_CMD_LEN,stdin);
command[strlen(command)-1]=0;
if(fork()==0)
{
/*子進(jìn)程執行此命令*/
execlp(command,command);
/*如果exec函數返回,表明沒(méi)有正常執行命令,打印錯誤信息*/
perror(command);
exit(errorno);
}
else
{
/*父進(jìn)程,等待子進(jìn)程結束,并打印子進(jìn)程的返回值*/
wait(rtn);
printf(childprocessreturn%dn,rtn);
}
}
}
這個(gè)函數實(shí)現了一個(gè)shell的功能,它讀取用戶(hù)輸入的進(jìn)程名和參數,并啟動(dòng)對應的進(jìn)程。
clone
clone是Linux2.0以后才具備的新功能,它較fork更強(可認為fork是clone要實(shí)現的一部分),可以使得創(chuàng )建的子進(jìn)程共享父進(jìn)程的資源,并且要使用此函數必須在編譯內核時(shí)設置clone_actually_works_ok選項。
clone函數的原型為:
intclone(int(*fn)(void*),void*child_stack,intflags,void*arg);
此函數返回創(chuàng )建進(jìn)程的PID,函數中的flags標志用于設置創(chuàng )建子進(jìn)程時(shí)的相關(guān)選項。
來(lái)看下面的例子:
intvariable,fd;
intdo_something(){
variable=42;
close(fd);
_exit(0);
}
intmain(intargc,char*argv[]){
void**child_stack;
chartempch;
variable=9;
fd=open(test.file,O_RDONLY);
child_stack=(void**)malloc(16384);
printf(Thevariablewas%dn,variable);
clone(do_something,child_stack,CLONE_VM|CLONE_FILES,NULL);
sleep(1);/*延時(shí)以便子進(jìn)程完成關(guān)閉文件操作、修改變量*/
printf(Thevariableisnow%dn,variable);
if(read(fd,tempch,1)1){
perror(FileReadError);
exit(1);
}
printf(Wecouldreadfromthefilen);
return0;
}
運行輸出:
Thevariableisnow42
FileReadError
程序的輸出結果告訴我們,子進(jìn)程將文件關(guān)閉并將變量修改(調用clone時(shí)用到的CLONE_VM、CLONE_FILES標志將使得變量和文件描述符表被共享),父進(jìn)程隨即就感覺(jué)到了,這就是clone的特點(diǎn)。
sleep
函數調用sleep可以用來(lái)使進(jìn)程掛起指定的秒數,該函數的原型為:
unsignedintsleep(unsignedintseconds);
該函數調用使得進(jìn)程掛起一個(gè)指定的時(shí)間,如果指定掛起的時(shí)間到了,該調用返回0;如果該函數調用被信號所打斷,則返回剩余掛起的時(shí)間數(指定的時(shí)間減去已經(jīng)掛起的時(shí)間)。
exit
系統調用exit的功能是終止本進(jìn)程,其函數原型為:
void_exit(intstatus);
_exit會(huì )立即終止發(fā)出調用的進(jìn)程,所有屬于該進(jìn)程的文件描述符都關(guān)閉。參數status作為退出的狀態(tài)值返回父進(jìn)程,在父進(jìn)程中通過(guò)系統調用wait可獲得此值。
wait
wait系統調用包括:
pid_twait(int*status);
pid_twaitpid(pid_tpid,int*status,intoptions);
wait的作用為發(fā)出調用的進(jìn)程只要有子進(jìn)程,就睡眠到它們中的一個(gè)終止為止;waitpid等待由參數pid指定的子進(jìn)程退出。
Linux的進(jìn)程間通信(IPC,InterProcessCommunication)通信方法有管道、消息隊列、共享內存、信號量、套接口等。套接字通信并不為L(cháng)inux所專(zhuān)有,在所有提供了TCP/IP協(xié)議棧的操作系統中幾乎都提供了socket,而所有這樣操作系統,對套接字的編程方法幾乎是完全一樣的。管道分為有名管道和無(wú)名管道,無(wú)名管道只能用于親屬進(jìn)程之間的通信,而有名管道則可用于無(wú)親屬關(guān)系的進(jìn)程之間;消息隊列用于運行于同一臺機器上的進(jìn)程間通信,與管道相似;共享內存通常由一個(gè)進(jìn)程創(chuàng )建,其余進(jìn)程對這塊內存區進(jìn)行讀寫(xiě);信號量是一個(gè)計數器,它用來(lái)記錄對某個(gè)資源(如共享內存)的存取狀況。
下面是一個(gè)使用信號量的例子,該程序創(chuàng )建一個(gè)特定的IPC結構的關(guān)鍵字和一個(gè)信號量,建立此信號量的索引,修改索引指向的信號量的值,最后清除信號量:
#include
#include
#include
#include
voidmain()
{
key_tunique_key;/*定義一個(gè)IPC關(guān)鍵字*/
intid;
structsembuflock_it;
unionsemunoptions;
inti;
unique_key=ftok(.,'a');/*生成關(guān)鍵字,字符'a'是一個(gè)隨機種子*/
/*創(chuàng )建一個(gè)新的信號量集合*/
id=semget(unique_key,1,IPC_CREAT|IPC_EXCL|0666);
printf(semaphoreid=%dn,id);
options.val=1;/*設置變量值*/
semctl(id,0,SETVAL,options);/*設置索引0的信號量*/
/*打印出信號量的值*/
i=semctl(id,0,GETVAL,0);
printf(valueofsemaphoreatindex0is%dn,i);
/*下面重新設置信號量*/
lock_it.sem_num=0;/*設置哪個(gè)信號量*/
lock_it.sem_op=-1;/*定義操作*/
lock_it.sem_flg=IPC_NOWAIT;/*操作方式*/
if(semop(id,lock_it,1)==-1)
{
printf(cannotlocksemaphore.n);
exit(1);
}
i=semctl(id,0,GETVAL,0);
printf(valueofsemaphoreatindex0is%dn,i);
/*清除信號量*/
semctl(id,0,IPC_RMID,0);
}
3.線(xiàn)程控制/通信編程
Linux本身只有進(jìn)程的概念,而其所謂的線(xiàn)程本質(zhì)上在內核里仍然是進(jìn)程。大家知道,進(jìn)程是資源分配的單位,同一進(jìn)程中的多個(gè)線(xiàn)程共享該進(jìn)程的資源(如作為共享內存的全局變量)。Linux中所謂的線(xiàn)程只是在被創(chuàng )建的時(shí)候克隆(clone)了父進(jìn)程的資源,因此,clone出來(lái)的進(jìn)程表現為線(xiàn)程。Linux中最流行的線(xiàn)程機制為L(cháng)inuxThreads,它實(shí)現了一種Posix1003.1cpthread標準接口。
線(xiàn)程之間的通信涉及同步和互斥,互斥體的用法為:
pthread_mutex_tmutex;
pthread_mutex_init(mutex,NULL);//按缺省的屬性初始化互斥體變量mutex
pthread_mutex_lock(mutex);//給互斥體變量加鎖
…//臨界資源
phtread_mutex_unlock(mutex);//給互斥體變量解鎖
同步就是線(xiàn)程等待某個(gè)事件的發(fā)生。只有當等待的事件發(fā)生線(xiàn)程才繼續執行,否則線(xiàn)程掛起并放棄處理器。當多個(gè)線(xiàn)程協(xié)作時(shí),相互作用的任務(wù)必須在一定的條件下同步。Linux下的C語(yǔ)言編程有多種線(xiàn)程同步機制,最典型的是條件變量(conditionvariable)。而在頭文件semaphore.h中定義的信號量則完成了互斥體和條件變量的封裝,按照多線(xiàn)程程序設計中訪(fǎng)問(wèn)控制機制,控制對資源的同步訪(fǎng)問(wèn),提供程序設計人員更方便的調用接口。下面的生產(chǎn)者/消費者問(wèn)題說(shuō)明了Linux線(xiàn)程的控制和通信:
#include
#include
#defineBUFFER_SIZE16
structprodcons
{
intbuffer[BUFFER_SIZE];
pthread_mutex_tlock;
intreadpos,writepos;
pthread_cond_tnotempty;
pthread_cond_tnotfull;
};
/*初始化緩沖區結構*/
voidinit(structprodcons*b)
{
pthread_mutex_init(b->lock,NULL);
pthread_cond_init(b->notempty,NULL);
pthread_cond_init(b->notfull,NULL);
b->readpos=0;
b->writepos=0;
}
/*將產(chǎn)品放入緩沖區,這里是存入一個(gè)整數*/
voidput(structprodcons*b,intdata)
{
pthread_mutex_lock(b->lock);
/*等待緩沖區未滿(mǎn)*/
if((b->writepos+1)%BUFFER_SIZE==b->readpos)
{
pthread_cond_wait(b->notfull,b->lock);
}
/*寫(xiě)數據,并移動(dòng)指針*/
b->buffer[b->writepos]=data;
b->writepos++;
if(b->writepos>=BUFFER_SIZE)
b->writepos=0;
/*設置緩沖區非空的條件變量*/
pthread_cond_signal(b->notempty);
pthread_mutex_unlock(b->lock);
}
/*從緩沖區中取出整數*/
intget(structprodcons*b)
{
intdata;
pthread_mutex_lock(b->lock);
/*等待緩沖區非空*/
if(b->writepos==b->readpos)
{
pthread_cond_wait(b->notempty,b->lock);
}
/*讀數據,移動(dòng)讀指針*/
data=b->buffer[b->readpos];
b->readpos++;
if(b->readpos>=BUFFER_SIZE)
b->readpos=0;
/*設置緩沖區未滿(mǎn)的條件變量*/
pthread_cond_signal(b->notfull);
pthread_mutex_unlock(b->lock);
returndata;
}
/*測試:生產(chǎn)者線(xiàn)程將1到10000的整數送入緩沖區,消費者線(xiàn)
程從緩沖區中獲取整數,兩者都打印信息*/
#defineOVER(-1)
structprodconsbuffer;
void*producer(void*data)
{
intn;
for(n=0;n10000;n++)
{
printf(%d--->n,n);
put(buffer,n);
}put(buffer,OVER);
returnNULL;
}
void*consumer(void*data)
{
intd;
while(1)
{
d=get(buffer);
if(d==OVER)
break;
printf(--->%dn,d);
}
returnNULL;
}
intmain(void)
{
pthread_tth_a,th_b;
void*retval;
init(buffer);
/*創(chuàng )建生產(chǎn)者和消費者線(xiàn)程*/
pthread_create(th_a,NULL,producer,0);
pthread_create(th_b,NULL,consumer,0);
/*等待兩個(gè)線(xiàn)程結束*/
pthread_join(th_a,retval);
pthread_join(th_b,retval);
return0;
}
4.小結
本章主要給出了Linux平臺下文件、進(jìn)程控制與通信、線(xiàn)程控制與通信的編程實(shí)例。至此,一個(gè)完整的,涉及硬件原理、Bootloader、操作系統及文件系統移植、驅動(dòng)程序開(kāi)發(fā)及應用程序編寫(xiě)的嵌入式Linux系列講解就全部結束了。
評論