DongHao: 01 2010存档

重现

| | Comments (2) | TrackBacks (0)
周一晚餐吃了碗牛肉汤,开始胃胀。
于是按太太的意见,买了点健胃消食片和大山楂丸。

今天中午吃饭。

我:  昨天吃了消食片和山楂丸,胃好多了。
太太:  说明你胃没什么事儿,就是吃太快了,牛肉不易消化。
我:  但是何以肯定是牛肉呢?如果是bug,必须能重现才行,所以,我要再喝一碗牛肉汤。
太太:  ....

使用http_load的过程中遇到了一个报错:Cannot assign requested address

网上找到了原因和解决方案 http://gcoder.blogbus.com/logs/41839731.html

但是要注意,这是客户端的问题,是客户端用光了端口号,所以要改客户端机器的配置和代码,不是改服务端的!

我在http_load.c里给socket加了一个SO_REUSEADDR,才能工作正常。为什么http_load不加上这个option呢?因为http_load测的是标准的http服务器,比如apache,这些服务端会关闭socket,而我测的是自己写的httpd,所以....看来细节很多啊。


====== 2010.08.18 ======

今天又遇到这个问题,在代码里加了SO_REUSEADDR也没用,只能采用在sysctl.conf里加:

net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_tw_recycle = 1

的办法

pthread_t    pthr;

pthread_create(&pthr, NULL, thread_handler, NULL);

...

void* thread_handler(void* arg)

{

    /* do something */

    pthread_join(pthr, NULL);

}


上面的代码不好使,pthread_join不能放在pthread调用的handler内,虽然不报错,但是thread无法正常回收,如果多次创建thread,内存会越来越大(另一种形式的内存泄露)。

正确的做法是在handler外面pthread_join:


pthread_t    pthr;

pthread_create(&pthr, NULL, thread_handler, NULL);

pthread_join(pthr, NULL);

...

void* thread_handler(void* arg)

{

    /* do something */

}


如果不用pthread_join,改用pthread_detach呢?那最方便,但要注意:pthread_detach最好是放在handler里面第一句。


void* thread_handler(void* arg)

{

    pthread_detach(pthr);

    /* do something */

}


如果pthread_create后紧跟pthread_detach,有可能会出错。

小心使用grep里的星号(*)

grep "/home/.*test"

上面这句是找出所有/home目录下,以test结尾的文件

grep "/home/*test"

这句可就不是了,这句grep只匹配以下的文件名:

/hometest

/home/test

/home//test

/home///test

