Archive for January, 2009


本杰明巴顿奇事-The Curious Case of Benjamin Button

这是一部获得了13项奥斯卡提名的影片,正因为此,才逼使我第一次看了TS版本的影片。画面和声音清晰度都有限,而且中间还有莫名的剪切。从某种角度来说,这部影片的确有个很好的想法,或者说是一个很荒谬的想法,不过一路看下来,2个半小时倒也觉得十分畅快。两段恋情贯穿始终,而从老婴儿回到小婴儿也算是一个前后呼应,还有那两句不断重复的Good Night Benjamin, Good Night Daisy。

不过对我来说感触深的不是在于这段倒着长的生活,也不是这种奇特的爱情。从人出生到死,一般也就70、80年,除去最开始的10年左右几乎无忧无虑的生活和最后10年可能的老弱多病,真正可以享受人生的只有40到50年的时间。而很多人能够意识到人生的长短的时候,实际上已经20出头了,再回首看看走过的岁月,会发现荒废了很多光阴,而前面又已经被甩下很多了。这种痛苦的感觉是我在看这部片时一直所想到的。继续,人是无法改变自己身体机能衰老的。现在处于25岁的我,也算是处于黄金阶段,什么事情都扛的下,吃喝拉撒都顺顺常常。不过再过10年以后呢,也就只是短短10年,我已经35了,再过10年我就45了。到那个时候,我还可以跳得起来么,还可以像现在这样熬夜,像现在这样大吃特吃吗?身体机能的限制,将会是生活失去很多很多。等你到了那个年龄再去后悔就更加来不及了。在继续,玩实况中的一球成名,会发现前几年的成长速度及其之快,可是到了25、26基本上数据就不长了。心寒,这是唯一的想法很多时候。

就不能不放鞭么?

一吵

二吵

三还是吵

PHP学习记录(5)–xampp篇

对于我这个不太懂PHP的人来说,以及习惯了使用VS或者是ECLIPSE这种简单IDE的人,对于PHP的环境搭配还是觉得头都是大的。一早上就为了安装xampp,和解决它的问题忙乎过去了。这个软件的确相对而言还是比较方便的,但是其中也不可否认的存在一些问题,谈不上是BUG,但在使用的过程中会给人小小地造成一些麻烦。

首先介绍一下xampp,这是一个大的软件集合体,根据官方的说法,包括:
Apache
MySQL
PHP
phpMyAdmin
FileZilla FTP Server
Mercury Mail Transport System
OpenSSL
看一下,就发现,如果你只是想开发简单的PHP网站的话,那么基本上所有需要的工具都包含在里面了,虽然后面几个目前来说我不太了解其用途。

然后再看一下,因为在PHP界,和xampp这几个字差不多的软件有几个。比较有名的一个是lamp,一个是wamp。其实会发现,本质都是一样的,amp代表着apache, mysql和php。而前一个l是指Linux环境下,而w指代的是windows环境。这样一解释就比较容易了。xampp是后期出现的一个产品,除了传统的amp支持外,多的一个p指代的是perl,而x就大概是兼容并包的意思吧,既可以支持Linux,又可以跑在windows上。
网上有很多介绍几者之间关系的文章,推荐这篇可以看看,搜索一下即可:解读wamp和lamp:从对立到融合

从xampp的官方网站上下载下安装程序,大概37M左右,双击安装。过程之中有两点需要注意,这也就是xampp的不人性之处:
1. 安装目录不要选择programe files,直接安装在根目录下,不然在后面的启动服务过程中会出现麻烦
2. 有三个选项要你选择是否将apache, mysql和fileZilla设置为服务,这个无关紧要,因为完成后也很容易修改。具体的办法是,对apache,\xampp\apache 目录下apache_installservice.bat文件就是安装服务,apache_uninstallservice.bat就是取消服务。对于其余两个同样在相关目录下。

安装成功后,可以启动Control Panel,在其中可以选择启动apache和mysql。如果你在安装过程中选择了安装服务,那么他们两个将会自动启动。但是这个启动过程中会出现不少的问题,比较常见的问题就是出现一段话:apache service not started或者是mysql service not started,原因一般也有以下几种:
1. 端口被占用:apache的默认端口是80,如果你在用VS开发使用IIS的话,冲突是不可避免的。同时,大部分的中国程序员都被迅雷所吸引,但是这玩意端口也是80。于是还是改一下apache的端口吧,地址\xampp\apache\conf下的httpd.conf,把80改成你喜欢的,当然你也不能太随便了。
2. mysql的端口是常见的3306,但是如果你曾经是一个JSP开发人员,可能自己就已经安装过mysql,而往往不会选择手动启动服务。于是你的电脑每次开机,都会自动加载mysql的服务,直接导致3306被占用。有个很简单的办法,进入服务,把原来的那个mysql的服务改为手动,就解决了。不过下次调试jsp的时候,一定记得在开tomcat的同时,打开mysql。
3. 按照的过程中,安装到了program files目录下,没有阅读前文,自行卸载解决。
4. 如果你点击了mysql的管理工具,但是却输错了密码,导致的直接结果就是不停地弹出一个包错的玩意,除非你把mysqlAdmin关掉。这件事提醒我们,一定要输入正确的密码,不然就会像我一样,卸载。初始密码统一是应户名root,密码空。

安装好了以后,启动apache,做一个简单的测试。直接浏览器,http://localhost。如果一切顺利,出现的应该是语言选择菜单,自己爱啥选啥,如果你还能懂波兰语或者是挪威语的话。进入以后,左边的菜单有一项状态Status,点击看就是当前的状态,表明你的安装状况。

xampp Status

补充一个关于美剧和英语的片子

看到我的博客的访问记录,很多都是在搜索引擎上搜索雅思和美剧跑来的,的确这是一个很好的学习工具,我也小说一下自己的体会。

