2009年12月16日星期三

点阵汉字显示

DOS下的点阵汉字

  你是否碰到过用启动盘启动系统后用DIR命令得到一串串莫名其妙的字符?有经验的朋友会告诉你:那是汉字。汉字?你不禁会问:怎么一个我一个也不认识。但那确确实实是汉字,如果你启动UCDOS或其他的汉字系统后,就会看到那是一个个熟悉的汉字。同样是汉字,为什么前后会看到不同的结果?呵呵,其实在电脑硬件中,根本没有汉字这个概念,也没有英文的概念,这铁玩意认识的概念只有——内码。

汉字的内码

  点头表示什么?是"对"、"YES",偏偏有的地方表示的意义却恰恰相反。一个动作,有不同的诠释;一个问题,有不同的答案;而一个符号,却有不同的意义,关键在于:你是如何地理解。在电脑中亦如此,所有的数据都是以0和1保存的,按不同的数据操作,可以得到不同的结果。对于显示英文操作,由于英文字母种类很少,只需要8位(一字节)即可。而对于中文,常用却有5000以上,于是我们的DOS前辈想了一个办法,就是将ASCII表的高128个很少用到的数值以两个为一组来表示汉字,即汉字的内码。而剩下的低128位则留给英文字符使用,即英文的内码。不信,你可以用记事本写一C文件:

main()
{
unsigned char *s,*e="ABcd",*c="你好";
clrscr();
printf("English char =");
s=e;
while(*s!=0) /*C的字符串以0为结束符*/
{
printf("%3d,",*s);
s++;
}
printf("\nChinease char=");
s=c;
while(*s!=0)
{
printf("%3d,",*s);
s++;
}
getch();
}

再用TC输入*.txt打开运行,看见了没有,那些数值即英文和汉字的各字节内码。

汉字字模

  得到了汉字的内码后,还仅是一组数字,那又如何在屏幕上去显示呢?这就涉及到文字的字模,字模虽然也是一组数字,但它的意义却与数字的意义有了根本的变化,它是用数字的各位信息来记载英文或汉字的形状,如英文的'A'在字模中是这样记载的:

而中文的"你"在字模中却是这样记载的:


  在硬件系统内,英文的字模信息一般固化在ROM里,即使在没有进入系统的CMOS里,也可以让你看到英文字符。而在DOS下,中文的字模信息一般记录在汉字库文件HZK16里。

汉字库文件

  了解字母和汉字是按字模位信息显示的原理后,那如何得到汉字的字模信息呢?难道要我们自己去做?NO。DOS前辈们经过艰辛的努力,将制作好的字模放到了一个个标准的库中以免去后辈的麻烦,这就是点阵字库文件。一般我们使用16*16的点阵宋体字库,所谓16*16,是每一个汉字在纵、横各16点的区域内显示的。不过后来又有了HZK12、HZK24,HZK32和HZK48字库及黑体、楷体和隶书字库。虽然汉字库种类繁多,但都是按照区位的顺序排列的。前一个字节为该汉字的区号,后一个字节为该字的位号。每一个区记录94个汉字,位号则为该字在该区中的位置。因此,汉字在汉字库中的具体位置计算公式为:94*(区号-1)+位号-1。减1是因为数组是以0为开始而区号位号是以1为开始的。这仅为以汉字为单位该汉字在汉字库中的位置,那么,如何得到以字节为单位得到该汉字在汉字库中的位置呢?只需乘上一个汉字字模占用的字节数即可,即:(94*(区号-1)+位号-1)*一个汉字字模占用字节数,而按每种汉字库的汉字大小不同又会得到不同的结果。以16*16点阵字库为例,计算公式则为:(94*(区号-1)+(位号-1))*32。汉字库文该从该位置起的32字节信息即记录了该字的字模信息。

汉字库文件

  了解点阵汉字及汉字库的构成原理后,显示汉字就变得简单。以16*16点阵字库为例,通常的方法是:将文件工作指针移到需要的汉字字模处、将汉字库文件读入一2*16数组再用for循环一位位地显示。以使用VGAHI模式显示"我"字为例,程序如下:
#include "graphics.h"
#include "stdio.h"
main()
{
int i=VGA,j=VGAHI,k;
unsigned char mat[16][2],chinease[3]="我";
FILE *HZK;
if((HZK=fopen("hzk16","rb"))==NULL)
exit(0);
initgraph(&i,&j,"");
i=chinease[0]-0xa0;j=chinease[1]-0xa0; /*获得区码与位码*/
fseek(HZK,(94*(i-1)+(j-1))*32l,SEEK_SET);
fread(mat,32,1,HZK);

for(j=0;j<16;j++)
for(i=0;i<2;i++)
for(k=0;k<8;k++)
if(mat[j][i]&(0x80>>k)) /*测试为1的位则显示*/
putpixel(i*8+k,j,WHITE);

getch();
closegraph();
fclose(HZK);
}
怎么样?只要掌握了正确的方法,显示汉字并不复杂。

打印字库文件和HZK12

  如果你有UCDOS的HZK24S(宋体)、HZK24K(楷体)或HZK24H(黑体),你还可以使用不同字体的大字模汉字了。HZK24系列是24*24的点阵字库,每字模占用3*24字节。如果你按照HZK16的显示方法的话,你会看到......呵呵,字被放倒了。这是因为该类字库与一般的汉字库不同,这类大字模汉字库是专供打印的打印字库,为了打印的方便将字模都放倒了,你使用时,只要将字模的位信息纵横转置显示即可。例如你如果定义为mat[24][3]则应该这样输出:
for(i=0;i<24;i++)
for(j=0;j<24;j++)
if((0x80>>i%8)&mat[j][i/8]) /*转置显示*/
putpixel(j+x,y+i,color);
  还有一类字库HZK12,虽然属于标准字库类型,但如果你将它的字模当作12*12位计算的话,根本无法正常显示汉字。因为字库设计者为了使用的方便,字摸每行的位数均补齐为8的整数倍,于是实际该字库的位长度是16*12,虽然每行都多出了4位,但这4位都是0(不显示),并不影响显示效果。

用wxWidgets写跨平台UI界面

一般写跨平台的UI,都是用QT。但QT的界面在不同的平台下显示都像Linux的。因此,Windows用户用起来不是很习惯。另一个是开源的QT使用是有限制的。而wxWidgets同QT类似,可以实现跨平台UI。同样,也是开源,而且完全免费,不受限。最大的好处,就是wxWidgets的图形表现和相关平台是一致的。Windows下就像Windows程序,Linux下像Linux程序,Mac
OS下像Mac OS X程序。

wxWidgets使用一套代码,就可以实现Win32,Mac OS
X,GTK+,X11,Motif,Wince和更多的平台界面。而且还支持C++,C#/.net,
Perl,Python等多种语言。由于使用本地平台的组件,而不是模拟出来,所以它能够和本地平台表现完全一致。wxWidgets是开源,免费,可扩展,成熟的库。

在Linux下打开32位I/O和DMA

Linux下的32位I/O和DMA默认是关闭的,可是现在电脑的硬盘不会那么落伍吧!Linux好是好,但如果用的是16位I/O的话,电脑的性能就无法得到充分发挥,你心里一定会觉得很冤!没关系,跟我来吧。

  要打开32位I/O和DMA,需要以Root身份进行设置。为了降低因出错而重启系统对文件系统的损害,最好以单用户模式进行。可以先用下面的命令检查硬件驱动器的I/O情况。

  #hdparm -t /dev/hda

  屏幕显示如下:

  /dev/hda:

  Timing buffered disk reads:64MB in 30.93 seconds=2.07MB/sec

  如果要显示当前I/O、DMA配置,命令分别如下:

  #hdparm -c /dev/had (注:显示I/O配置命令)

  屏幕显示如下:

  /dev/hda:

  I/O support=0(16bit)

  # hdparm -d /dev/had (注:显示DMA配置命令)

  屏幕显示如下:

  /dev/hda:

  using_dma=0(off)

  在确定32位I/O和DMA是关闭的以后,用下面的命令将其打开:

  #hdparm -c 1 /dev/hda (注:打开32位I/O的命令)

  屏幕显示如下:

  /dev/hda:

  setting 32-bit I/O support flag to 1

  I/O support= 1 (default 16-bit)

  #hdparm -d 1 /dev/had (注:打开DMA的命令)

  屏幕显示如下:

  /dev/hda:

  setting using_dma to 0(on)

  hda:DMA inabled

  using_dma=1 (on)

  然后执行下面的命令以使配置生效:

  #hdparm -k 1 /dev/hda

  屏幕显示如下:

  /dev/hda:

  setting keep_settings to 1(on)

  keepsettings=1(on)

  然后,可以再用命令:hdparm -t /dev/hda 来看看效果:

  /dev/hda:

  Timing buffered disk reads:64MB in 14.53 seconds=4.40MB/sec

  怎么样,性能提高了一倍多!

  上面的方法,可以实现在一次开机中生效,而下一次开机又要重新设置。如果要实现每一次开机时都生效,应该在文件/etc/rc.d/rc.local的结尾加入以下命令:hdparm