看明白了,星号(*)代表重复其前面的字符任意次(包括零),而“/*“当然就表示重复”/”任意次。

另外注意grep后面的正则表达式务必加上双引号,如果不加,在一些特殊场合会有完全不同的含义。

比如我当前目录下有六个文件:

testa

testb

testc

testd

tt

t

运行:

ls -l|grep t*t

它会把这五个文件都列出来吗?不,结果显示只找到了文件tt

因为上面的shell命令,grep实际上是在找以t开头且以t结尾的文件(这一规则和ls的规则一样)

要想grep把后面的”t*t”当成正则表达式,需要加上双引号:

ls -l|grep "t*t"

这下结果ok了:所有六个文件都可以被找到。

struct example
{
    ....
    int errno
};

上面的example结构使用errno做成员变量名,按理说不会和<errno.h>里的errno宏冲突,因为它在struct里,何况我根本没有包含errno.h文件
在大部分机器上,这可以编译成功。但是,在有的环境确实无法编译通过,gcc不认errno前面的“->”指针符号。
看来<errno.h>里的errno宏漏进了文件,但是怎么漏进来的?不详。
这位老兄和我遇到的问题一样: http://gcc.gnu.org/ml/gcc/2001-12/msg00766.html
但那毕竟是老gcc下的问题了,现在应该没有了。

在查到详细原因之前,还是把errno改了为妙,毕竟变量名和系统全局变量(或宏)弄成一样,是有风险的。

未了的传奇

| | Comments (0) | TrackBacks (0)
      上世纪60年代,泛美航空的老大和波音公司的老大碰了个头,泛美的老大说:
      “俺们要一款特能装人的飞机,最好是分上下两层,使劲装,装得越多,俺们钱赚得越多哩”
      波音的老大说:
      好办!我叫人做个超大的,像货轮那么大的!”
      两个老大拍了板,下面的人就忙起来。

      乔.萨特从小在西雅图长大,只比1916年建立的波音公司小5岁,他的童年是看着波音的一架架试飞飞机渡过的,每款他都很熟悉,于是干脆二战结束后就进了波音,当工程师。
      听到航空公司的客户需求代表说要造大飞机,乔.萨特问:
      “你要装200人?300人?还是400人?”
      “450人!“
      “扶住我......”,乔.萨特喘了口气,重新站稳:“没问题!俺们给你造!”

      乔.萨特出任波音747项目的技术总监,为了在几年内造出这架人类史上最大的喷气式客机,整个747项目组都在加班,从9点到23点,从周一到周六,经常半夜上线....不好意思,说蹿了,是半夜交付设计图纸。

      波音当时还有“超音速运输机”计划、“第二代737改型“计划,还派出工程师去帮NASA造火箭——总之,747项目的资源很少,顶尖的工程师和管理人员都在搞很前卫的“超音速运输机”项目。
      乔. 萨特不仅要去抢资源,还要对付“外来的婆婆”——就是从其它项目调来的、一来就想代替萨特当主管的新管理人员,这些人很讨厌,由于中途做747项目,他们 啥也不清楚,但却想表现自己的“决断力”,于是,一通瞎指挥。此时,乔.萨特表现出了他工程师的斩钉截铁的个性,他直接对总部说:
      “要么调他们离开,要么调我离开”
      说完开车回家。
      要不说波音牛呢,他们在关键时刻选择了“专家”,而不是“专管”。于是,那家伙走人了,乔.萨特复原职。

      波音747用了4台普惠公司的JT9D发动机,其中一台的推力就是4万磅(波音737的发动机是一台1.5万磅,大家可以畅想一下),如此巨大而新型的发动机,当然是问题不断。747原型机在测试的时候,经常会有发动机损坏、起火、融化、爆炸等,俨然四个定时炸弹。
      乔.萨特每遇问题,必狠踢普惠公司的屁股,直到惠普交付合格的发动机。
   
      但是,普惠的JT9D发动机当时还有“喘振”的问题。所谓“喘振”,就是发动机会突然像患了哮喘一样,一会儿吸不进气,一会儿有大量吸进,有时甚至倒转往前喷气。没有哪个乘客想遭遇这样的发动机吧?
      “喘振”问题是航空发动机最难克服的问题,很难重现,重现条件不明,原因也就难以定位,就像软件的内存泄露bug一样,难以对付。
      更夸张的是,惠普根本不打算去fix这个bug,他们等着波音来fix。波音也算人才济济,居然有空气动力学的专家找出了原因,告诉了普惠,普惠这才有了今天优良的JT9D发动机。
      所以,用别人的组件遇到问题时,别光等,找找原因,如果自己克服了,以后就能鄙视提供组件的人。

      波音747于1969年首飞成功,缔造了航空业的传奇。成为美国航空技术的标志。即使到今天也只有美、俄、欧洲能造大型客机。其它的国家还有机会吗?先确立正确的科学观念、培养实用的技术人才再说吧。

      今天大家只记得采用成熟技术的波音747飞机,而那个采用太空技术的高级货“超音速运输机”项目,早已没人记得了。
      技术这种东西,重要的是好用,好赚钱,而不是为了好听,或听起来酷。
      就像当年何等火热的COM技术、ATL、网格计算、SOA等等,现在应该是冷门了吧?那今天的云计算、框计算,是否也会一样呢?让我们拭目以待。
      先看一段新闻《中大面试问“偷菜” 男生不知是何物向母哭诉》
      可怜的乖学生,估计平时刻苦读书,不怎么上网,结果大学面试却问网络词汇,他一定觉得很冤:我这么刻苦,却反而不如爱上网玩的“懒学生”吗?!
      我多年前的名言早有预见,足见在下已然半仙附体。

      记得读研的时候,我乖乖的学操作系统、学编译器、学数据结构,成天对着教材看,东西是学会了,也理解得比较深。
      但当我去面试的时候,考官问了:“你懂网络攻防吗?”,我木然,我是乖学生啊,从不敢攻击别人的机器,又怎么知道该如何防别人攻击呢?
      我再去面试的时候,考官问了:“你懂广告防恶意点击吗?”,我再次木然,我是乖学生啊,从不敢恶意乱点别人的广告,又怎么知道该如何防别人恶意点击呢?

      你熟悉操作系统?毛病,这儿又没人做操作系统。
      你喜欢编译器?毛病,这儿又没人做编译器。
      你老老实实的读书顶个屁用?

      熊猫烧香的作者出狱了,一出来就多家公司聘请,他可是大学都没读过。对比当下连研究生找工作都困难,我只能说:
      如今已是坏孩子的天空。

      所以,各位还在苦读的同学——知道该怎么做了吧。
作者:董昊 (要转载的同学帮忙把名字和博客链接http://oldblog.donghao.org/uii/带上,多谢了!)

做项目中遇到一个问题。两台机器上用socket建立一个TCP连接,双向通信,流量很大,这时,通过在路由器上设置100%的丢包率将网络断开,这时 socket当然是发不了包,也收不了,出现大量的重传,然后,取消路由器上的设置,恢复网络,结果,TCP连接client去往server的流量正常 了,但server去往client却不通,任凭你如何使劲的send,返回值就是0,而且errno为EAGAIN。
我用tcpdump看了一下此时的包数据(tc2是server,tc1是client):

  12:08:21.020291 IP tc1.corp.com.42171 > tc2.corp.com.3003: S 4009389430:4009389430(0) win 5840
  12:08:21.020571 IP tc2.corp.com.3003 > tc1.corp.com.42171: R 0:0(0) ack 4009389431 win 0
  12:08:38.934329 IP tc2.corp.com.3903 > tc1.corp.com.3904: P 2398055392:2398056153(761) ack 2538876742 win 724
  12:08:38.934519 IP tc1.corp.com.3904 > tc2.corp.com.3903: . ack 2165 win 13756
  12:08:39.958457 IP tc1.corp.com.3904 > tc2.corp.com.3903: P 1:763(762) ack 2165 win 13756
  12:08:39.958485 IP tc2.corp.com.3903 > tc1.corp.com.3904: . ack 763 win 1448
  12:08:39.958653 IP tc1.corp.com.3904 > tc2.corp.com.3903: P 763:881(118) ack 2165 win 13756
  12:08:39.958660 IP tc1.corp.com.3904 > tc2.corp.com.3903: P 881:997(116) ack 2165 win 13756
  12:08:39.958719 IP tc2.corp.com.3903 > tc1.corp.com.3904: . ack 997 win 1448
  12:08:39.958890 IP tc1.corp.com.3904 > tc2.corp.com.3903: P 997:1114(117) ack 2165 win 13756
  12:08:39.958898 IP tc1.corp.com.3904 > tc2.corp.com.3903: P 1114:1232(118) ack 2165 win 13756
  12:08:39.958903 IP tc1.corp.com.3904 > tc2.corp.com.3903: P 1232:1349(117) ack 2165 win 13756
  12:08:39.958971 IP tc2.corp.com.3903 > tc1.corp.com.3904: . ack 1349 win 1448
  12:08:39.959141 IP tc1.corp.com.3904 > tc2.corp.com.3903: P 1349:1466(117) ack 2165 win 13756
  12:08:39.959149 IP tc1.corp.com.3904 > tc2.corp.com.3903: P 1466:1583(117) ack 2165 win 13756
  12:08:39.959154 IP tc1.corp.com.3904 > tc2.corp.com.3903: P 1583:1700(117) ack 2165 win 13756
  12:08:39.959222 IP tc2.corp.com.3903 > tc1.corp.com.3904: . ack 1700 win 1448
 

tc2不发自己的数据,却只是一味的ACK从tc1传来的数据,等上半个小时,依然如此。它为什么不发呢?

最后发现是因为我们在socket上设了TCP_NODELAY。去掉这个设置,重启程序,断网恢复以后,TCP双向正常工作。同样用tcpdump看:

  16:05:38.782427 IP tc2.corp.alimama.com.3903 > tc1.corp.alimama.com.3904: P 0:887(887) ack 1 win 26064
  16:05:38.782619 IP tc1.corp.alimama.com.3904 > tc2.corp.alimama.com.3903: . ack 3783 win 25352
  16:05:38.782634 IP tc2.corp.alimama.com.3903 > tc1.corp.alimama.com.3904: . 3783:5231(1448) ack 1 win 26064
  16:05:38.782637 IP tc2.corp.alimama.com.3903 > tc1.corp.alimama.com.3904: . 5231:6679(1448) ack 1 win 26064
  16:05:38.782890 IP tc1.corp.alimama.com.3904 > tc2.corp.alimama.com.3903: . ack 5231 win 25352 
  16:05:38.782896 IP tc2.corp.alimama.com.3903 > tc1.corp.alimama.com.3904: . 6679:8127(1448) ack 1 win 26064
  16:05:38.782898 IP tc2.corp.alimama.com.3903 > tc1.corp.alimama.com.3904: . 8127:9575(1448) ack 1 win 26064
  16:05:38.782901 IP tc1.corp.alimama.com.3904 > tc2.corp.alimama.com.3903: . ack 6679 win 25352 
  16:05:38.782904 IP tc2.corp.alimama.com.3903 > tc1.corp.alimama.com.3904: . 9575:11023(1448) ack 1 win 26064
  16:05:38.783183 IP tc1.corp.alimama.com.3904 > tc2.corp.alimama.com.3903: . ack 8127 win 25352
  16:05:38.783188 IP tc2.corp.alimama.com.3903 > tc1.corp.alimama.com.3904: . 11023:12471(1448) ack 1 win 26064
  16:05:38.783191 IP tc1.corp.alimama.com.3904 > tc2.corp.alimama.com.3903: . ack 9575 win 25352
  16:05:38.783193 IP tc2.corp.alimama.com.3903 > tc1.corp.alimama.com.3904: . 12471:13919(1448) ack 1 win 26064
  16:05:38.783196 IP tc1.corp.alimama.com.3904 > tc2.corp.alimama.com.3903: . ack 11023 win 25352
  16:05:38.783199 IP tc2.corp.alimama.com.3903 > tc1.corp.alimama.com.3904: . 13919:15367(1448) ack 1 win 26064
  16:05:38.783201 IP tc2.corp.alimama.com.3903 > tc1.corp.alimama.com.3904: . 15367:16815(1448) ack 1 win 26064
  16:05:38.783502 IP tc1.corp.alimama.com.3904 > tc2.corp.alimama.com.3903: . ack 12471 win 25352
  16:05:38.783506 IP tc2.corp.alimama.com.3903 > tc1.corp.alimama.com.3904: . 16815:18263(1448) ack 1 win 26064
  16:05:38.783509 IP tc1.corp.alimama.com.3904 > tc2.corp.alimama.com.3903: . ack 13919 win 25352
  16:05:38.783512 IP tc2.corp.alimama.com.3903 > tc1.corp.alimama.com.3904: . 18263:19711(1448) ack 1 win 26064
  16:05:38.783514 IP tc1.corp.alimama.com.3904 > tc2.corp.alimama.com.3903: . ack 15367 win 25352
  16:05:38.783517 IP tc2.corp.alimama.com.3903 > tc1.corp.alimama.com.3904: . 19711:21159(1448) ack 1 win 26064
  16:05:38.783519 IP tc1.corp.alimama.com.3904 > tc2.corp.alimama.com.3903: . ack 16815 win 25352

tc2这次发自己的数据流了,tc1对其ACK,过了一段时间,tc1也开始发数据,最后双向正常。

为什么带了TCP_NODEALY的socket,在网络好了以后恢复不了正常?
看看recv系统调用的实现(2.6.9内核),一直追溯到tcp_recvmsg函数:

[net/ipv4/tcp.c --> tcp_recvmsg]
   813     while (--iovlen >= 0) {
   814         int seglen = iov->iov_len;
   815         unsigned char __user *from = iov->iov_base;
   816
   817         iov++;
   818
   819         while (seglen > 0) {
   820             int copy;
   821
   822             skb = sk->sk_write_queue.prev;
   823
   824             if (!sk->sk_send_head ||
   825                 (copy = mss_now - skb->len) <= 0) {
   826
   827 new_segment:
   828                 /* Allocate new segment. If the interface is SG,
   829                  * allocate skb fitting to single page.
   830                  */
   831                 if (!sk_stream_memory_free(sk))
   832                     goto wait_for_sndbuf;
   833
   834                 skb = sk_stream_alloc_pskb(sk, select_size(sk, tp),
   835                                0, sk->sk_allocation);
   836                 if (!skb)
   837                     goto wait_for_memory;