个人觉得通过这种办法学英语的确是一个一举多得的事情,首先是不会觉得枯燥,因为有剧情,特别是美剧往往都是很跌宕起伏的。其次,就是学会的语言不是书面化的,更多的和实际中口语相关。比如你在书面上绝对看不到dude这个词,但是实际上这是很常用的称呼兄弟好朋友的办法,如果你不懂,某些场合会比较影响自己的交流。再次,让你习惯各种口音,虽然美剧里面以美式为主,但是现在大部分的剧集中都有少部分他国人士。比如Big Bang Theory中的印度人,LOST中的澳洲英语,伊拉克英语,韩国英语还有英式英语,FRIENDS中也有一些英式英语等等。要知道美式英语绝对是世界上最容易懂的英语,听得懂CNN不等于你能在世界上通行,想挑战自我的话,从印度开始,还有一个选择就是老太太的英语。最后,看美剧,然后上论坛聊聊天,发发帖子,看看WIKIPEDIA,都是英文的啊。

这里又想到另外一个问题,大多数的中国人在使用GOOGLE搜索的时候,使用的都是GOOGLE中国,结果就是搜索到的大部分都是中文信息,这个是很妨碍学习英文,特别是你想要找到很多英文资料的时候。比如我可不想在输入ABC的时候,第一个选项是ABC个人护理网站。因此,建议想学英文的人,可以把你的某一种浏览器的语言地区改为英语,这样可以解决这个搜索引擎的问题了。

下面说说我对这几部剧的看法:
1. Friends,绝对适合学英语,特别是初学者,里面主人公的英语速度都不是很快,而且句式简单,思想不复杂。特别是里面经常会带有一些常见的诸如骂人,当然也包括一些和SEX有关的土语,而这些是你在其他地方都无法学到的。印象最深的是PHD这个词,自己查字典吧。
2. Two and a Half Man,我觉得只要是喜剧都应该很适合学英语,这部戏的SEX内容更多,但这不是我们关注的对象。其实因为它比FRIENDS拍摄要晚差不多10年,所以内容的更新更贴近于现在的生活,很多新词新用法是很不错的。特别是想学会怎么骂人,看看兄弟两加上BERTA吧。
3. LOST,剧情的经典大过于学英语,以至于我看完4季只花了不到一个星期,还是在备考雅思的关键时刻。提供了一个良好的机会,接触多国语言,对于要在澳洲生活的人,这部片相当适合。几个主要任务,Jack、kate、Locke、Sawyer、Ben都是很标准的美音,很好的机会,不过又有一点点区别。前三个人的口音很容易听懂,Sawyer的说话很不正式,而且喜欢起外号,说些不占头脑的话,比如freckle这个词,就是专门用来形容Kate的,但是很不常见。而Ben说话速度特别快,思维跳跃性也很高,有难度。接着Sayid是中东口音,这部片里他说话速度慢,所以还是好理解的,可以当个敲门砖。Sun的韩式英语,也算是简单说的清楚,不像实际生活中的版本。Charlie的英式英语还是相当不错的,很有参考价值,但是desmond的那种苏格兰英语以及一口一个brother真的叫人很难受。(让我想到了Tim的挪威英语。)还有Micheal的黑人英语,想学rap么?最后的就是澳式英语,这里面比较明显的就是Claire了,还有在悉尼机场出现的几个服务员。民族大融合。
4. Big Bang Theory,如果你不是搞物理专业的,如果你不是很懂科学,如果你不是很擅长玩一些游戏,还有一部什么电影,这部片不适合学英语。。。虽然相当好看。
5. 24小时,很好看,我喜欢看,特别是Jack的沙哑式英语。这部戏大部分场合都是在阴暗的深处拍摄,说话人的声音都小心翼翼,所以很适合大家学习如何说悄悄话。
6. Prison Break,很可惜终于这部片要被拿下了,不明白为什么拍这么多。也算是一个学英语的好剧,虽然演员们说话速度都比较快,而且很喜欢说些莫名其妙的话,让人匪夷所思。
7. Heroes,经典的学习日本英语的教程,另外这部剧绝对适合学英语,说话速度都很慢,而且很好懂。

最后说的就是,如果你是真的想学英语而不是只是为了看美剧,那么请一定把字幕给遮上,不然你的吸引力会在98%以上的时间集中在屏幕下端那一排排方方正正的字上面。遮字幕的办法很多,我最喜欢用的是打开记事本,notepad,然后把它缩小到正好遮住字幕的大小。

过去的一年和下面的一年

考完了雅思,现在人基本上处于闲置状态了,本来想继续好好看看FLEX和PHP的,不过觉得心情无法静下来了。好在暂时也没有什么压力,就继续放任自己下去这段小日子,每天抱着电脑看珠光宝气。刚刚在FACEBOOK上被一个摩纳哥人邀请,加入一个小组,不知道那个人为什么这么对我感兴趣,还推荐我去他们网站。一看,全部是看不懂的,貌似是土耳其语,还是什么。。。想象人家欧洲人,一生可以学会10几种语言,咱现在还在为英语而发愁,造化啊。

总结了一下过去的一年里的一些事情,不分先后顺序:

1. 开始了在墨尔本的生涯,不知道会在这座城市呆多久,记忆中最清晰的是7月份回到墨尔本时,在207BUS上闻到的空气和BUS的味道,这种味道是墨尔本独有的。
2. 刚刚考完的雅思,复习了一个半月,结果如何谈不上,不过有足够的心里准备再考一次。什么时候才能让我过4个7呢。
3. 4月12日在swanston上游行,4月26日在Canberra看火炬,第一次参加这种所谓的爱国主义活动,估计也是最后一次。
4. 去年的新年是在成都过的,很开心,滑雪,好吃的,就是不知道和5月份的地震有没有什么关系。
5. 24岁了,两个循环过完了。
6. 自己学会了做很多菜,不过发现做菜的欲望是越来越小,人是越来越懒,特别是知道了有包饺子这个简单的事情之后,最后的一个月几乎吃了400个以上的饺子。
7. 打工,几乎切掉我的指头,刻骨铭心的记忆,这个故事告诉我们,当不会拿刀的时候,还是远离刀比较好。当然能够赚到自己的钱自己花肯定是最好的。
8. 在香港机场由头一天下午2点,呆到第二天早上9点,创下历史转机记录。要不是香港机场可以无线上网,我会坐死的。
9. 参加教会活动,以前的我是肯定不愿意搞这种事情的,不过发现有时候学学圣经还是很有意思的。
10. 看完了10季的FRIENDS, 6季的24,4季的LOST,5季的TWO AND A HALF MAN,还有很多。。。留学的日子就等于电脑,电脑+电脑,当然眼睛是最倒霉的了。

接下来的一年,有几个小小的计划:

1. 考过雅思,这个绝对是首要的任务。
2. 09年年底的旅游活动,心中初步有谱了,详细规划再议.
3. 找到一份工作,不是餐馆的工作,是正正规规的工作
4. 提高英语,口语,作文,口语,作文,口语,还有作文。
5. 多照相,不能把照相机给浪费了。
6. 有几个技术要挖深一点。

以上纯属自己的胡乱构想,还有一些私人的隐秘信息的总结和展望就不予以写出了。

Linux TCP/IP 协议栈的关键数据结构Socket Buffer(sk_buff ) (转)

sk_buff结构可能是linux网络代码中最重要的数据结构,它表示接收或发送数据包的包头信息。它在<include/linux/skbuff.h>中定义,并包含很多成员变量供网络代码中的各子系统使用。

这个结构在linux内核的发展过程中改动过很多次,或者是增加新的选项,或者是重新组织已存在的成员变量以使得成员变量的布局更加清晰。它的成员变量可以大致分为以下几类:

  • Layout 布局

  • General 通用

  • Feature-specific功能相关

  • Management functions管理函数

这个结构被不同的网络层(MAC或者其他二层链路协议,三层的IP,四层的TCP或UDP等)使用,并且其中的成员变量 在结构从一层向另一层传递时改变。L4向L3传递前会添加一个L4的头部,同样,L3向L2传递前,会添加一个L3的头部。添加头部比在不同层之间拷贝数 据的效率更高。由于在缓冲区的头部添加数据意味着要修改指向缓冲区的指针,这是个复杂的操作,所以内核提供了一个函数skb_reserve(在后面的章 节中描述)来完成这个功能。协议栈中的每一层在往下一层传递缓冲区前,第一件事就是调用skb_reserve在缓冲区的头部给协议头预留一定的空间。

skb_reserve同样被设备驱动使用来对齐接收到包的包头。如果缓冲区向上层协议传递,旧的协 议层的头部信息就没什么用了。例如,L2的头部只有在网络驱动处理L2的协议时有用,L3是不会关心它的信息的。但是,内核并没有把L2的头部从缓冲区中 删除,而是把有效荷载的指针指向L3的头部,这样做,可以节省CPU时间。

1. 网络参数和内核数据结构

就像你在浏览TCP/IP规范或者配置内核时所看到的一样,网络代码提供了很多有用的功能,但是这些功能并不是必须的, 比如说,防火墙,多播,还有其他一些功能。大部分的功能都需要在内核数据结构中添加自己的成员变量。因此,sk_buff里面包含了很多像#ifdef这 样的预编译指令。例如,在sk_buff结构的最后,你可以找到:

struct sk_buff {
      ... ... ...
#ifdef CONFIG_NET_SCHED
      _ _u32      tc_index;
#ifdef CONFIG_NET_CLS_ACT
      _ _u32      tc_verd;
      _ _u32      tc_classid;
#endif
#endif
}

它表明,tc_index只有在编译时定义了CONFIG_NET_SCHED符号才有效。这个符号可以通过选择特定的 编译选项来定义(例如:”Device Drivers Networking supportNetworking options QoS and/or fair queueing”)。这些编译选项可以由管理员通过make config来选择,或者通过一些自动安装工具来选择。

前面的例子有两个嵌套的选项:CONFIG_NET_CLS_ACT(包分类器)只有在选择支持“QoS and/or fair queueing”时才能生效。

顺便提一下,QoS选项不能被编译成内核模块。原因就是,内核编译之后,由某个选项所控制的数据结构是不能动态变化的。一般来说,如果某个选项会修改内核数据结构(比如说,在sk_buff
里面增加一个项tc_index),那么,包含这个选项的组件就不能被编译成内核模块。

你可能经常需要查找是哪个make config编译选项或者变种定义了某个#ifdef标记,以便理解内核中包含的某段代码。在2.6内核中,最快的,查找它们之间关联关系的方法, 就是查找分布在内核源代码树中的kconfig文件中是否定义了相应的符号(每个目录都有一个这样的文件)。在
2.4内核中,你需要查看Documentation/Configure.help文件。

2. Layout Fields

有些sk_buff成员变量的作用是方便查找或者是连接数据结构本身。内核可以把sk_buff组织成一个双向链表。当然,这个链表的结构要比常见的双向链表的结构复杂一点。

就像任何一个双向链表一样,sk_buff中有两个指针next和prev,其中,next指向下一个节点,而
prev指向上一个节点。但是,这个链表还有另一个需求:每个sk_buff结构都必须能够很快找到链表头节点。为了满足这个需求,在第一个节点前面会插入另一个结构sk_buff_head,这是一个辅助节点,它的定义如下:

struct sk_buff_head {
    /* These two members must be first. */     struct sk_buff      * next;   
     struct sk_buff      * prev;      
     _ _u32          qlen;   
     spinlock_t      lock;   
 };

qlen代表链表元素的个数。lock用于防止对链表的并发访问。

sk_buff和sk_buff_head的前两个元素是一样的:next和prev指针。这使得它们可以放到同一个链表中,尽管sk_buff_head要比sk_buff小得多。另外,相同的函数可以同样应用于sk_buff和sk_buff_head。

为了使这个数据结构更灵活,每个sk_buff结构都包含一个指向sk_buff_head的指针。这个指针的名字是list。图1会帮助你理解它们之间的关系。

Figure 1. List of sk_buff elements

其他有趣的成员变量如下:

struct sock *sk
这 是一个指向拥有这个sk_buff的sock结构的指针。这个指针在网络包由本机发出或者由本机进程接收时有效,因为插口相关的信息被L4(TCP或 UDP)或者用户空间程序使用。如果sk_buff只在转发中使用(这意味着,源地址和目的地址都不是本机地址),这个指针是NULL。

unsigned int len
这是缓冲区中数据部分的长度。它包括主缓冲区中的数据长度(data指针指向它)和分片中的数据长度。它的值在缓冲区从一个层向另一个层传递时改变,因为往上层传递,旧的头部就没有用了,而往下层传递,需要添加本层的头部。len同样包含了协议头的长度。

unsigned int data_len
和len不同,data_len只计算分片中数据的长度。

unsigned int mac_len
这是mac头的长度。