-c 1 -d 1 -k 1 /dev/hda,它可以使设置在每次重新启动系统时生效。

利用linux处理网络流量数据

网络压力测试,需要处理原始数据,将原始的Byte/s
处理为Mbps,并通过直观曲线图表展示出来。

1.统计网卡流量
sar -n DEV 1 100 > net

得到网卡流量,原始数据如下:
10:32:41 IFACE rxpck/s txpck/s rxbyt/s txbyt/s rxcmp/s
txcmp/s rxmcst/s
10:32:42 lo 0.00 0.00 0.00 0.00
0.00 0.00 0.00
10:32:42 eth0 0.00 0.00 0.00 0.00
0.00 0.00 0.00
10:32:42 eth1 0.00 0.00 0.00 0.00
0.00 0.00 0.00
10:32:42 sit0 0.00 0.00 0.00 0.00
0.00 0.00 0.00

10:32:42 IFACE rxpck/s txpck/s rxbyt/s txbyt/s rxcmp/s
txcmp/s rxmcst/s
10:32:43 lo 0.00 0.00 0.00 0.00
0.00 0.00 0.00
10:32:43 eth0 79164.00 79161.00 8581912.00 8574255.00
0.00 0.00 0.00
10:32:43 eth1 0.00 0.00 0.00 0.00
0.00 0.00 0.00
10:32:43 sit0 0.00 0.00 0.00 0.00
0.00 0.00 0.00
...
10:34:19 IFACE rxpck/s txpck/s rxbyt/s txbyt/s rxcmp/s
txcmp/s rxmcst/s
10:34:20 lo 0.00 0.00 0.00 0.00
0.00 0.00 0.00
10:34:20 eth0 0.00 0.00 0.00 0.00
0.00 0.00 0.00
10:34:20 eth1 0.00 0.00 0.00 0.00
0.00 0.00 0.00
10:34:20 sit0 0.00 0.00 0.00 0.00
0.00 0.00 0.00
10:34:20 IFACE rxpck/s txpck/s rxbyt/s txbyt/s rxcmp/s
txcmp/s rxmcst/s
10:34:21 lo 0.00 0.00 0.00 0.00
0.00 0.00 0.00
10:34:21 eth0 79657.00 79657.00 8602734.00 8606166.00
0.00 0.00 0.00
10:34:21 eth1 0.00 0.00 0.00 0.00
0.00 0.00 0.00
10:34:21 sit0 0.00 0.00 0.00 0.00
0.00 0.00 0.00

Average: IFACE rxpck/s txpck/s rxbyt/s txbyt/s rxcmp/s
txcmp/s rxmcst/s
Average: lo 0.00 0.00 0.00 0.00
0.00 0.00 0.00
Average: eth0 40091.69 40091.51 4329888.64 4331543.93
0.00 0.00 0.00
Average: eth1 0.00 0.00 0.00 0.00
0.00 0.00 0.00
Average: sit0 0.00 0.00 0.00 0.00
0.00 0.00 0.00
其中rxbyt/s 表示每秒接受的byte数,txbyt/s
表示每秒发送的byte数。我们可以将其加工成Mbps

2. 我们需要处理 eth0网卡的流量,将其余流量去掉
cat net | grep eth0 > net1

我们得到数据如下:
10:32:42 eth0 0.00 0.00 0.00 0.00
0.00 0.00 0.00
10:32:43 eth0 79164.00 79161.00 8581912.00 8574255.00
0.00 0.00 0.00
...
10:34:20 eth0 0.00 0.00 0.00 0.00
0.00 0.00 0.00
10:34:21 eth0 79657.00 79657.00 8602734.00 8606166.00
0.00 0.00 0.00
Average: eth0 40091.69 40091.51 4329888.64 4331543.93
0.00 0.00 0.00
3.我们需要处理rxbyt/s ,txbyt/s 将其余的列去掉
gawk '{print $5,$6}' net1 > net2
gawk 命令的执行格式:
gawk '模式匹配|命令语句 {动作}' 处理文件
我们这里没有模式匹配和语句,直接执行动作,打印第5第6列。