831行判断sndbuf里还有没有空间,如果没有,跳到wait_for_sndbuf

[net/ipv4/tcp.c --> tcp_recvmsg]
   958 wait_for_sndbuf:
   959             set_bit(SOCK_NOSPACE, &sk->sk_socket->flags);
   960 wait_for_memory:
   961             if (copied)
   962                 tcp_push(sk, tp, flags & ~MSG_MORE, mss_now, TCP_NAGLE_PUSH);
   963
   964             if ((err = sk_stream_wait_memory(sk, &timeo)) != 0)
   965                 goto do_error;
   966
   967             mss_now = tcp_current_mss(sk, !(flags&MSG_OOB));
   968         }
   969     }
   970
   971 out:
   972     if (copied)
   973         tcp_push(sk, tp, flags, mss_now, tp->nonagle);
   974     TCP_CHECK_TIMER(sk);
   975     release_sock(sk);
   976     return copied;
   977
   978 do_fault:
   979     if (!skb->len) {
   980         if (sk->sk_send_head == skb)
   981             sk->sk_send_head = NULL;
   982         __skb_unlink(skb, skb->list);
   983         sk_stream_free_skb(sk, skb);
   984     }
   985
   986 do_error:
   987     if (copied)
   988         goto out;
   989 out_err:
   990     err = sk_stream_error(sk, flags, err);
   991     TCP_CHECK_TIMER(sk);
   992     release_sock(sk);
   993     return err;