atomic_t users
这 是一个引用计数,用于计算有多少实体引用了这个sk_buff缓冲区。它的主要用途是防止释放sk_buff后,还有其他实体引用这个sk_buff。因 此,每个引用这个缓冲区的实体都必须在适当的时候增加或减小这个变量。这个计数器只保护sk_buff结构本身,而缓冲区的数据部分由类似的计数器 (dataref)来保护.
有时可以用atomic_inc和atomic_dec函数来直接增加或减小users,但是,通常还是使用函数skb_get和kfree_skb来操作这个变量。

unsigned int truesize
这是缓冲区的总长度,包括sk_buff结构和数据部分。如果申请一个len字节的缓冲区,alloc_skb函数会把它初始化成len+sizeof(sk_buff)。

struct sk_buff *alloc_skb(unsigned int size,int gfp_mask)
{
       ... ... ...
       skb->truesize = size + sizeof(struct sk_buff);
       ... ... ...
}

当skb->len变化时,这个变量也会变化。

unsigned char *head
unsigned char *end
unsigned char *data
unsigned char *tail
它 们表示缓冲区和数据部分的边界。在每一层申请缓冲区时,它会分配比协议头或协议数据大的空间。head和end指向缓冲区的头部和尾部,而data和 tail指向实际数据的头部和尾部,参见图2。每一层会在head和data之间填充协议头,或者在tail和end之间添加新的协议数据。图2中右边数 据部分会在尾部包含一个附加的头部。

Figure 2. head/end versus data/tail pointers
void (*destructor)(…)
这 个函数指针可以初始化成一个在缓冲区释放时完成某些动作的函数。如果缓冲区不属于一个socket,这个函数指针通常是不会被赋值的。如果缓冲区属于一个 socket, 这个函数指针会被赋值为sock_rfree或sock_wfree(分别由skb_set_owner_r或skb_set_owner_w函数初始 化)。这两个sock_xxx函数用于更新socket的队列中的内存容量。

3. General Fields

本节描述sk_buff的主要成员变量,这些成员变量与特定的内核功能无关:

struct timeval stamp
这个变量只对接收到的包有意义。它代表包接收时的时间戳,或者有时代表包准备发出时的时间戳。它在netif_rx里面由函数net_timestamp设置,而netif_rx是设备驱动收到一个包后调用的函数。

struct net_device *dev
这 个变量的类型是net_device,net_device它代表一个网络设备。dev的作用与这个包是准备发出的包还是刚接收的包有关。当收到一个包 时,设备驱动会把sk_buff的dev指针指向收到这个包的设备的数据结构,就像下面的vortex_rx里的一段代码所做的一样,这个函数属于 3c59x系列以太网卡驱动,用于接收一个帧。(drivers/net/3c59x.c):

static int vortex_rx(struct net_device *dev)
{
             ... ... ...
          skb->dev = dev;
             ... ... ...
          skb->protocol = eth_type_trans(skb, dev);
          netif_rx(skb); /* Pass the packet to the higher layer */
             ... ... ...
}

当一个包被发送时,这个变量代表将要发送这个包的设备。在发送网络包时设置这个值的代码要比接收网络包时设置这个值的代 码复杂。有些网络功能可以把多个网络设备组成一个虚拟的网络设备(也就是说,这些设备没有和物理设备直接关联),并由一个虚拟网络设备驱动管理。当虚拟设 备被使用时,dev指针指向虚拟设备的net_device结构。而虚拟设备驱动会在一组设备中选择一个设备并把dev指针修改为这个设备的 net_device结构。因此,在某些情况下, 指向传输设备的指针会在包处理过程中被改变。

struct net_device *input_dev
这是收到包的网络设备的指针。如果包是本地生成的,这个值为NULL。对以太网设备来说,这个值由eth_type_trans初始化,它主要被流量控制代码使用。

struct net_device *real_dev
这个变量只对虚拟设备有意义,它代表与虚拟设备关联的真实设备。例如,Bonding和VLAN设备都使用它来指向收到包的真实设备。

union {…} h
union {…} nh
union {…} mac

这些是指向TCP/IP各层协议头的指针:h指向L4,nh指向L3,mac指向L2。每个指针的类型都是一个联合,包 含多个数据结构,每一个数据结构都表示内核在这一层可以解析的协议。例如,h是一个包含内核所能解析的L4协议的数据结构的联合。每一个联合都有一个 raw变量用于初始化,后续的访问都是通过协议相关的变量进行的。

当接收一个包时,处理n层协议头的函数从n-1层收到一个缓冲区,它的skb->data指向n层协议的头。处理 n层协议的函数把本层的指针(例如,L3对应的是skb->nh指针)初始化为skb->data,因为这个指针的值会在处理下一层协议时改 变(skb->data将被初始化成缓冲区里的其他地址)。在处理n层协议的函数结束时,在把包传递给n+1层的处理函数前,它会把skb- >data指针指向n层协议头的末尾,这正好是n+1层协议的协议头(参见图3)。

发送包的过程与此相反,但是由于要为每一层添加新的协议头,这个过程要比接收包的过程复杂。

Figure 3. Header’s pointer initializations while moving from layer two to layer three
struct dst_entry dst
这个变量在路由子系统中使用。

char cb[40]
这 是一个“control buffer”,或者说是一个私有信息的存储空间,由每一层自己维护并使用。它在分配sk_buff结构时分配(它目前的大小是40字节,已经足够为每一 层存储必要的私有信息了)。在每一层中,访问这个变量的代码通常用宏实现以增强代码的可读性。例如,TCP用这个变量存储tcp_skb_cb结构,这个 结构在include/net/tcp.h中定义:

struct tcp_skb_cb {
      ... ... ...
      _ _u32          seq;          /* Starting sequence number */
      _ _u32          end_seq;      /* SEQ + FIN + SYN + datalen*/
      _ _u32          when;         /* used to compute rtt's      */
      _ _u8           flags;        /* TCP header flags.          */
      ... ... ...
};

下面这个宏被TCP代码用来访问cb变量。在这个宏里面,有一个简单的类型转换:

#define TCP_SKB_CB(_ _skb)      ((struct tcp_skb_cb *)&((_ _skb)->cb[0]))

下面的例子是TCP子系统在收到一个分段时填充相关数据结构的代码:

int tcp_v4_rcv(struct sk_buff *skb)
{
          ... ... ...
          th = skb->h.th;
          TCP_SKB_CB(skb)->seq = ntohl(th->seq);
          TCP_SKB_CB(skb)->end_seq = (TCP_SKB_CB(skb)->seq + th->syn + th->fin +
                                      skb->len - th->doff * 4);
          TCP_SKB_CB(skb)->ack_seq = ntohl(th->ack_seq);
          TCP_SKB_CB(skb)->when = 0;
          TCP_SKB_CB(skb)->flags = skb->nh.iph->tos;
          TCP_SKB_CB(skb)->sacked = 0;
          ... ... ...
}

如果想要了解cb中的参数是如何被取出的,可以查看net/ipv4/tcp_output.c中的tcp_transmit_skb函数。这个函数被TCP用于向IP层发送一个分段。

unsigned int csum
unsigned char ip_summed
表示校验和以及相关状态标记。

unsigned char cloned
一个布尔标记,当被设置时,表示这个结构是另一个sk_buff的克隆。在“克隆和拷贝缓冲区”一节中有描述。

unsigned char pkt_type
这个变量表示帧的类型,分类是由L2的目的地址来决定的。可能的取值都在include/linux/if_packet.h中定义。对以太网设备来说,这个变量由eth_type_trans函数初始化。
类型的可能取值如下:

PACKET_HOST
包的目的地址与收到它的网络设备的L2地址相等。换句话说,这个包是发给本机的。

.PACKET_MULTICAST
包的目的地址是一个多播地址,而这个多播地址是收到这个包的网络设备所注册的多播地址。

PACKET_BROADCAST
包的目的地址是一个广播地址,而这个广播地址也是收到这个包的网络设备的广播地址。
PACKET_OTHERHOST
包的目的地址与收到它的网络设备的地址完全不同(不管是单播,多播还是广播),因此,如果本机的转发功能没有启用,这个包会被丢弃。

PACKET_OUTGOING
这个包将被发出。用到这个标记的功能包括Decnet协议,或者是为每个网络tap都复制一份发出包的函数。

PACKET_LOOPBACK
这个包发向loopback设备。由于有这个标记,在处理loopback设备时,内核可以跳过一些真实设备才需要的操作。

PACKET_FASTROUTE
这个包由快速路由代码查找路由。快速路由功能在2.6内核中已经去掉了。

_ _u32 priority
这 个变量描述发送或转发包的QoS类别。如果包是本地生成的,socket层会设置priority变量。如果包是将要被转发的, rt_tos2priority函数会根据ip头中的Tos域来计算赋给这个变量的值。这个变量的值与DSCP(DiffServ CodePoint)没有任何关系。

unsigned short protocol
这 个变量是高层协议从二层设备的角度所看到的协议。典型的协议包括IP,IPV6和ARP。完整的列表在include/linux/if_ether.h 中。由于每个协议都有自己的协议处理函数来处理接收到的包,因此,这个域被设备驱动用于通知上层调用哪个协议处理函数。每个网络驱动都调用 netif_rx来通知上层网络协议的协议处理函数,因此protocol变量必须在这些协议处理函数调用之前初始化。

unsigned short security
这是包的安全级别。这个变量最初由IPSec子系统使用,但现在已经作废了。

4. Feature-Specific Fields

linux内核是模块化的,你可以选择包含或者删除某些功能。因此,sk_buff结构里面的一些成员变量只有在内核选择支持某些功能时才有效,比如防火墙(netfilter)或者qos:

unsigned long nfmark
_ _u32 nfcache
_ _u32 nfctinfo
struct nf_conntrack *nfct
unsigned int nfdebug
struct nf_bridge_info *nf_bridge
这 些变量被netfilter使用(防火墙代码),内核编译选项是“Device Drivers->Networking support-> Networking options-> Network packet filtering”和两个子选项“Network packet filtering debugging”和“Bridged IP/ARP packets filtering”

union {…} private
这 个联合结构被高性能并行接口(HIPPI)使用。相应的内核编译选项是“Device->Drivers ->Networking support ->Network device support ->HIPPI driver support”

_ _u32 tc_index
_ _u32 tc_verd
_ _u32 tc_classid
这 些变量被流量控制代码使用,内核编译选项是“Device Drivers ->Networking->support ->Networking options ->QoS and/or fair queueing”和它的子选项“Packetclassifier API”

struct sec_path *sp
这个变量被IPSec协议用于跟踪传输的信息。

5. Management Functions

有很多函数,通常都比较短小而且简单,内核用这些函数操作sk_buff的成员变量或者sk_buff
链表。图4会帮助我们理解其中几个重要的函数。我们首先来看分配和释放缓冲区的函数,然后是一些通过移动指针在缓冲区的头部或尾部预留空间的函数。

如果你看过include/linux/skbuff.h和net/core/skbuff.c中的函数,你会发现,基 本上每个函数都有两个版本,名字分别是do_something和__do_something。通常第一种函数是一个包装函数,它会在第二种函数的基础 上增加合法性检查或者锁。一般来说,类似__do_something的函数不能被直接调用(除非满足特定的条件,比如说锁)。那些违反这条规则而直接引 用这些函数的不良代码会最终被更正。

Figure 4. Before and after: (a)skb_put, (b)skb_push, (c)skb_pull, and (d)skb_reserve

5.1. Allocating memory: alloc_skb and dev_alloc_skb

alloc_skb是net/core/skbuff.c里面定义的,用于分配缓冲区的函数。我们已经知道,数据缓冲区 和缓冲区的描述结构(sk_buff结构)是两种不同的实体,这就意味着,在分配一个缓冲区时,需要分配两块内存(一个是缓冲区,一个是缓冲区的描述结构 sk_buff)。

alloc_skb调用函数kmem_cache_alloc从缓存中获取一个sk_buff结构,并调用kmalloc分配缓冲区(如果有缓存的话,它同样从缓存中获取内存)。简化后的代码如下:

      skb = kmem_cache_alloc(skbuff_head_cache, gfp_mask & ~_ _GFP_DMA);
      ... ... ...
      size = SKB_DATA_ALIGN(size);
      data = kmalloc(size + sizeof(struct skb_shared_info), gfp_mask);

在调用kmalloc前,size参数通过SKB_DATA_ALIGN宏强制对齐。在函数返回前,它会初始化结构中的一些变量,最后的结构如图5所示。在图5右边所示的内存块的底部,你能看到对齐操作所带来的填充区域。

