2004年11月19日

2004年11月17日 by Quickmouse(quickmouse@263.net)


    今天被这两个函数涮了一把,其实是自己太笨了。先是看自己写的两个socket BPF函数对于数据的处理用了不同的转换,其中一个用htonl,另外一个用了ntohl,于是写了一个这样的测试程序,呵呵:
#include <stdio.h>
#include <netinet/in.h>


int main(void)
{
        char mac[]=”\x01\x02\x03\x04\x05\x06″;
        unsigned int k1,k2, k3;


        printf(“length:%d\n”, sizeof(mac));
        k1 = htonl(*(unsigned int *)(mac+2));
        k2 = ntohl(*(unsigned int *)(mac+2));
        k3 = (*(unsigned int *)(mac+2));
        printf(“k1:%x\n”, k1);
        printf(“k2:%x\n”, k2);
        printf(“k3:%x\n”, k3);
        return 1;
}


  猜结果怎么着:
[zgh@Kernel26 test]$ ./a.out
length:7
k1:3040506
k2:3040506
k3:6050403


    发现htonl和ntohl结果是一样的,郁闷了好久,终于发现这两个函数其实都是把数据顺序交换一下位置而已,从结果上来说,它们是一样的。用不同的函数名只是为了好记罢了,唉。

2004年11月17日

by Quickmouse(quickmouse@263.net) 2004年11月4日


    第一次听说socket BPF的东西是CTO说sniffer要注意效率问题,需要针对规则设定一定的过滤规则,这样可以减少程序在用户空间和内核空间的切换。于是就去google那个东西了。不过结果并不是很理想的,似乎研究这个的人不多。从方方面面的情况看,似乎用libpcap库设置BPF的过滤器是比较容易的,但是我的机器并没有装libpcap,man了半天就是没有东西,呵呵。不过折腾了一下也是弄出来了,那都是大半年前的事情了。今天写程序又用到BPF了,突然想到应用过程当中有一个逻辑问题,所以就想顺便写点什么吧。如果你不想装libpcap库,又想折腾BPF,看这篇文章就对了。不过,如果你是打算空手套白狼,不会用tcpdump,或者想从头学怎么写BPF规则,那我还没有钻研得那么深,咱们可以以后讨论讨论,呵呵。
    设置BPF过滤器是通过setsockopt调用来完成的,格式如下:
    setsockopt(sd, SOL_SOCKET, SO_ATTACH_FILTER, &Filter, sizeof(Filter));
    这个调用的格式大家都很熟悉了,不清楚的在参数Filter的设置上。Filter的定义是struct sock_fprog Filter; 此结构在linux/filter.h当中有定义:
struct sock_fprog       /* Required for SO_ATTACH_FILTER. */
{
        unsigned short          len;    /* Number of filter blocks */
        struct sock_filter      *filter;
};


    其中的filter指针指向结构为struct sock_filter的BPF过滤代码。结构同样也在同一个文件当中定义:
struct sock_filter      /* Filter block */
{
        __u16   code;   /* Actual filter code */
        __u8    jt;     /* Jump true */
        __u8    jf;     /* Jump false */
        __u32   k;      /* Generic multiuse field */
};


    其实我们并不关心如何具体的编写struct sock_filter内的东西,因为tcpdump已经内置了这样的功能。例如,想要对所接受的数据包过滤,只想接收udp数据包,那么在tcpdump当中的命令就是tcpdump udp。如果你想让tcpdump帮你编译这样的过滤器,则用tcpdump udp -d,可以得到输出:
[root@Kernel26 root]# tcpdump udp -d
(000) ldh      [12]
(001) jeq      #0×86dd          jt 2    jf 4
(002) ldb      [20]
(003) jeq      #0×11            jt 7    jf 8
(004) jeq      #0×800           jt 5    jf 8
(005) ldb      [23]
(006) jeq      #0×11            jt 7    jf 8
(007) ret      #96
(008) ret      #0
    瞧,这就是BPF的代码了,看不懂吧@_@,其实挺像汇编的,琢磨一下就会了,ld开头的表示加载某地址数据,jeq是比较啦,jt就是jump when true,jf呢就是jump when false,后面表示行号。不过这样的东西用在程序里还是不习惯,再用tcpdump udp -dd,可以得到:
[root@Kernel26 root]# tcpdump udp -dd
{ 0×28, 0, 0, 0×0000000c },
{ 0×15, 0, 2, 0×000086dd },
{ 0×30, 0, 0, 0×00000014 },
{ 0×15, 3, 4, 0×00000011 },
{ 0×15, 0, 3, 0×00000800 },
{ 0×30, 0, 0, 0×00000017 },
{ 0×15, 0, 1, 0×00000011 },
{ 0×6, 0, 0, 0×00000060 },
{ 0×6, 0, 0, 0×00000000 },
    哈哈,这个像什么?像c当中的数组的定义吧。不错,这个就是过滤udp包的struct sock_filter的数组代码。把这部分复制到程序当中,将Filter.filter指向这个数组,Filter.len设置长度,就可以用setsockopt设置过滤器了。
    不过使用这样的过滤器还是有一些需要注意的问题的,例如,设置一个过滤器,只允许两个源MAC地址的数据包进入,我们先用:
[root@Kernel26 root]# tcpdump ether src 01:02:03:04:05:06 or ether src 04:05:06:07:08:09 -dd
{ 0×20, 0, 0, 0×00000008 },
{ 0×15, 0, 2, 0×03040506 },
{ 0×28, 0, 0, 0×00000006 },
{ 0×15, 3, 4, 0×00000102 },
{ 0×15, 0, 3, 0×06070809 },
{ 0×28, 0, 0, 0×00000006 },
{ 0×15, 0, 1, 0×00000405 },
{ 0×6, 0, 0, 0×00000060 },
{ 0×6, 0, 0, 0×00000000 },
    生成模板,我们注意到第2、4行比较了第一个MAC地址,第5、7行比较了第二个MAC地址,所以我们只需要在我们的程序当中动态的改变这四行当中的数值就可以了,例如:
SetFilter(char *mac1, char *mac2)
{
        struct sock_filter code[]={
        { 0×20, 0, 0, 0×00000008 },
        { 0×15, 0, 2, ntohl(*(unsigned int *)(mac1 + 2)) },
        { 0×28, 0, 0, 0×00000006 },
        { 0×15, 3, 4, ntohs(*(unsigned short *)mac1) },
        { 0×15, 0, 3, ntohl(*(unsigned int *)(mac2 + 2)) },
        { 0×28, 0, 0, 0×00000006 },
        { 0×15, 0, 1, ntohs(*(unsigned short *)mac2) },
        { 0×6, 0, 0, 0×00000060 },
        { 0×6, 0, 0, 0×00000000 }
        };

}
    这里,需要用ntohl/ntohs等函数将网络字节序转换为主机字节序。但是这段代码是有逻辑问题的。它首先比较第一个mac地址的后4个字节,如果不正确转入比较第二个mac地址,如果正确转入比较第一个mac地址的高2个字节。因此,如果打算将这个代码用作通用的mac比较,那么在输入的两个mac地址后4字节都相同的情况下就会出现逻辑覆盖错误,即无法对满足第二个mac地址的条件进行判断。因此在这种情况下必须要准备两段比较代码,根据情况进行设置。具体不再累述。
    此外,这段BPF代码还存在的一个问题是,一般情况下tcpdump只返回所捕获包的头96字节,也就是0×60字节,可见代码的倒数第二行是ret #96。对于需要完整的包处理还是不行的,因此你需要将其设置为0×0000ffff,或者在用tcpdump生成的时候用tcpdump -s 65535 -dd … 来生成。
    最后,用tcpdump生成的BPF代码只能用于SOCK_RAW的socket,这类socket是可以直接操作数据链路层的,如果你打算将BPF用于ip层等较高层次的socket,那么你需要手工修改部分行的code.k,也就是修改如ldh [12]当中的[12]这个数值,因为这个数值的偏移量是按照从链路层开始计算得到的,在没有链路层之后,这个值就发生了变化,这个是需要注意的。


参考资料:《Linux下Sniffer程序的实现》作者:Gianluca Insolvibile,
http://www.nsfocus.net/index.php?act=magazine&do=view&mid=1797

2004年11月02日

寄语Dian团队的队员

动笔于 2004年10月18日 星期一 13时49分

    昨天晚上在Dian团队的例会上草草讲了近一个小时,因为没有仔细的进行准备,有一些凌乱。夜晚在火车上,我又理了一下自己的思路,觉得有必要把一些要点整理出来,以便以后大家可以讨论、改进。
1、经常问自己“公司为什么要录用我?”这个问题
    “公司为什么要录用我?”,和“企业为什么付我这么多的薪水”等问题是一个类型的。提出这样的问题并不一定要大家具体的回答出来,而是提醒大家应该想一想自己现在有什么价值,或者俗气一点就是“值多少银子”。各位至少都是本科生了,团队当中挑大梁的多是研究生,也有博士生。或许当初各位在步入大学校门的时候,都或多或少的涌起过这样的自豪感“嗯,我现在是大学生了”。但是,经过改革开放二十几年的变迁,经过市场经济十来年的洗刷,经历了大学扩招,研究生扩招以后的冲击,大学生还有什么资格沾沾自喜,又还有多少本钱觉得自己高人一等?一方面是大学生甚至研究生、博士生数量的急剧膨胀,另一方面是生源质量整体下滑所致的教学目标、要求的下降,若不是我们国家长期以来形成的重学历的评判标准,大学生们已经没有什么优势可言。而对学历的倚重现象,在不少地方已经悄悄的发生变化,可悲的是我们的学生并没能注意到这个问题。
    我在不同的场合与不同的人就子女的教育问题交流过看法,对于“你觉得子女教育问题是高中阶段对你的压力大还是大学阶段压力大”,大部分人回答都是“高中阶段”。大部分人认为高考是自己对子女最挂心的事情。对于“你对子女上大学的目标”,大部分人回答都是“找一个好工作”;而对“如何能找到一个好工作”的回答,大部分集中在“上一个好专业”、“学习好”等等上面。这反映出中学阶段学生学习目标考核被大部分的家长搬到了大学阶段来,或者家长对达到目标所需要的评判标准模糊不清。对高中阶段,学生的目标是明确的,为了高考,为了分数,很单纯,家长的目标,也是很单纯的,盯住子女的成绩。然而到了大学一切都发生了变化。对于这样的情况,我们的学生又是怎么想的呢?还不得而知。
事实上,由于不同单位、不同工作岗位的需求差异,如何判断一个应聘者是否符合岗位需求是千变万化的,没有一个统一的标准去衡量。更糟糕的是这种标准的变化又是惊人的,今天你去面试A公司,明天去面试B公司,他们的要求可能就不一样,标准在一夜之间发生了变化,大家都迷惘了。如果我们的学生只有唯一的成绩可以拿得出手,无疑是想以不变应万变,可惜这不顶用。在这种竞争中,没有自己的特色,没有“人无我有,人有我优”的长处,是无法在步入社会的时候抢得天机的。于是,这要求我们的学生自己去适应这种变化。每一个学生都有自己就业的目标和方向,如何根据社会的要求不断调整自己的能力以符合社会的需要,成为了我们学生是否优秀的唯一标准。与中学阶段不同的是,大学里面没有谁,或者说至少目前没有发现专门有谁为每一个大学生去设计他应该考虑的衡量标准。因此,我们的学生必须要学会自己去规划、掌握自己的路。
    当然,Dian团队也不可能为每一位队员制定自己的发展规划和评价标准,Dian老师也不可能有这个时间和精力。我们的队员应该多想想自己需要什么,然后在团队现有的条件下自己能得到什么,这样自己才有进步,团队才有发展的空间。一味的“等、靠、要”是不现实的。
因此,回答“公司为什么要录用我?”这个问题,就是回答自己如何规划自己大学的人生路这样的问题。
2、学会注意观察,随时随地学习
    其实说起来,注意观察这个要求对于大学生来说再简单不过了。但是我感觉大家似乎并没有对此有足够的重视,或许这个要求以往都是跟小学生们说的。“注意观察、随时随地学习”意味着要求大家保持对新事物的高度敏感性。这里的“新事物”并不一定指技术领先或者先进领域等等,而是对大家以往没有见过的东西的统称,尤其是大家在参与课题过程当中见到的东西。例如:主控组的同学就可以在嘉铭的工厂里面看到设备怎么安装、摆放,设备内部连接、布局等等新的东西。这些东西往往都是经过实践检验过的,但是又不是在我们以往的课程和实践当中能够遇到的。
    再例如,现在的火车新型卧铺车,都有一个床头灯,是白光LED的,开关是点按式的。如果注意一下,会发现开关内有一个很暗很暗的绿色LED亮着。这个设计就非常的好,晚上在灯完全关闭后既可以让乘客看到开关的位置,又不至于在黑暗中让人觉得刺眼。另外,车辆连接处的门开关也改成了触摸式的缩拉门,使得封闭的环境下开关门占用了最小的空间。现在我国铁路上跑的客车,大部分都是长春客车制造厂和四方机车制造厂生产的,据说一个是跟加拿大庞巴迪合资的,一个是跟法国阿尔斯通合资的。我觉得最近几年生产的客车人性化的设计进步非常大,我想大部分是合资之后引进的设计观念带来的变化,这都是学习的结果。
再举一个大家天天都看得到的例子,教室里的投影仪,如果大家用过台式的,注意一下关机的时候从来不建议直接断电的,按了关机以后有一个小风扇还在呼呼的吹好久的风——它还在工作。为什么?散热呀,投影用的灯泡热量很大的,关机以后还要吹很久才不至于让余热积累太厉害减少仪器的寿命。这些例子都非常的多,生活当中常常碰到。
    我这个人很喜欢逛超市,尤其是家乐福,沃尔玛这样的大超市。不是为了买东西,而是喜欢去超市里面寻找那些“好玩”的东西,给我们的生活方式带来新意的物件。我觉得通过这些东西,我可以领会到很多设计上的技巧,学到以往遗漏掉的细节。我们是工科的学生,电信也是飞速发展的领域,有很多知识没有出现在我们的课本上,而工程领域,也就是核心技术向通用化、大众化接口的部分是我们不曾学习过的地方,也是我们最薄弱之处,同样也是我们最容易提高之处,希望我们的队员不要放过任何一个可以给自己积累和提高的机会,哪怕是一点点,往往胜负就在这一线之间。
