嵌入式微博

这次东南大学第四届嵌入式系统设计竞赛我的作品题目是嵌入式微博。这里把源代码全部释出,代码和文档均遵循Apache License。

下面是作品简介:

嵌入式微博是一个专门为嵌入式Linux设计的微博客户端,适合在小屏幕、低配置的设备上运行,系统配置要求极低。同时使用MiniGUI作为其图形库,可以适应非常广泛的设备,在低端产品实现富网络社交应用。本作品有下列特性:

1. 系统配置要求低,可用在小型低配置设备上流畅运行,同时使用MiniGUI绘图,可以确保适应绝大部分嵌入式系统。
2. 界面设计美观,全部UI均是手工精心绘制,一方面保证了作品的个性化和与同类产品的差异化,另一方面避免了大量使用丑陋古板的标准控件,同时还提升了整体界面的响应速度。
3. 专为小屏幕设备设计,考虑到小屏幕的特点而设计了专门的虚拟键盘,只要点击输入框就能调出虚拟键盘进行输入。虚拟键盘设计漂亮,功能强大,可以输入字母、标点、特殊符号,还带有一个简单但完善的全拼输入法。
4. 微博功能实现完备,支持发微博、转发消息、获取时间线、搜索话题、获取热门话题等常用微博操作,同时能完整展现富微博内容,可以完整漂亮的绘制头像、内容、转发消息、图片等,可以作为最终产品推出。
5. 微博内容绘制精美,同时做了大量的优化,可以保证在显示大量图片内容的情况下界面依然流畅。
6. 网络操作全部异步完成,在界面显示、操作流畅的同时,新开线程执行网络操作,在新的线程中下载、加载非常耗时的网络内容,让用户感觉不到网络的延迟。
7. 代码编写规范,整体模块化,可以很快适应之后的需求变更,能很快根据用户需求作出修改,开发出新产品。
8. 项目代码使用Autotools管理,非常适合Linux下开发,方便迁移至各种平台、架构下。

几张照片:

源码包:miniweibo-0.1.0.tar.gz

设计报告:嵌入式微博设计报告.doc

由于协议非常宽松,有同学想拿去用的话,请随意。也欢迎商业合作。

多线程环境下libcurl的一个Bug

今天运气不错,解决了两个非常棘手的Bug,这一个是关于libcurl的。

我的程序在一个线程中执行主循环,需要从网络下载东西时,就开启另一个线程,执行curl_easy_perform。问题是,程序在执行一段时间后会自己崩溃掉,backtrack如下:

#0  0x4001e416 in __kernel_vsyscall ()
#1  0x40491941 in raise (sig=6) at ../nptl/sysdeps/unix/sysv/linux/raise.c:64
#2  0x40494e42 in abort () at abort.c:92
#3  0x404c9305 in __libc_message (do_abort=2, 
    fmt=0x4059f36a "*** %s ***: %s terminated\n")
    at ../sysdeps/unix/sysv/linux/libc_fatal.c:189
#4  0x4054c970 in __fortify_fail (msg=<value optimized out>)
    at fortify_fail.c:32
#5  0x4054c8da in ____longjmp_chk ()
    at ../sysdeps/unix/sysv/linux/i386/____longjmp_chk.S:76
#6  0x4054c849 in __longjmp_chk (env=<value optimized out>, val=1)
    at ../setjmp/longjmp.c:40
#7  0x4020dd08 in alarmfunc () from /usr/lib/libcurl-gnutls.so.4
#8  <signal handler called>
#9  0x4001e416 in __kernel_vsyscall ()
#10 0x4004b930 in sem_wait@GLIBC_2.0 ()
    at ../nptl/sysdeps/unix/sysv/linux/i386/i686/../i486/sem_wait.S:318
#11 0x400fa7ac in PeekMessageEx (pMsg=0xbffff0b8, hWnd=134580240, 
    iMsgFilterMin=0, iMsgFilterMax=0, bWait=1, uRemoveMsg=1) at message.c:628
#12 0x0804b45f in GetMessage (this=0xbffff104)
    at /usr/local/include/minigui/window.h:1935
#13 WeiboUI::Application::run (this=0xbffff104) at application.cc:99
#14 0x08049df0 in MiniGUIAppMain (argc=1, argv=0xbffff274) at main.cc:56
#15 0x08049f4c in main (args=1, argv=0xbffff274) at main.cc:56

简单说明一下,就是主循环在执行sem_wait時,被一个信号打断,然后导致整个程序的崩溃。问题的关键就在那个alarmfunc (),是libcurl打断了我的循环。

最后以alarmfunc为关键词搜索后才发现,原来libcurl居然使用alarm来实现超时的判断!在多线程环境里,alarm产生的信号会打断一切主循环,然后导致非常难以察觉的Bug。

