管道編程

  管道(有名)編程-是一種進程間的通信方式。未完......

一個典型例子:(來自網絡)+自己分析。

     

/*led_player服務器*/
/*實現對管道/tmp/led-control的監控*/
/*並實現對led的控制*/

#include <stdio.h> /*標準輸入輸出*/

#include <stdlib.h> /*標準庫*/

#include <unistd.h> /* */

#include <sys/ioctl.h> /*輸入輸出控制*/

#include <sys/types.h> /*一些類型*/

#include <sys/stat.h> /*一些狀態*/

#include <fcntl.h> /* */

#include <sys/select.h> /*select函數*/

#include <sys/time.h> /*時間方面的*/

/*包含的頭文件越多,程序也就越複雜,實現的功能也就越強大*/
/*也就是說,頭文件的多少,是判斷一個軟件規模的重要指標*/
/*我們希望儘量多包含一些頭文件,但是不希望包含不必要的頭文件*/
/*而且,一定要清楚所包含的頭文件是幹什麼的!!!*/

/*對static的理解:編譯時即做好內存的分配工作,只要整個程序開始運行,
則分配空間,一直到整個程序終止,才釋放空間.*/
/*dynamic則不同,比如,子程序內部定義的一個局部變量,是在用到子程序,
並且到達創建該局部變量的語句時才創建,然後子程序執行完,就釋放空間*/

static int led_fd; /*靜態變量,led設備號*/

static int type = 1; /*類型,據此實現對led運作方式的控制*/

/*push_leds()函數*/
/*根據led的控制參數,實現對led的控制*/
/*事實上,很多子函數完成的纔是核心的任務*/
/*而主函數做的工作則是用戶界面,即人機交互方面的工作*/
/*也就是如何讓用戶來調用這些完成實際工作的子函數*/
/*GUI,圖形用戶界面則是人機交互的理想方式*/

static void push_leds(void);

/*主函數入口*/

