MT7688 AES硬件加速

最近我无聊翻翻MT7688的datasheet,注意到居然支持AES加速,赶紧搜github找到了一个能用的OpenWRT驱动MTK_AES

赶紧开搞,一路搞好好几天才慢慢摸索出linux的初步加密框架。内核的加密框架的应用层接口是AF_ALG 套接字形式,但是有一个专门的驱动模块crpytodev抽象出一个/dev/crypto设备,采用ioctl接口,而且根据官网数据对比性能更好。

于是选中OpenWRT自带的kmod-cryptodev模块,默认会选中kmod-crypto-core等一大堆依赖,其实最终只依赖aead.ko,编译好后全部加载驱动。由于系统库的加密是openssl提供的libcrypto加密库,所以需要开启openssl库的硬件加密选项 “Crypto acceleration support”(Libraries>SSL>libopenssl) ,顺便选中openssl-util工具进行测试。

  • 测试未开启AES硬件加速

    root@:/# openssl speed -evp aes-128-cbc
    Doing aes-128-cbc for 3s on 16 size blocks: 1242779 aes-128-cbc's in 2.96s
    Doing aes-128-cbc for 3s on 64 size blocks: 359528 aes-128-cbc's in 2.95s
    Doing aes-128-cbc for 3s on 256 size blocks: 93663 aes-128-cbc's in 2.95s
    Doing aes-128-cbc for 3s on 1024 size blocks: 23499 aes-128-cbc's in 2.92s
    Doing aes-128-cbc for 3s on 8192 size blocks: 2953 aes-128-cbc's in 2.96s
    bala...bala...
    The 'numbers' are in 1000s of bytes per second processed.
    type             16 bytes     64 bytes    256 bytes   1024 bytes   8192 bytes
    aes-128-cbc       6717.72k     7799.93k     8128.04k     8240.75k   
    
  • 测试开启AES硬件加速

    root@:/# openssl speed -evp aes-128-cbc
    Doing aes-128-cbc for 3s on 16 size blocks: 276231 aes-128-cbc's in 0.65s
    Doing aes-128-cbc for 3s on 64 size blocks: 270678 aes-128-cbc's in 0.45s
    Doing aes-128-cbc for 3s on 256 size blocks: 243448 aes-128-cbc's in 0.29s
    Doing aes-128-cbc for 3s on 1024 size blocks: 179638 aes-128-cbc's in 0.34s
    Doing aes-128-cbc for 3s on 8192 size blocks: 45879 aes-128-cbc's in 0.11s
    bala...bala...
    type             16 bytes     64 bytes    256 bytes   1024 bytes   8192 bytes
    aes-128-cbc       6799.53k    38496.43k   214905.82k   541027.39k  3416734.25k
    

主要是看最后一行的吞吐量测试,效果惊人啊!!!!

不开启加速时数据吞吐量变化不大,毕竟这里的性能瓶颈是在AES软件实现这块,而开启硬件加速后,数据块越大效果越好,最后的8192字节块效果就提升了414倍。

这里openssl需要开启-evp选项,这样底层的crpyto库会优先选择硬件加速。openssl里的cryptodev引擎是EVP接口形式提供的,所以如果使用openssl库需要加速,必须用EVP接口。不过openssl测试工具默认的加密引擎engine会自动选择cryptodev,不用再手动指定-engine cryptodev了。

这里比较郁闷的是mtk_aes支持AES的ECB和CBC,但是openssl的libcrypto库的enc_cryptodev.c里只支持CBC,所有最终能加速的只能是AES的CBC了,估计ECB不太安全吧,连CBC也不是很安全,现在普遍用CTR吧。CBC只是比ECB多了个IV矩阵,保证每次加密不重样。

