操作系统: 01 2013存档

想测一测Cubieboard最多能支撑多少tcp长链接(当然,是比较空闲的链接)。

我的cubieboard上安装的是linaro(其实就是linux),首先得调节单个进程打开的句柄数(修改/etc/security/limit.conf),然后还要修改sysctl里tcp_mem等网络参数(具体配置可以参考这里 1, 2) 然写了一套简单的TCP链接客户端和服务端的代码。Server端用epoll模型; Client端创建500个链接然后把fd放入数组里,每个connection每5秒发送128字节。
开始测试时,在cubieboar上我分别对5个端口启动了5个Server,客户端(我的笔记本)则是用脚本不停的创建Client实例。
测试结果:cubieboard可以支撑到10万个tcp长链接,网络流量稳定保持在3.75MB/s,此时系统内存还有大约300MB free(总共1G),CPU idle在20%左右
cubieboard只有网口没有网卡(也就是没有network controller),所有的收包解包都由CPU完成,所以能支持的流量比较有限,不过撑个10万的闲链接还是没问题的。

还想了解一下cubieboard对https的支撑能力,于是我又在cubieboard上装了tengine(在arm上编译还挺顺利,连warning都没有,编译时间4分钟24秒),配上https签名(参加这里)再用ab压力测试,ab压测脚本是:
ab -n 20000 -c 20 -k url
测试结果:访问https的QPS为755, 访问http的QPS为1269

看来用cubieboard搭个小网站是绰绰有余了,一个小的聊天服务器兴许也可以。


====== 2013.1.28 ======

应 @pythonee 同学的要求,测试了200个并发:

ab -n 20000 -c 200 -k url

测试结果:访问https的QPS为858,访问http的QPS为1741,毕竟是单核,提升不明显,压测的时候CPU idle已经为0了
我另外还试了一下把openssl的签名长度改为512bit,以减轻CPU的压力,再测https,QPS可以到959,当然,签名长度再短,QPS也不可能高于http的1741了

cubieboard

皓庭同学在给一台测试机做压力的时候出现了kernel panic:

<4>[  223.202997] RIP: 0010:[<ffffffff81194e79>]  [<ffffffff81194e79>] iput+0x69/0x70
......
<4>[  223.210475]  [<ffffffffa007de6d>] ext4_free_io_end+0x2d/0x40 [ext4]
<4>[  223.210864]  [<ffffffffa007e02c>] ext4_end_aio_dio_work+0xac/0xf0 [ext4]
<4>[  223.211277]  [<ffffffffa007df80>] ? ext4_end_aio_dio_work+0x0/0xf0 [ext4]
<4>[  223.211693]  [<ffffffff8108cc50>] worker_thread+0x170/0x2a0
<4>[  223.212033]  [<ffffffff810925c0>] ? autoremove_wake_function+0x0/0x40
<4>[  223.212425]  [<ffffffff8108cae0>] ? worker_thread+0x0/0x2a0
<4>[  223.227238]  [<ffffffff81092256>] kthread+0x96/0xa0
<4>[  223.241935]  [<ffffffff8100c10a>] child_rip+0xa/0x20
<4>[  223.256518]  [<ffffffff810921c0>] ? kthread+0x0/0xa0
<4>[  223.271085]  [<ffffffff8100c100>] ? child_rip+0x0/0x20

重现的环境和步骤都很简单:只要在redhat linux-2.6.32-279.14.1 kernel上进一个使用ext4的目录一跑:

stress --cpu 2 --io 2 --vm 1 --vm-bytes 128M --hdd 2 --timeout 1d

几分钟后就出现。
这个stress工具果然是神器。
由于重现步骤简单,我也没有太多需要绞尽脑汁的猜测和bug重现工作,就是加trace_printk一点一点调试,最后找到了原因:

stress测试工具是用DIRECT_IO发起的aio(异步IO)请求,在ext4里对于DIRECT_IO请求,都会用ext4_init_io_end初始化一个ext4_io_end_t结构,初始化时调用igrab对该inode里面的i_count进行递增;

struct inode *igrab(struct inode *inode)
{               
        spin_lock(&inode_lock);                                                    
        if (!(inode->i_state & (I_FREEING|I_CLEAR|I_WILL_FREE)))
                __iget(inode);
        else {  
                /*
                 * Handle the case where s_op->clear_inode is not been             
                 * called yet, and somebody is calling igrab
                 * while the inode is getting freed.
                 */          
                printk("%s: %lx\n", __func__, inode->i_state);                     
                inode = NULL;
        }
        spin_unlock(&inode_lock);
        return inode;
}       

这些aio结束后,会调用ext4_free_io_end进而调用iput把inode的i_count递减,等到i_count递减到0时,就用iput_final把该inode置为I_CLEAR,意思是该inode可被清除了(当内存不够的时候会把这种I_CLEAR的inode占的内存空间回收)。
但是,如果一个文件已经被rm了,即已经调用了generic_delete_inode,那么inode就已经置上了I_FREEING,此时再调用igrab时,由于第一个判断无效,inode里的i_count就不会被加1了,i_count会一直保持0!等到aio结束后,iput被调用了多次,第一次触发iput_final把inode置上I_CLEAR,第二次就坏了,触发BUG_ON

void iput(struct inode *inode)          
{                                                                            
        if (inode) {                                                               
                BUG_ON(inode->i_state == I_CLEAR);              
                              
                if (atomic_dec_and_lock(&inode->i_count, &inode_lock))
                        iput_final(inode);
        }                                                                          
}

因为一般对同一个inode,调了iput_final就不能再调iput了,因为你已经final了、没了。

说来说去,就是igrab似乎不该做那个判断,或者,干脆不要用igrab,用别的计数方式。翻了一下upstream,得,又已经被fix了,commit f7ad6d2e9201a6e1c9ee6530a291452eb695feb8, Ted两年前就已经fix了,抛弃了igrab改为自己管理计数。这个fix backport到rhel-2.6.32-279 kernel还需要一些修改,好在有文卿帮我review代码。

还要感谢皓庭同学帮忙找到了这么快速的重现方法,以及余峰老哥推荐的stress这个生猛工具。


====== 2013.1.18 ======

很不好意思,patch的标题写错了,还请忽略标题,我回头再改,patch内容是对的。