sndbuf 不够,于是设个bit位,961行的判断不成立,因为这会儿还啥也没发送,copied为0。继续,执行sk_stream_wait_memory,顾 名思义,它是等snbbuf有可用空间,但是我们的socket是设了NONBLOCK的,所以sk_stream_wait_memory很快返回,并 设返回值为-EAGAIN。所以,又要跳到do_error,987行的判断依然不成立,于是到了out_err,最后带着-EAGAIN离开 tcp_recvmsg函数。
这就是我们不停send,却返回结果为0且errno为EAGAIN的原因。
如果一切正常,socket不停的往外发数据,早晚sndbuf会出现可用空间的。但如果异常呢?比如设了TCP_NODELAY而网络又断了,那就瞬间会发送大量的包,对端却没有ACK。
我们再看看如果正常,tcp_sendmsg会如何:832行的跳转是不会发生了,于是,程序继续往下(略去一部分skb的操作代码)

[net/ipv4/tcp.c --> tcp_sendmsg]
   936             if (!copied)
   937                 TCP_SKB_CB(skb)->flags &= ~TCPCB_FLAG_PSH;
   938
   939             tp->write_seq += copy;
   940             TCP_SKB_CB(skb)->end_seq += copy;
   941             skb_shinfo(skb)->tso_segs = 0;
   942
   943             from += copy;
   944             copied += copy;
   945             if ((seglen -= copy) == 0 && iovlen == 0)
   946                 goto out;

