色婷婷AⅤ一区二区三区|亚洲精品第一国产综合亚AV|久久精品官方网视频|日本28视频香蕉

          新聞中心

          EEPW首頁 > 嵌入式系統(tǒng) > 設計應用 > 基于ARM的嵌入式Linux移植真實體驗(5)――應用實例

          基于ARM的嵌入式Linux移植真實體驗(5)――應用實例

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


          評論


          技術專區(qū)

          關閉