int main(void)
{
int led_control_pipe; /*管道設備號,實現的是對管道的讀取*/

int null_writer_fd; /*也是管道設備號,實現的是對管道的寫入*/
    /*似乎是沒怎麼用到它*/

double period = 0.5; /*間歇時間,led的控制參數之一*/

/*打開leds設備,其設備號賦給led_fd*/
/*leds設備的驅動函數事實上已經寫好了*/
/*我們做的工作就是調用這些驅動函數*/

led_fd = open("/dev/leds",0);

/*對led_fd的處理*/
/*包括對leds設備錯誤打開的處理和正確打開的處理*/
/*通常對錯誤的處理即是要輸出錯誤信息*/
/*而設備正確打開的處理,則要複雜得多*/

/*在這裏,leds設備正確打開,則接下來要對leds設備進行控制*/
/*對leds設備進行控制,也不是一件輕易完成的事情*/
/*我們需要有控制信息吧?從哪裏獲取這些控制信息呢?*/

/*而且,這些控制信息本身已經很複雜了,所以要弄清楚控制信息的結構*/
/*然後要清楚*/
/*控制信息是誰產生的,從哪裏獲得控制信息,如何獲得控制信息*/
/*從那獲取控制信息之後,是不是要將那裏清零呢?*/

/*獲得控制信息之後,要依據控制信息,對leds設備進行相應的操作*/
/*下面我們開始吧*/

if(led_fd < 0) /*打開錯誤*/
{
   perror("open device leds");
   exit(1);
}

/*下面則是打開正確的處理*/

/*包括控制參數的獲取和依據控制參數進行設備的控制*/
/*由於本程序是leds設備服務器程序,所以需要不斷檢測控制信息的變化*/
/*這就需要一個無限循環,在這個循環裏完成檢測任務,一旦檢測到需要的東西*/
/*則跳出檢測循環,依據檢測到的信息去作相應的控制工作*/

/*而我們對leds的控制工作也不是簡單的一蹴而就的事情*/
/*它事實上是一個無限循環的動態過程,涉及到時間的處理*/
/*如何改變該循環的模式以及如何跳出該循環是我們關心的重點*/

/*改變循環的模式當然還是要根據檢測到的控制信息*/

/*下面開始*/

/*解開管道的連接*/

unlink("/tmp/led-control"); /*unlink()函數*/

mkfifo("/tmp/led-control",0666); /*mkfifio()函數,fifo方式的確立*/

/*打開管道*/
/*注意O_RDONLY和O_NONBLOCK的打開方式*/
/*管道號賦給led_control_pipe*/

led_control_pipe = open("/tmp/led-control",O_RDONLY | O_NONBLOCK);

/*led_control_pipe的處理*/

if(led_control_pipe < 0) /*打開錯誤*/
{
   perror("open control pipe for read");
   exit(1);
}

/*以O_WRONLY方式打開管道,並將管道號賦給null_writer_fd*/
/*其與前面的led_control_pipe管道號,構成了一讀一寫*/

null_writer_fd = open("/tmp/led-control",O_WRONLY | O_NONBLOCK);

/*對null_writer_fd的處理*/

if(null_writer_fd < 0) /*打開錯誤*/
{
   perror("open control pipe for write");
   exit(1);
}

/*for循環,對leds設備的動態控制*/

for(;;)
{
   fd_set rds; /*結構體,文件描述符的集合*/
     /*結合後面select()函數的使用*/
     /*rds,就是read descriptor set的縮寫*/
     /*select()函數就用來檢測rds中是否有文件可讀*/

   struct timeval step; /*時間的結構體*/
      /*有秒和毫秒兩個成員*/
        /*要注意此處的step與子函數中的step*/

   int ret; /*select()函數的返回值*/

   FD_ZERO(&rds); /*宏FD_ZERO()初始化rds*/

   FD_SET(led_control_pipe,&rds); /*宏FD_SET()*/
      /*led_control_pipe文件符被放入rds*/

   /*step結構體變量的賦值*/

   step.tv_sec = period; /*period是在主函數剛開始定義的*/
      /*秒的賦值*/
      /*period作爲控制參數之一,肯定會有所變化的*/

   step.tv_usec = (period - step.tv_sec) * 1000000L;
     /*毫秒的賦值,事實上被賦爲0*/

   /*select()函數的使用*/
   /*select()函數能夠監測需要監視的文件描述符的變化情況*/
   /*此處也就是監測管道是否有新的消息,從而可讀*/

   /*這裏,要注意,別的進程寫控制消息到管道的時候*/
   /*一定要對管道的可讀標誌位進行相關的處理才行*/
   /*這樣,我們的服務器才能檢測到管道有新消息,並可讀了*/

   ret = select(led_control_pipe + 1,&rds,NULL,NULL,&step); /*此處step相當於延時處理,也就是leds設備的運作週期*/

   /*led_control_pipe + 1 限定文件描述符的最大範圍*/
   /*&step監測的時間跨度*/
   /*即花一定時間檢測管道是否有新的控制消息*/
   /*如果有,就讀取*/
   /*如果沒有,則按以前的模式繼續執行任務*/

   /*對select()函數返回值ret的處理*/

   if(ret < 0) /*表明select()出錯*/
   {
    perror("select");
    exit(1);
   }

   if(ret == 0) /*表明沒有文件可讀*/
     /*也就是說沒有新消息過來*/
   {
    /*那麼就不用接收新消息*/
    /*繼續執行我們對leds設備的控制操作*/
    /*即調用子函數push_leds()*/

    push_leds(); /*注意,子函數中一定有能夠接收消息的變量*/
   }
   else /*即ret大於0時,此時表示管道可讀*/
    /*即管道已經被寫入了新的控制消息*/
    /*那麼我們接下來就是要讀取消息了*/

    if(FD_ISSET(led_control_pipe,&rds))
     /*此處作判斷有必要嗎?*/(本人認爲,應該是先調用select如過返回值大於0,說明有可讀的文件描述符,具體是哪一個,再調用FD_ISSET來判斷具體的文件描述符。)
     /*led_control_pipe本來就在rds裏面*/
    {
     /*要讀取消息,總要有緩衝區唄*/
     static char buffer[200]; /*注意是static型的*/

     /*讀取是一個字符一個字符地讀*/
     /*這裏用一個無限循環來做*/
     /*跳出循環的時刻在於讀到回車符*/
     /*也要注意對空格符的處理*/

     for(;;)
     {
      /*存放單個字符的變量*/
      char c;

      /*len很重要,指向buffer中消息的末尾*/
      /*添加新的字符,就靠len來定位*/
      /*因此,len也要不斷地更新*/

      int len = strlen(buffer);
       /*strlen()函數*/

      /*判斷buffer是否已滿*/
      /*如果滿了,則對buffer清空*/
      /*只有清空了,才能繼續盛放東西*/
      /*並跳出循環*/

      /*我覺得判斷是否已滿是沒有必要的*/
      /*因爲buffer容量夠大了,存放消息足夠了*/
      /*而消息一到來,我們接收之後,就一定會作清空處理的*/
      /*所以不會出現滿的情況*/

      if(len >= sizeof(buffer) - 1)
        /*注意這裏-1的意義*/
      {
       memset(buffer,0,sizeof(buffer));
        /*memset()函數*/

       break;
      }

      /*從管道中讀取字符,先放入c字符變量*/
      /*之後放入buffer中*/
      /*這纔是控制信息獲取的核心步驟*/
      /*這實質上是管道設備的驅動程序完成的*/
      /*通過read()函數完成*/

      if(read(led_control_pipe,&c,1) != 1) /*讀取異常*/
      {
       break;
      }

      /*本來緊接着就該把c裏的字符放進buffer中*/
      /*但是對c中的字符進行判斷*/
      /*因爲並不是所有的字符都要放進緩衝區中*/
      /*如\r,好像是空格,如\n,是回車*/
      /*對這些特殊的字符有特別的處理,要非常小心*/
      /*而對一般的控制信息的字符,則正常地放進buffer中就行了*/

      /*對\r的處理,跳出本次循環,進入下次循環,continue語句*/

      if(c == '\r') /*即是空格符就跳過去*/
      {
       continue;
      }

      /*對\n的處理*/
      /*\n實質上是跳出整個消息獲取循環的標誌*/
      /*回車符一到來,就證明消息結束了*/
      /*之後我們要做的工作有兩個*/
      /*一個是將buffer中存儲的消息傳達給有關變量*/
      /*之後,就需要清空buffer*/

      /*至於對管道的清空處理,這裏不是本服務器作的*/
      /*有個疑問,在管道讀取的過程中,是否可以繼續寫入呢?*/

      if(c == '\n')
      {
       /*兩個臨時變量,存儲空喊控制參數*/

       int tmp_type; /*存儲模式*/
       double tmp_period; /*存儲時間*/

       /*標準輸入函數sscanf()*/
       /*我們放進buffer裏的是字符*/
       /*但我們從buffer裏拿出來的時候卻要是數字*/
       /*這就是通過格式化輸入完成的*/

       if(sscanf(buffer,"%d%lf",&tmp_type,&tmp_period) == 2)
       {
        /*等於2則說明輸入正確*/
        /*則將臨時變量的值輸入正式變量裏*/

        /*這就是兩個控制參數*/

        type = tmp_type;
        period = tmp_period;
       }


       /*輸出提示信息*/

       fprintf(stderr,"type is %d,period is %lf\n",type,period);
        /*爲什麼要輸到stderr中呢*/

       memset(buffer,0,sizeof(buffer)); /*清空buffer*/

       /*跳出循環*/

       break;
      }

      /*正常的c的處理*/

      buffer[len] = c; /*讀取字符到buffer*/
     }
    }
}

/*關閉leds設備*/

close(led_fd); /*close()函數*/

/*主函數返回*/

return 0;
}