得到数据如下:
0.00 0.00
8581912.00 8574255.00
0.00 0.00
8723086.00 8726654.00
0.00 0.00
8718490.00 8721642.00
0.00 0.00
8558670.00 8562184.00
...
0.00 0.00
8602734.00 8606166.00
4329888.64 4331543.93
4.以上的数据,每行代表1秒,但网卡流量每2秒才能取到一次数据。所以是奇偶相间的数据。第0行开始的偶数行都是0.
我们将偶数行的 0 去掉。
这里方法很多。
a.) sed -n '{n;p}' net2 > net3
该命令表示用sed,隔一行打印一行。单引号里大括号的n表示跳到下一行,分号分隔命令,p表示打印命令。
-n表示安静模式,只打印输出。否则会读一行打印一行。
b.) gawk '$1 != 0.00 ' net2 > net3
gawk
可以带if表达式,循环,功能很强。因为数据很有规律,我们可以偷点懒,直接将第一列是0.00的数据行删掉。该命令表示第一列不等于0.00就打印出来。

5.将byte/s 处理为Mbps,并求2秒的均值。
gawk 'a=$1*8/(2*1024*1024), b=$2*8/(2*1024*1024){print a,b}' net3 > net4
将每一列的值*8,得到bit数,除以1M,得到Mbps,除以2得到均值,并赋给变量a,b。再打印出a和b的值。

6.将文件处理为csv文件,再通过excel程序来生成统计图表
用vi将最后一行平均值去掉。并可以在vi中用:%s/ /,/g 将空格替换为逗号,:w
net.csv

7.用winSCP将net.csv传到windows,用excel 2007打开。
选中这两列数据,点插入,折线图,得到如下的图。可以调整一下标题和坐标说明。

ubuntu 9.10 gnome 终端vi 的esc键失效?

ubuntu使用中发现gnome终端vi的esc键有时失效。如果是插入模式,再也回不到命令模式,非常为难。只好直接关闭终端,留一下一个在编辑的垃圾文件。而CTRL+ALT+F1切换到字符界面,vi没有该问题。
后面有人提示,说是QQ的问题。
我从腾讯官网下载了QQ beta1的deb包安装的。一打字就导致QQ退出。而使用
Empathy则经常登不上QQ,修改client版本为qq2008也不行,以致引起QQ被锁死。采用编辑/usr/bin/qq,添加export
GDK_NATIVE_WINDOWS=true才解决QQ死的问题,如下。
zhouhh@zhhofs:~$ cat /usr/bin/qq
#!/bin/sh
export GDK_NATIVE_WINDOWS=true
cd /usr/share/tencent/qq/
./qq

QQ问题解决了,又引起Vi的问题。编辑文件后ESC不起作用。
因此,只好安装一个虚拟机或采用远程桌面的方式来使用QQ。
而ubuntu9.10缺省安装的vim-tiny非常不好用,缺省是与vi兼容模式,没有色彩。方向键会变成A
B C D。直接安装vim增强版
sudo apt-get install vim即可解决该问题。

比亚迪:巴菲特打破常规的投资