Figure 5. alloc_skb function

dev_alloc_skb也是一个缓冲区分配函数,它主要被设备驱动使用,通常用在中断上下文中。这是一个 alloc_skb函数的包装函数,它会在请求分配的大小上增加16字节的空间以优化缓冲区的读写效率,它的分配要求使用原子操作 (GFP_ATOMIC),这是因为它是在中断处理函数中被调用的。

static inline struct sk_buff *dev_alloc_skb(unsigned int length)
{
      return _ _dev_alloc_skb(length, GFP_ATOMIC);
}

static inline
struct sk_buff *_ _dev_alloc_skb(unsigned int length, int gfp_mask)
{
      struct sk_buff *skb = alloc_skb(length + 16, gfp_mask);
      if (likely(skb))
              skb_reserve(skb, 16);
      return skb;
}

如果没有体系架构相关的实现,缺省使用__dev_alloc_skb的实现。

5.2. Freeing memory: kfree_skb and dev_kfree_skb

这两个函数释放缓冲区,并把它返回给缓冲池(缓存)。kfree_skb可以直接调用,也可以通过包装函数 dev_kfree_skb调用。后面这个函数一般被设备驱动使用,与之功能相反的函数是dev_alloc_skb。dev_kfree_skb仅是一 个简单的宏,它什么都不做,只简单地调用kfree_skb。这些函数只有在skb->users为1地情况下才释放内存(没有人引用这个结构)。 否则,它只是简单地减小
skb->users。如果缓冲区有三个引用者,那么只有第三次调用dev_kfree_skb或kfree_skb时才释放内存。

图6中的流程图显示了分配一个缓冲区所需要的步骤。当sk_buff释放后,dst_release同样会被调用以减小相关dst_entry数据结构的引用计数。

如果destructor被初始化过,相应的函数会在此时被调用.

在图5中,我们看到,一个简单的场景是:一个sk_buff结构与另一个内存块相关,这个内存块里存储的是真正的数据。 当然,内存块底部的skb_shared_info数据结构可以包含指向其他分片的指针(参见图5)。如果存在分片,kfree_skb同样会释放这些分 片所占用的内存。最后,kfree_skb 把sk_buff结构返回给skbuff_head_cache缓存。

5.3. Data reservation and alignment: skb_reserve, skb_put, skb_push, and skb_pull

skb_reserve可以在缓冲区的头部预留一定的空间,它通常被用来在缓冲区中插入协议头或者在某个边界上对齐。这 个函数改变data和tail指针,而data和tail指针分别指向负载的开头和结尾,图4(d)展示了调用skb_reserve(skb,n)的结 果。这个函数通常在分配缓冲区之后就调用,此时的
data和tail指针还是指向同一个地方。

如果你查看某个以太网设备驱动的收包函数(例如,drivers/net/3c59x.c中的vortex_rx), 你就会发现它在分配缓冲区之后,在向缓冲区中填充数据之前,会调用下面的函数:

skb_reserve(skb, 2);      /* Align IP on 16 byte boundaries */
Figure 6. kfree_skb function

由于以太网帧的头部长度是14个八位组,这个函数把缓冲区的头部指针向后移动了2个字节。这样,紧跟在以太网头部之后的IP头部在缓冲区中存储时就可以在16字节的边界上对齐。如图7所示。

Figure 7. (a) before skb_reserve, (b) after skb_reserve, and (c) after copying the frame on the buffer

图8展示了一个在发送过程中使用skb_reserve的例子。