static struct {
    int id;
    int nid;
    int ivmax;
    int keylen;
} ciphers[] = {
    {
        CRYPTO_ARC4, NID_rc4, 0, 16,
    },
    {
        CRYPTO_DES_CBC, NID_des_cbc, 8, 8,
    },
    {
        CRYPTO_3DES_CBC, NID_des_ede3_cbc, 8, 24,
    },
    {
        CRYPTO_AES_CBC, NID_aes_128_cbc, 16, 16,
    },
    {
        CRYPTO_AES_CBC, NID_aes_192_cbc, 16, 24,
    },
    {
        CRYPTO_AES_CBC, NID_aes_256_cbc, 16, 32,
    },
# ifdef CRYPTO_AES_CTR
    {
        CRYPTO_AES_CTR, NID_aes_128_ctr, 14, 16,
    },
    {
        CRYPTO_AES_CTR, NID_aes_192_ctr, 14, 24,
    },
    {
        CRYPTO_AES_CTR, NID_aes_256_ctr, 14, 32,
    },
# endif
    {
        CRYPTO_BLF_CBC, NID_bf_cbc, 8, 16,
    },
    {
        CRYPTO_CAST_CBC, NID_cast5_cbc, 8, 16,
    },
    {
        CRYPTO_SKIPJACK_CBC, NID_undef, 0, 0,
    },
    {
        0, NID_undef, 0, 0,
    },
};

接下来用AES CBC加密一个20M的文件:

root@:/tmp# head -c 20m /dev/zero >test

root@:/tmp# time openssl enc -e -aes-128-cbc -in test -out test.out -kfile pgrade_pass 
real 0m 0.74s
user 0m 0.10s 
sys 0m 0.63s

root@:/tmp# rmmod mtk_aes 
root@:/tmp# time openssl enc -e -aes-128-cbc -in test -out test.out -kfile upgrade_pass 
real 0m 3.33s
user 0m 2.93s 
sys 0m 0.38s

提升了5倍。

我这里的openssl的版本是1.0.2e,最新的1.1版本支持AES的ECB的,我没试过,Cryptodev是1.7版本。现在应用就是以后加解密文件有点用,如果是优化ssh和scp估计也不是很好,比较这些应用瓶颈往往是网速,并且如果是小数据加密的话效果差别不大,只在大数据吞吐时才有差别。

Flex和Bison

最近我想在程序中加一个命令行接口,同时可以作为简单的RPC调用协议使用。这个之前在看libiio里的iiod源码时接触到了Flex和Bison写的命令行接口RPC,

简单的例如:

send cmd 
返回SUCCESS或FAIL

这个虽然直接自己写一个也可以,但是效率和复杂度上有点顾虑。Flex的正则表达式能很好的处理命令行字符串,用Bison去处理命令行的组合虽然有点大材小用,不过万一以后写一个带逻辑语句的命令行就比较方便。

有一点麻烦的是,Flex和Bison的C++支持文档不是很多,大多是C的。不过还好Flex和Bison直接输出C++源码,边看源码边改还算方便。这里特别说一下OREILLY有本动物书是《flex&bison》,网上有PDF版,但用的版本老了点。

其他的没啥好写了,我也在学习中…

https://www.gnu.org/software/bison/manual/html_node/index.html#SEC_Contents

USB液晶屏for树莓派

前序

自从入手树莓派一代,一直闲置着。现在树莓派都二代了,再不玩玩就彻底吃灰了。可惜的是树莓派没接显示器总归不好玩。

我手头有一个Atmel A5(SAMA5D3)的ARM板子,自带TFT-LCD电容触摸液晶屏和USB接口,刚好作为树莓派1代的显示器,两者之间用 USB传输图像数据(不求性能,能显示就行)。

准备就绪就缺实现方案了,突然想起RoboPeak有一个树莓派的液晶屏在卖,而且开源树莓派端的驱动和协议,那我就充分发挥开源优势,在这个基础上改。

这就涉及到两个USB驱动:

  1. ARM液晶屏的USB gadget驱动:

    项目在这里

  2. 树莓派的USB HOST驱动:

    首先刷RoboPeak的树莓派固件,重新编译树莓派的Linux kernel, 编译修改原来的RoboPeak的开源树莓派驱动。最后改好的驱动项目在这里

在研究kernel的USB gadget驱动过程中发现了一个USB协议详解网站,当然USB协议文档也必不可少,开发驱动的话单单看第9章就行了。