巴菲特为什么要投资比亚迪(1211.HK)?按照此前人们总结的规律,比亚迪绝非巴菲特青睐的企业。然而,巴菲特最终还是向王传福投了赞成票,给价值投资者们上了很好的一课:不要墨守成规。

  险些失之交臂

  从目前的结果来看,巴菲特投资比亚迪成绩斐然。巴菲特旗下的中美能源(MidAmerican
Energy)于2008年9月26日与比亚迪签订协议,以每股8港元的价格认购比亚迪2.25亿股H股。以12月1日比亚迪收盘价74.1港元计算,1年多的时间里这笔投资净收益高达826.25%。从图1来看,巴菲特选择的投资时机可以称得上完美。如果考虑到由于等待中国证监会的批准,中美能源完成购股实际出资的时间为2009年7月30日,这笔投资的资金使用效率相当高。

  不过,巴菲特做出投资比亚迪的决定并不轻松。据报道,巴菲特是在朋友和长期合作伙伴、伯克希尔-哈撒韦公司(Berkshire
Hathaway)的副董事长查理·芒格的推荐下,打破了一贯遵循的原则,投资于比亚迪。芒格对记者称,经营比亚迪的企业家王传福,是一位兼有爱迪生的能够解决科技问题能力和GE杰克。韦尔奇能够办到所有需要完成事情的人物。在巴菲特征询了另一位值得信赖的合作伙伴David
Sokol的意见,并请他到中国考察了比亚迪的实际情况后,才最终做出了投资决定。与仅看了两份年报就断然出手中石油(13.78,-0.13,-0.93%)相比,巴菲特此次投资显然更加慎重。

  芒格由于经常说"不"而曾被巴菲特这样开玩笑:"如果你向查理询问某些事情,而他给出的回答是'不',我们就会倾囊进行投资。如果他的答案是'那是我所听说过的最愚蠢的事情',我们就会进行适量投资。"既然连芒格都如此欣赏王传福,巴菲特似乎就不用多考虑了。此外,此次投资比亚迪只需18亿港元,与伯克希尔两千多亿美元的资产相比微不足道,似乎也不必太认真。

  虽然如此,我相信巴菲特还是认真地看过了比亚迪年报,确认这家公司有过人之处才敢下决心。人们评论这起投资时往往认为巴菲特打破了不投资高科技企业的常规,其实,从财务方面来看巴菲特也是打破了常规。

  打破财务常规

  此前,人们总结的巴氏投资原则中财务原则有4条,分别是:

  1. 注重权益回报而不是每股收益;

  2. 计算"股东收益";

  3. 寻求高利润率的公司;

  4. 公司保留1美元都要确保创立1美元市值。

  比亚迪乍看上去并不符合这几条原则。

  先看第一条原则。比亚迪的净资产收益率并不是非常出色,与竞争对手富士康国际(2038.HK)相比略逊一筹,而且资产负债率偏高,巴菲特偏爱资产负债率低的企业也是有名的。如果仅从这两个财务指标作判断,比亚迪使用了更多的财务杠杆,股东权益回报却仍然不如竞争对手,显然不是一家值得投资的企业。事实上,比亚迪曾经在2006年年报中披露公司2007年3月与多家银行订立延长贷款协议,其中7亿元的长期贷款获延续超过1年,这足以说明公司财务压力之大。需要说明的是,本文很多时候只分析比亚迪2007年及以前年度的报表,是因为巴菲特在做出投资决定时只能看到这些。

  再来看第二条。所谓"股东收益",或者称"所有者收益",代表(a)报告收益(即会计利润),加上(b)折旧费用、折耗费用、摊销费用和其他非现金费用,减去(c)企业为维护其长期竞争地位和单位产量而用于厂房和设备的年平均资本化开支,等等。其重点在(c),巴菲特不喜欢资本开支大的企业。

  近来颇为流行的《巴菲特教你读财报》一书对此有更为具体的陈述,"巴菲特发现,如果一家公司将净利润用于资本开支的比例一直保持有50%之下,那么你可以把它列入具有持续竞争优势公司的候选者名单。如果该比例保持低于25%的水平,那这家公司就很可能具有于之有利的持续性竞争优势。"

  比亚迪的资本支出却是惊人的大,请看表1,我们以投资活动产生的现金流量净额(负数)来估计资本支出。比亚迪经营活动产生的现金流,根本负担不起庞大的资本支出,不得不高度依赖筹资活动。

  比亚迪的毛利率也不高,而且逐年下滑。按《巴菲特教你读财报》一书中的说法,"相对于我们所熟知的这些优质企业(可口可乐、穆迪、伯灵顿北方圣太菲铁路运输公司、箭牌公司),那些长期经济运行情况不太良好的公司,其毛利率就相形见绌了。例如:濒临破产的美国航空公司,它的毛利率仅为14%;陷入困境的汽车制造商——通用汽车制造公司,其毛利率只有21%;曾经陷入困境,但现在已经扭亏为盈的美国钢铁公司的毛利率为17%;一年四季都在运营的固特异轮胎公司,在经济状况不太良好时,毛利率也只有20%。"显然,比亚迪的毛利率水平和那些"长期经济运行情况不太良好的公司"在同一个档次。

  此外,比亚迪2007年研发支出高达6.95亿元,占毛利的16.37%。我们再次引用《巴菲特教你读财报》的说法,"这是巴菲特的一个原则:那些必须花费巨额研发开支的公司都有在竞争优势上的缺陷,这将使它们的长期经营前景置于风险中,意味着它们并不太保险。如果不是一项比较保险的投资,巴菲特是不会对其产生兴趣的。"

  第4条原则我们就不讨论了,市值问题相对复杂,仅从上面所分析的内容来看,比亚迪似乎已经可以确认为没有持续性竞争优势的公司了。

  读财报无定法

  然而,巴菲特最终还是投资了比亚迪。那么,人们此前的总结错了么?《巴菲特教你读财报》的作者之一是巴菲特的前儿媳,她也没有学全老人家的财务分析技能?

  说实话,我对《巴菲特教你读财报》这类书颇有些不以为然,财报分析并非简单地算几个比例就能做好的,否则,全世界会有成千上万个巴菲特了。在拙著《年报掘金》一书中我主张"读年报要灵活",并举了巴菲特的例子:2007年,央视记者在采访巴菲特曾经问过这样一个问题:"您最关心年报中的哪些方面?"巴老的回答是:"学生们总是问我这个问题,但是所有的年报都是不同的,如果你要找个男人的话,什么样的吸引你?是有体育才能的,还是帅的,还是聪明的?同样看企业也有不同的方法,一个企业到另外一个企业,我看的是不同的东西。"

  在书中那一节里,我还强调了分部数据的重要性,其实,巴菲特早就有过这样的阐述,"我们在伯克希尔准备合并数值的唯一原因是为了达到外部的要求。另一方面,查理和我一直在研究我们的分部数据。"

  然而,《巴菲特教你读财报》一书中却没有涉及到分部数据的内容,而恰恰是业务分部数据能够让我们发现比亚迪其实是具有一定竞争优势的。

  2005至2007年,比亚迪各业务分部的营业收入均有所增长,而汽车及相关产品增速尤其迅猛。但是,汽车及相关产品业务对比亚迪的利润却贡献甚微。2005至2007年,比亚迪汽车业务的利润率分别只有-14.53%、3.59%和5.28%。在这里,我们用经营利润(比亚迪财报中称之为分部业绩)除以营业收入(比亚迪财报中称之为分部收益)计算出利润率,需要说明的是经营利润(分部业绩)没有扣除融资成本,可以大致认为是息税前利润。

  之所以会这样,无非是因为比亚迪的汽车业务尚未形成规模,但是,如果其汽车业务继续高速成长,规模效应一旦形成,盈利情况就会有一个飞跃。事实上,比亚迪汽车业务2009年上半年的利润率已经上升到14.41%。

  也就是说,在比亚迪2007年以前的年报中,如果投资者只看合并数据,那么颇具价值的汽车业务其实是扮演了"价值杀手"的角色,这显然是一种误读。

  再来看手机部件业务,与2006年相比,2007年比亚迪这块业务的营业收入增长了78.92%,经营利润却只增长了18.99%,利润率为11.68%,同比下降了5.88个百分点。虽然比亚迪的数据有美中不足之处,但竞争对手富士康国际的数据就更不能令人满意,2007年营业收入增长了3.38%,经营利润却下降了1.50%,且利润率只有7.33%。

  从利润率指标来看,比亚迪可能比富士康更具成本优势。在2007年年报中,两家公司都认为手机行业竞争加剧,产品价格压力加大,在这种情况下,显然比亚迪的日子会更好过些。2009年上半年,比亚迪的手机部件业务的利润率跌至3.14%,而富士康国际竟然亏损了1870万美元。

  尽管从合并报表数据来分析,比亚迪的竞争优势不如富士康,然而具体到分部数据后,结论却是相反的。这一案例再次证明,分部数据的重要性理应受到投资者的重视。

  还有别的理由么?

  对巴菲特投资比亚迪,人们还可以找出更多的理由,如巴老看好新能源的发展前景等。事实上,我们也可以把比亚迪的电动车业务看做一项期权来估值,尽管这一业务目前还没有产生什么效益。然而,我认为这一案例给我们最大的启示还是要不拘泥于常规。

  以2007年的净利润计算,巴菲特是以10.18倍市盈率投资比亚迪的,对于一家成长型企业来说,这样的市盈率是很低的。价格低廉,应该也是巴菲特敢于打破常规的一个重要原因。

  比亚迪是一家周期性企业,但是,就连芒格这样擅长说"不"的人都认为,"我们并不期待它(收购的候选企业)直线型地增长,只要价格适中,周期性增长亦可以。"由此来看,买入价格低对价值投资的重要性无论如何强调都不为过。

  不过,在巴菲特的思考过程中,如果比亚迪将要发行A股也是一个重要因素,那就值得内地的投资者警惕了。众所周知,巴菲特当年卖出中石油股票后,国内一片哗然,不少人对他不等到中石油回归A股后再卖出颇有些不屑,想来巴菲特也听到了这些声音。其实,上市公司高价发行新股总是对老股东有利的,这似乎也可以为巴老投资比亚迪增添安全边际。不过,还没等到比亚迪高价发行A股,10月22日,港交所披露,国际大投行巴克莱以近10倍于巴菲特认购价的78.145港元的高价斥资25.26亿港元增持比亚迪。由此来看,比亚迪高价回归A股是大概率事件。

  以芒格的性格,我相信他不会同意在短期内抛售比亚迪的股票,即使股价已严重透支。如果我的判断正确,伯克希尔长期持有比亚迪这样一只周期股,恐怕又会让国内一些"价值投资者"们吃惊了!