如果这一把就把消息全放进了skb,且iovec也轮完了,此时945行的判断就生效了,直接跳转out,执行tcp_push。tcp_push调用__tcp_push_pending_frame:

[net/ipv4/tcp.h --> __tcp_push_pending_frame]
  1508 static __inline__ void __tcp_push_pending_frames(struct sock *sk,
  1509                          struct tcp_opt *tp,
  1510                          unsigned cur_mss,
  1511                          int nonagle)
  1512 {          
  1513     struct sk_buff *skb = sk->sk_send_head;
  1514    
  1515     if (skb) {
  1516         if (!tcp_skb_is_last(sk, skb))
  1517             nonagle = TCP_NAGLE_PUSH;
  1518         if (!tcp_snd_test(tp, skb, cur_mss, nonagle) ||
  1519             tcp_write_xmit(sk, nonagle))
  1520             tcp_check_probe_timer(sk, tp);
  1521     }
  1522     tcp_cwnd_validate(sk, tp);
  1523 }

1518行的这个"||"符号很讲究,只有tcp_snd_test返回1了,tcp_write_xmit才会被执行。所以我们先看tcp_snd_test

[net/ipv4/tcp.h --> tcp_snd_test]
  1452 static __inline__ int tcp_snd_test(struct tcp_opt *tp, struct sk_buff *skb,
  1453                    unsigned cur_mss, int nonagle)
  1454 {
  1455     int pkts = tcp_skb_pcount(skb);
  1456
  1457     if (!pkts) {
  1458         tcp_set_skb_tso_segs(skb, tp->mss_cache_std);
  1459         pkts = tcp_skb_pcount(skb);
  1460     }
  1461
  1462     /*  RFC 1122 - section 4.2.3.4
  1463      *
  1464      *  We must queue if
  1465      *
  1466      *  a) The right edge of this frame exceeds the window
  1467      *  b) There are packets in flight and we have a small segment
  1468      *     [SWS avoidance and Nagle algorithm]
  1469      *     (part of SWS is done on packetization)
  1470      *     Minshall version sounds: there are no _small_
  1471      *     segments in flight. (tcp_nagle_check)
  1472      *  c) We have too many packets 'in flight'
  1473      *
  1474      *  Don't use the nagle rule for urgent data (or
  1475      *  for the final FIN -DaveM).
  1476      *
  1477      *  Also, Nagle rule does not apply to frames, which
  1478      *  sit in the middle of queue (they have no chances
  1479      *  to get new data) and if room at tail of skb is
  1480      *  not enough to save something seriously (<32 for now).
  1481      */
  1482
  1483     /* Don't be strict about the congestion window for the
  1484      * final FIN frame.  -DaveM
  1485      */
  1486     return (((nonagle&TCP_NAGLE_PUSH) || tp->urg_mode
  1487          || !tcp_nagle_check(tp, skb, cur_mss, nonagle)) &&
  1488         (((tcp_packets_in_flight(tp) + (pkts-1)) < tp->snd_cwnd) ||
  1489          (TCP_SKB_CB(skb)->flags & TCPCB_FLAG_FIN)) &&
  1490         !after(TCP_SKB_CB(skb)->end_seq, tp->snd_una + tp->snd_wnd));
  1491 }