Figure 8. Buffer that is filled in while traversing the stack from the TCP layer down to the link layer
  1. 当TCP发送数据时,它根据一些条件分配一个缓冲区(比如,TCP的最大分段长度(mss),是否支持散读散写I/O等

  2. TCP在缓冲区的头部预留足够的空间(用skb_reserve)用于填充各层的头部(如TCP,IP,链路层 等)。MAX_TCP_HEADER参数是各层头部长度的总和,它考虑了最坏的情况:由于tcp层不知道将要用哪个接口发送包,它为每一层预留了最大的头 部长度。它甚至考虑了出现多个IP头的可能性(如果内核编译支持IP over IP, 我们就会遇到多个IP头的情况)。

  3. 把TCP的负载拷贝到缓冲区。需要注意的是:图8只是一个例子。TCP的负载可能会被组织成其他形式。例如它可以存储到分片中。

  4. TCP层添加自己的头部。

  5. TCP层把缓冲区传递给IP层,IP层同样添加自己的头部。

  6. IP层把缓冲区传递给邻居层,邻居层添加链路层头部。

当缓冲区在协议栈中向下层传递时,每一层都把skb->data指针向下移动,然后拷贝自己的头部,同时更新skb->len。这些操作都使用图4中所展示的函数完成。

skb_reserve函数并没有把数据移出或移入缓冲区,它只是简单地更新了缓冲区的两个指针,这两个指针在图4(d)中有描述。

static inline void skb_reserve(struct sk_buff *skb, unsigned int len)
{
      skb->data+=len;
      skb->tail+=len;
}

skb_push在缓冲区的开头加入一块数据,而skb_put在缓冲区的末尾加入一块数据。与skb_reserve
类似,这些函数都不会真的往缓冲区中添加数据,相反,它们只是移动缓冲区的头指针和尾指针。数据由其他函数拷贝到缓冲区中。skb_pull通过把head指针往前移来在缓冲区的头部删除一块数据。图4展示了这些函数是如何工作的。

2.1.5.4. The skb_shared_info structure and the skb_shinfo function

如图5所示,在缓冲区数据的末尾,有一个数据结构skb_shared_info,它保存了数据块的附加信息。这个数据结构紧跟在end指针所指的地址之后(end指针指示数据的末尾)。下面是这个结构的定义:

struct skb_shared_info {
      atomic_t          dataref;
      unsigned int      nr_frags;
      unsigned short    tso_size;
      unsigned short    tso_seqs;
      struct sk_buff    *frag_list;
      skb_frag_t        frags[MAX_SKB_FRAGS];
};

dataref表示数据块的“用户”数,这个值在下一节(克隆和拷贝缓冲区)中有描述。nf_frags, frag_list和frags用于存储IP分片。skb_is_nonlinear函数用于测试一个缓冲区是否是分片的,而skb_linearize 可以把分片组合成一个单一的缓冲区。组合分片涉及到数据拷贝,它将严重影响系统性能。

有些网卡硬件可以完成一些传统上由CPU完成的任务。最常见的例子就是计算L3和L4校验和。有些网卡甚至可以维护L4 协议的状态机。在下面的例子中,我们主要讨论TCP段卸载TCP segmentation offload,这些网卡实现了TCP层的一部分功能。tso_size和tso_seqs就在这种情况下使用。

需要注意的是:sk_buff中没有指向skb_shared_info结构的指针。如果要访问这个结构, 就需要使用skb_info宏,这个宏简单地返回end指针:

#define skb_shinfo(SKB)      ((struct skb_shared_info *)((SKB)->end))

下面的语句展示了如何使用这个宏来增加结构中的某个成员变量的值:

skb_shinfo(skb)->dataref++;

2.1.5.5. Cloning and copying buffers

如果一个缓冲区需要被不同的用户独立地操作,而这些用户可能会修改sk_buff中某些变量的值(比如h和nh值),内 核没有必要为每个用户复制一份完整的sk_buff以及相应的缓冲区。相反,为提高性能,内核克隆一个缓冲区。克隆过程只复制sk_buff结构, 同时修改缓冲区的引用计数以避免共享的数据被提前释放。克隆缓冲区使用skb_clone函数。

一个使用包克隆的场景是:一个接收包的过程需要把这个包传递给多个接收者,例如包处理函数或者一个或多个网络模块。

被克隆的sk_buff不会放在任何链表中,同时也不会有到socket的引用。原始的和克隆的sk_buff中的 skb->cloned值都被置为1。克隆包的skb->users值被置为1,这样,在释放时,可以先释放sk_buff结构。同时,缓冲 区的引用计数(dataref)增加1(因为有多个sk_buff结构指向它)。图9展示了克隆缓冲区的例子。

Figure 9. skb_clone function

skb_cloned函数可以用来测试skb的克隆状态。

图9展示了一个分片缓冲区的例子,这个缓冲区的一些数据保存在分片结构数组frags中。

skb_share_check用于检查引用计数skb->users,如果users变量表明skb是被共享的, 则克隆一个新的sk_buff。

如果一个缓冲区被克隆了,这个缓冲区的内容就不能被修改。这就意味着,访问数据的函数没有必要加锁。因此,当一个函数不 仅要修改sk_buff,而且要修改缓冲区内容时, 就需要同时复制缓冲区。在这种情况下,程序员有两个选择。如果他知道所修改的数据在skb->start和skb->end
之间,他可以使用pskb_copy来复制这部分数据。如果他同时需要修改分片中的数据,他就必须使用skb_copy。图10展示了pskb_copy和skb_copy的结果。skb_shared_info结构也可以包含一个
sk_buff的链表(链表名称是frag_list)。这个链表在pskb_copy和skb_copy中的处理方式与frags数组的处理方式相同(图10忽略了frag_list)。

Figure 10. (a) pskb_copy function and (b) skb_copy function

在决定克隆或复制一个缓冲区时,子系统的程序员不能预测其他内核组件(其他子系统)是否需要使用缓冲区里的原始数据。内 核是模块化的,它的状态变化是不可预测的,因此,每个子系统都不知道其他子系统是如何操作缓冲区的。因此,内核程序员需要记录它们对缓冲区的修改,并且在 修改缓冲区前,复制一个新的缓冲区,以避免其他子系统在使用缓冲区的原始数据时出现错误。

2.1.5.6. List management functions

这些函数管理sk_buff的链表(也被称作队列)。在<include/linux/skbuff.h>和<net/core/skbuff.c>中有函数完整列表。以下是一些经常使用的函数:

skb_queue_head_init
初始化sk_buff_head结构,创建一个空队列。

skb_queue_head, skb_queue_tail
把一个缓冲区加入队列的头部或尾部。

skb_dequeue, skb_dequeue_tail
从队列的头部或尾部取下一个缓冲区。第一个函数的名字应该是skb_dequeue_head,以保持和其他函数的名字风格一致。

skb_queue_purge
清空一个队列。

skb_queue_walk
Runs a loop on each element of a queue in turn.

这些函数都是原子操作,它们必须先获取sk_buff_head中的自旋锁,然后才能访问队列中的元素。否则,它们有可能被其他异步的添加或删除操作打断,比如在定时器中调用的函数,这将导致链表出现错误而使得系统崩溃。

因此,每个函数的实现都采用下面这种形式:

static inline function_name ( parameter_list )
{
          unsigned long flags;

          spin_lock_irqsave(...);
          _ _ _function_name ( parameter_list )
          spin_unlock_irqrestore(...);
}

这些函数先获取锁,然后调用一个以两个下划线开头的同名函数(这个函数做具体的操作,而且不需要锁),然后释放锁。

1月17日雅思考试

Ielts

闭关一周

1月17日考雅思

我服了南航的在线购票系统

本来还准备给周围人推荐以后就在网上直接购买南航的快乐飞的,价格的确是便宜,从武汉到上海原价810的票只卖150,而且是官方网站,还有什么不放心的呢。

结果,彻底地被南航订票支付系统,这一套软件给完全折服了。前前后后,查询订票N次,都无法提交,最后提交了才发现信用卡还无法支付。

1)预定航班前必须登陆,注意,必须是在首页登陆,之后所有的登陆都无效。包括系统弹出提示说你还未登陆,然后给你一个登陆框,你会发现通过这个框是永远无法登上的。

2)即使登上了这一次,会发现一旦你跳转到任何一面,这个登陆的session就没有了,又需要重新登陆。即使是你已经提交了订单,想要查询,在左边的那一系列菜单中无论选哪一项,都需要重新登陆。

3)在填乘客名单的时候,只给了我觉得不到3分钟的时间,超过了系统就认为你超时。我可是要一次买两张票,需要填写名字,护照号,电话,邮箱,等信息,而且一定需要核对的。3分钟填两个人,努力一下还可以,要是6个人呢?

4)如果你在首页的选项中选的只是一个人,那么即使你在第二个页面中改为两个人,系统提交的还是只有一个人。

5)最后只给你40分钟付款,之前也不告诉你有哪些付款方式,40分钟一PASS,所有的作废。