/*子函數*/

static void push_leds(void)
{
static unsigned step; /*此處的step與上層的step時間結構體不同*/
    /*注意它是靜態的,而且還會進行自增運算*/
    /*以實現一個大的週期*/
    /*比如type一直爲1時,其運作週期爲256*period*/
    /*step最初沒有初始化,所以會隨機給定一個值*/

unsigned led_bitmap; /*led設備的最終控制參數就是它*/

int i; /*循環變量,以實現對4個led的逐個控制*/

/*switch語句,根據type來對led_bitmap賦值*/

switch(type)
{
   case 0:    /*模式0*/
    if(step >= 6) /*6,7,8,...*/
     step = 0; /*就意味着此計數週期不算大*/
      /*纔到6就要截止了*/

    if(step < 3) /*0,1,2的處理*/
    {
     led_bitmap = 1 << step; /*<<是什麼運算呢?*/
       /*移位運算*/
       /*若step爲2,則其二進制爲10*/
       /*左移一位,則爲100,就是4*/
       /*賦值給led_bitmap,從而實現對led的控制*/
    }
    else /*3,4,5的處理*/
    {
     led_bitmap = 1 << (6 - step);
       /*比如step爲3時*/
       /*則led_bitmap爲110,即爲6*/
    }

    break; /*跳出*/

   case 1: /*模式1*/
    if(step > 255) /*此循環還是比較大的*/
    {
     step = 0;
    }

    led_bitmap = step; /*對led_bitmap賦值*/

    break; /*跳出,顯然,模式1還是相對簡單的*/

   default:
    led_bitmap = 0; /*其它模式,都是0,就是全滅*/
}

step ++; /*step的自增運算,跑馬燈就要靠它來動態實現了*/
    /*step每隔period變化一次,顯示狀態就會發生變化*/
    /*注意說的是顯示狀態,而不是說顯示模式*/
    /*就是說,同一個顯示模式下,狀態也會不斷變化*/
     /*而不同模式,led的變化情況是不同的*/

/*下面依據led_bitmap對leds設備進行處理*/

for(i = 0; i < 4; i++) /*依此對0,1,2,3燈管進行處理*/
{
   /*ioctl()函數*/
   /*leds設備的核心驅動函數*/

   ioctl(led_fd,led_bitmap & 1,i);
    /*對i號燈進行控制*/
    /*led_bitmap & 1,這一步非常考究*/
    /*其結果要麼爲0,要麼爲1,就是開關*/
    /*比如對0號燈進行開處理*/
    /*1011&0001事實上就是屏蔽掉了前三位,而只留下一位*/
     /* 當然可想而知,我們必須對led_bitmap不斷右移*/

   /*對led_bitmap進行右移,以實現控制位的對齊*/
  
   led_bitmap >>= 1;
}
}