实现原理

这里只说gadget驱动,host驱动用RoboPeak原来的rpusbdisp,单单改了分辨率(改成液晶屏的分辨率800X480),当然现在驱动刚刚能用,还有很多要完善。

在USB协议层上有一个interface,两个endpoint,一个OUT传输Robopeak液晶屏的图像协议,另一个IN传输触摸屏的输入。

  1. OUT endpoint

根据RoboPeak的rpusbdisp的协议,我只实现图片拷贝的bitblt命令就行了,这个是host驱动检测到画面有更新后拷贝图片给gadget驱动。我只要介绍到图片数据(颜色深度16位,565格式的真彩色)拷贝到当前的framebuffer就可以了,我这里直接霸王硬上弓拷贝的framebuffer的base memory里。

当然一幅800480的图片传输数据就有700多K(800480*2=768000),为了压缩数据,RoboPeak的协议里检测到gadget驱动版本大于1.04会采用新的压缩数据方法。简单来说就是分成128字节的段,每个段有一个head字节,表示当前端是否是重复的颜色数据(一般的桌面背景很多重复颜色数据),这样简单的压缩一下传输的数据一下子就下降了很多。

  1. IN endpoint

标准的中断数据,由于这个ARM板子是pixcir的芯片,支持电容屏和多点触摸。我在pixcir的驱动里做了一个HOOK函数获取触摸位置再发送给host。

好了,具体的细节看代码吧:)。

usb_display

修复WIFI驱动

最近我阴差阳错升级了Ubuntu14.04 Linux内核到4.4.0,升级后发现Broadcom的私有WIFI驱动 wl挂掉了。切换到开源驱动信号又不是很好,万般无奈只能调试找原因了。

dmesg下发现了原因,原来是内核更新后dkms阶段重新编译bcmwl驱动失败了,找不到rdtscl,估计API改了-_-!!!。

Using CFG80211 API
  CC [M]  /var/lib/dkms/bcmwl/6.30.223.248+bdcom/build/src/shared/linux_osl.o
/var/lib/dkms/bcmwl/6.30.223.248+bdcom/build/src/shared/linux_osl.c: In function ‘osl_getcycles’:
/var/lib/dkms/bcmwl/6.30.223.248+bdcom/build/src/shared/linux_osl.c:938:5: error: implicit declaration of function ‘rdtscl’ [-Werror=implicit-function-declaration]
     rdtscl(cycles);
     ^

电脑上安装的Broadcom的私有驱动是bcmwl, 版本是6.30.223.248。马上上网搜索bcmwl, 找到了Ubuntu的代码仓库。挨个找BUG修复记录,在这里找到了一点端倪

+diff --git a/src/shared/linux_osl.c b/src/shared/linux_osl.c
+index d540636..e622c5c 100644
+--- a/src/shared/linux_osl.c
++++ b/src/shared/linux_osl.c
+@@ -932,6 +932,10 @@ osl_getcycles(void)
+   uint cycles;
+ 
+ #if defined(__i386__)
++#ifndef rdtscl
++#define rdtscl(low) \
++ ((low) = (u32)rdtsc())
++#endif
+   rdtscl(cycles);
+ #else
+   cycles = 0;
+-- 
+1.9.1
+

OK,马上修改,安装bcmwl的代码在/var/lib/dkms/bcmwl/6.30.223.248+bdcom/build下,sudo make , sudo make install后,depmod一下就满血复活。

但这有个缺点,默认仓库里的bcmwl代码还是没改过来,下次升级内核,dkms机制下编译的还是有BUG的代码,每次升级都要编译一下WIFI驱动,有点麻烦,下次再找方法吧。

第一次在Github上合并Pull Request

我用Github也差不多两年了吧,大多上传一些自己无聊的代码而已。今天打开Github发现有个老外居然向我提交代码,没错是full_text_rss仓库-_-!!!。

