文章归档

wordpress latex的bug

latex for wordpress 插件的这个bug,因为目录不可写的情况下仍然尝试从LaTex Image server获取formula image,过多的进程以及处于等待数据状态的socket,可能导致httpd进程崩溃。

昨晚休息之前手机就收到linode的警告,说DISK io rate已经超过阈值,可惜实在太累,只好倒头就睡,顾不上这么多了。早上7点多陈老师消息过来,说web服务器无法访问,而这时linode的警告又来了,这次不但说DISK,连CPU都出问题了。然后我意识到,服务器可能被攻击了。 勉强通过ssh登录vps服务器,发现一堆httpd进程,这倒没什么,top一下cpu进程持续100%,查了apache的日志,很奇怪 access_log/error_log 的内容关于 May 2这天的日志都是空的,也就是从早上凌晨开始到现在,没有一个人访问过我们的服务器,我们的RP再低,也不至于这种情况吧。。。

再看了看linode后台的监控信息:

也就是说从昨晚6点到现在,CPU一直都是满负荷的而网站几乎没什么流量。但是昨晚这个时候我恰好在撰写blog,说明server应该没啥问题的。我重启了httpd服务,再刷新一下blog,可以正常访问,但是速度很慢,这个昨晚我也明显感觉到了。

我喜欢用latex写数学公式,有时为了查错经常需要刷新页面,所以我猜测会不会是latex的js引擎渲染的问题呢,然后对比测试了一下单独开启js渲染和cache image,结果发现js渲染居然比cache image要快很多很多,但是cache image的时候并没有显示image,可能是cache目录没有写权限。 按常理来说没法理解啊呵呵,没显示我的image又耗掉我那么多性能,联系到今天server的状况,大概是latex for wordpress有问题了。这个插件做了什么事情让server变得那么慢呢?然后看了看它代码。

if (!is_file($cache_formula_path) || filesize($cache_formula_path) < 10) { if (!class_exists('Snoopy')) require_once (ABSPATH.'/wp-includes/class-snoopy.php'); $snoopy = new Snoopy; $formula_text_html = str_replace('%C2%A0', '%20', rawurlencode(html_entity_decode(preg_replace('/\\label{.*?}/', '', $formula_text)))); $snoopy->fetch(get_option('latex_img_server').$formula_text_html); if (strlen($snoopy->results) < 10) $snoopy->fetch('http://www.quantnet.com/cgi-bin/mathtex.cgi?'.rawurlencode(($formula_text))); $cache_file = fopen($cache_formula_path, 'w'); fputs($cache_file, $snoopy->results); fclose($cache_file); }

之所以会导致这个问题,是因为latex for wordpress在做缓存的时候,是不检查目录是否可写的,它只检查文件是否存在或者文件大小小于10bytes,它在目录不可写的时候仍然尝试去下载formula image。如果你的page里有很多20条latex formula的话,httpd就不得不为每个连接创建20个子进程来接收这些数据,并发的情况下可能来不及接收完毕httpd就已经僵死在那里了。 解决的办法也简单,在做缓存的时候,优先检查目录是否具有写权限,如果目录不可写,就不去获取formula image如下:

$fileRWX = fileperms($cache_path); if (($fileRWX & 0x01B6) == 0x01B6) { if (!is_file($cache_formula_path) ||

»» 继续阅读全文

ae的“陷阱”?

ae (redis:ae.h ae.c) -> Asynchronous Event (libevent)

redis之后,ae这东西都快要酱成糊了。似乎很多人都在用,antirez 当初没有直接引入libev或者libevent这样的外部库,而是从中抽取了两个文件(就是redis src目录下的 ae.h ae.c 文件)改成适合自己的东西。其实ae不复杂,就是对epoll的API进行了一层简单的封装。

今天在写一个server的时候忽然想到了这个问题,遗留下来的,可惜一直没怎么注意过去看ae的实现而且一直纠结的认为是epoll工作原理的问题以致在理解方面走了很多弯路,很长的一段时间里我一度视ae为epoll的代名词,实际上不是那么一回事。

因为ae(或者说epoll)并发处理的性能实在太好了,好到每当我使用ae的时候都在犹豫要不要在回调函数里给全局变量加锁的笑剧,ae并发处理得那么快,我担心会不同步。举个例子,我用ae监听listenfd描述符,如果有连接来,执行一个回调函数,如 cbfunc(),cbfunc 会accept()返回一个client的sockfd,然后进行某些数据处理,期间会占用到我上面提到的某个全局变量一段时间。但是如果cbfunc()在执行期间,listenfd有有了一个新事件,有新的客户端连接进来了,然后继续执行回调函数cbfunc(),这里注意,是两个cbfunc()在同时执行吗?还是第二个cbfunc()的执行会等待第一个执行完成?如果不是,需要锁吗?

为了形象一下这个过程,我使用了nessDB和redis-cli两个程序来做模拟,因为nessDB就是使用了这个ae,而且nessDB兼容部分redis协议。

我修改了nessDB的db-server.c代码,阻塞第一个client连接10s,看看第二个client是否会先执行还是一直等待第一个client完成任务。

  1. ......
  2. static int cli_tok = 0;
  3. ......
  4. if (cli_tok == 0) {
  5. cli_tok = fd;
  6. sleep(10);
  7. }
  8. ......

cli_tok是全局变量,默认初始化为0,cli_tok的作用是标志当前链接是否为第一个连接,因为第一个连上server的client会对cli_tok修改,而修改之后cli_tok的值非0,其他的连接就会跳过sleep(10)的过程。然后启动db-server进程,启动两个redis-cli实例A和B。A先连接上db-server,然后执行一条get命令,然后B再连接上db-server,同样执行一条get命令,结果:

  1. $ ./redis-cli (A)
  2. redis 127.0.0.1:6379> get fangdong
  3. "fangdong_ok"
  4. (10.00s)
  5. redis 127.0.0.1:6379>
  1. $ ./redis-cli (B)
  2. redis 127.0.0.1:6379> get fangdong
  3. "fangdong_ok"
  4. (7.17s)
  5. redis 127.0.0.1:6379>

显然,B任务赤裸裸的被A阻塞了。

再看看ae中主要的实现代码

  1. #include "ae.h"
  2. ......
  3. n = epoll_wait(el->efd, el->ready, AE_SETSIZE, tvp ? (tvp->tv_sec*1000 + tvp->tv_usec/1000) : -1);
  4. n = n > 0 ? n : 0;
  5. for

    »» 继续阅读全文

透过BIOS获取Linux/Unix的设备信息

c 的标准里并没有提供读取硬件基本信息方面的API,但是有时候我们需要知道一些系统方面的信息,比如内存的规格,缓存,CPU以及磁盘等等。目前比较常见的方法就是通过读取BIOS信息。下文所述的方法理论上在各种Linux/Unix发行版里是适用的,但是没有仔细代码测试过,而且BIOS信息因各厂商不同而异,难有统一的标准,另一方面,BIOS信息量巨大,我仅作简单的描述

  1. Linux i386, x86-64, ia64
  2. FreeBSD i386, amd64
  3. NetBSD i386, amd64
  4. OpenBSD i386, amd64
  5. BeOS i386
  6. Cygwin i386
  7. Solaris x86
  8. Haiku i586

BIOS表

BIOS信息会在系统刚刚启动的时候加载到内存的0xF0000地址处,以表的形式组成,长度不会超过65536个字节。将那块内存区域的数据拷贝出来就可以看到,首先最前面的是_SM_和_DMI_两个信息头,如下,所有数据都是ASCII码

0x80613f8: "_SM_30370205d" 0x8061402: "" 0x8061403: "" 0x8061404: "" 0x8061405: "" 0x8061406: "" 0x8061407: "" 0x8061408: "_DMI_&3630520" 0x8061412: "16" 0x8061414: ","

还有别忘了上面每个字符串后面其实有一个'',编译器给省略了显示。首先分析_SM_头,挑重要的说。0205 表示SMBIOS信息的版本是2.5,37是_SM_头的长度,单位为字节。还有一个很重要的特性,是为了防止BIOS数据块损坏而起校验作用的,就是_SM_头内所有字节值的和是256的倍数,下面_DMI_的校验和这个也是一样的。_SM_头后紧接着就是_DMI_头,长度固定,15字节,_DMI_头里记录着后面记录的起始地址和长度,36305 两个字节表示长度,20001600 四个字节表示内存中的地址,而 ',' 表示记录的数量,这里 ',' 的值是44

BIOS记录

BIOS记录数据的首地址长度等信息在前面已经提到,这块区域储存了各种计算机设备的基本信息,包括CPU,缓存,内存,磁盘,鼠标,等等。

每条记录主要有两部分内容,一个是记录头,一个是记录备注,记录头里记录了记录头信息的长度,而记录备注则是连串的字符串,但是没有长度。那么如何确定下一条记录的位置呢,也不难,除了第一条记录之外,此后的每条记录前面必有两个连续的 00 字节。如下

0x806a04c: "013301" 0x806a050: "0102030420345y01J~21˥O355 370-21531206" 0x806a066: "03LENOVO" 0x806a06e: "28747GC" 0x806a076: "ThinkPad SL410" 0x806a085: "LRHVT48" 0x806a08d: "" 0x806a08e: "021702" /* 0x806a08e - 0x806a04c == 33 */ 0x806a092: "01020304" 0x806a097: ""

第一个01是Handle标志,33表示记录头的长度,后面的 01是DMI标志。如果DMI的值是127,表示记录到此结束。Handle标志表示该设备属于哪类设备,有43个值,参考如下:

"BIOS", /* 0 */ "System", "Base Board", "Chassis", "Processor",

»» 继续阅读全文

动态dns那点事

最近在捣腾着家里的老爷机准备搭建一台服务器,因为在 linux 环境中,动态域名解析的事可没 windows 那么方便,就只好自己动手来解决了。主要还是依赖 oray 的服务。

ip 问题

实践证明,不依赖于外界条件,仅从本地环境是无法获取公网ip的。想来也是,内网与外网通信很多本身就是靠NAT地址转换来工作的。服务器发出去的数据包,只包含真实的目的ip地址,而源ip却是由内网分配的,经过路由的时候会被转换。

所以不管用什么途径 gethostbyname() 也好,getaddrinfo() 也好 ,还是靠分析socket套接口的内容来获得 ip 地址等等,均不能获得其公网的 ip。原因很简单,看看你底层硬件网卡所记录的信息就知道了。

 

Linux/C 代码:获取网卡ip地址

  1. int getpubipv4(char *ipv4)
  2. {
  3. int sockfd;
  4. struct ifreq ifr;
  5. if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) == -1)
  6. return(0);
  7. ifr.ifr_addr.sa_family = AF_INET;
  8. strncpy(ifr.ifr_name, "eth0", IFNAMSIZ-1);
  9. ioctl(sockfd, SIOCGIFADDR, &ifr);
  10. sprintf(ipv4,"%s",
  11. inet_ntoa(((struct sockaddr_in *)&ifr.ifr_addr)->sin_addr));
  12. close(sockfd);
  13. return(0);
  14. }