3、不要轻易说“我做完了”
    “我做完了”,是大家常常引以为豪的一句话,说出来经常有一种气壮山河的感觉,呵呵。可惜这种感觉往往容易被随之而来的种种打击弄得一点脾气也没有。究其原因,无非是“做完了”这个标准的问题。
    做东西,无论是软件还是硬件,都有一个规律。一般而言,80%的工作只需要20%的时间就可以完成,而剩下20%的工作却需要花费80%的时间才能做完。大家做事情的时候也往往是先易后难,慢慢的解决问题,通常也就是首先把80%的工作完成了。这个时候,整件事情已经有了基本的眉目,软件的,核心部分已经完成,只是不够稳定(stable),不够友好(friendly);硬件的,基本可以工作了,只是一些连接还有些凌乱,一些设计处于验证方案的末期。但是大家已经很高兴了,整个项目(project)感觉交差是没有问题了,该出来的东西已经出来了。所以就开始有人说“做完了”。但是,这只是你们的标准,不是用户的标准。你做的东西是给用户使用的,在合同中称为甲方,而不是给你自己用的。所以你要想想,甲方现在可以使用了没有?软件方面的,是不是install了就可以用(如果需要install的话)?是不是copy了就可以用?如果操作不正确,有没有相应的提示。千万不要你写的软件只有你自己会用,那不叫软件,只是你自己口袋里的tool。硬件方面的,是不是可以装箱了?所有的连接线是不是足够牢固了,不要随便挪腾一下就罢工了。甲方可不比你自己,他不知道,不需要知道,也不应该知道什么地方碰不得,什么地方敲不得。如果你做的硬件,要在手册上写名某某位置不可触摸,某某地方不能震动等等,这个东西我建议还是摆在自己家的储物间比较好,那里一般不会有人随便走动碰倒了。
    于是,我们知道了还需要在稳定性、操作(界面)友好性、容错性上继续下功夫改进。要知道软件上容错代码和保护代码往往占到1/3的大小,硬件的保护电路往往也有1/5的电路板面积(虽然常常画了不装,但那是生产厂家节省成本的问题,和我们做工程师的设计出来是两码事,设计了不装是厂家的问题,没有设计就是你工程师的水平问题了)。所以,你在说了“做完了”之后开始花时间来fix这些bug(姑且称为bug吧),无奈这些bug虽然很小,但是却很多,要面面俱到。最后,你果真花了80%的时间才达到真正的做完了的标准。
    所以,回头再仔细看看,在你说“我做完了”之前,想想,是不是真的做完了,自己的东西是不是真的经得起别人折腾。如果没有把握,让旁边的朋友帮忙测试一下。总比被发现了问题,再抓耳挠腮的嘿嘿一笑好得多。
4、学会自信、学会负起自己的责任
    不够自信和不懂得负起自己的责任是常见的两个毛病。首先说自信吧,以前常说的“华工不缺将才,但是没有帅才”,我觉得在一定程度上反映了我们学生不够自信的情况。自信不是与生俱来的,自信是在不断的学习、实践、提高当中,在不断的与周围的人的比较当中逐渐形成的一种气质与魄力。因此,要学会自信就要学会如何的学习提高自己,就要学会如何在实践中挖掘自己的能力,同时要不断的与周围的人进行比较,寻找差距,保持优势。
    另一方面,自信又是与负起自己的责任息息相关的。一个自信的人必然会主动承担自己能力范围之内的职责,因为自信,所以你对所担负的责任有信心。而又因为你能够有效的负起你的责任,所以你会不断的增加自己的自信心。它们是相辅相成的。
    再来看看学会负起自己的责任。为什么要说学会?因为我们很多学生还不会负起自己的责任。我们的责任是什么?好好学习天天向上?这未免太肤浅了一点。说大了,我们的责任是要对国家、对社会有所贡献,要对得起自己的国家,对得起提供给我们如此环境的社会,并尽自己的所能让她变得更好、更强大;如果觉得这个口号遥远了,那就看小处,我们的责任就是对关爱自己的人有所回报,对父母、对家人、对关心自己的同学和朋友,不让他们失望。经常有人说“我对自己负责就行了吧”,其实未必他说这个话的时候就能意识到什么是对自己负责。人之所以为人,是因为他处于社会当中,处于一个与其他的人有着千丝万缕的关系的环境当中,如果脱离了社会这个范畴,那“他”就只是个“它”,是动物,而不是人。我们今天能够在这里学习,是父母家人辛苦养育的结果,是老师谆谆教诲的结果,也有师兄师姐、同学朋友鼎力帮助的结果。所以,对自己负责意味着你要对得起这么多年来父母的养育之恩,对得起老师的辛苦培养,对得起关心和帮助过自己的同学朋友。这样看来,我们身上的担子其实是很重的,因为我们不单单是为自己活着,而且还是为那些养育和关心我们的人活着。听起来是一个沉重的话题,但事实就是这样,逃避不是办法,正确的去面对这份责任才是正途。
    希望Dian团队的队员们能在日后的学习生活当中锻炼自己的能力,在处理自己力所能及的事情时多一些自信,勇于承担自己应当承担的工作,负起自己的责任,这样你们将会在今后面对社会时能给他人一个很好的印象。
5、提高自己的素养,少锋芒毕露,多内敛
    不够自信的另一个极端则是自负。其实真正来说,学生里面有自负本钱的远比有自负心理的人少得多。一个人如果慢慢自负起来,一方面是周围环境中没有能够与之匹敌的竞争对手出现,另一方面则是对自己的能力过于自信了。前者,是大家的问题,如果没有人能够挑战团队当中的权威,就是团队的错,因为这说明团队当中没有上进的意识;后者,是个人的问题,过于自信容易摔大跟头,容易被暂时的领先优势所迷惑,丧失了进一步提高的优势,殊不知天外有天人外有人。
    如果你已经是团队当中的高手了,请多花一些时间关心一下自己周围的队员吧。个人水平发展的不同犹如小平所说的“让一部分人先富起来”,但是先富起来的那部分人有责任和义务帮助还没有富裕起来的人。如果一个团队的整体水平没有在逐步提高,那么这个团队当中的高手是有责任的,虽说“不想当将军的士兵不是好士兵”,可你若是当了将军却带不好士兵,一样不是好将军。从另一个角度看,学技术和学做人是等同的,在大家的眼里,如果你的技术越高,大家对你待人处事的期望也就越高(而反过来并不一定成立)。因为大家很难接受一个品德不高的人掌握着高超的技术。我们拿黑客来举例,黑客的原意是那些掌握着很高计算机技术,并以钻研技术为目的的人,虽然他们会侵入计算机系统,但是从来不以偷窃技术、机密或者破坏、要挟为目的。在网络世界当中,标准的黑客是令人尊敬的高手,而不像那些以利用别人提供的工具侵入系统并涂鸦炫耀的脚本小子(script kids)。
    另外,在任何时间、任何地点,大家都要谨记我们处于一个竞争的社会,犹如逆水行舟,不进则退。我们随时处于别人比较的眼光当中,提高自己的水平比之到处去炫耀重要得多。炫耀容易给处于弱势的群体带来伤害,也容易带来矛盾。如果能记得对自己的长处收敛一点、低调一点,而在需要的时候显示自己的实力,负起自己的责任,那么对于生活在深受儒家思想影响的你来说,无疑更容易获得别人的尊敬和仰慕。
    Dian团队从来都不会缺乏高手,过去有,现在有,将来更有。我关心的是高手们是否能恰如其份的担负自己的责任,能否恰当的扮演自己在团队当中的角色,能否相互之间合作融洽。这一切都决定了最终的效果是一加一大于等于二还是小于一。而对于还没有处于突出位置的队员们来说,这一点也是你们应当注意的为人处事的准则之一。毕竟融洽的人际关系远比处处锋芒毕露给别人的感觉要好。