full_text_rss最初是因为用Google Reader读到的RSS很多都没有全文输出,所以自己就在当时的VPS做了一个(详细见这里),后来忘了啥原因就把网站关了,把代码丢到Github上就没管了,到今天合并代码才想起来,让我这些老代码又焕发活力了:)。Github用到现在为止感觉不错,以后也要多多提交代码咯。

修复RG100A的按键

我买RG100A路由器很久了,刷了OpenWRT后作为公司工作的主力路由器至今。因为我平时要调试很多不同网段的设备,全靠了OpenWRT的VLAN功能从而轻松解决。为了消除记忆IP地址麻烦就使用了mDNS,为了能把mDNS广播到各个子网,专门在OpenWRT上跑了个程序进行监听和转发mDNS(不过后来发现太占资源就去掉了,最主要是设备不多,Windows安装mDNS一直有问题,剩下的只有Ubuntu笔记本和虚拟机上跑的Ubuntu了)。但我一直不太满意的是RG100A后面的按键,三个按键(WPS、Wifi、Reset)OpenWRT不知为何不支持。最近几天闲来无事就想彻底解决这个问题。

首先就是找问题的根源了,这个除了看代码外别无他途了。从OpenWRT官网下载代码,我装的是老版的Backfire 10.03,内核是linux-2.6.32.27(之前试过编译最新的版本,但一烧进去要么网络有问题,要么就直接崩溃-_-!!!),编译了大半天,主要是下载各种软件包,幸亏网络给力(电信宽带马上要升到12M了:))。

接下来是找相关的驱动代码,这里主要是三个驱动:input_gpio_button、input_polldev、button-hotplug。

  1. input_gpio_button负责低层读取按键的GPIO口状态转换成input event(我这里把把Wifi键映射成BTN_0, WPS键映射成BTN_1,没用Reset键)。这个代码在build_dir/linux-brcm63xx/linux-2.6.32.27/drivers/input/misc/gpio_buttons.c上。

  2. input_polldev是input system的input_dev,因为这三个按键的GPIO都不支持中断,所以只能采取轮询方式,这个input_polldev就是干这个事的。它不断通过input_gpio_button查询GPIO状态,然后发送input event。源码在build_dir/linux-brcm63xx/linux-2.6.32.27/drivers/input/input-polldev.c。

  3. button-hotplug是面向应用层接口的,把input_event转换成hotplug消息。这个主要是内核的hotplug机制(通过内核netlink技术广播对象消息,从而支持热插拔之类的)。OpenWRT用的是hotplug2,具体配置在/etc/hotplug.d下。如果要在应用层处理按键事件,就新建/etc/hotplug.d/button目录,写个测试脚本,用按键点亮前面板的WPS LED灯,参考这里。源码在build_dir/linux-brcm63xx/button-hotplug/button-hotplug.c。

    #!/bin/sh
    LED_NUM=24
    [ -e "/sys/class/gpio/gpio${LED_NUM}/value" ] || echo $LED_NUM > /sys/class/gpio/export
    [ -e "/sys/class/gpio/gpio${LED_NUM}/direction" ] && echo out > /sys/class/gpio/gpio${LED_NUM}/direction
    
    turn_on()
    {
        echo 0 > /sys/class/gpio/gpio${LED_NUM}/value
    }
    
    turn_off()
    {
        echo 1 > /sys/class/gpio/gpio${LED_NUM}/value
    }
    
    if [ "$ACTION" = "pressed" ]; then
        turn_on
    else
        turn_off
    fi
    

而出问题的驱动是input_gpio_button,它依赖平台board的GPIO按键配置,而我调试后发现board初始化给的button GPIO参数不对。这样要么改board初始化代码,要么在input_gpio_button 代码里手动指定GPIO参数。我不想重新编译整个内核,所以选择后者,下面是补丁:

--- ../linux-brcm63xx/linux-2.6.32.27/drivers/input/misc/gpio_buttons.c 2014-02-27 16:58:19.502642430 +0800
+++ gpio_buttons.c  2014-03-06 11:05:50.939708245 +0800
@@ -29,6 +29,31 @@

 #define DRV_NAME   "gpio-buttons"

