qyb的博客

setproctitle

从 javaeye 的 robbin 那儿看到 rails 的这个技巧,觉得还是挺实用的,于是就下令在 web.py 上实现类似的功能。

经过 douyuan 同学的一番折腾,然后我也做了几个小实验,最后结论如下:

  • FreeBSD 下有 setproctitle(3) 函数来完成这个功能
  • Linux 对应的是系统调用 prctl(2), option 是 PR_SET_NAME, 宏定义是 15。注意 15 调用是从 2.6.9 引入的,虽然在 2.6.9 的内核头文件 linux/prctl.h 里没有 PR_SET_NAME 的宏定义,但仍然可以直接用 15 来成功调用... 换句话说,RHEL4/CentOS4的系统是可以利用这个特性的

对于 Linux 来说,prctl 并没有修改 argv/cmdline 的值,而是 /proc 文件系统里的内容(comm)。用 top 看是可以看到修改结果的,如果用 ps 命令,必须用参数 c 或者增加 output 格式 comm 才能看到修改结果,比如命令 ps axc (详情请参考 manual)

这样看在 FreeBSD or Linux 2.6.9+ 上用 ctypes 就可以搞定我想要的功能了,但对于更古老的 Linux 内核甚至别的操作系统怎么处理呢?看起来就只能去修改 argv[0] 的内容了,不过修改 argv[0] 也不是一件那么容易的事情,Linux 下运行例程:

  1. #include <unistd.h>
  2.  
  3. extern char **environ;
  4. int main(int argc, char *argv[])
  5. {
  6.     int i;
  7.     for (i = 0; i < argc; i++) {
  8.         printf("%p, %s\n", argv[i], argv[i]);
  9.     }
  10.     for (i = 0; environ[i]; i++) {
  11.         printf("%p, %s\n", environ[i], environ[i]);
  12.     }
  13. }

可以看到命令行参数,以及 environ 环境变量,是存储在一整块连续的内存上的(据说 Solaris 也是这样),如果向 argv[0] 里复制内容过长,有可能造成 environ 被破坏,进而对程序运行产生影响。正确的做法是先把 environ 内容复制出来,再修改 environ[] 数组里的指针地址到新的位置,然后就可以对 argv[0] 为所欲为了。nginx 的 os/unix/ngx_setproctitle.c 也是这么工作的。

换个角度,argv/environ 的这种存储方式使得如果知道所有 argv 的长度之和,从 environ[0] 向前偏移这个数字就是 argv[0] 的地址。这样看写一个 python extension 完成这个功能是可行的——虽然无法在模块里直接获得 argv[0] 地址,但可以取到 environ[0] 的地址,argv 的总长度则可以从 sys.argv 里计算出来然后作为参数传入... 这种方法潜在的问题也不少,比如在一个 python 进程里多次调用该怎么办,可似乎也只能这么来做了。

UPDATE: 发现 prctl 的一个问题是,它最多能设 15 字节长度的 comm, 这就导致可用性降低,也许最终还是要改 argv

Topic: 技术

You're beautiful

吃晚饭的时候,听到背后电视里传来 "You're beautiful, you're beautiful, you're beautiful, it's true",我很诧异——怎么经济节目突然放起 James Blunt 了,回过头来才发现,原来这是大众新宝来的广告曲..

我几乎喷饭,这也太扯了吧,怎么用这首歌来做广告?歌词的结尾可是 But it's time to face the truth,I will never be with you

估计看我 blog 的人知道 James Blunt 的也比较少,分享一下这首歌的 MV

Topic: 音乐

立起一个口香糖瓶

From blog 配图

绝非PS

我右手网络冲浪鼠标点来点去的时候,左手就在玩这个瓶子。瓶里大约还剩21-22颗口香糖的时候,成功的将瓶子斜着立起来了

现在还剩18颗,仍然可以这样立起来。

Topic: 生活

前两天碰到 greader/gmail 暴慢的故障

前一分钟还好好的,突然 gmail 和 greader 就同时慢了下来

故障现象是加载页面(包括 js)速度还算可以接受,但 Ajax 开始运行后就没有数据返回了。在这个期间内其它同事的应用都很正常,可见不是网络问题。被迫用 gmail 的 BasicHTML 模式——不过也是很慢。持续了大约一天的时间,然后突然又同时恢复。

猜测在请求个人的数据的时候(mail & feed),google 的前端 web 服务器收到 request 后,首先要和某个服务交互一下,然后才能继续请求其它服务。好比以前 gmail 的 addressbook 服务故障后导致所有人登录不了邮箱一样,这次又是 reader 和 mail 同时依赖的一个服务出了故障.

下回再碰到这种情况应该就有经验了,耐心等待就是

Topic: 技术

奇怪的休止符

在小步舞曲里面除了波音之外,还出现了一种诡异的休止符(五线谱外的休止符),见下:

From blog 配图

从右手谱来看这里明明是3/4拍的曲子,可左手谱...数一数居然是5/4拍了

