宋寶華談 ARM 的嵌入式 Linux 移植體驗之五:應用執行個體

來源:互聯網
上載者:User

宋寶華談 ARM 的嵌入式 Linux 移植體驗之五:應用執行個體

        應用執行個體的編寫實際上已經不屬於 Linux 作業系統移植的範疇,但是為了保證本系列文章的完整性,這裡提供一系列針對嵌入式 Linux 開發應用程式的執行個體。
        編寫 Linux 應用程式要用到如下工具:
        (1)編譯器:GCC
        GCC 是 Linux 平台下最重要的開發工具,它是 GNU 的 C 和 C++ 編譯器,其基本用法為:gcc [options] [filenames]。
        我們應該使用 arm-linux-gcc。
        (2)調試器:GDB
        gdb 是一個用來調試 C 和 C++ 程式的強力調試器,我們能通過它進行一系列調試工作,包括設定斷點、觀查變數、單步等。
        我們應該使用 arm-linux-gdb。
        (3)Make
        GNU Make 的主要工作是讀進一個文字檔,稱為 makefile。這個檔案記錄了哪些檔案由哪些檔案產生,用什麼命令來產生。Make 依靠此 makefile 中的資訊檢查磁碟上的檔案,如果目的檔案的建立或修改時間比它的一個依靠檔案舊的話,make 就執行相應的命令,以便更新目的檔案。
        Makefile 中的編譯規則要相應地使用 arm-linux- 版本。
        (4)代碼編輯
        可以使用傳統的 vi 編輯器,但最好採用 emacs 軟體,它具備文法高亮、版本控制等附帶功能。
        在宿主機上用上述工具完成應用程式的開發後,可以通過如下途徑將程式下載到目標板上運行:
        (1)通過串口通訊協定 rz 將程式下載到目標板的檔案系統中(感謝 Linux 提供了 rz 這樣的一個命令);
        (2)通過 ftp 通訊協定從宿主機上的 ftp 目錄裡將程式下載到目標板的檔案系統中;
        (3)將程式拷入 USB 磁碟機,在目標機上 mount USB 磁碟機,運行 USB 磁碟機中的程式;
        (4)如果目標機 Linux 使用 NFS 檔案系統,則可以直接將程式拷入到宿主機相應的目錄內,在目標機 Linux 中可以直接使用。
        1. 檔案編程
        Linux 的檔案操作 API 涉及到建立、開啟、讀寫和關閉檔案。
        建立

int creat(const char *filename, mode_t mode); 
        參數 mode 指定建立檔案的存取許可權,它同 umask 一起決定檔案的最終許可權(mode&umask),其中 umask 代表了檔案在建立時需要去掉的一些存取許可權。umask 可通過系統調用 umask() 來改變:

int umask(int newmask); 
        該調用將 umask 設定為 newmask,然後返回舊的 umask,它隻影響讀、寫和執行許可權。
        開啟

int open(const char *pathname, int flags);<br />int open(const char *pathname, int flags, mode_t mode); 
        讀寫
        在檔案開啟以後,我們才可對檔案進行讀寫了,Linux 中提供檔案讀寫的系統調用是 read、write 函數:

int read(int fd, const void *buf, size_t length);<br />int write(int fd, const void *buf, size_t length);  
        其中參數 buf 為指向緩衝區的指標,length 為緩衝區的大小(以位元組為單位)。函數 read() 實現從檔案描述符 fd 所指定的檔案中讀取 length 個位元組到 buf 所指向的緩衝區中,傳回值為實際讀取的位元組數。函數 write 實現將把 length 個位元組從 buf 指向的緩衝區中寫到檔案描述符 fd 所指向的檔案中,傳回值為實際寫入的位元組數。
        以 O_CREAT 為標誌的 open 實際上實現了檔案建立的功能,因此,下面的函數等同 creat() 函數:

int open(pathname, O_CREAT | O_WRONLY | O_TRUNC, mode);  
        定位
        對於隨機檔案,我們可以隨機的指定位置讀寫,使用如下函數進行定位:

int lseek(int fd, offset_t offset, int whence);  
        lseek() 將檔案讀寫指標相對 whence 移動 offset 個位元組。操作成功時,返迴文件指標相對於檔案頭的位置。參數 whence 可使用下述值:
        SEEK_SET:相對檔案開頭
        SEEK_CUR:相對檔案讀寫指標的當前位置
        SEEK_END:相對檔案末尾
        offset 可取負值,例如下述調用可將檔案指標相對當前位置向前移動 5 個位元組:

lseek(fd, -5, SEEK_CUR); 
        由於 lseek 函數的傳回值為檔案指標相對於檔案頭的位置,因此下列調用的傳回值就是檔案的長度:

lseek(fd, 0, SEEK_END);  
        關閉
        只要調用 close 就可以了,其中 fd 是我們要關閉的檔案描述符:

int close(int fd); 
        下面我們來編寫一個應用程式,在目前的目錄下建立使用者可讀寫檔案"example.txt",在其中寫入"Hello World",關閉檔案,再次開啟它,讀取其中的內容並輸出在螢幕上:

#include <sys/types.h><br />#include <sys/stat.h><br />#include <fcntl.h><br />#include <stdio.h><br />#define LENGTH 100<br />main()<br />{<br /> int fd, len;<br /> char str[LENGTH];<br /> fd = open("hello.txt", O_CREAT | O_RDWR, S_IRUSR | S_IWUSR); /* 建立並開啟檔案 */<br /> if (fd)<br /> {<br />  write(fd, "Hello, Software Weekly", strlen("Hello, software weekly"));<br />  /* 寫入Hello, software weekly字串 */<br />  close(fd);<br /> }</p><p> fd = open("hello.txt", O_RDWR);<br /> len = read(fd, str, LENGTH); /* 讀取檔案內容 */<br /> str[len] = '/0';<br /> printf("%s/n", str);<br /> close(fd);<br />} 
        2. 進程式控制制/通訊編程
        進程式控制制中主要涉及到進程的建立、睡眠和退出等,在 Linux 中主要提供了 fork、exec、clone 的進程建立方法,sleep 的進程睡眠和 exit 的進程退出調用,另外 Linux 還提供了父進程等待子進程結束的系統調用 wait。
        fork
        對於沒有接觸過 Unix/Linux 作業系統的人來說,fork 是最難理解的概念之一,因為它執行一次卻返回兩個值,以前"聞所未聞"。先看下面的程式:

int main()<br />{<br /> int i;<br /> if (fork() == 0)<br /> {<br />  for (i = 1; i < 3; i++)<br />   printf("This is child process/n");<br /> }<br /> else<br /> {<br />  for (i = 1; i < 3; i++)<br />   printf("This is parent process/n");<br /> }<br />} 
        執行結果為:

This is child process<br />This is child process<br />This is parent process<br />This is parent process 
        fork 在英文中是"分叉"的意思,一個進程在運行中,如果使用了 fork,就產生了另一個進程,於是進程就"分叉"了。當前進程為父進程,通過 fork() 會產生一個子進程。對於父進程,fork 函數返回子程式的進程號而對於子程式,fork 函數則返回零,這就是一個函數返回兩次的本質。
        exec
        在 Linux 中可使用 exec 函數族,包含多個函數(execl、execlp、execle、execv、execve 和 execvp),被用於啟動一個指定路徑和檔案名稱的進程。exec 函數族的特點體現在:某進程一旦調用了 exec 類函數,正在執行的程式就被幹掉了,系統把程式碼片段替換成新的程式(由 exec 類函數執行)的代碼,並且原有的資料區段和堆棧段也被廢棄,新的資料區段與堆棧段被分配,但是進程號卻被保留。也就是說,exec 執行的結果為:系統認為正在執行的還是原先的進程,但是進程對應的程式被替換了。
        fork 函數可以建立一個子進程而當前進程不死,如果我們在 fork 的子進程中調用 exec 函數族就可以實現既讓父進程的代碼執行又啟動一個新的指定進程,這很好。fork 和 exec 的搭配巧妙地解決了程式啟動另一程式的執行但自己仍繼續啟動並執行問題,請看下面的例子:

char command[MAX_CMD_LEN];<br />void main()<br />{<br /> int rtn; /* 子進程的返回數值 */<br /> while (1)<br /> {<br />  /* 從終端讀取要執行的命令 */<br />  printf(">");<br />  fgets(command, MAX_CMD_LEN, stdin);<br />  command[strlen(command) - 1] = 0;<br />  if (fork() == 0)<br />  {<br />   /* 子進程執行此命令 */<br />   execlp(command, command);<br />   /* 如果exec函數返回,表明沒有正常執行命令,列印錯誤資訊*/<br />   perror(command);<br />   exit(errorno);<br />  }<br />  else<br />  {<br />   /* 父進程,等待子進程結束,並列印子進程的傳回值 */<br />   wait(&rtn);<br />   printf(" child process return %d/n", rtn);<br />  }<br /> }<br />} 
        這個函數實現了一個 shell 的功能,它讀取使用者輸入的進程名和參數,並啟動對應的進程。
        clone
        clone 是 Linux2.0 以後才具備的新功能,它較 fork 更強(可認為 fork 是 clone 要實現的一部分),可以使得建立的子進程共用父進程的資源,並且要使用此函數必須在編譯核心時設定 clone_actually_works_ok 選項。
        clone 函數的原型為:

int clone(int (*fn)(void *), void *child_stack, int flags, void *arg); 
        此函數返回建立進程的 PID,函數中的 flags 標誌用於設定建立子進程時的相關選項。
        來看下面的例子:

int variable, fd;</p><p>int do_something() {<br /> variable = 42;<br /> close(fd);<br /> _exit(0);<br />}</p><p>int main(int argc, char *argv[]) {<br /> void **child_stack;<br /> char tempch;</p><p> variable = 9;<br /> fd = open("test.file", O_RDONLY);<br /> child_stack = (void **) malloc(16384);<br /> printf("The variable was %d/n", variable);</p><p> clone(do_something, child_stack, CLONE_VM|CLONE_FILES, NULL);<br /> sleep(1); /* 延時以便子進程完成關閉檔案操作、修改變數 */</p><p> printf("The variable is now %d/n", variable);<br /> if (read(fd, &tempch, 1) < 1) {<br />  perror("File Read Error");<br />  exit(1);<br /> }<br /> printf("We could read from the file/n");<br /> return 0;<br />} 
        運行輸出:

The variable is now 42<br />File Read Error 
        程式的輸出結果告訴我們,子進程將檔案關閉並將變數修改(調用 clone 時用到的 CLONE_VM、CLONE_FILES 標誌將使得變數和檔案描述符表被共用),父進程隨即就感覺到了,這就是 clone 的特點。
        sleep
        函數調用 sleep 可以用來使進程掛起指定的秒數,該函數的原型為: 

unsigned int sleep(unsigned int seconds); 
        該函數調用使得進程掛起一個指定的時間,如果指定掛起的時間到了,該調用返回 0;如果該函數調用被訊號所打斷,則返回剩餘掛起的時間數(指定的時間減去已經掛起的時間)。
        exit
        系統調用 exit 的功能是終止本進程,其函數原型為:

void _exit(int status); 
        _exit 會立即終止發出調用的進程,所有屬於該進程的檔案描述符都關閉。參數 status 作為退出的狀態值返回父進程,在父進程中通過系統調用 wait 可獲得此值。
        wait
        wait 系統調用包括:

pid_t wait(int *status);<br />pid_t waitpid(pid_t pid, int *status, int options); 
        wait 的作用為發出調用的進程只要有子進程,就睡眠到它們中的一個終止為止; waitpid 等待由參數 pid 指定的子進程退出。
        Linux 的處理序間通訊(IPC,InterProcess Communication)通訊方法有管道、訊息佇列、共用記憶體、訊號量、套介面等。通訊端通訊並不為 Linux 所專有,在所有提供了 TCP/IP 協議棧的作業系統中幾乎都提供了 socket,而所有這樣作業系統,對通訊端的編程方法幾乎是完全一樣的。管道分為有名管道和無名管道,無名管道只能用於親屬進程之間的通訊,而有名管道則可用於無親屬關係的進程之間;訊息佇列用於運行於同一台機器上的處理序間通訊,與管道相似;共用記憶體通常由一個進程建立,其餘進程對這塊記憶體區進行讀寫;訊號量是一個計數器,它用來記錄對某個資源(如共用記憶體)的存取狀況。
        下面是一個使用訊號量的例子,該程式建立一個特定的 IPC 結構的關鍵字和一個訊號量,建立此訊號量的索引,修改索引指向的訊號量的值,最後清除訊號量:

#include <stdio.h><br />#include <sys/types.h><br />#include <sys/sem.h><br />#include <sys/ipc.h><br />void main()<br />{<br /> key_t unique_key; /* 定義一個IPC關鍵字*/<br /> int id;<br /> struct sembuf lock_it;<br /> union semun options;<br /> int i;</p><p> unique_key = ftok(".", 'a'); /* 產生關鍵字,字元'a'是一個隨機種子*/<br /> /* 建立一個新的訊號量集合*/<br /> id = semget(unique_key, 1, IPC_CREAT | IPC_EXCL | 0666);<br /> printf("semaphore id=%d/n", id);<br /> options.val = 1; /*設定變數值*/<br /> semctl(id, 0, SETVAL, options); /*設定索引0的訊號量*/</p><p> /*列印出訊號量的值*/<br /> i = semctl(id, 0, GETVAL, 0);<br /> printf("value of semaphore at index 0 is %d/n", i);</p><p> /*下面重新設定訊號量*/<br /> lock_it.sem_num = 0; /*設定哪個訊號量*/<br /> lock_it.sem_op = - 1; /*定義操作*/<br /> lock_it.sem_flg = IPC_NOWAIT; /*操作方式*/<br /> if (semop(id, &lock_it, 1) == - 1)<br /> {<br />  printf("can not lock semaphore./n");<br />  exit(1);<br /> }</p><p> i = semctl(id, 0, GETVAL, 0);<br /> printf("value of semaphore at index 0 is %d/n", i);</p><p> /*清除訊號量*/<br /> semctl(id, 0, IPC_RMID, 0);<br />} 
        3. 線程式控制制/通訊編程
        Linux 本身只有進程的概念,而其所謂的"線程"本質上在核心裡仍然是進程。大家知道,進程是資源分派的單位,同一進程中的多個線程共用該進程的資源(如作為共用記憶體的全域變數)。Linux 中所謂的"線程"只是在被建立的時候"複製"(clone)了父進程的資源,因此,clone出來的進程表現為"線程"。Linux 中最流行的線程機製為LinuxThreads,它實現了一種 Posix 1003.1c "pthread" 標準介面。
        線程之間的通訊涉及同步和互斥,互斥體的用法為:

pthread_mutex_t mutex;<br />pthread_mutex_init(&mutex, NULL); //按預設的屬性初始化互斥體變數mutex<br />pthread_mutex_lock(&mutex); // 給互斥體變數加鎖<br />… //臨界資源<br />phtread_mutex_unlock(&mutex); // 給互斥體變數解鎖 
        同步就是線程等待某個事件的發生。只有當等待的事件發生線程才繼續執行,否則線程掛起並放棄處理器。當多個線程協作時,相互作用的任務必須在一定的條件下同步。Linux 下的 C 語言編程有多種線程同步機制,最典型的是條件變數(condition variable)。而在標頭檔 semaphore.h 中定義的訊號量則完成了互斥體和條件變數的封裝,按照多線程程式設計中存取控制機制,控制對資源的同步訪問,提供者設計人員更方便的調用介面。下面的生產者/消費者問題說明了 Linux 線程的控制和通訊:

#include <stdio.h><br />#include <pthread.h><br />#define BUFFER_SIZE 16<br />struct prodcons<br />{<br /> int buffer[BUFFER_SIZE];<br /> pthread_mutex_t lock;<br /> int readpos, writepos;<br /> pthread_cond_t notempty;<br /> pthread_cond_t notfull;<br />};<br />/* 初始化緩衝區結構 */<br />void init(struct prodcons *b)<br />{<br /> pthread_mutex_init(&b->lock, NULL);<br /> pthread_cond_init(&b->notempty, NULL);<br /> pthread_cond_init(&b->notfull, NULL);<br /> b->readpos = 0;<br /> b->writepos = 0;<br />}<br />/* 將產品放入緩衝區,這裡是存入一個整數*/<br />void put(struct prodcons *b, int data)<br />{<br /> pthread_mutex_lock(&b->lock);<br /> /* 等待緩衝區未滿*/<br /> if ((b->writepos + 1) % BUFFER_SIZE == b->readpos)<br /> {<br />  pthread_cond_wait(&b->notfull, &b->lock);<br /> }<br /> /* 寫資料,並移動指標 */<br /> b->buffer[b->writepos] = data;<br /> b->writepos++;<br /> if (b->writepos > = BUFFER_SIZE)<br />  b->writepos = 0;<br /> /* 設定緩衝區非空的條件變數*/<br /> pthread_cond_signal(&b->notempty);<br /> pthread_mutex_unlock(&b->lock);<br />}</p><p>/* 從緩衝區中取出整數*/<br />int get(struct prodcons *b)<br />{<br /> int data;<br /> pthread_mutex_lock(&b->lock);<br /> /* 等待緩衝區非空*/<br /> if (b->writepos == b->readpos)<br /> {<br />  pthread_cond_wait(&b->notempty, &b->lock);<br /> }<br /> /* 讀資料,移動讀指標*/<br /> data = b->buffer[b->readpos];<br /> b->readpos++;<br /> if (b->readpos > = BUFFER_SIZE)<br />  b->readpos = 0;<br /> /* 設定緩衝區未滿的條件變數*/<br /> pthread_cond_signal(&b->notfull);<br /> pthread_mutex_unlock(&b->lock);<br /> return data;<br />}</p><p>/* 測試:生產者線程將1 到10000 的整數送入緩衝區,消費者線<br />程從緩衝區中擷取整數,兩者都列印資訊*/<br />#define OVER ( - 1)<br />struct prodcons buffer;<br />void *producer(void *data)<br />{<br /> int n;<br /> for (n = 0; n < 10000; n++)<br /> {<br />  printf("%d --->/n", n);<br />  put(&buffer, n);<br /> } put(&buffer, OVER);<br /> return NULL;<br />}</p><p>void *consumer(void *data)<br />{<br /> int d;<br /> while (1)<br /> {<br />  d = get(&buffer);<br />  if (d == OVER)<br />   break;<br />  printf("--->%d /n", d);<br /> }<br /> return NULL;<br />}</p><p>int main(void)<br />{<br /> pthread_t th_a, th_b;<br /> void *retval;<br /> init(&buffer);<br /> /* 建立生產者和消費者線程*/<br /> pthread_create(&th_a, NULL, producer, 0);<br /> pthread_create(&th_b, NULL, consumer, 0);<br /> /* 等待兩個線程結束*/<br /> pthread_join(th_a, &retval);<br /> pthread_join(th_b, &retval);<br /> return 0;<br />} 
        4.小結
        本章主要給出了 Linux 平台下檔案、進程式控制制與通訊、線程式控制制與通訊的編程執行個體。至此,一個完整的,涉及硬體原理、Bootloader、作業系統及檔案系統移植、驅動程式開發及應用程式編寫的嵌入式 Linux 系列講解就全部結束了。
原文連結:http://dev.yesky.com/131/2531131.shtml。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.