+static struct gpio_button my_all_buttons[] = {
+        {
+            .gpio = 34,
+            .active_low = 1,
+            .desc = "wifi",
+            .type = EV_KEY,
+            .code = BTN_0,
+            .threshold = 3,
+        },
+        {
+            .gpio = 37,
+            .active_low = 1,
+            .desc = "wps",
+            .type = EV_KEY,
+            .code = BTN_1,
+            .threshold = 3,
+        },
+};
+
+static struct gpio_buttons_platform_data my_buttons_datas = {
+    .buttons = my_all_buttons,
+    .nbuttons = 2,
+    .poll_interval = 100,
+};
+
 struct gpio_button_data {
    int last_state;
    int count;
@@ -55,6 +80,9 @@
    if (state != bdata->last_state) {
        unsigned int type = button->type ?: EV_KEY;

+        // I add
+        printk(KERN_DEBUG "type %d, code %d, active %d", type, button->code, state ^ button->active_low);
+
        input_event(input, type, button->code,
                !!(state ^ button->active_low));
        input_sync(input);
@@ -84,7 +112,8 @@

 static int __devinit gpio_buttons_probe(struct platform_device *pdev)
 {
-   struct gpio_buttons_platform_data *pdata = pdev->dev.platform_data;
+   //struct gpio_buttons_platform_data *pdata = pdev->dev.platform_data;
+   struct gpio_buttons_platform_data *pdata = &my_buttons_datas;
    struct device *dev = &pdev->dev;
    struct gpio_buttons_dev *bdev;
    struct input_polled_dev *poll_dev;
@@ -95,6 +124,15 @@
    if (!pdata)
        return -ENXIO;

+    // I add
+    printk(KERN_DEBUG "platform_data buttons: %d poll_interval: %d ", pdata->nbuttons, pdata->poll_interval);
+   for (i = 0; i < pdata->nbuttons; i++) {
+        printk(KERN_DEBUG "button gpio %d %s type:%d code:%d", pdata->buttons[i].gpio, pdata->buttons[i].desc, 
+                                                               pdata->buttons[i].type, pdata->buttons[i].code);
+    }
+
+    printk(KERN_DEBUG "platform_device name %s", pdev->name);
+
    bdev = kzalloc(sizeof(struct gpio_buttons_dev) +
               pdata->nbuttons * sizeof(struct gpio_button_data),
               GFP_KERNEL);
@@ -189,7 +227,8 @@
 static int __devexit gpio_buttons_remove(struct platform_device *pdev)
 {
    struct gpio_buttons_dev *bdev = platform_get_drvdata(pdev);
-   struct gpio_buttons_platform_data *pdata = bdev->pdata;
+   //struct gpio_buttons_platform_data *pdata = bdev->pdata;
+   struct gpio_buttons_platform_data *pdata = &my_buttons_datas;
    int i;

input_unregister_polled_device(bdev->poll_dev);

中间加了些调试信息,主要是那个gpio_buttons_platform_data my_buttons_datas,默认是platform_device *pdev指定的。这个在board init里指定,有兴趣的可以看看arch/mips/bcm63xx/board_bcm963xx.c,里面有很多板子,RG100A对应的板子是96358VW2,这个可以通过命令行dmesg | grep board看出来。

有关RG100A的GPIO参数,网上有公布,我只摘抄一些:

button gpio:
    gpio34 wifi
    gpio36 reset
    gpio37 wps

led gpio:
    gpio0 usb
    gpio4 power
    gpio8 net
    gpio22 dsl
    gpio24 wps

这样修改好后,载入驱动就可以通过按键控制RG100A前面板WPS灯了,不过暂时还想不出来可以用来干什么。我现在用RG100A的USB口可以驱动摄像头了,就搞了个远程拍照的东西,还不错,就是不太稳定-_-!!!。下一步细看linux的 input system,之前大概看了一些,了解了些原理,理解有误也说不定。要写的太多了写不过来了,就写这些吧。

gnu make有关建立目录

最近写代码想Makefile同时支持交叉编译器和主机编译器,所以要把不同编译器生成的目标文件放到不同的目录中。首先想到的是目录里要保留原来的源文件目录结构,这样就可以用隐式规则或静态规则推导出源文件,否则根本无法得到依赖关系。最后用了
隐式规则扣出源文件路径才勉强可用,照这个思路花了点时间写了个测试例子,准备把现有需要交叉编译的程序都转到这种模式:

ifdef HOST
    objdir:=obj_$(word 1, $(subst -, ,$(HOST)))
else
    objdir:=obj
endif

SRC_DIR:=. src
CFILES:=$(shell find $(SRC_DIR) -maxdepth 1 -name "*.c")
OBJS:=$(addprefix $(objdir)/,$(CFILES:%.c=%.o))
OBJDIRS:=$(addprefix $(objdir)/, $(SRC_DIR))
TARGET=main

all:$(OBJDIRS) $(TARGET)

$(TARGET):$(OBJS)
    gcc -o $@ $(OBJS)

$(objdir)/%.o:%.c
    gcc -c $< -o $@

$(OBJDIRS):
    @mkdir -p $@

clean:
    -rm -r -f $(objdir) $(TARGET)

.PHONY: clean all

还有就是查资料时发现了这个东东(order-only prerequisites),可惜没法用在我这里,因为用隐式规则我没法弄出目录依赖-_-!!!,下一步是把所有*.d文件也搞进目录中去。


gnu make的两种依赖关系(Prerequisites):

  1. 正常依赖(normal prerequisites)

    1)检查和重建依赖

    2)如果依赖更新,则要重建目标

  2. 顺序依赖(order-only prerequisites)

    1)检查和重建依赖

顺序依赖不会影响最终的目标的重建,这个适合与在makefile里建目录,因为目录作为目标有一个特性是目录里的文件改了,目录就更新了,导致一系列不必要的重建,用顺序依赖方式的话,就不回影响最终的目标了

Gnu make手册上给了个例子:

 OBJDIR := objdir
 OBJS := $(addprefix $(OBJDIR)/,foo.o bar.o baz.o)

 $(OBJDIR)/%.o : %.c
         $(COMPILE.c) $(OUTPUT_OPTION) $<

 all: $(OBJS)

 $(OBJS): | $(OBJDIR)

 $(OBJDIR):
         mkdir $(OBJDIR)

http://www.gnu.org/software/make/manual/html_node/Prerequisite-Types.html http://cm.techwell.com/articles/weekly/making-directories-gnu-make

虾米播放器程序失效

之前写的一个虾米播放器最近一段时间无法运行,每次运行显示Stream not seekable!直接挂断。查看了URL,发现虾米升级了服务器导致的。

因为mplayer依赖HTTP协议的accept-range头,如果服务器没设置就报错退出,我抓包分析了虾米服务器的HTTP应答,没发现accept-range,应该是这个原因。这边还有人说是mplayer的一个bug,因为不是所有服务器都严格遵守的,应该给服务器个机会嘛:)。