这个函数的注释比实现代码还多,return后面复杂的条件判断可以被拆开:其实是三个条件的“and”操作,我们看第二个条件,就是:
(((tcp_packets_in_flight(tp) + (pkts-1)) < tp->snd_cwnd) || (TCP_SKB_CB(skb)->flags & TCPCB_FLAG_FIN))
其中,tcp_packets_in_flight是指正在“飞行中”的packet数,也就是正在网络上的包数,它的计算方法是:

        发送过一次的包数 + 重发过的包数 - 队列中存留的包数

而TCPCB_FLAG_FIN是指一端是否发完了数据,这个在我们的项目中不存在,我们的数据没个完。
这下清楚了:
如 果设了NODELAY,则关闭了nagle算法,大量的小数据包被发出去(看看上面第一个tcpdump的数据),在突然断网时,in_flight的包 很多,多得超过了snd_cwnd即发送窗口的大小,于是tcp_snd_test返回0,真正的发送没有发生。不发送存着的数据,snd_buf中的空 间就腾不出来,tcp_sendmsg就一直返回0。恶性循环。

有人要问了,既然snd_buf没空间了,那ACK又是怎么发出去的呢?答案是:发ACK不需要snd_buf空间,它直接就扔出去了。在socket收消息时,会调用tcp_recvmsg,收完后会清空读缓冲cleanup_rbuf,cleanup_rbug里会发送ACK消息,如图:



tcp_write_xmit里的操作其实就是从发送队列里循环拿skb,然后调用tcp_transmit_skb发到网络上去,而ACK是直接就调用tcp_transmit_skb,故而不经过发送队列,也就不受snd_buf空间的影响。