2004年10月29日

    也不知道什么时候开始喝茶的,大约是从那次芦山之行以后,因为买了一点所谓的芦山云雾才开始的吧。以前总觉得茶没有什么好喝的,一股苦苦的味道,那么多人还乐此不疲的每天上班就泡上大大的一杯,也有人大口大口的喝着叫大碗茶的东西。如果是提神,我更愿意喝咖啡;如果是要味道,或许我会冲些果汁一类的甜品。
    不过既然喝上了也就喝上了,慢慢的品出了一点味道,浓一些像生活的艰辛,淡一些像悠然的惬意。现在天天喝的是一年前超市里买的金银花,压在袋子里大半年了才被我翻出来。虽都已然黄了,泡出来还是一样悠悠的味道,自己也都不知道为什么会每天上班就泡上大大的一杯了。
    再喝咖啡是这次从家里过来,捎上的大瓶咖啡以及伴侣,家里说是别人送的喝不习惯。以前都是喝袋装的1+2,咖啡、伴侣、糖都调好了,尽管冲水就ok。这次提着它们到办公室,才发现没有糖。那冲出来的味道一定很苦,我估摸着。在办公室用散装糖不太方便,又跑去家乐福搜出了方糖,一斤不到要五块多,算是了了事吧。一勺咖啡、一勺伴侣还得两块方糖才够味道,一块方糖是不够甜的。
    其实想想,茶挺好的,不像咖啡,每天只要一点点,不需要加其他的配料,不用伴侣,不用糖,只要有热水,就可以冲出香郁的味道。还有,可以无限的续杯(曾经被我们讨论为在麦当劳或者肯德鸡要热饮的好处),当味道渐渐淡去,体味着生活的激情化归平凡,一天的生活就这样缓缓的落幕。茶也不像咖啡那样有咖啡因,不用担心咖啡因的杀精子副作用,还含有牡荆碱、桑色素和儿茶素等等有益身心的天然物质,也难怪流传于五千年的华夏文明史。
    不过,咖啡还是要喝,毕竟得消灭掉这些礼品,顺便对付一下中午的睡神:)

2004年10月22日

by Quickmouse(quickmouse@263.net) 2004年10月22日

     今天在写了一个程序,把上次发的《如何在Linux当中判断USB控制器的类型》实现了,中途才发现原来自己机器的控制器是uhci-hcd,但是用lsmod却是显示uhci_hcd。很好奇的,就决定把这个东西搞清楚到底怎么弄的。
    因为在modprobe.conf里面注明了usb-controller是uhci-hcd,所以就满天地的去找这个uhci-hcd是怎么对应到uhci_hcd上面去的,系统又怎么加载的。找/lib/modules/2.6.9/下面的alias、pcimap等文件,都没有说明。一咬呀去看modprobe的代码,发现居然是遍历/lib/modules/2.6.9/kernel下的所有目录下的文件,再去仔细看那个驱动的名字(我从来不仔细看的),果然是uhci-hcd.ko,我真是晕菜了,效率太低了吧~~
    然后接着又搞不清楚了,modprobe uhci_hcd和modprobe uhci-hcd都是一个效果,是没有uhci_hcd.ko这个文件啊?怎么回事呢?又反过来查吧,为什么uhci_hcd系统知道去load那个uhci-hcd.ko,郁闷。再次找alias文件等未果,于是派strace上场,仔细看代码以后发现有一个name_matches()函数,判别时把’-'和’_'等同看待,又吐血一次。
    往下看看,那个函数对.ko和.ko.gz后缀都认为是找到了文件,一想,莫非驱动还可以用gzip压缩?尝试用gzip压缩了一下smbfs模块,然后modprobe一下,报错,看来白高兴了。:)

2004年10月21日

  这是自己在2003年初发表与华中白云黄鹤BBS Linux讨论区的一篇文章了,摘录于此,权做一个纪念,顺便更正当中的一些错误。不过文中的一些内容现在看来是有冗余的了,配置文件的理解也不如现在透彻,以后在撰文详述吧,搞一个patch?呵呵

发信站: 武汉白云黄鹤站 (2003年01月14日15:36:07 星期二), 站内信件 

小弟近日对Linux进行了小小的裁减,偶有心得,愿大家共享

说到裁减Linux,无非是为了减小磁盘占用或者是为了某些特定场合的应用(如嵌入式系统)。以RedHat 7.3为例,其最小安装仍然达到了300M,这不得不让人对一直号称小而全的Linux系统感到疑惑。

作为自己手中课题的一个铺垫,不久前我尝试了对Linux进行裁减,虽然没有达到预期的一张软盘大小,但结果也相当有吸引力。下面我对此一一做说明。

参考文档:
Linux bootdisk-HOWTO: 
    http://www.linux.org.tw/CLDP/gb/Bootdisk-HOWTO.html

initrd introduce on Linux system: 
   /usr/src/linux-2.4/Documentation/initrd.txt

裁减Linux一般有两种办法,其一是重新生成kernel和文件系统,其二是在原有的系统上删除不必要的文件缩小“体积”

对从一个完整的RedHat 7.3版本而言,其最小安装也有300M,因此,第二个方法是不太现实的。于是重构文件系统和kernel成为了必然。

裁减目标:构成一最小Linux系统担负实验室网关工作,系统载体为硬盘,运行使用RAMDISK,从而减小意外断电造成的文件系统修整消耗提高系统可靠性。

目标平台:P2-400,8G/64M,8139LAN adapt x 2

首先裁减kernel,既然是最小系统,则kernel里所有必须的部件都将直接编译进入内核。但是内核对module的支持需要保留。 