搜索到一个达人解释,但看得还是稀里糊涂。不过天才qyb终于最后想明白了这里该怎么弹,就是:左手可以视为在弹两组音。第一小节的一组(一指弹奏)是一个1/4休止符+1/2的 Re,另一组(三指弹奏)是1/2的 Si + 1/4的Si;第二小节同样是(一指弹奏)1/4休止+1/2 Mi,以及 (二指弹奏)1/2 Do + 1/4 Do

详细说第一小节的指法是,左手三指首先弹 Si, 1/4 拍后一指弹 Re, 再 1/4 拍后三指抬起,再落下,1/4拍后小节结束..

详细曲谱在http://www.free-scores.com/download-sheet-music.php?pdf=239

PS: 我的练琴现在完全是自学。女儿还是继续她的汤普森+拜厄教程,我则是按照随电钢琴带的 60 首曲子的难度从低到高练习——难度分A/B/C三档。A档大概是8首曲子,而经典到恶俗的《致爱丽丝》被列入B档,估计要到明年中才开始练习了。电钢琴+Youbute,我觉得是互联网时代的新潮组合,比看什么教学DVD有趣多了

Topic: 音乐

today

1. 第一次一个人开车.
2. 小步舞曲勉勉强强能弹完整了
3. 把内部wiki路径整理了一遍

4. 发现搜狐内部原来有一个 cygwin 的镜像,同时被维护的还有一个 CPAN 镜像.

Topic: 生活

佩措尔德 G大调小步舞曲

《快乐的农夫》早就练好了,现在的目标是 Minuet in G major。

这首曲子听起来是相当的耳熟,以前一直传闻是巴赫的,后来考证出乃 Christian Petzold 的作品。现在右手已经很熟练了,左手的第一部分也很熟了。但在双手合练时碰到一个障碍——有一个小节是右手弹1/4音符,左手弹1/8音符——对右撇子来说,让左手弹得比右手快还保持节奏是挺不习惯的;而且紧跟这一小节后右手是一个波音左手有一个指位的变化,就更增加了偶这种菜鸟的心理畏惧感。

最近几个小时的练琴都是在攻克这个难关,争取本周能搞定,这个月把这首曲子练过去,08年结束之前再学一首新曲子

Topic: 音乐

关于 "#include"

我想写C程序都通常是把系统头文件放在最上面,然后是第三方库的,最后是我自己的,比如下面这样

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3.  
  4. #include <mysql.h>
  5.  
  6. #include <my_foo.h>
  7. ...

前天动手把我们的 apache module 移植到 nginx 上,里面自然是要 include 一堆 nginx 自己的头文件的。但它需要调 openssl 的库,那把 openssl 的头文件放哪里呢?我想了一下,觉得 openssl-devel 是系统自己 yum 安装上的,应该比 nginx 有更高的优先级,于是就是这么一个顺序

  1. #include <openssl/rsa.h>
  2. #include <openssl/pem.h>
  3.  
  4. #include <ngx_config.h>
  5. #include <ngx_core.h>
  6. #include <ngx_http.h>

(你看,我还是相当注重长幼尊卑的,正统思想啊~~)

这样程序似乎也没有问题,可就一个麻烦事情——调用 nginx 的 log 功能时就会 crash!用 gdb 跟踪,发现是使用 request->connection->log 的指针为空...

但就在 nginx 的代码里,向我的 handler 传入 request 之前,r->c->log 还是一个合法的指针,只要一进我的函数,这个就成了 NULL,而且一旦 handler 结束,指针值又非空了..

写到这里,聪明的读者就应该知道这是由于头文件预处理,导致两边结构体定义不一致。connection 这个结构体是如此定义的:

  1. struct ngx_connection_s {
  2. ...
  3. #if (NGX_SSL)
  4.     ngx_ssl_connection_t  *ssl;
  5. #endif
  6. ...
  7. };

就这么一个小小的 bug,足足花了2个小时才找到原因。

获得什么教训呢?

  1. 下回再用 gdb 跟踪到这种情况,应该能第一时间意识到结构体错误
  2. 如果要提供开发接口给第三方,那第三方可能直接使用的结构体里千万不要包括预处理代码
  3. 只要小心,是可以避免预处理的。但人总有大意的时候,那就养成一个好习惯,把预处理部分都放在结构体的最后,这就考验第三方开发者的人品了

最后是一篇新鲜热辣刚出炉的Nginx模块开发备忘:http://www.dup2.org/files/nginx_module_development.html
nginx 的中文 wiki 上有人写了一篇很好的翻译,我的备忘和其比起来,多了一些数据结构的分析。

Topic: 技术

发现 PyCrypto 不如 ctypes 来得方便

至少对于一个C程序员来说是这样..

有一段RSA签名的程序,原本是用 C 调 libcrypto 包装成 jni 由 java 使用的,现在打算在 python 里面实现一个类似的功能,这段 C 代码大概是这样:

int rsa_sign(char *private_keyfile, char *message, int message_len, char *buf, int buf_size)
{
RSA *key;
int malloc_size, rsa_size;

unsigned char *rsa;

BIO *bio;

bio = BIO_new_file(private_keyfile, "r");
if (NULL == bio) {
return 0;
}
key = (RSA *)PEM_read_bio_RSAPrivateKey(bio, NULL, NULL, NULL);
BIO_free(bio);
if (NULL == key) {
return 0;

}

malloc_size = RSA_size(key);
rsa = (unsigned char *)malloc(malloc_size);
if (NULL == rsa) {
RSA_free(key);
return 0;

}

if (0 == RSA_sign(NID_md5, message, message_len, rsa, &rsa_size, key)) {
free(rsa);
RSA_free(key);
return 0;
}

RSA_free(key);

if (rsa_size/3 * 4 >= buf_size) { /* 结果的 Base64 编码后长度大于 buf_size, 等于也错误(后面需要放 '\0' 的位置) */
free(rsa);
return 0;

}

b64_encode(rsa, rsa_size, buf);

free(rsa);
return 1;
}

可是找遍 PyCrypto, 楞是没有看到怎么实现 PEM_read_bio_RSAPrivateKey 这样从一个 PEM 格式的私钥文件里加载的功能。然后去看哪些基于 PyCrypto 包装的其他的包,比如 ezPyCrypto, Keyczar(这还是 google 发布的),包括 mail-list 上提供的 patch.... 这些似乎倒是可以 load key 了,可缺省好像也并没有对 NID_MD5 的支持, 绝望~~~~

于是抱着试试的心态用 ctypes 直接去调 libcrypto——以前只是在 PyS60 上玩票似的用过 ctypes,以及在 Win32 下用过,而且那时候 ctypes 还不太成熟——结果发现 python2.5 的 ctypes 竟如此的好用:

import ctypes

import base64

crypto_handler = ctypes.cdll.LoadLibrary("/usr/lib/libcrypto.so")
BIO_new_file = crypto_handler.BIO_new_file
PEM_read_bio_RSAPrivateKey = crypto_handler.PEM_read_bio_RSAPrivateKey
RSA_size = crypto_handler.RSA_size
RSA_sign = crypto_handler.RSA_sign
BIO_free = crypto_handler.BIO_free
RSA_free = crypto_handler.RSA_free

NID_md5 = 4

bio = BIO_new_file("/yourpathfile.key", "r")
key = PEM_read_bio_RSAPrivateKey(bio, 0, 0, 0)
r = BIO_free(bio)
if r != 1:
# break here

print 'BIO_free error'

rsa_size = RSA_size(key)
rsa = ctypes.create_string_buffer(rsa_size)

sign_size = ctypes.create_string_buffer(4)

message = "foobar"
if 1 != RSA_sign(NID_md5, message, len(message), rsa, sign_size, key):
print 'RSA_sign error'

RSA_free(key)

print base64.urlsafe_b64encode(rsa.raw)

Topic: 技术

Apache Module 之在各个 request 之间共享数据(资源)的方法

不论是1.3还是2.x,apache 的模式都包括一个进程服务好多次请求后再退出。现在有一个需求,在每个 request handler 里面希望保存一些数据,这些数据在以后该进程处理其他的 request 中可能还要用到。

最开始我从 request->server->process->pool 顺藤摸瓜看到一个貌似是和进程有关系的资源池。于是仿照 RUN_INIT_ONCE 的apr_pool_userdata_get/apr_pool_userdata_set 来访问资源。这样做的确可以在 request handler 之间共享数据,可是进程退出(apachectl stop)的时候不会去调 cleanup,非常之讨厌。

google 了半天,并参考 apache 自己的 LDAP 模块,发现官方解决方案好像是这样的:

  • 定义module_config结构体的时候,增加一个资源指针
  • 注册child_init的hook函数,从传入的第一个参数(pool指针)里面申请资源
  • 用apr_pool_cleanup_register注册cleanup函数在这个 pool 上(这个是cleanup的核心步骤,我感觉理论上完全可以自己重新创建一个pool,或哪怕是自己malloc,但关键是把cleanup函数注册到这个pool上。因为在进程退出的时候apache只会去销毁这个pool上的资源,而cleanup函数是"资源"之一)
  • ap_get_module_config, 把申请到的资源挂到自己定义的config结构体里

这样在进程退出的时候就自动 cleanup 了。这也是为什么到了 apache 2.0 后就不再提供 child_exit hook 的原因,因为开发者觉得由于有 cleanup 注册的机制,对应 child_init 的 child_exit 就不再有存在的必要。

唯一的疑问就是,request->server->process->pool 和在 child_init 时传入的 pool 是什么关系..... 不过懒得再去寻根究底了,先把需求实现了再说。


2008/11/25 UPDATE:上述的过程有一些问题,就是用 module_config 放资源指针,这样会导致虚拟主机的访问可能取不到 config。最简单粗暴的方法可能也是最有效的方法是用全局变量,当然别忘了 static 修饰。

Topic: 技术
订阅 RSS - qyb的博客