还有人可能问,这岂不是linux tcp协议栈的bug?我觉得有可能,因为在linux的2.6.32的内核里,tcp_snd_test函数已经没有了(实际上从2.6.13开始tcp_snd_test就没了,用rhel5的人可以松口气了),__tcp_push_pending_frame里那个别扭的“||”操作也拿掉了,改为直接调用tcp_write_xmit,再在tcp_write_xmit里对窗口和nagle算法就行判断,决定是否该发送包。逻辑更清晰,bug也避开了。
      法国人做的菜,我个人觉得很生、很难吃,但他们有一个习惯却是我们应该学习的:如果一个法国人在餐厅吃到了自己觉得很可口的菜,他会问服务生:
      “我能见一下为我做这份晚餐的厨师吗?”
      因为他要当面感谢这个为烹饪美食的厨师。
      食物是上帝赐予的,所以要感谢上帝;美餐是厨师制造的,所以也要感谢厨师。这是多好的习惯!法式料理全球有名,皆是此伟大习惯所赐。

      昨晚梅坚请客,看了电影《avatar》。没啥可说的,也不需我再加评论——满分。这才称得上大片。大家抓紧去电影院看,12年了,我就推荐大家去电影院看这么一部,不算过分了。
      真得好好感谢导演,James Cameron同学,他从97年拍完《泰坦尼克号》就开始构思《avatar》的剧情,2006年8月开始拍摄,三年多的辛劳,终成大作。James,可爱的老头,12年了,终于落叶归根,从商业片里沉淀而出,回到了童年,拍出了这部如所有人童年一般纯洁透明的电影。
       做为一个对音乐和声音敏感的人,我还要感谢影片的作曲,老熟人 James Horner,之所以称熟人,是因为我所喜欢的《勇敢的心》、梅坚所喜欢的《异形2》、大家所喜欢的《泰坦尼克号》(97年)、奥斯卡评委所喜欢的《美丽 心灵》,都是由他作曲配乐。而《avatar》里的配乐,更秉承了James Horner一贯的轻盈、灵快的风格。感谢James Horner为我们带来的天籁之音。
       当然,还要感谢梅坚。有人请客看电影,感觉更好。

       当我用gcc的时候,我感谢Richard Stallman,如果不是他带头做出了如此出色的编译器,带头启动了GNU,我可能还在用VC开发,VC太贵了,我买不起的。
       当我用linux的时候,我感谢Linus,感谢Alon Cox,当然,还有所有内核开发者,不是他们做出了这么出色的OS,我现在可能还在windows下开发,windows太贵了,我买不起的。
       感谢这些计算机领域的英雄,他们就是潘多拉星球上的“魅影骑士”,有他们在,我们的editor,我们的compiler,我们的OS,我们的家园,才不会被某个超级跨国软件公司所霸占、所蚕食、所掠夺。我们才能在大树下自由的开发。
       请追随这些英雄们。




《avatar》开篇曲
Gmail,Outlook都是支持html格式的邮件的,比plain txt内容更丰富。但是console下怎么发html格式的邮件呢?mail?mutt?我google了好几篇文章,都没搞定(用mutt加header发送是没用的),最后,一位仁兄给出答案:
http://www.liamdelahunty.com/tips/linux_send_html_email_from_command_line.php

很简单,在html文件(比如my.html)前面补上三行:
To: hao.bigrat@example.com
Subject: OhMyGod
Content-Type: text/html; charset="gbk"

<html>
<table>
......

然后
sendmail hao.bigrat@example.com < my.html

契约

| | Comments (2) | TrackBacks (0)
      小学的时候就读过《鲁滨逊漂流记》缩减本(又是从姨妈那里哭来的),那时候觉得真精彩真好看。前几天又读了一遍全译版,对比教育局缩减的版本,发现他们给剪掉了两部分:对宗教的描写和结尾处“找回财产”的情节。

      没有基督教信仰带来的力量,鲁滨逊无法生存,一个人吃饱喝足就是生存的全部了吗?但国人是没有宗教信仰的,所以当初看的时候,肯定觉得挺别扭,故而删之。可 惜啊,一部优秀小说最后被我们的教科书评价为:“代表了资本主义的扩张”,多么恶心的历史教科书(亏我当年还背得那么卖力,乖孩子总是比较倒霉)。

      鲁滨逊在那个小岛上生活了数年后,大家就都承认他是岛屿的“总督”,没有哪个人说“这岛是属于我们大英帝国的,所以就是女王陛下的,所以不是你的”。又想起 美国的西进运动,规定说:谁占的地就世世代代归谁,这才让开拓者们有了底气,才有了今天这么大的美国。说到做到,是领导者的基本素质,为什么中国历史上的 改革成功的那么少?因为能像商鞅那样“城门立木”的改革家太少了。昨天才看一个经济频道的新闻,河南某锁厂在市中心广场开擂台,说谁能打开他们研 发的新锁,就当场给50万元奖励,有个中年人上去应战,三下五除二就把锁打开了,猜怎么着?那厂领导叫上保安,把开锁者当场轰走了(坏了他们的好事),瞧,这就 是国内企业的水平,众目睽睽之下都敢反悔!相较之下,往牛奶里加三聚氰胺,那还不是小菜一碟?

      小说的结尾尤其让我感叹:一个在文明社会消 失了30年的人,回家乡后居然能找回自己全部的财产!西方人多么注重“契约”这个概念,只要有契约在,不管多少年、发生了多少事,你的财产依然圣神不可侵 犯。以前看《辛巴达七海传奇》,发现人类居然可以和神订立契约,而即使贵为神灵,也要遵守和人的契约,不可打破。这不单是神的戒律,也是西方世界的基本道 德。

      说到做到、信守契约。当年英国能够成为殖民地最为广大的国家(日不落帝国),难道完全是偶然吗?西方国家比东方富足,难道也仅仅是因为昔日的掠夺吗?