另外的一種分析:

arm開發板LED應用程序詳細分析

來源:dpj365 作者:雨人 發佈時間:2010-12-28 點擊: 162次

下面這個程序時現在ARM開發板常用的一個LED應用程序實例。我參考資料詳細做了分析。這段程序用管道來傳遞LED控制參數。但是我只看到了讀管道,沒看到寫管道。希望哪位高手能指點一下。

/*************************************

NAME:ledplayer.c
COPYRIGHT:www.dpj365.cn

*************************************/

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/select.h>
#include <sys/time.h>

static int led_fd;//靜態變量,LED設備號
static int type = 1;//LED的點亮方式

static void push_leds(void)
{
 static unsigned step;//靜態變量,進行自增運算,實現一個完整的LED點亮週期
 unsigned led_bitmap;
 int i;

 switch(type) {
 //方式1:led依次點亮第1個燈-第2個燈-第3個燈-第4個燈-第3個燈-第2個燈-第1個燈,
 //每次點亮一個燈,不斷循環
 case 0:
  if (step >= 6) {
   step = 0;
  }
  if (step < 3) {
   led_bitmap = 1 << step;//這裏是1左移step位,不是有些帖子裏面解釋的step左移1位
  } else {
   led_bitmap = 1 << (6 - step);
  }
  break;
 //方式2:led用亮表示1,滅表示0,顯示0-15(因爲只有4個LED,不能顯示到255) 
 case 1:
  if (step > 255) {
   step = 0;
  }
  led_bitmap = step;
  break;
 //默認方式:led全滅 
 default:
  led_bitmap = 0;
 }
 step++;
 for (i = 0; i < 4; i++) {
  //leds設備的核心驅動函數
  //對i號燈進行控制,led_bitmap & 1的結果要麼爲1,要麼爲0
  ioctl(led_fd, led_bitmap & 1, i);
  //led_bitmap低4位每一位對應一個LED狀態,左移一位,與for循環同步
  led_bitmap >>= 1;
 }
}

