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、少损失点钱,也是好的啊。

关于存档

This page is an archive of entries from 01 2010 listed from newest to oldest.

12 2009 is the previous archive.

02 2010 is the next archive.

Find recent content on the main index or look in the 存档 to find all content.