故障浩劫

| | Comments (3) | TrackBacks (0)
      山东教育卫视有个《真实》节目(每晚19:32),这一段时间在讲近20年以来的世界空难。
      空难都是故障造成的,和我们平时工作中系统故障一样,但我们还好,顶多就是损失钱或声誉,而对航空界来说,故障就直接要了人的命。

      英国的一架BAC 1-11小型客机起飞后,机长解开安全带,喝咖啡,这时驾驶舱前窗突然脱落,强大的吸力直接把机长拉出了飞机!所幸脚被卡住,后被乘务员拉住,于是,机长 昏迷着在飞机外吹了20分钟的零下17度寒风,副驾驶还算冷静,开着敞篷飞机安全降落,降落后,由于后怕和对机长的悲伤,副驾驶都吓哭了。但奇迹是,这次 事故无人伤亡,机长居然活了下来,疗养了几个月后,他老人家又重新坐上了他心爱的飞机(要换我,早拉倒不干了)。
      这个事情教育我们:只要飞机还在空中,你最好还是老老实实的系好安全带,别耍酷。
      后来的调查发现,驾驶舱前窗的设计居然是从外往内固定,这样,飞行中,机舱内是高压,始终有力量推着前窗。而维修人员在维修这个螺丝老旧的前窗时,是目测选 新螺丝的,而且是在一个备用机棚,光线十分灰暗......可能很多人想不到飞机这种高科技玩意,是在一个多么脏乱的大棚里维修的,原因是航空公司为了节 省成本,让很多维修人员倒班,工棚当然不够用,而维修人员也是瞌睡连天,于是不再遵从规范(规范是查手册找螺丝,不是目测),于是前窗用了尺寸偏小的螺 丝,于是在空中前窗飞去,于是机长被吸,成为了最“拉风”的人......
      首先,航空公司混蛋,为了利润不折手段,加大维修人员的工作强度,这就 像某些IT公司,为了所谓业务,拼命压榨程序员,一天从9点到23点,一周从周一到周六,最后人员疲惫、故障百出。其次,设计人员笨蛋,窗户设计有缺陷, 细节疏漏,这种疏忽即使压力测试都不一定能发现(压力测试使用完好的飞机,不是换了螺丝的),所以,设计review很重要(记得叫上中中)。

      2002年,俄罗斯乌法市小学的优秀孩子坐图-154客机去西班牙参加一个联合国教科文组织的活动,经过瑞士上空时,和一架货机相撞,乘客全部丧生,包括52个孩子!
      我和老婆看完那期的节目,两人都十分愤懑,并一致认为——瑞士空管局要负全部责任!
      瑞士空管局的人,由于晚上途经的飞机少,任务也少,居然一部分跑去吃宵夜,一个小伙子只好守着三个显示屏指挥飞机,一架飞机要降落了,他只好全神贯注,殊不 知另一个显示屏上,两架飞机就要相撞了!吃你个头的夜宵!空中几百条鲜活的生命,包括52个活蹦乱跳的孩子,他们身为空管,居然吃夜宵去了!?把任务都丢 给一个人。我对瑞士人谨慎细致的印象一落千丈。以后选航线,尽量远离瑞士。
      而且,飞机快要相撞时,德国的空管自动系统发出了报警(这时起码的系统功能),德国空管却不能给飞行员直接去电话,这是国际空管规则......我现在才深刻理解了“要遵守规范,但不能拘泥于规范”的意思。

      总之,感触良多,做工程师除了小心,还要耐心,多看几眼,多做点安全检查,虽然出不了人命,少coredump、少损失点钱,也是好的啊。