int main(void)
{
 int led_control_pipe;//管道設備號
 int null_writer_fd; // for read endpoint not blocking when control process exit

 double period = 0.5;
  //“/dev/TQ2440_leds”設備節點,在驅動程序中創建,和主設備號,設備名對應
 led_fd = open("/dev/TQ2440_leds", 0);// 以只讀方式打開,O_RDONLY爲0
 //open函數失敗返回-1,成功返回文件描述符
 if (led_fd < 0) {
  perror("open device leds");
  exit(1);
 }
 unlink("/tmp/led-control");//先刪除有名管道,可能不是必須的
 mkfifo("/tmp/led-control", 0666);//創建一個可讀寫不可執行的有名管道

 //以非阻塞讀方式打開有名管道
 led_control_pipe = open("/tmp/led-control", O_RDONLY | O_NONBLOCK);
 if (led_control_pipe < 0) {
  perror("open control pipe for read");
  exit(1);
 }
 //以非阻塞寫方式打開有名管道
 null_writer_fd = open("/tmp/led-control", O_WRONLY | O_NONBLOCK);
 if (null_writer_fd < 0) {
  perror("open control pipe for write");
  exit(1);
 }

 for (;;) {
  //結構體,文件描述符的集合,配合系統調用select()函數使用
  fd_set rds;
  /*timeval的結構定義如下:
   struct timeval{
    long tv_sec; //表示幾秒
    long tv_usec; //表示幾微妙
   }
   timeout取不同的值,該調用就表現不同的性質:
   1.timeout爲0,調用立即返回;
   2.timeout爲NULL,select()調用就阻塞,直到知道有文件描述符就緒;
   3.timeout爲正整數,就是一般的定時器。*/
  struct timeval step;
  /*select調用返回時,除了那些已經就緒的描述符外,select將清除readfds、writefds和exceptfds中的所有沒有就緒的描述符。select的返回值有如下情況:
   1.正常情況下返回就緒的文件描述符個數;
   2.經過了timeout時長後仍無設備準備好,返回值爲0;
   3.如果select被某個信號中斷,它將返回-1並設置errno爲EINTR。
   4.如果出錯,返回-1並設置相應的errno。*/
  int ret;
    //宏FD_ZERO清除文件描述符集fdset中的所有位(既把所有位都設置爲0)。
  FD_ZERO(&rds);
  //宏FD_SET設置文件描述符集fdset中對應於文件描述符fd的位(設置爲1)
  //文件描述符集fd_set相關知識請看 http://www.dpj365.cn/bbs/viewthread.php?tid=369&extra=page%3D1
  FD_SET(led_control_pipe, &rds);
  step.tv_sec  = period;//tv_sec = 0
  step.tv_usec = (period - step.tv_sec) * 1000000L;//tv_usec=500 000

  ret = select(led_control_pipe + 1, &rds, NULL, NULL, &step);
  if (ret < 0) {
   perror("select");
   exit(1);
  }
  if (ret == 0) {
   push_leds();//沒有文件可讀,繼續執行先前對LED設備的控制操作
  }
  //在調用select後使用FD_ISSET來檢測文件描述符集fdset中對應於文件描述符fd的位是否被設置。
  else if (FD_ISSET(led_control_pipe, &rds)) {
   static char buffer[200];
   for (;;) {
    char c;
    int len = strlen(buffer);//計算字符個數,不包括'\0'
    //如果len=199,sizeof(buffer)=200, 那麼buffer已經滿了
    if (len >= sizeof(buffer) - 1) {
     memset(buffer, 0, sizeof buffer);//將buffer清零
     break;
    }
    //從管道讀一個字符
    if (read(led_control_pipe, &c, 1) != 1) {
     break;
    }
    /*\r 是回車,return
     \n 是換行,newline

     我們在平時使用電腦時,已經習慣了回車和換行一次搞定,敲一個回車鍵,即是回車,又是換行,但在早期的打字機上,要另起一行打字需要兩個步驟,首先要發送命令"\r”將打字頭復位,即回車,然後再發送命令"\n”讓打字機走紙移到下一行,所以這個歷史遺留問題導致瞭如今我們在DOS-Windows的系統裏需要區分"\r\n”和“\n”,但在Unix中只有"\n”。

     用UltraEdit打開文本查看,會看到換行處顯示的是0x0D0A,0x0D即"\r”,0x0A即"\n”。 */
    if (c == '\r') {
     continue;//如果是‘\r’,跳過讀下一個字節
    }
    if (c == '\n') {
    /*接收到'\n' ,證明消息結束了,接着要將buffer中存儲的消息傳給相關變量,最後清空buffer*/
     int tmp_type;
     double tmp_period;
     if (sscanf(buffer,"%d%lf", &tmp_type, &tmp_period) == 2) {
      type = tmp_type;
      period = tmp_period;
     }
     fprintf(stderr, "type is %d, period is %lf\n", type, period);
     memset(buffer, 0, sizeof buffer);
     break;
    }
    buffer[len] = c;//這裏沒有len++,len通過前面的len = strlen(buffer)得到新值,相當於加1
   }
  }
 }

 close(led_fd);//關閉LED設備
 return 0;
}


 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章