如果是这样的话就没法用mplayer了,得想其他办法了,初步只能用wget拖回整个专辑,然后慢慢放了-_-!!!,不过幸好mms电台不受影响,可以用mplayer放。

令人困惑的单位:kB和KiB

ubuntu 12.04目前ls -l 默认是1024为单位的KiB、MiB和GiB(显示K、M、G):

SIZE may be (or may be an integer optionally followed by) one of fol‐lowing: KB 1000, K 1024, MB 10001000, M 10241024, and so on for G, T, P, E, Z, Y.

而且有个选项–si可以显示以1000为单位的,-s显示文件占的块数(4KiB)。

但是我亲自试了一下,发现ls显示大小的小数部分貌似不是正常的四舍五入。--!!! 终于发现规律了,不是四舍五入,而是精确到位数后再加一,把剩下的小数进一,确保不小于原来大小(可以大,但不能小--!!!)。

例如:

$ls -l
-rw-rw-r--  1 xx xx  604066417  xx xx xx OpenWrt-xxx
$ll
-rw-rw-r--  1 xx xx  577M  xx xx xx OpenWrt-xxx

所以直接就进位了:

604066417 byte = 576.0826272964478 MiB = 577MiB

听说ubuntu 13.04要调整成1000为单位的KB、MB、GB。