总之,就是一个狗屁系统,这种系统也不知道是谁做的,竟然就敢拿出来给国人使用,还有英文版么?没注意,岂不是完全丢尽了国人的脸。这次这么敢放心的在网上订票,主要原因就是在Qantas和KLM上的订票都非常顺利,完全不费功夫。这次失望至极啊。

飞机漫谈(1)

十多年前,当那个时候的中国还比较贫困的时候,坐飞机的确是一件很奢侈的事情,能让我碰到的只有爸爸出差的时候,混一拨一起出去转。记得还是在小学的时候,第一次坐了从武汉到广州的飞机,那个时候武汉还是在王家墩机场,广州也还是旧机场,貌似都是没有廊桥而使用摆渡车的。以后坐的飞机次数比较多了,国内的几个大机场基本都去过了,各种类型的航班也尝试了不少。今天首先来说说航空公司吧。

国内目前的局面是三足鼎立,国航,南航,和东航。客观地来说,在坐国际航班的时候,我基本上是不会考虑坐国内航空公司的飞机的。原因很简单,要服务没服务,要舒适没舒适,要娱乐没娱乐,除了价钱相对可能低一点之外。我坐过从广州飞曼谷,新加坡飞广州的南航,的确是谈不上有什么让人记忆的地方,而东航的服务则是更为我身边的同学所诟病的。不过相对而言,如果必须要选择坐国内航空公司的国际航班的时候,还是考虑国航吧,相对而言服务质量好一些,注意我说的可是相对而言。

但是在飞国内航线的时候,是没有选择的,于是我们来矮子里面挑一些高子出来。几年前的时候,当三大航空还没有像如今这样整合的时候,国内的航空公司的实力相对而言还是比较平均的。就武汉而言,有南航,东航,国航,还有武航。当年的飞机基本上是清一色的波音737,偶尔能碰到一家747就是让人很愉悦的事情了。而且当年的飞机上流行发那种檀香扇,一飞机上都飘那股味道。后来武汉被东航兼并了,也就永远的在武汉的机场少了这个武汉自己的名字,剩下的只有东航武汉分公司和南航湖北分公司。

那个时候,海航,厦航等在一段时间内相当的吸引人注意力,特别是海航以其良好的服务,得到了很多人的好评。即使在现在,海航也仍然能够在国内航空力量中占据十分重要的位置。问题在于海航的大本营在海口,而去海南的机会还是比较少的,所以我也只坐过一次,印象中还是在深圳中间停了一次。现在海航的服务已经做的很大了,从武汉出发的可以基本上大小城市都包括了。此外海航还是国内不多的三大之外的有较多国际航线的,目前开通的包括柏林,布达佩斯,布鲁塞尔,西雅图,新西伯利亚,伊尔库茨克,基本上欧美亚通吃了。

除了这些地域性的航空公司之外,国内目前也涌出了很多民营公司,最出名的自然包括咱武汉的东星航空,可惜至今为此一次没坐过。本来这次从上海回武汉订的是这一趟航班的,结果临行前三天,一个电话通知说航班取消了,就是因为客流量太小了。其实这也是民营航企最大的问题,航班时不时会突然取消,而且没有其他的候补航班可以更改,以至于这次去上海,虽然我看到了吉祥有便宜的票,仍旧不敢去选的重要原因。

在国际航班上,我的发言权不多,坐过的相对而言是很有限的。但就我坐过的国泰/港龙系列,和澳航Qantas来说,两家各有特点,而且都绝对值得我今后在这条线上长期使用。首先说说国泰,最大的方便就是可以直接在武汉坐到,香港的转机时间长,可以在哪里慢慢的多逛逛,毕竟是个巨大的机场。其次,服务态度也是一流,英语,粤语,国语通用。而Qantas最大的问题就是不能说中文,这样对很多不会说英语的人来说是个巨大的挑战。不过Qantas上有最好喝的Hot Chocolate,而且有人完全不间断送水,直接造成了一路上去了5次厕所的丢人历史。其次是视频系统方面,Qantas是支持个人播放的,也就是说你可以暂停,前进,后退,而国泰的只能选台,但是不能控制时间,经常会导致从中间开始看的后果。不过总的来说两家航空公司都是非常不错,值得信赖的。

除了这两家,我还坐过亚航的从曼谷到新加坡的廉价航班,没有饮料食品,座位先来先挑原则,基本上给人的感觉不是特别像国际航班,十分简陋。而另一家是坐的从广州到雅加达的Garuda航空公司,以前听说过许多印尼的空难事件,不过就我坐的这两次来说,飞行的可是相当稳定,而且服务态度一流,飞机也都是大的330系列,而且一般人不多,坐着很舒服。特别是其服务员的高素质是让人称赞的,当我的眼睛忘记拿了的时候,还有地面服务人员专门带我重新回到飞机上去找眼睛,也看到了飞机舱门都打开时做清洁的样子。

除了这些我坐过的航班,从网上,各种旅游网站上所了解的情况来说,这几个公司的航班有机会是一定要品尝的,首先是日韩系列。无论是JAL,还是ANA,还是大韩或者韩亚,最大的特点就是服务超级之好,这也秉承了这两个国家长久以来的服务优势。其次是新航,马航和泰航,这三家东南亚的航空公司特别是新航绝对是超级有名,不过价格方面比较昂贵一点,但是有机会的话是一定要尝试的。其他几个值得推荐的是,Qatar航空,如果要去欧洲的同志们,下次不妨尝试一下在多哈转机,绝对别有一番风味。而Emirate虽然大名在外,而且还有Dubai之旅,但是传说中的经济舱效果很差,而且布满了去中东打工的中国人和去中国打工的中东人。在欧洲方面,名气最响的三大组合,汉莎,KLM/法航,和英航。其中汉莎是价格最高的,相对而言影音系统也不错,而KLM和法航是没有个人的娱乐系统的,这也倒合乎价格。英航了解不多,大概是因为在Heathraw转机必须要英国签证这点很让人郁闷吧。

美国方面,南美方面和非洲方面几乎不熟悉,放弃之。。。

Older Entries
  • English Version

    • Cannot read Chinese? Please take a look at my English site, hope you can find more you need there!
  • 感谢支持

  • twitter

    facebook

    linkedin

  • Categories