遭遇SIGPIPE

我写了一个服务器程序, 在Windows下在cygwin环境编译后执行,
然后用C#写了多线程客户端进行压力测试. 程序一直运行正常.
但当在Linux下测试时, 总是莫名退出. 最后跟踪到是write调用导致退出.
用gdb执行程序, 退出时提示"Broken pipe".

最后问题确定为, 对一个对端已经关闭的socket调用两次write,
第二次将会生成SIGPIPE信号, 该信号默认结束进程.

具体的分析可以结合TCP的"四次握手"关闭. TCP是全双工的信道,
可以看作两条单工信道, TCP连接两端的两个端点各负责一条. 当对端调用close时,
虽然本意是关闭整个两条信道, 但本端只是收到FIN包. 按照TCP协议的语义,
表示对端只是关闭了其所负责的那一条单工信道, 仍然可以继续接收数据.
也就是说, 因为TCP协议的限制,
一个端点无法获知对端的socket是调用了close还是shutdown.


截图来自: UNPv1

对一个已经收到FIN包的socket调用read方法, 如果接收缓冲已空, 则返回0,
这就是常说的表示连接关闭. 但第一次对其调用write方法时, 如果发送缓冲没问题,
会返回正确写入(发送). 但发送的报文会导致对端发送RST报文,
因为对端的socket已经调用了close, 完全关闭, 既不发送, 也不接收数据. 所以,
第二次调用write方法(假设在收到RST之后), 会生成SIGPIPE信号, 导致进程退出.