如何编译内核,不再累述,具体说说哪些选项先:
Code maturity level options —> 不选
Loadable module support —> 当中的Set version….的可以不要,其他两个留着
Processor type and features —> 按照目标系统选择对应的Process Family ,其他的嘛,留下Machine Check Exception、Low Latency….、HIGHMEM Support,其余都可以不要 
General setup —>当中,PCI的选上,其他的不要(注意对照你的系统),SYSTEM V IPC、BSD Process accounting、sysctl support留下,Kernel support ELF binary留着,其他的可以不要
Binary emulation of other systems —>
Memory Technology Devices (MTD) —>
Parallel port support —>
以上三项都是可以不要的
Plug and Play configuration —> 选上,不过如果没有ISA设备,可以不选对ISA P&P的支持(比如我的目标系统)
Block devices —>各取所需了,一般来说,如果你要用软盘,就选上Normal floppy disk support,大多数嵌入式系统是不要的。中间几个也是没有的;Loopback device是一定要的,Network block device我也没把握,可能可以不要不过我选了,呵呵,RAM Disk一定要,Initrd RAM Disk support当然要选。至于Default Ramdisk size就无所谓了,反正可以在启动的时候修改,呵呵。 

Multi-device support (RAID and LVM) —> 这个一般也用不上,不选了。
Networking options —>既然是打算做网关,呵呵,里面大部分东西都要选上而且是[*],编译入内核(前面已经说过了,没有编译为模块的)。从上到下一直选到IP: Virtual Server Configuration —>(从这个开始(含),可以不要了)。