不过没有关系,获取公网 ip 还有很多方便的办法,查看路由表(原理上应该没有问题,没有实际验证过)和借助第三方API工具,比如 oray 的 checkip 服务就非常的不错,http://ddns.oray.com/checkip 如果你不担心 oray 服务稳定性问题的话。oray 提供的 web 查询服务,我们用代码来模拟一个 http 请求即可完成,大概是这样

GET /checkip HTTP/1.1[CRLF] Host: ddns.oray.com[CRLF] Connection: close[CRLF] User-Agent: oray[CRLF] Accept-Charset: ISO-8859-1,UTF-8;q=0.7,*;q=0.7[CRLF] Cache-Control:

»» 继续阅读全文

用kindle来阅读源代码

最近新买了一台kindle 4, 好家伙,可惜不能用来看论文,糟蹋了,转换也是乱七八糟不能看

于是萌生了在kindle上面阅读源代码的想法,方法如下

第一步:合并源文件

检索一个source目录,遍历src下所有的.h .c 文件合并输出到一个文件,支持分级,每一个文件为一个chapter。如果有多个子目录,封装,再迭代函数

C++代码

  1. #include <stdio.h>
  2. #include <string.h>
  3. #include <sys/types.h>
  4. #include <dirent.h>
  5. #include <stdlib.h>
  6. #define BUFFSIZE 1024
  7. int main(int argc, char **argv)
  8. {
  9. char buf[BUFFSIZE], *p = buf;
  10. FILE *fp, *nfp;
  11. DIR *dp;
  12. struct dirent *dir;
  13. char chapter[BUFFSIZE];
  14. int cp = 1;
  15. if (argc!=3) {
  16. printf("Usage:......n");
  17. exit(0);
  18. }
  19. strcpy(buf,argv[1]);
  20. p = p+strlen(argv[1]);
  21. *p++ = '/';
  22. if ((nfp = fopen(argv[2], "w")) == NULL) {
  23. printf("error for open the file:%sn",argv[2]);
  24. exit(0);
  25. }
  26. »» 继续阅读全文

github的'\n'文件名漏洞

这几天在部署github的时候发现了一个小问题,恰好我有个文件的名字是包含 'n' 换行符的,push上去之后结果导致整个project webpages不能访问,一直提示:

An unexpected error seems to have occurred. Why not try refreshing your page? Or you can contact us if the problem persists.

排查了很久才发现原来是这个的问题。

n 在ASCII码里属于控制字符,而我们平时一般习惯把文件名这类当作字符流来操作的。比如在某些应用程序间的通信,就很有可能将n当作特殊字来处理。ext等文件系统又默认是支持带n的文件名。所以,后话就来了

我立马发上很多应用程序都不能正确处理这种情况,会出现各种莫名其妙的问题:

  • p7zip仅保留n前面的字符并写到结果文件里面去,但是结果文件的文件名却没有改变
  • gzip可以正常解压缩,但是通过gzip处理的文件,file-roller是不能读取,Segmentation fault. 调试了一下,发现在 gtk_main() 里堆栈崩溃了,也没法跟进去
  • jar和zip等会将n替换成 ^J 这样的字符,或者 ^J
  • rar无法正常工作,根本没法压缩。奇怪的是,通过file-roller来调用rar却可以生成压缩文档,不过没法读取
  • 。。。。。

 

目前的猜想,应该只是webpage的问题,和后端服务器程序没有关系。

Smaz的小对象压缩技术

基于小对象的压缩算法。一个叫做 Smaz 的项目,https://github.com/antirez/smaz 目前应用在redis数据库上面。

与传统的压缩算法不同的是,Smaz更适合小对象的压缩,比如几个字节到几十个字节不等。除开字典的硬编码部分,压缩过程和解压过程加起来120行代码,非常的短小精罕,却有不俗的表现。平均测试能够达到40%~50%左右的压缩效果。

当然,代码在某些方面不是完美的,也不健壮,但是很实用。 antirez坚持追求高速度而放弃部分的安全性。一些相关讨论,请看

http://news.ycombinator.com/item?id=540048

 

核心代码

Smaz的核心代码只有两部分:

  1. 字典的设计
  2. Hash函数

字典方面,Smaz 采用了硬编码的方式,antirez应该是对大量的相关语料进行统计,分析,进而得出最常用的字符组合

C++代码

  1. /* Reverse compression codebook, used for decompression */
  2. static char *Smaz_rcb[254] = {
  3. " ", "the", "e", "t", "a", "of", "o", "and", "i", "n", "s", "e ", "r", " th",
  4. " t", "in", "he", "th", "h", "he ", "to", "", "l", "s ", "d", " a", "an",
  5. "er", "c", " o", "d ", "on", " of", "re", "of ", "t ", ", ", "is", "u", "at",
  6. " ", "n ", "or", "which", "f", "m", "as", "it", "that",

    »» 继续阅读全文

Textpress开发笔记

美的东西一直是我所追求的,比如简洁与效率。

这几年用过不少Blog,先是 WordPress、MovableType、Drupal、Plone 后有 Textpattern、TextPress 等等,但总无法找到一个真正适合自己的Blog程序,也许是因为过分的追求完美,才对某些小小的问题过分苛刻吧。那个时候起,有了想自己动手写Blog程 序的想法,我这个人就是有点毛病,如果一样东西还能在我所能忍受的程度里解决我的需求,我就坚决不去改变它,所以我用了3年的vim,连自动补全都不会

真正动起手来是最近我在深圳研究生院(以下简称深研院)的那一个月里,大部分的空闲时间我都是在思考Blog程序的构架与设计上面,并且翻阅了不少 其他博客系统的源代码,包括 WordPress 和 Textpattern 等等。Textpattern 实在是太优秀了,我曾经花费过很长一段的时间去研究她,尤其是她的textile写作语法。

经过近一个月的不断调试和修改,我的第一个Blog程式诞生了,取名 Textpress 。没有 Tags ,没有 TrackBack ,只有简单的网志功能和一些基本的后台管理,但是我很喜欢它,她能满足我的需求,并且在我需要的时候,我可以很容易的扩展她,原始与质朴!

作为一名开发者,你想知道什么产品适合你,最好的办法就是:实践它,直到你满意为止

这是我深研院来为数不多的几张照片之一,感谢我女朋友,帮我记录了这些

后记:很怀念在深研院的这个月里,每天都过得很充实。我从来都没有这么深刻、认真的考虑过,我是否了解这个行业,我在做什么以及我将往何处走,在离开这个地方的时候,我给自己找到了一个答案。

很感谢我的女朋友,在这个月里给予我很多支持与鼓励,有时晚上回到家里还要加班到很夜没给她睡一个安稳觉,她也没怨过我,有时候忙得连饭都懒得吃, 本来可以好好打个瞌睡的时间却还要跑去食堂那么远的地方给我买饭。爱情的力量真是伟大啊还可以长肉,在我女朋友的悉心呵护下这个月我胖了四斤,真是前所未 有闻所未闻,希望回到学校不要一下子瘦掉,不然我会很心疼的。

今天女朋友研一新生开学了,心里很高兴,亲亲她

就这样吧,先去补牙

平衡二叉树的自平衡策略

本文的目的,旨在为数据结构或者算法的设计提供一些启发性的思路。

数组 --> 二分搜索 --> 链表 --> 二叉搜索树

二分搜索

二分搜索是一种很重要的算法。最普遍的莫过于对排序数组的处理。而数组的一个特质,使得二分搜索在离散存储结构里难以实现

  • 连续的内存区域,依靠下标来自动计算偏移量

就是说,对排序数组的操作,仅仅依靠下标就可以进行的。但是在实际的系统中,我们有时无法一次性提供足够大的连续存储空间或者海量处理那些离散地存储在内存里的数据的时候,如链表等等,这种方法不适用。

回想一下对排序数组进行二分搜索的操作,每次二分是不是都在最中间进行,那链表该如何才能做到呢?

答案是,做不到吧。离散的本质就是随机,你不可能找到中结点的位置。二叉搜索树的出现解决了这个问题,直接从中结点出发和决定下一分支。

所以现在的问题是:谁来担任中结点。

  1. 对于静态的数据,递归选举中结点,然后形成最优二叉搜索树
  2. 对于动态的数据,关键的地方在于,当插入一个结点或者删除一个结点的时候,如何平衡整个二叉搜索树的二分结构。

所以出现了自平衡,解决了二分搜索思想在离散存储结构里的应用。

自平衡的策略

所谓自平衡是指通过执行一系列维持某种性质的操作,这个性质,是为了更好的实现二分搜索的思想。如AVL的平衡因子,红黑树的5个强制约束,等等。

而旋转操作则是实现自平衡最常用的一项策略,通过左右子树迁移结点以实现结构的动态平衡

左旋,相当于右子树往左字树迁移两个结点,右旋同理。下面以红黑树为例,讨论自平衡策略的实现

旋转的时机

左右子树的结点数不想等时结构处于不平衡状态,不平衡就要旋转。但具体的实现取决于不同的场景和需求,由结构自身的性质来决定的。如AVL树在当左右子树结点总数相差1时进行旋转,(待)

封装epoll的异步API

epoll的设计坚持Unix哲学KISS原则,keep it simple, stupid

但是KISS并不等于实用。epoll提供三个简单的函数供应用程序进行异步事件处 理,epoll_create(),epoll_ctl(),epoll_wait()。这几个函数并不友好,尤其面对复杂的业务逻辑时,缺乏清晰的思路, 需要进一步的封装。

通常开发人员需要什么:

  1. 异步处理的对象,文件描述符
  2. 需要监听的事件,可读或可写(EPOLLIN|EPOLLOUT)
  3. 事件产生时的处理函数,epoll并不提供,机制与策略分离

所以我对 epoll 提供的接口进行简单易用的封装,以适合更一般化的场景需求。

数据结构

C 代码

  1. typedef void eProc(struct eloop *el, int fd, void *argv, int mask);
  2. /* File event structure */
  3. typedef struct efile {
  4. int mask;
  5. eProc *rProc;
  6. eProc *wProc;
  7. void *data;
  8. } efile_t;
  9. typedef struct eloop {
  10. int efd;
  11. int stop;
  12. efile_t ev[AE_SETSIZE];
  13. efire_t rd[AE_SETSIZE];
  14. } EL;

efile_t 作为基本的异步io对象,保存事件发生时调用的处理函数以及传参的数据。eloop 为最主要的数据结构,efd 为 epoll_create() 返回的 epoll 专用描述符号,负责多个epoll_event。ev[AE_SETSIZE] 保存正在监听的所有事件。如果 epoll_t 是监控机,则 efd为执行者,events 为被监控对象。每次 epoll_wait() 返回的结果保存在 rd[AE_SETSIZE] 里。

编程接口

C

»» 继续阅读全文

第 3 页,共 4 页1234