解决方法很简单,把CURLOPT_NOSIGNAL设为1就行了,这样libcurl将不会产生任何信号:

http://curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTNOSIGNAL

记一次修复MiniGUI相关Bug的过程

这个Bug连着困扰了我六七个小时,而且表现神秘不定,我觉得很有必要把它详细地记录下来。

事情源于我正在编写的一个GUI库,底层基于MiniGUI,主要用在嵌入式Linux下显示图形界面。最开始的表现是,我在本机编译完成后跑得好好的程序,在交叉编译后放到arm板子上一启动就崩溃,而且诡异的是,崩溃的几率和我类的参数有关,最开始我在类的destructor前加上了virtual之后,崩溃消失了,然后我又在类里加了些变量,程序又崩溃了。而且本机上一点崩溃的迹象也没有。所以我一度认为这是交叉编译器的Bug,无奈。

之后事情终于有了转机,本机上编译的程序终于也崩溃了!可见,很多时候程序员们是多么渴望程序崩溃啊!

于是马上调出gdb,崩溃处的代码如下:

dskOnNewCtrlInstance (hWnd=1075427776, message=361, wParam=134576144,
lParam=134575776) at desktop-comm.c:2683
2683 pNode->hWnd = (HWND)pNewCtrl;

p pNode 发现pNode是0x00,找到你了!难道是MiniGUI的Bug?

再仔细观察代码:

if (pNewCtrl->dwExStyle & WS_EX_CTRLASMAINWIN) {
    PZORDERNODE pNode = pNewCtrl->pZOrderNode;

不对!代码理应不会跳到这一段,我的dwExStyle并没有包含WS_EX_CTRLASMAINWIN,我设置的应该是WS_EX_NONE,最后前往注册窗口类的代码处,终于发现了Bug所在:

/* Register the common WINDOW_CLASS_NAME */
WNDCLASS WindowClass;
 
WindowClass.spClassName = (char*) WINDOW_CLASS_NAME;
WindowClass.dwStyle = WS_EX_NONE;
WindowClass.hCursor = GetSystemCursor(IDC_ARROW);
WindowClass.iBkColor = COLOR_lightwhite;
WindowClass.WinProc = BasicWindow::dispatch;

RegisterWindowClass(&WindowClass);

原来我没有设置dwExStyle,于是dwExStyle被填充入了垃圾数据,运气好時不会启动这个Bug,运气不好時就会崩溃,归根到底问题还是出在我自己身上……

最后虽然MiniGUI没有错,但也许一句 assert (NULL != pNode); 就能省下我许许多多的时间,也算是今后自己写代码的一个教训吧。

Begin coding now!

最近几周懒懒散散混了过去,代码一行也没敲过,真不像自己。

不过到此为止,学校的嵌入式竞赛正式开始,不出意料进入了复赛名单,准备开始艰辛地垒代码吧!

继续阅读

用printf的风格格式化std::string

一直很偏爱C风格的printf式格式化字符串,清晰而且高效。但是很遗憾C++的std::string并不支持这种格式化方法,只能用丑陋的ostringstream来模仿:

 std::ostringstream os;
 os << "Hello test " << someint << ' ' << somestring;
 std::string buffer(os.str());

不过我们可以自己写一个:

#include <stdio.h>
#include <stdarg.h>

std::string format(const char *fmt, ...)
{
    va_list ap;
    va_start(ap, fmt);

    const size_t SIZE = 512;
    char buffer[SIZE] = { 0 };
    vsnprintf(buffer, SIZE, fmt, ap);

    va_end(ap);

    return std::string(buffer);
}

原理很简单,vsnprintf可以直接使用变长变量列表来格式化字符串,我们只需要提供一个返回std::string的变长变量函数,然后使用vsnprintf来进行格式化操作即可。

当然还有几个小问题:buffer是固定字长的,会截断结果;返回的std::string需要对buffer进行一次复制,略有效率损失。

为什么不用匈牙利命名法

一直不喜欢匈牙利命名法,但周围的人几乎都推荐使用匈牙利命名法,尤其是很多计算机老师。大概他们都是伴随着微软长大的吧,匈牙利命名法见得多了就开始信奉了。

不过今天总算是找到支持者了。Imperfect C++ 里举了这么一个例子:

typedef map<string, map<string, int> > string_2_string_2_int_map_map_t;

string_2_string_2_int_map_map_t s2s2immIncludesDependencyTree;

不许笑!这可是实实在在出现在项目里的代码。真觉得,喜欢匈牙利命名法的人写代码時大概经常复制粘贴吧。

当然,并非微软的命名方式都是坏的,比如因为MFC而流行起来的 m_value ,就是在数据成员的名字前加上 m_ 前缀,我就很喜欢。Imperfect C++ 的脚注里这样评价道:

You see, there is something good about MFC!

嗯,这本书真对我胃口。

不打口水仗

小百合和虎踞龙盘上,十大第一名都在为日本争争吵吵。我一时兴起也参合了几句,现在想想,何必呢,谁都不可能说服谁的。

口水仗最没意思不过,永远不会分出结果,还得费上大量的时间和精力,最后弄了个蛮不痛快。所以,一定要提醒自己,不打口水仗。

淘宝暑期实习通过了

很意外,离上次电话面试过去了一个多星期了,而且自己感觉也很烂。结果今天下午接到了淘宝的电话,居然通过了,开心!

undefined reference to `rpl_malloc'

今天交叉编译一个程序時遇到了一个很奇怪的问题,编译到最后一步链接時,发生了下面的错误:

/opt/toolschain/3.4.1/arm-linux/lib/libjson.so: undefined reference to `rpl_realloc'
/opt/toolschain/3.4.1/arm-linux/lib/libjson.so: undefined reference to `rpl_malloc'

很显然,在链接libjson库時发生了错误,libjson不知道为什么链接了两个不存在的函数:rpl_realloc和rpl_malloc。因为工具链中的所有库都是自己编译的,所以只能从自己身上找错误==。

遂重新编译libjson,发现config.h里有下面的几句话:

/* Define to 1 if your system has a GNU libc compatible `malloc' function, and
to 0 otherwise. */
#define HAVE_MALLOC 0

/* Define to 1 if your system has a GNU libc compatible `realloc' function,
and to 0 otherwise. */
#define HAVE_REALLOC 0

...

/* Define to rpl_malloc if the replacement function should be used. */
#define malloc rpl_malloc

/* Define to rpl_realloc if the replacement function should be used. */
#define realloc rpl_realloc

看来是交叉编译時autotools认为我的工具链的libc中不包含malloc和realloc,然后擅自做主张给我替换成了rpl_malloc和rpl_realloc。把上面的几句话删掉后重新编译,就正常了。

bras图形客户端0.1.2

brasd是一个图形端的bras拨号程序,可以自动照料好网关和路由表。

安装方法
========
对于Ubuntu用户,可以从网络进行安装:
sudo add-apt-repository ppa:zcbenz/brasd
sudo apt-get update
sudo apt-get install brasd

或者直接安装附件中的deb包。

对于其他发行版的用户,需要自行编译源码进行安装:
tar zxvf brasd-0.1.2.tar.gz
cd brasd-0.1.2
./configure --prefix /usr
make
sudo make install
sudo make install-service # 安装系统服务

注意:
如果你的发行版的系统脚本没有储存在/etc/init.d/,请不要执行
make install-service,请将源码树根目录下的brasd.init文件复制到你的系统
脚本目录中。

程序依赖
========
libevent 和 gtkmm

更新日志
========
* bras-client可以记住多个账号和密码
* brasd支持多个客户端
* brasd启动時会结束正在运行的实例
* brasd使用/etc/brasd来进行设置
* 路由表分离到单独的文件中
* 代码更加健壮

已知问题
========
连接到bras后,如果中途切换了网络,可能会导致网络不正常,这时请重新连接一次
网络。这是由于在和bras服务器的连接断开后,xl2tpd不会马上结束,而是重试多次
后才会结束,此时会导致切换后的网络的路由表被破坏。

项目主页
========
https://launchpad.net/brasd

如何回报问题
============
1. 首先以debug模式启动brasd:
sudo brasd -D

如果brasd输出 "Cannot init server, brasd already runs?" 后退出,
请多试几次

2. 然后在另一个终端下记下此时的路由表:
route -n

3. 进行拨号

4. 记下此时的路由表:
route -n

5. 请把上面所有命令的输出记录下来,贴在回报问题的帖子中

6. 你可以在虎踞龙盘BBS的Linux版块回报问题,但我还是建议在launchpad的项目
页面Report a bug:
https://bugs.launchpad.net/brasd/+filebug

脚本化
======
对bras的控制通过sockets进行,也就是说你可以通过脚本来控制bras的状态。

查看bras状态:
echo "STAT" | netcat 127.0.0.1 10086

设置用户名和密码:
echo "SET username password" | netcat 127.0.0.1 10086

连接bras:
echo "CONNECT" | netcat 127.0.0.1 10086

断开bras:
echo "DISCONNECT" | netcat 127.0.0.1 10086

出于安全考量,默认情况下你无法远程控制bras,你可以在/etc/brasd中加上下面
的设置来打开网络控制:
internet on

这样将上面的127.0.0.1修改为远程机器的密码即可远程控制bras。

 

源码包下载地址:

http://launchpad.net/brasd/0.1/0.1.2/+download/brasd-0.1.2.tar.gz