管道编程

原创 2012年03月26日 12:36:42

  管道(有名)编程-是一种进程间的通信方式。未完......

一个典型例子:(来自网络)+自己分析。

     

/*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;
}


 

 

版权声明:本文为博主原创文章,未经博主允许不得转载。

相关文章推荐

STDIN_FILENO的作用及与stdin 的区别

1.STDIN_FILENO的作用 STDIN_FILENO属于系统API接口库,其声明为 int 型,是一个打开文件句柄,对应的函数主要包括 open/read/write/close 等系统...

关于写博客

=============用勇气与诚实写博客============= 0.一定要有勇气。写得再烂,写得再入门,也要坚持,相信成功的人都是在实践中磨砺自己的。(注意,不要写错的东西。言语可以朴实,但...

STDIN_FILENO 与stdin的区别

1.STDIN_FILENO的作用 STDIN_FILENO属于系统API接口库,其声明为 int 型,是一个打开文件句柄,对应的函数主要包括 open/read/write/close 等系统级调...

linux select 函数

(转)EDN China 电子设计技术互动社区>博客>thinkker博客 >Iinux中select函数的使用 + 实验心得     Iinux中select函...

有关网络编程的一些面试

1,首先说明socket是什么??       我们知道:信息的交互需要涉及到tcp的四层模型              从上图可以看到:...

命名管道编程的原理及实现

概述 管道(Pipe)实际是用于进程间通信的一段共享内存,创建管道的进程称为管道服务器,连接到一个管道的进程为管道客户机。命名管道(NamedPipes)是在管道服务器和一台或多台管道客户机之间进行...
  • qtycr
  • qtycr
  • 2013-02-26 10:50
  • 1293

Linux下的管道编程技术

  • 2009-10-20 14:42
  • 136KB
  • 下载

Linux编程—进程间通信—无名管道(1)

本文介绍基本的管道编程技术,算是学习linux管道编程的一点总结。管道的更多特性将会在下一篇探寻。          关键字:管道  无名管道  pipe    &#...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:深度学习:神经网络中的前向传播和反向传播算法推导
举报原因:
原因补充:

(最多只允许输入30个字)