为了避免进程退出, 可以捕获SIGPIPE信号, 或者忽略它,
给它设置SIG_IGN信号处理函数:

signal(SIGPIPE, SIG_IGN);

这样, 第二次调用write方法时, 会返回-1, 同时errno置为SIGPIPE.
程序便能知道对端已经关闭.

PS: Linux下的SIGALRM似乎会每1秒钟往后偏移1毫秒,
但Windows下经过测试完全准时, 不差1毫秒.

忽略SIGPIPE信号的方法
http://hi.baidu.com/greathongjian/blog/item/2f695643091885139213c65a.html
struct sigaction sa;
sa.sa_handler = SIG_IGN;//设定接受到指定信号后的动作为忽略
sa.sa_flags = 0;
if (sigemptyset(&sa.sa_mask) == -1 || //初始化信号集为空
sigaction(SIGPIPE, &sa, 0) == -1) { //屏蔽SIGPIPE信号
perror("failed to ignore SIGPIPE; sigaction");
exit(EXIT_FAILURE);
}

pthread线程里如何屏蔽SIGPIPE异常
hi.baidu.com/ailacy/blog/item/a7eb65f8b8b55707d8f9fdd5.html
http://bbs2.chinaunix.net/viewthread.php?tid=985166&extra=&page=1
在pthread中,可能会遇到Program received signal SIGPIPE, Broken
pipe的问题,解决方法是每一个线程启动之前时,先执行下面代码:

#ifndef WIN32
sigset_t signal_mask;
sigemptyset (&signal_mask);
sigaddset (&signal_mask, SIGPIPE);
int rc = pthread_sigmask (SIG_BLOCK, &signal_mask, NULL);
if (rc != 0) {
printf("block sigpipe error\n");
}