需要说明的是,其中的IP: Netfilter Configuration —>子项即便选择全部编译到内核,似乎并没有什么变化,用iptables的时候一样要iptables的.so支持 :( 不过对irc和ftp的跟踪倒是不需要insmod了 

Telephony Support —>
SCSI support —>
Fusion MPT device support —>
I2O device support —>
Amateur Radio support —>
IrDA (infrared) support —>
ISDN subsystem —>
Old CD-ROM drivers (not SCSI, not IDE) —>
上面几个都不用,为什么RedHat那么大,他们有不小的功劳哟~~~

Network device support —>里面找出你目标系统的网卡(我这里是8139)选上,其他的统统去掉吧。 
Input core support —>如果你不是用的USB接口鼠标键盘,可以不用选他们。 
Character devices —>这里面我只选了Virtual Terminal以及Support for console on virtual terminal,其他好多东西都没有选。 
Multimedia devices —>
Crypto Hardware support —>

这两个对一般的最小系统来说都是不用的 

File systems —>这个是内核大小的大头,ext2(Second extended…)是必要的,ext3也用上吧,/proc有必要,DOS FAT/VFAT(win-95)估计你可能也需要,分区表只要支持PC BIOS就可以了,Native Language我把iso8859-1给内置了
console drivers->我只选了VGA text console
Sound —>
USB support —>
Additional device driver support —>
Kernel hacking —>
这几个都没有选,make dep;make clean;make bzImage
看看吧,内核大概是700~800k左右

以前我总以为裁减kernel就是裁减Linux了,后来才发现是大错特错。
以前总以为最难的是裁减kernel,后来才发现自己多么无知。学习裁减内核,大概只用了一两天,编译一次内核也就20分钟不到,可是后来居然重建文件系统花了一两个星期,呜呜~~~

关于文件系统和kernel的关系,从参考文档里面可以知道,大家自己去看。如果连这个都不懂,建议暂时不要做裁减的事情,以为照着我的文章依葫芦画瓢多半是不能成功的。 

首先按照ramdisk的生成方法或者loopback device的生成方法生成一个8M的磁盘挂接到/mnt(或者其他目录)上,就可以以/mnt为根目录构造文件系统。注意将其按照ext2方式格式化
lrwxrwxrwx    1 root     root            4 Dec 28 09:31 bin -> sbin
drwxr-xr-x    5 root     root         1024 Dec 27 13:42 dev
drwxr-xr-x    7 root     root         1024 Jan  6 15:14 etc
drwxr-xr-x    2 root     root         1024 Dec 12 08:33 initrd
drwxr-xr-x    4 root     root         1024 Dec 30 06:52 lib
drwxr-xr-x    2 root     root         1024 Dec 11 07:52 mnt

dr-xr-xr-x   24 root     root            0 Jan  6 15:14 proc
drwxr-xr-x    2 root     root         1024 Dec 26 03:03 root
drwxr-xr-x    2 root     root         1024 Dec 30 07:28 sbin
drwxr-xr-x    2 root     root         1024 Dec 26 03:04 sysroot
drwxr-xr-x    2 root     root         1024 Apr 19  2002 tmp
drwxr-xr-x    3 root     root         1024 Dec 12 07:45 usr
drwxr-xr-x    5 root     root         1024 Dec 12 02:43 var

这几个目录是必须的

先看看bin下面有什么
lrwxrwxrwx    1 root     root            6 Dec 30 07:28 ash -> ./bash
-rwxr-xr-x    1 root     root       541096 Dec 30 07:27 bash
-rwxr-xr-x    1 root     root        16020 Dec 13 08:56 cat
-rwxr-xr-x    1 root     root        16680 Dec 27 15:40 chmod
-rwxr-xr-x    1 root     root        36360 Dec 28 09:10 cp
-rwxr-xr-x    1 root     root        62756 Dec 28 09:25 ftp
-rwxr-xr-x    1 root     root       100624 Dec 28 09:14 grep
-rwxr-xr-x    1 root     root         8672 Dec 26 03:27 halt
-rwxr-xr-x    1 root     root         9624 Dec 28 09:14 hostname
-rwxr-xr-x    1 root     root        54316 Dec 28 09:14 ifconfig
-rwxr-xr-x    1 root     root        26920 Dec 12 02:42 init
-rwxr-xr-x    1 root     root       105768 Dec 27 13:44 ip
-rwxr-xr-x    1 root     root        60764 Dec 28 09:15 iptables
-rwxr-xr-x    1 root     root         7764 Dec 26 17:26 kill
-rwxr-xr-x    1 root     root        19080 Dec 12 02:25 login
-rwxr-xr-x    1 root     root         9172 Dec 11 07:54 losetup
-rwxr-xr-x    1 root     root        46888 Dec 13 08:55 ls
-rwxr-xr-x    1 root     root        10316 Dec 13 08:37 mingetty
-rwxr-xr-x    1 root     root        17992 Dec 27 14:15 mkdir
-rwsr-xr-x    1 root     root        60104 Dec 11 07:54 mount
-rwxr-xr-x    1 root     root        43496 Dec 28 10:02 mv
-rwxr-xr-x    1 root     root        22196 Dec 26 02:09 nash
-rwxr-xr-x    1 root     root        29464 Dec 28 09:49 ping
-r-xr-xr-x    1 root     root        63304 Dec 26 16:57 ps
lrwxrwxrwx    1 root     root            4 Dec 26 03:33 reboot -> halt
-rwxr-xr-x    1 root     root        26216 Dec 26 17:35 rm
lrwxrwxrwx    1 root     root            6 Dec 30 07:28 sh -> ./bash
-rwxr-xr-x    1 root     root        14952 Dec 11 09:44 shutdown
-rwxr-xr-x    1 root     root       219932 Dec 28 10:06 ssh
-rwxr-xr-x    1 root     root       260616 Dec 27 14:04 sshd
lrwxrwxrwx    1 root     root            6 Dec 26 16:48 swapoff -> swapon
-rwxr-xr-x    1 root     root         7108 Apr  1  2002 swapon
-rwxr-xr-x    1 root     root        27208 Dec 27 14:13 syslogd
-rwxr-xr-x    1 root     root        78808 Dec 28 09:30 telnet
-rwsr-xr-x    1 root     root        30664 Dec 27 14:23 umount
-rwxr-xr-x    1 root     root         7832 Dec 12 01:54 update
-rwxr-xr-x    1 root     root       386120 Dec 28 09:13 vi
-rwxr-xr-x    1 root     root        13896 Dec 30 06:53 who

这里面包含了ftp、telnet、ssh客户端以及sshd服务器常用的命令和网络设置命令,iptables防火墙,vi编辑器,shell用的是bash,虽然ash很小但是总是不习惯没有auto complete功能,tcsh不大不小功能又全,可是对一些shell脚本的支持不太好。nash用来解析linuxrc,后面会讲到(如果你看了最前面提到的initrd.txt)也会明白。

接着,用ldd命令看bin目录下面的各个可执行文件分别都和哪些动态库连接把他们cp到/mnt/lib目录下,如用ldd看mv命令,结果如下

   libc.so.6 => /lib/i686/libc.so.6 (0×42000000) 
    /lib/ld-linux.so.2 => /lib/ld-linux.so.2 (0×40000000)

把/lib/i686/libc.so.6和/lib/ld-linux.so.2复制到/mnt/lib下面即可。
libc.so.6是基本的libc库,好像不同的处理器还不一样,我就在一台P-MMX下面用P2的libc,结果死掉了。查了半天 :(
iptables命令除了其显示的以外,还要把/lib/iptables目录复制到/mnt/lib下面
/lib/security下面是PAM需要的库,最小系统只需要复制/lib/security下面的pam_unix.so、pam_stack.so到/mnt/lib/security下面即可

/etc下面的东西最是麻烦,要改的不是一点点
/etc/inittab init的配置文件,我改的是这样的: 

id:3:initdefault:
si::sysinit:/etc/rc
1:2345:respawn:/sbin/mingetty tty1
2:23:respawn:/sbin/mingetty tty2
l0:0:wait:/etc/rc0
l6:6:wait:/etc/rc6 

呵呵,简单吧 

/etc/fstab记录系统启动应该mount的文件系统,因为系统在ramdisk上面跑,所以是这样的: 

/dev/ram0               /                       ext2    defaults        1 0
none                    /proc                   proc    defaults        0 0

从inittab里面知道,启动后执行脚本/etc/rc我是这样写的: 

#!/bin/sh
/bin/mount -n -t proc /proc /proc
/bin/mount -n -o remount,rw /
/bin/mount -av
/bin/hostname MiniLinux
/etc/rc.network
/etc/rc.firewall
/bin/sshd 

第一行mount /proc,第二行把根remount为rw模式(漏了这个害得我查了两三天),第三行检查fstab里面是否还有其他的需要mount的分区,第四行设置主机名,后面分别根据脚本设置网络和防火墙,最后开启sshd服务。

设置网络在最小系统里面再也不是像/etc/init.d/network start那么简单,
呵呵,其实也不麻烦。通过命令ip、ifconfig可以很方便的设置。例如rc.network为: 

#!/bin/sh
/bin/ifconfig eth0 192.168.0.254
/bin/ifconfig eth1 211.69.200.1
/bin/ip route add default via 211.69.200.2 dev eth1
/bin/ip route replace 192.168.0.0/24 dev eth0 scope link
/bin/ip route replace 211.69.200.0/24 dev eth1 scope link 

前两行设置ip地址,第三行设置缺省网关,后面两行更改本网路由。 

rc.firewall的写法大家可以自己参照iptables的HOWTO去完成 

/etc/passwd,/etc/shadow,/etc/group记录有系统帐号信息,在最小系统上,我只留了root组(用户)的信息。

裁减Linux过程中,PAM是一个很关键的部分,由于资料不多,很多人束手无策。 

如果仅仅要使用最小系统,从console登陆需要修改/etc/pam.d/login,从ssh 上来修改/etc/pam.d/sshd,不妨复制系统原来的配置文件略作修改/etc/pam.d/login为 

#%PAM-1.0
auth       required     /lib/security/pam_stack.so service=system-auth
account    required     /lib/security/pam_stack.so service=system-auth
password   required     /lib/security/pam_stack.so service=system-auth
session    required     /lib/security/pam_stack.so service=system-auth
 
/etc/pam.d/sshd和login的内容一样。从其可知它们调用了system-auth这个服务,则还需要/etc/pam.d/system-auth,内容为: 

#%PAM-1.0
# This file is auto-generated.
# User changes will be destroyed the next time authconfig is run.
auth        sufficient    /lib/security/pam_unix.so likeauth nullok
account     required      /lib/security/pam_unix.so
password    sufficient    /lib/security/pam_unix.so nullok  md5 shadow
session     required      /lib/security/pam_unix.so

关机和重启

关机和重启在完整的Linux下面是有很长的脚本支持的,就像启动脚本/etc/rc.sysinit 等等。但是在最小系统上面,这些都需要自己来写,复制原有系统的肯定不行。 

不过从前面/etc/inittab里面可以知道,最小系统上面reboot执行的是/etc/rc6,关机是/etc/rc0,如果不需要“善后”,则很简单,rc6如下: 

[root@MiniLinux etc]# cat rc6
/sbin/reboot -i -d 

rc0则为:
[root@MiniLinux etc]# cat rc0
/sbin/halt -i -d -p 

整个etc目录下的东西列表大致为:
[root@MiniLinux etc]# ll
total 891
-rw-r–r–    1 root     root         6639 Apr 19  2002 fonts.cgz
-rw-r–r–    1 root     root          109 Dec 30 06:19 fstab
-rw-r–r–    1 root     root           14 Dec 14 00:10 group
-rw-r–r–    1 root     root          146 Dec 27 15:41 inittab
-rw——-    1 root     root           60 Jan  6 15:14 ioctl.save
-rw-r–r–    1 root     root           57 Dec 12 01:26 issue
-rw-r–r–    1 root     root        28436 Apr 19  2002 keymaps.gz
-rw-r–r–    1 root     root         3758 Apr 19  2002 kon.cfg
-rw-r–r–    1 root     root         1281 Apr 19  2002 lang-table
-rw-r–r–    1 root     root         1320 Dec 30 06:55 ld.so.cache
-rw-r–r–    1 root     root           18 Dec 12 07:53 ld.so.conf
-rw-r–r–    1 root     root        54692 Apr 19  2002 loader.tr
-rw-r–r–    1 root     root         1180 Dec 23 09:07 login.defs
-rw-r–r–    1 root     root        30303 Apr 19  2002 minikon.fnt
-rw-r–r–    1 root     root            0 Dec 13 23:39 mtab
-rw-r–r–    1 root     root          270 Dec 23 04:03 nsswitch.conf
drwxr-xr-x    2 root     root         1024 Dec 30 04:48 pam.d
-rw-r–r–    1 root     root           28 Dec 30 06:29 passwd
-rwxr-xr-x    1 root     root          401 Dec 30 07:43 profile
-rw-r–r–    1 root     root        12359 Apr 19  2002 ramfs.img
lrwxrwxrwx    1 root     root            7 Dec 26 03:03 rc -> rc.d/rc
drwxr-xr-x    2 root     root         1024 Dec 27 15:33 rc.d
-rwxr-xr-x    1 root     root         2631 Jan  6 02:18 rc.firewall
-rwxr-xr-x    1 root     root          246 Jan  6 02:17 rc.network
-rwxr-xr-x    1 root     root           20 Dec 27 15:38 rc0
-rwxr-xr-x    1 root     root           19 Dec 27 15:39 rc6
-r——–    1 root     root           59 Dec 30 06:20 shadow
drwxr-xr-x    2 root     root         1024 Dec 26 06:37 ssh
-rw-r–r–    1 root     root       737535 Dec 23 10:18 termcap 

其中目录ssh为sshd的配置文件,复制原来机器上的即可。其他的大部分文件都是按照HOWTO上面提到的一些必备文件复制的。 

nsswitch.conf是系统寻找一些配置文件的配置文件,呵呵,很拗口,man nsswitch.conf看看吧,稍作修改为:
[root@MiniLinux /]# cat etc/nsswitch.conf
passwd:     files
shadow:     files
group:      files
hosts:      files
services:   files
networks:   files
protocols:  files
rpc:        files
ethers:     files
netmasks:   files
bootparams: files
automount:  files
aliases:    files
netgroup:   files
publickey:  files 
 

profile文件是bash shell的登陆脚本,主要为了限制历史命令记录大小 

[root@MiniLinux etc]# cat profile
# /etc/profile
# System wide environment and startup programs, for login setup
# Functions and aliases go in /etc/bashrc
HISTSIZE=1000
HISTFILESIZE=20
PATH=/bin
PS1=’[\u@\h \W]\$ ‘
HOSTNAME=’/bin/hostname’
export PATH HISTSIZE HISTFILESIZE HOSTNAME PS1
alias l.=’ls -d .[a-zA-Z]* –color=tty’

alias ll=’ls -l –color=tty’
alias ls=’ls –color=tty’
alias rm=’rm -i’
alias cp=’cp -i’
alias mv=’mv -i’

关于/dev目录 

完整linux的/dev目录下有很多设备文件,不过仔细辨别一下就会发现很多其实
用不上。我列出在我的最小Linux下面用到的设备文件:
[root@MiniLinux dev]# ls
agpgart  hda   hda6     input  loop3     psaux  ptyp3  ram3  tty3   ttyp4
console  hda1  hda7     kbd    loop4     ptmx   ptyp4  ram4  tty4   urandom
fb       hda2  hda8     kmem   loop5     pts    ram    shm   ttyp0  zero
fb0      hda3  hda9     loop0  mem       ptyp0  ram0   tty0  ttyp1
fd0      hda4  initctl  loop1  null      ptyp1  ram1   tty1  ttyp2
fd1      hda5  initrd   loop2  openprom  ptyp2  ram2   tty2  ttyp3

其中input、shm、pts是目录,似乎是系统自己生成的,fb连接到fb0,ram连接到ram0 ,关于硬盘的保留了hda?,loop[0-5]用于支持回环设备(loopback devices),tty[0-4]用于支持主机直接操作,ttyp[0-4] & ptyp[0-4]用于共同支持ssh登陆,ram?用于支持虚拟盘,urandom是sshd服务必须的设备。

所有的设备文件均可以用cp -dpR从原系统的/dev目录下复制过来。

关于linuxrc
linuxrc是一个在initrd.img里面展开后直接自动执行的一个脚本。关于这个脚本的用途,建议大家读一下/usr/src/linux-2.4/Documentation/initrd.txt,我也是直接把系统提供的initrd-2.4.18-3.img里面带的linuxrc拿来用而已:
[root@MiniLinux /]# cat linuxrc
#!/bin/nash
echo Mounting /proc filesystem
mount -t proc /proc /proc
echo Creating root device
mkrootdev /dev/root
echo 0×0100 > /proc/sys/kernel/real-root-dev
echo Mounting root filesystem
mount –ro -t ext2 /dev/root /sysroot
umount /proc
pivot_root /sysroot /sysroot/initrd 

注意,它用的shell是/bin/nash,而不是通常用的/bin/sh,大家man nash可以看到很多有意思的东西。

最后的一些工作 

当你把一个文件虚拟为一个磁盘并挂接在系统上,复制了需要的可执行文件、库文件、配置文件并做了必要的修改之后,一个文件系统基本上已经成形了。
前面列出的最小系统的目录,如果没有特别提到都是留空的。这里要说的,最后的工作就是如何把kernel和文件系统结合起来。 

我看到过很多讲一张或者两张软盘启动的linux,里面都提到用rdev定位文件系统,还要如何如何算。我是看明白了,不过觉得特别繁琐,就投机取巧了一番。 

首先假定刚才我们挂载的根文件系统是挂载到现在的/mnt目录下,文件名是/root/newfs则首先umount 

#umount /root/newfs 

接着将newfs压缩 

#gzip -v9 /root/newfs 

此时会生成newfs.gz,接着rename: 

#mv newfs.gz newfs.img 

把它和前面编译的内核bzImage放到/boot目录下去。 

我用的Linux引导器是grub。为什么不用LILO?我基本上没有用过LILO,是出道很晚很晚的Linux使用者,从grub的介绍上我发现它比LILO功能强很多,使用新内核不需要像grub那样重新安装,而且内置支持一些常见的文件系统。 

看看/boot/grub/grub.conf吧,在其中增加一段:
title Test Combine
        root (hd0,4)
        kernel /bzImage ro ramdisk_size=8192 root=/dev/ram0
        initrd /newfs.img
 
当然,你不可照抄我的配置,需要按照你的系统更改root (hd?,?),如果你建立的文件系统(未压缩前)容量是其他数值的,请用合适的值替代8192,单位是KB(还记得我前面说过的编译内核的时候不需要刻意更改缺省ramdisk容量吗?就在这里指定即可) 

好了,可以试试裁减以后的系统了。大概有多大呢?我裁减出来的系统内核大约是<800k,文件系统8M,压缩成.img的是约3M,很小吧。

感想 及 说明 

先说说这个最小Linux的启动过程。grub将内核载入以后,让内核将newfs.img载入内存并展开(自动展开到/dev/ram0)为临时根文件系统,此时执行/linuxrc,在linuxrc中又指定了新的文件系统。接着内核按照grub带入的参数root=/dev/ram0作为根文件系统正式init,此时/dev/ram0中的内容正是newfs.img的内容(如果你仔细观察的话,会发现完整linux启动过程中有Unmounting initrd….的字样,说明initrd.img是在进入init脚本的过程中才被卸载的),按照/etc/inittab脚本执行,缺省为runlevel 3,执行/etc/rc,最后由mingetty启动login完成引导。 

我个人感觉,/linuxrc里面的脚本似乎对内核正式init时的根分区指定没有多少影响,并不像/usr/src/linux-2.4/Documentation/initrd.txt里面linuxrc例子那样要求严格。好像决定正式init根分区的是由grub带入内核的参数root=… 来决定的更多。有经验的大侠请多指点。 

经过这次实验,感受颇多。首先对linux引导过程以及/etc下面的很多配置文件有了深入了解。Linux可改变的弹性很大,不过也需要大家沉得住气,慢慢去研究,man、HOWTO等等是少不了看的,第一手的资料还是man和英文HOWTO最权威,其次,缩减以后的大小让人非常振奋,裁减以后,系统加载重启登陆的速度都变得非常快,很多东西简直就是一眨眼就过去了。 

文末,感谢各位耐心看完,不对之处请斧正。本人也是linux的新手,万望海涵。

2004年10月11日

      怀着忐忑不安的心情去拿了照片回来97张照片只有4张没有效果,算是不错的了,总算没有给深圳5天之行留下遗憾。

    就如预料的那样,九十几张照片当中,风景和场景照占了绝大部分,留给自己和美眉的照片屈指可数,两人的合影干脆就没有啦。这也是没办法的事情,两个人出去玩,不想带三角架,又早过了儿童时代那种抢在相机前头的时代,也难怪美眉说意料当中了,呵呵。

    场景照当中的效果真是出奇的好,当时在民俗文化村看的夜场演出谋杀了我一整卷胶片啊,幸好效果出来了,绝大部分都是长焦、1/10秒以上长时间手持曝光的,看来自己技术还不错,自夸一下,嘿嘿。

    还是觉得用胶片挺麻烦的,下次改用数码吧。这回和美眉商量的理光R1的机器不错,等着降价吧,在春节应该能排上用场。^_^

by Quickmouse (quickmouse@263.net) 2004年10月11日

    一直弄不清楚Linux是如何加载USB控制器的,终于到了非弄清楚不可的时候,不然手头的一个东西通用性就不好了。以RedHat Linux为例吧,我用的7.3的版本。老掉牙了吧,呵呵。
    在启动过程当中的加载是从/etc/rc.sysinit当中完成的,其中有加载usbcore模块和usb控制器模块的脚本。usbcore模块好说,/lib/modules(实际为/lib/modules/内核版本号/下面,省略掉内核版本号,下同)里面本身就有usbcore.o或者usbcore.ko(2.6.x kernel)的模块,但是usb控制器怎么加载呢,这就是今天弄清楚的东西。
      /etc/rc.sysinit当中加载控制器是通过”modprobe usb-controller”命令来完成的,在/lib/modules当中没有usb-controller这个模块,它的定义是通过/etc/modules.conf或者/etc/modprobe.conf(2.6.x kernel)完成的。当中我们可以找到”alias usb-controller usb-uhci”或者”alias usb-controller uhci-hcd”(2.6.x kernel,下不再注明)。于是我们知道了usb控制器的模块实际上是一个别名,其真实模块指向的是uhci。
   当然,uhci只是USB控制器的一种,就如我们知道的还有ohci的USB控制器,源于Intel/VIA/SIS等厂商对USB控制器所在桥控制器的描述不同。如果我们需要构造一个比较通用的USB模块加载的脚本/程序,例如像RedHat发行版这样的,那如何办呢?从我们每个Linux的系统看,这些都是写死了的。让我们想想看modules.conf里面的别名是怎么来的……
   对了,是kudzu写入的。kudzu是Linux下一个类似于windoz当中硬件检测器一样的东东。我们当然不需要关心kudzu是如何从头到尾工作的。只需要关注其检测usb控制器的流程即可。USB控制器是挂接在PCI总线当中的,因此,检测USB控制器的类型实际上就是要通过pci总线上的信息判断USB控制器是属于uhci还是ohci甚至是ehci。
   首先,PCI设备的信息可以通过/proc/bus/pci下的文件得到。文件devices里面表示了系统自动得到的所有pci设备的信息,基本上都是数字,嘿嘿,比较难看懂。不过我们只需要每一行的第一个16位数,例如:
003a    80867112        9       00000000        00000000        00000000       00000000 00001061        00000000        00000000        00000000        0000000000000000        00000000        00000020        00000000        00000000

   这只是其中一行的数据,我们仅仅关心其中的003a。这是16进制表示的,其中高八位表示总线号,中间五位表示设备号,低三位表示功能号。这里我们知道这一行表示总线00上的07号设备的2号功能。这样我们可以打开/proc/bus/pci/00/07.2得到该设备的PCI配置空间(256B)内容。相对于这个256B,我们关心的内容也非常非常的少,把首16B列在下面吧:
      86 80 12 71 05 00 80 02 00 00 03 0c 00 40 00 00
   其中vender是0×8086,16bit;device是0×7112,16bit;04H-07H不关心;08H的0×00是Revision ID;09H-0BH是Class Code,其中Class Code只占用16bit,0×0C03,余下的最高8bit是Programmer Interface,即0×0。Class Code当中的高8位是Base Class,低8位是Sub Class。后面的信息就用不着拉。

   再来看看kudzu如何区分uhci/ohci/ehci,奥秘在/lib/modules下的modules.alias当中,我把它摘录出来:
alias pci:v*d*sv*sd*bc0Csc03i00* uhci_hcd
alias pci:v*d*sv*sd*bc0Csc03i10* ohci_hcd
alias pci:v*d*sv*sd*bc0Csc03i20* ehci_hcd
   嘿嘿,看出什么来了?v表示vendor,d表示device,sv/sd表示subsystem vender/device,bc表示Base Class,sc表示Sub Class,i表示Programmer Interface。于是判断属于uhci/ohci/ehci的标准是Class Code为0×0C03,区分他们的标志即为看Programmer Interface。
   到这里我想大家应该可以写出一个较为通用的加载程序了吧。首先通过/proc/bus/pci/devices得到现在所有的pci设备所在的总线和设备号,依次打开它们的配置空间,读出Class Code和Programmer Interface,如果Class Code是0×0C03即表明是USB控制器,再根据Programmer Interface决定加载uhci还是ohci或者是ehci。
   就这么简单吧 ^_^

参考资料:
1、《如何在LINUX下实现硬件的自动检测(上)》 
    于辰涛 (scu_yct@263.net),http://www-900.ibm.com/developerWorks/cn/linux/hardware/auto-detecting/part1/index.shtml
2、linux 2.6.8.1 src code,~/scripts/mod/file2alias.c

2004年10月10日

人的想法总是很奇怪的,真的是应了林清玄散文当中的那句“有时候过了五分钟,心情就完全不同了”。在blog上安一个家也只是5分钟的心血来潮。那就这样吧,开始慢慢打理这个家吧

?^______^