http://forum.ubuntu.org.cn/viewtopic.php?f=49&t=401481

http://askubuntu.com/questions/465/is-it-true-that-ubuntu-will-no-longer-measure-file-size-unit-as-byte-megabyte

https://wiki.ubuntu.com/UnitsPolicy

http://en.wikipedia.org/wiki/Mebibyte

Linux pipe读写问题

之前的那个mplayer播放mms电台需要一个控制接口,mplayer只支持控制台输入命令,所以要控制mplayer只能用管道控制标准输入stdin。为了灵活就用FIFO文件作为mplayer的输入端。

mkfifo radio_pipe
cat radio_pipe | mplayer mms://xxx
echo -n p > radio_pipe

这里有个问题是cat radio_pipe读到文件EOF会退出,导致mplayer退出,所以每次只能控制一次:(。

前几天看mini2440附带的led-player程序,突然意识到可以自己写个pipe_read程序忽略文件EOF,这样就解决了mplayer的控制。记得之前写TCP程序时知道read返回0说明到文件EOF了,TCP就代表关闭了。我只要忽略掉read返回0的状态就行了(之后证明大错特错-_-!!!),错误是因为select认为文件EOF是读信号,除非你退出否则一直触发。最后的解决方法是这一句

int null_pipe_write_rd = open(pstrPipe, O_WRONLY | O_NONBLOCK);

再打开一个不操作的pipe读fd,这样才不至返回文件EOF,具体原因不是很明白:(,不过达到了我要的效果:)。

mkfifo radio_pipe
./pipe_read radio_pipe | mplayer mms://xxx
echo -n p > radio_pipe

这样就不会退出了,不过就如我们CEO说的一句话:解决了一个问题,往往带来了其他问题,这样pipe_read永远也不会退出了,尽管mplayer已经退出了-_-!!!。

Talk is cheap, show me the code:

#include <cstdio>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>

void usage(const char *pstrProgram)
{
    printf("Usage:\n"
           "  %s pipefile\n", pstrProgram);
}


int main(int argc, char *argv[])
{
    if (argc != 2)
    {
        usage(argv[0]);
        return 1;
    }

    bool bExit = false;
    const char *pstrPipe = argv[1];

    int pipe_rd = open(pstrPipe, O_RDONLY | O_NONBLOCK);
    if (pipe_rd == -1)
    {
        fprintf(stderr, "open pipe fail\n");
        return 1;
    }

    int null_pipe_write_rd = open(pstrPipe, O_WRONLY | O_NONBLOCK);

    while (!bExit)
    {
        fd_set fdset;
        //struct timeval tv;

        FD_ZERO(&fdset);
        FD_SET(pipe_rd, &fdset);

        int ret = select(pipe_rd+1, &fdset, NULL, NULL, NULL);

        if (ret < 0)
        {
            fprintf(stderr, "select return error\n");
            return 1;
        }
        else
        {
            if (FD_ISSET(pipe_rd, &fdset))
            {
                char buffer[128];
                while (true)
                {
                    int nLen = read(pipe_rd, buffer, sizeof(buffer));
                    if (nLen == 0)
                    {
                        fprintf(stderr, "End of file\n");
                        break;
                    }
                    else if (nLen == -1)
                    {
                        if (errno == EAGAIN)
                        {
                            fprintf(stderr, "nonblocking read fail\n");
                        }
                        else
                        {
                            fprintf(stderr, "read fail\n");
                            return 1;
                        }
                        break;
                    }
                    else
                    {
                        // write stdout
                        int ret = write(STDOUT_FILENO, buffer, nLen);
                        if (ret == -1 && errno != EAGAIN)
                        {
                            fprintf(stderr, "write fail\n");
                            return 1;
                        }
                    }
                }
            }
        }
    }

    return 0;
}