新聞中心

EEPW首頁 > 嵌入式系統(tǒng) > 設(shè)計(jì)應(yīng)用 > 基于ARM的嵌入式Linux移植真實(shí)體驗(yàn)(5)――應(yīng)用實(shí)例

基于ARM的嵌入式Linux移植真實(shí)體驗(yàn)(5)――應(yīng)用實(shí)例

作者: 時(shí)間:2016-11-09 來源:網(wǎng)絡(luò) 收藏
應(yīng)用實(shí)例的編寫實(shí)際上已經(jīng)不屬于Linux操作系統(tǒng)移植的范疇,但是為了保證本系列文章的完整性,這里提供一系列針對(duì)嵌入式Linux開發(fā)應(yīng)用程序的實(shí)例。
編寫Linux應(yīng)用程序要用到如下工具:
(1)編譯器:GCC
GCC是Linux平臺(tái)下最重要的開發(fā)工具,它是GNU的C和C++編譯器,其基本用法為:gcc [options] [filenames]。
我們應(yīng)該使用arm-linux-gcc。
(2)調(diào)試器:GDB
gdb是一個(gè)用來調(diào)試C和C++程序的強(qiáng)力調(diào)試器,我們能通過它進(jìn)行一系列調(diào)試工作,包括設(shè)置斷點(diǎn)、觀查變量、單步等。
我們應(yīng)該使用arm-linux-gdb。
(3)Make
GNU Make的主要工作是讀進(jìn)一個(gè)文本文件,稱為makefile。這個(gè)文件記錄了哪些文件由哪些文件產(chǎn)生,用什么命令來產(chǎn)生。Make依靠此makefile中的信息檢查磁盤上的文件,如果目的文件的創(chuàng)建或修改時(shí)間比它的一個(gè)依靠文件舊的話,make就執(zhí)行相應(yīng)的命令,以便更新目的文件。
Makefile中的編譯規(guī)則要相應(yīng)地使用arm-linux-版本。
(4)代碼編輯
可以使用傳統(tǒng)的vi編輯器,但最好采用emacs軟件,它具備語法高亮、版本控制等附帶功能。
在宿主機(jī)上用上述工具完成應(yīng)用程序的開發(fā)后,可以通過如下途徑將程序下載到目標(biāo)板上運(yùn)行:
(1)通過串口通信協(xié)議rz將程序下載到目標(biāo)板的文件系統(tǒng)中(感謝Linux提供了rz這樣的一個(gè)命令);
(2)通過ftp通信協(xié)議從宿主機(jī)上的ftp目錄里將程序下載到目標(biāo)板的文件系統(tǒng)中;
(3)將程序拷入U(xiǎn)盤,在目標(biāo)機(jī)上mount U盤,運(yùn)行U盤中的程序;
(4)如果目標(biāo)機(jī)Linux使用NFS文件系統(tǒng),則可以直接將程序拷入到宿主機(jī)相應(yīng)的目錄內(nèi),在目標(biāo)機(jī)Linux中可以直接使用。
1.文件編程
Linux的文件操作API涉及到創(chuàng)建、打開、讀寫和關(guān)閉文件。
創(chuàng)建
int creat(const char *filename, mode_t mode);
參數(shù)mode指定新建文件的存取權(quán)限,它同umask一起決定文件的最終權(quán)限(mode&umask),其中umask代表了文件在創(chuàng)建時(shí)需要去掉的一些存取權(quán)限。umask可通過系統(tǒng)調(diào)用umask()來改變:
int umask(int newmask);
該調(diào)用將umask設(shè)置為newmask,然后返回舊的umask,它只影響讀、寫和執(zhí)行權(quán)限。
打開
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
讀寫
在文件打開以后,我們才可對(duì)文件進(jìn)行讀寫了,Linux中提供文件讀寫的系統(tǒng)調(diào)用是read、write函數(shù):
int read(int fd, const void *buf, size_t length);
int write(int fd, const void *buf, size_t length);
其中參數(shù)buf為指向緩沖區(qū)的指針,length為緩沖區(qū)的大?。ㄒ宰止?jié)為單位)。函數(shù)read()實(shí)現(xiàn)從文件描述符fd所指定的文件中讀取length個(gè)字節(jié)到buf所指向的緩沖區(qū)中,返回值為實(shí)際讀取的字節(jié)數(shù)。函數(shù)write實(shí)現(xiàn)將把length個(gè)字節(jié)從buf指向的緩沖區(qū)中寫到文件描述符fd所指向的文件中,返回值為實(shí)際寫入的字節(jié)數(shù)。
以O(shè)_CREAT為標(biāo)志的open實(shí)際上實(shí)現(xiàn)了文件創(chuàng)建的功能,因此,下面的函數(shù)等同creat()函數(shù):
int open(pathname, O_CREAT | O_WRONLY | O_TRUNC, mode);
定位
對(duì)于隨機(jī)文件,我們可以隨機(jī)的指定位置讀寫,使用如下函數(shù)進(jìn)行定位:
int lseek(int fd, offset_t offset, int whence);
lseek()將文件讀寫指針相對(duì)whence移動(dòng)offset個(gè)字節(jié)。操作成功時(shí),返回文件指針相對(duì)于文件頭的位置。參數(shù)whence可使用下述值:
SEEK_SET:相對(duì)文件開頭
SEEK_CUR:相對(duì)文件讀寫指針的當(dāng)前位置
SEEK_END:相對(duì)文件末尾
offset可取負(fù)值,例如下述調(diào)用可將文件指針相對(duì)當(dāng)前位置向前移動(dòng)5個(gè)字節(jié):
lseek(fd, -5, SEEK_CUR);
由于lseek函數(shù)的返回值為文件指針相對(duì)于文件頭的位置,因此下列調(diào)用的返回值就是文件的長(zhǎng)度:
lseek(fd, 0, SEEK_END);
關(guān)閉
只要調(diào)用close就可以了,其中fd是我們要關(guān)閉的文件描述符:
int close(int fd);
下面我們來編寫一個(gè)應(yīng)用程序,在當(dāng)前目錄下創(chuàng)建用戶可讀寫文件“example.txt”,在其中寫入“Hello World”,關(guān)閉文件,再次打開它,讀取其中的內(nèi)容并輸出在屏幕上:
#include
#include
#include
#include
#define LENGTH 100
main()
{
int fd, len;
char str[LENGTH];
fd = open("hello.txt", O_CREAT | O_RDWR, S_IRUSR | S_IWUSR); /* 創(chuàng)建并打開文件 */
if (fd)
{
write(fd, "Hello, Software Weekly", strlen("Hello, software weekly")); /* 寫入Hello, software weekly字符串 */
close(fd);
}
fd = open("hello.txt", O_RDWR);
len = read(fd, str, LENGTH); /* 讀取文件內(nèi)容 */
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)程退出調(diào)用,另外Linux還提供了父進(jìn)程等待子進(jìn)程結(jié)束的系統(tǒng)調(diào)用wait。
fork
對(duì)于沒有接觸過Unix/Linux操作系統(tǒng)的人來說,fork是最難理解的概念之一,因?yàn)樗鼒?zhí)行一次卻返回兩個(gè)值,以前“聞所未聞”。先看下面的程序:
int main()
{
int i;
if (fork() == 0)
{
for (i = 1; i < 3; i++)
printf("This is child processn");
}
else
{
for (i = 1; i < 3; i++)
printf("This is parent processn");
}
}
執(zhí)行結(jié)果為:
This is child process
This is child process
This is parent process
This is parent process
fork在英文中是“分叉”的意思,一個(gè)進(jìn)程在運(yùn)行中,如果使用了fork,就產(chǎn)生了另一個(gè)進(jìn)程,于是進(jìn)程就“分叉”了。當(dāng)前進(jìn)程為父進(jìn)程,通過fork()會(huì)產(chǎn)生一個(gè)子進(jìn)程。對(duì)于父進(jìn)程,fork函數(shù)返回子程序的進(jìn)程號(hào)而對(duì)于子程序,fork函數(shù)則返回零,這就是一個(gè)函數(shù)返回兩次的本質(zhì)。
exec
在Linux中可使用exec函數(shù)族,包含多個(gè)函數(shù)(execl、execlp、execle、execv、execve和execvp),被用于啟動(dòng)一個(gè)指定路徑和文件名的進(jìn)程。exec函數(shù)族的特點(diǎn)體現(xiàn)在:某進(jìn)程一旦調(diào)用了exec類函數(shù),正在執(zhí)行的程序就被干掉了,系統(tǒng)把代碼段替換成新的程序(由exec類函數(shù)執(zhí)行)的代碼,并且原有的數(shù)據(jù)段和堆棧段也被廢棄,新的數(shù)據(jù)段與堆棧段被分配,但是進(jìn)程號(hào)卻被保留。也就是說,exec執(zhí)行的結(jié)果為:系統(tǒng)認(rèn)為正在執(zhí)行的還是原先的進(jìn)程,但是進(jìn)程對(duì)應(yīng)的程序被替換了。
fork函數(shù)可以創(chuàng)建一個(gè)子進(jìn)程而當(dāng)前進(jìn)程不死,如果我們?cè)趂ork的子進(jìn)程中調(diào)用exec函數(shù)族就可以實(shí)現(xiàn)既讓父進(jìn)程的代碼執(zhí)行又啟動(dòng)一個(gè)新的指定進(jìn)程,這很好。fork和exec的搭配巧妙地解決了程序啟動(dòng)另一程序的執(zhí)行但自己仍繼續(xù)運(yùn)行的問題,請(qǐng)看下面的例子:
char command[MAX_CMD_LEN];
void main()
{
int rtn; /* 子進(jìn)程的返回?cái)?shù)值 */
while (1)
{
/* 從終端讀取要執(zhí)行的命令 */
printf(">");
fgets(command, MAX_CMD_LEN, stdin);
command[strlen(command) - 1] = 0;
if (fork() == 0)
{
/* 子進(jìn)程執(zhí)行此命令 */
execlp(command, command);
/* 如果exec函數(shù)返回,表明沒有正常執(zhí)行命令,打印錯(cuò)誤信息*/
perror(command);
exit(errorno);
}
else
{
/* 父進(jìn)程,等待子進(jìn)程結(jié)束,并打印子進(jìn)程的返回值 */
wait(&rtn);
printf(" child process return %dn", rtn);
}
}
}
這個(gè)函數(shù)實(shí)現(xiàn)了一個(gè)shell的功能,它讀取用戶輸入的進(jìn)程名和參數(shù),并啟動(dòng)對(duì)應(yīng)的進(jìn)程。
clone
clone是Linux2.0以后才具備的新功能,它較fork更強(qiáng)(可認(rèn)為fork是clone要實(shí)現(xiàn)的一部分),可以使得創(chuàng)建的子進(jìn)程共享父進(jìn)程的資源,并且要使用此函數(shù)必須在編譯內(nèi)核時(shí)設(shè)置clone_actually_works_ok選項(xiàng)。
clone函數(shù)的原型為:
int clone(int (*fn)(void *), void *child_stack, int flags, void *arg);
此函數(shù)返回創(chuàng)建進(jìn)程的PID,函數(shù)中的flags標(biāo)志用于設(shè)置創(chuàng)建子進(jìn)程時(shí)的相關(guān)選項(xiàng)。
來看下面的例子:
int variable, fd;
int do_something() {
variable = 42;
close(fd);
_exit(0);
}
int main(int argc, char *argv[]) {
void **child_stack;
char tempch;
variable = 9;
fd = open("test.file", O_RDONLY);
child_stack = (void **) malloc(16384);
printf("The variable was %dn", variable);
clone(do_something, child_stack, CLONE_VM|CLONE_FILES, NULL);
sleep(1); /* 延時(shí)以便子進(jìn)程完成關(guān)閉文件操作、修改變量 */
printf("The variable is now %dn", variable);
if (read(fd, &tempch, 1) < 1) {
perror("File Read Error");
exit(1);
}
printf("We could read from the filen");
return 0;
}
運(yùn)行輸出:
The variable is now 42
File Read Error
程序的輸出結(jié)果告訴我們,子進(jìn)程將文件關(guān)閉并將變量修改(調(diào)用clone時(shí)用到的CLONE_VM、CLONE_FILES標(biāo)志將使得變量和文件描述符表被共享),父進(jìn)程隨即就感覺到了,這就是clone的特點(diǎn)。
sleep
函數(shù)調(diào)用sleep可以用來使進(jìn)程掛起指定的秒數(shù),該函數(shù)的原型為:  
unsigned int sleep(unsigned int seconds);
該函數(shù)調(diào)用使得進(jìn)程掛起一個(gè)指定的時(shí)間,如果指定掛起的時(shí)間到了,該調(diào)用返回0;如果該函數(shù)調(diào)用被信號(hào)所打斷,則返回剩余掛起的時(shí)間數(shù)(指定的時(shí)間減去已經(jīng)掛起的時(shí)間)。
exit
系統(tǒng)調(diào)用exit的功能是終止本進(jìn)程,其函數(shù)原型為:
void _exit(int status);
_exit會(huì)立即終止發(fā)出調(diào)用的進(jìn)程,所有屬于該進(jìn)程的文件描述符都關(guān)閉。參數(shù)status作為退出的狀態(tài)值返回父進(jìn)程,在父進(jìn)程中通過系統(tǒng)調(diào)用wait可獲得此值。
wait
wait系統(tǒng)調(diào)用包括:
pid_t wait(int *status);
pid_t waitpid(pid_t pid, int *status, int options);
wait的作用為發(fā)出調(diào)用的進(jìn)程只要有子進(jìn)程,就睡眠到它們中的一個(gè)終止為止; waitpid等待由參數(shù)pid指定的子進(jìn)程退出。
Linux的進(jìn)程間通信(IPC,InterProcess Communication)通信方法有管道、消息隊(duì)列、共享內(nèi)存、信號(hào)量、套接口等。套接字通信并不為L(zhǎng)inux所專有,在所有提供了TCP/IP協(xié)議棧的操作系統(tǒng)中幾乎都提供了socket,而所有這樣操作系統(tǒng),對(duì)套接字的編程方法幾乎是完全一樣的。管道分為有名管道和無名管道,無名管道只能用于親屬進(jìn)程之間的通信,而有名管道則可用于無親屬關(guān)系的進(jìn)程之間;消息隊(duì)列用于運(yùn)行于同一臺(tái)機(jī)器上的進(jìn)程間通信,與管道相似;共享內(nèi)存通常由一個(gè)進(jìn)程創(chuàng)建,其余進(jìn)程對(duì)這塊內(nèi)存區(qū)進(jìn)行讀寫;信號(hào)量是一個(gè)計(jì)數(shù)器,它用來記錄對(duì)某個(gè)資源(如共享內(nèi)存)的存取狀況。
下面是一個(gè)使用信號(hào)量的例子,該程序創(chuàng)建一個(gè)特定的IPC結(jié)構(gòu)的關(guān)鍵字和一個(gè)信號(hào)量,建立此信號(hào)量的索引,修改索引指向的信號(hào)量的值,最后清除信號(hào)量:
#include
#include
#include
#include
void main()
{
key_t unique_key; /* 定義一個(gè)IPC關(guān)鍵字*/
int id;
struct sembuf lock_it;
union semun options;
int i;
unique_key = ftok(".", a); /* 生成關(guān)鍵字,字符a是一個(gè)隨機(jī)種子*/
/* 創(chuàng)建一個(gè)新的信號(hào)量集合*/
id = semget(unique_key, 1, IPC_CREAT | IPC_EXCL | 0666);
printf("semaphore id=%dn", id);
options.val = 1; /*設(shè)置變量值*/
semctl(id, 0, SETVAL, options); /*設(shè)置索引0的信號(hào)量*/
/*打印出信號(hào)量的值*/
i = semctl(id, 0, GETVAL, 0);
printf("value of semaphore at index 0 is %dn", i);
/*下面重新設(shè)置信號(hào)量*/
lock_it.sem_num = 0; /*設(shè)置哪個(gè)信號(hào)量*/
lock_it.sem_op = - 1; /*定義操作*/
lock_it.sem_flg = IPC_NOWAIT; /*操作方式*/
if (semop(id, &lock_it, 1) == - 1)
{
printf("can not lock semaphore.n");
exit(1);
}
i = semctl(id, 0, GETVAL, 0);
printf("value of semaphore at index 0 is %dn", i);
/*清除信號(hào)量*/
semctl(id, 0, IPC_RMID, 0);
}
3.線程控制/通信編程
Linux本身只有進(jìn)程的概念,而其所謂的“線程”本質(zhì)上在內(nèi)核里仍然是進(jìn)程。大家知道,進(jìn)程是資源分配的單位,同一進(jìn)程中的多個(gè)線程共享該進(jìn)程的資源(如作為共享內(nèi)存的全局變量)。Linux中所謂的“線程”只是在被創(chuàng)建的時(shí)候“克隆”(clone)了父進(jìn)程的資源,因此,clone出來的進(jìn)程表現(xiàn)為“線程”。Linux中最流行的線程機(jī)制為L(zhǎng)inuxThreads,它實(shí)現(xiàn)了一種Posix 1003.1c “pthread”標(biāo)準(zhǔn)接口。
線程之間的通信涉及同步和互斥,互斥體的用法為:
pthread_mutex_t mutex;
pthread_mutex_init(&mutex, NULL); //按缺省的屬性初始化互斥體變量mutex
pthread_mutex_lock(&mutex); // 給互斥體變量加鎖
… //臨界資源
phtread_mutex_unlock(&mutex); // 給互斥體變量解鎖
同步就是線程等待某個(gè)事件的發(fā)生。只有當(dāng)?shù)却氖录l(fā)生線程才繼續(xù)執(zhí)行,否則線程掛起并放棄處理器。當(dāng)多個(gè)線程協(xié)作時(shí),相互作用的任務(wù)必須在一定的條件下同步。Linux下的C語言編程有多種線程同步機(jī)制,最典型的是條件變量(condition variable)。而在頭文件semaphore.h 中定義的信號(hào)量則完成了互斥體和條件變量的封裝,按照多線程程序設(shè)計(jì)中訪問控制機(jī)制,控制對(duì)資源的同步訪問,提供程序設(shè)計(jì)人員更方便的調(diào)用接口。下面的生產(chǎn)者/消費(fèi)者問題說明了Linux線程的控制和通信:
#include
#include
#define BUFFER_SIZE 16
struct prodcons
{
int buffer[BUFFER_SIZE];
pthread_mutex_t lock;
int readpos, writepos;
pthread_cond_t notempty;
pthread_cond_t notfull;
};
/* 初始化緩沖區(qū)結(jié)構(gòu) */
void init(struct prodcons *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)品放入緩沖區(qū),這里是存入一個(gè)整數(shù)*/
void put(struct prodcons *b, int data)
{
pthread_mutex_lock(&b->lock);
/* 等待緩沖區(qū)未滿*/
if ((b->writepos + 1) % BUFFER_SIZE == b->readpos)
{
pthread_cond_wait(&b->notfull, &b->lock);
}
/* 寫數(shù)據(jù),并移動(dòng)指針 */
b->buffer[b->writepos] = data;
b->writepos++;
if (b->writepos > = BUFFER_SIZE)
b->writepos = 0;
/* 設(shè)置緩沖區(qū)非空的條件變量*/
pthread_cond_signal(&b->notempty);
pthread_mutex_unlock(&b->lock);
}
/* 從緩沖區(qū)中取出整數(shù)*/
int get(struct prodcons *b)
{
int data;
pthread_mutex_lock(&b->lock);
/* 等待緩沖區(qū)非空*/
if (b->writepos == b->readpos)
{
pthread_cond_wait(&b->notempty, &b->lock);
}
/* 讀數(shù)據(jù),移動(dòng)讀指針*/
data = b->buffer[b->readpos];
b->readpos++;
if (b->readpos > = BUFFER_SIZE)
b->readpos = 0;
/* 設(shè)置緩沖區(qū)未滿的條件變量*/
pthread_cond_signal(&b->notfull);
pthread_mutex_unlock(&b->lock);
return data;
}
/* 測(cè)試:生產(chǎn)者線程將1 到10000 的整數(shù)送入緩沖區(qū),消費(fèi)者線
程從緩沖區(qū)中獲取整數(shù),兩者都打印信息*/
#define OVER ( - 1)
struct prodcons buffer;
void *producer(void *data)
{
int n;
for (n = 0; n < 10000; n++)
{
printf("%d --->n", n);
put(&buffer, n);
} put(&buffer, OVER);
return NULL;
}
void *consumer(void *data)
{
int d;
while (1)
{
d = get(&buffer);
if (d == OVER)
break;
printf("--->%d n", d);
}
return NULL;
}
int main(void)
{
pthread_t th_a, th_b;
void *retval;
init(&buffer);
/* 創(chuàng)建生產(chǎn)者和消費(fèi)者線程*/
pthread_create(&th_a, NULL, producer, 0);
pthread_create(&th_b, NULL, consumer, 0);
/* 等待兩個(gè)線程結(jié)束*/
pthread_join(th_a, &retval);
pthread_join(th_b, &retval);
return 0;
}
4.小結(jié)
本章主要給出了Linux平臺(tái)下文件、進(jìn)程控制與通信、線程控制與通信的編程實(shí)例。至此,一個(gè)完整的,涉及硬件原理、Bootloader、操作系統(tǒng)及文件系統(tǒng)移植、驅(qū)動(dòng)程序開發(fā)及應(yīng)用程序編寫的嵌入式Linux系列講解就全部結(jié)束了。


評(píng)論


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

關(guān)閉