2011年12月4日星期日

阮一峰的网络日志

阮一峰的网络日志


理解inode

Posted: 03 Dec 2011 09:30 PM PST

inode是一个重要概念,是理解Unix/Linux文件系统和硬盘储存的基础。

我觉得,理解inode,不仅有助于提高系统操作水平,还有助于体会Unix设计哲学,即如何把底层的复杂性抽象成一个简单概念,从而大大简化用户接口。

下面就是我的inode学习笔记,尽量保持简单。

===================================

理解inode

作者:阮一峰

一、inode是什么?

理解inode,要从文件储存说起。

文件储存在硬盘上,硬盘的最小存储单位叫做"扇区"(Sector)。每个扇区储存512字节(相当于0.5KB)。

操作系统读取硬盘的时候,不会一个个扇区地读取,这样效率太低,而是一次性连续读取多个扇区,即一次性读取一个"块"(block)。这种由多个扇区组成的"块",是文件存取的最小单位。"块"的大小,最常见的是4KB,即连续八个 sector组成一个 block。

文件数据都储存在"块"中,那么很显然,我们还必须找到一个地方储存文件的元信息,比如文件的创建者、文件的创建日期、文件的大小等等。这种储存文件元信息的区域就叫做inode,中文译名为"索引节点"。

每一个文件对应一个inode,硬盘上有多少文件,就有多少个inode。

二、inode的内容

inode包含文件的元信息,具体来说有以下内容:

  * 文件的字节数

  * 文件拥有者的User ID

  * 文件的Group ID

  * 文件的读、写、执行权限

  * 文件的时间戳,共有三个:ctime指inode上一次变动的时间,mtime指文件内容上一次变动的时间,atime指文件上一次打开的时间。

  * 链接数,即有多少文件名指向这个inode

  * 文件数据block的位置

可以用stat命令,查看某个文件的inode信息:

  stat example.txt

总之,除了文件名以外的所有文件信息,都存在inode之中。至于为什么没有文件名,下文会有详细解释。

三、inode的大小

inode也会消耗硬盘空间,所以硬盘格式化的时候,操作系统自动将硬盘分成两个区域。一个是数据区,存放文件数据;另一个是inode区(inode table),存放inode所包含的信息。

每个inode节点的大小,一般是128字节或256字节。inode节点的总数,在格式化时就给定,一般是每1KB或每2KB就设置一个inode。假定在一块1GB的硬盘中,每个inode节点的大小为128字节,每1KB就设置一个inode,那么inode table的大小就会达到128MB,占整块硬盘的12.8%。

查看每个硬盘分区的inode总数和已经使用的数量,可以使用df命令。

  df -i

查看每个inode节点的大小,可以用如下命令:

  sudo dumpe2fs -h /dev/hda | grep "Inode size"

由于每个文件都必须有一个inode,因此有可能发生inode已经用光,但是硬盘还未存满的情况。这时,就无法在硬盘上创建新文件。

四、inode号码

每个inode都有一个号码,操作系统用inode号码来识别不同的文件。

这里值得重复一遍,Unix/Linux系统内部不使用文件名,而使用inode号码来识别文件。对于系统来说,文件名只是inode号码便于识别的别称或者绰号。

表面上,用户通过文件名,打开文件。实际上,系统内部这个过程分成三步:首先,系统找到这个文件名对应的inode号码;其次,通过inode号码,获取inode信息;最后,根据inode信息,找到文件数据所在的block,读出数据。

使用ls -i命令,可以看到文件名对应的inode号码:

  ls -i example.txt

五、目录文件

Unix/Linux系统中,目录(directory)也是一种文件。打开目录,实际上就是打开目录文件。

目录文件的结构非常简单,就是一系列目录项(dirent)的列表。每个目录项,由两部分组成:所包含文件的文件名,以及该文件名对应的inode号码。

ls命令只列出目录文件中的所有文件名:

  ls /etc

ls -i命令列出整个目录文件,即文件名和inode号码:

  ls -i /etc

如果要查看文件的详细信息,就必须根据inode号码,访问inode节点,读取信息。ls -l命令列出文件的详细信息。

  ls -l /etc

六、硬链接

一般情况下,文件名和inode号码是"一一对应"关系,每个inode号码对应一个文件名。但是,Unix/Linux系统允许,多个文件名指向同一个inode号码。

这意味着,可以用不同的文件名访问同样的内容;对文件内容进行修改,会影响到所有文件名;但是,删除一个文件名,不影响另一个文件名的访问。这种情况就被称为"硬链接"(hard link)。

ln命令可以创建硬链接:

  ln 源文件 目标文件

运行上面这条命令以后,源文件与目标文件的inode号码相同,都指向同一个inode。inode信息中有一项叫做"链接数",记录指向该inode的文件名总数,这时就会增加1。

反过来,删除一个文件名,就会使得inode节点中的"链接数"减1。当这个值减到0,表明没有文件名指向这个inode,系统就会回收这个inode号码,以及其所对应block区域。

这里顺便说一下目录文件的"链接数"。创建目录时,默认会生成两个目录项:"."和".."。前者的inode号码就是当前目录的inode号码,等同于当前目录的"硬链接";后者的inode号码就是当前目录的父目录的inode号码,等同于父目录的"硬链接"。所以,任何一个目录的"硬链接"总数,总是等于2加上它的子目录总数(含隐藏目录)。

七、软链接

除了硬链接以外,还有一种特殊情况。

文件A和文件B的inode号码虽然不一样,但是文件A的内容是文件B的路径。读取文件A时,系统会自动将访问者导向文件B。因此,无论打开哪一个文件,最终读取的都是文件B。这时,文件A就称为文件B的"软链接"(soft link)或者"符号链接(symbolic link)。

这意味着,文件A依赖于文件B而存在,如果删除了文件B,打开文件A就会报错:"No such file or directory"。这是软链接与硬链接最大的不同:文件A指向文件B的文件名,而不是文件B的inode号码,文件B的inode"链接数"不会因此发生变化。

ln -s命令可以创建软链接。

  ln -s 源文文件或目录 目标文件或目录

八、inode的特殊作用

由于inode号码与文件名分离,这种机制导致了一些Unix/Linux系统特有的现象。

  1. 有时,文件名包含特殊字符,无法正常删除。这时,直接删除inode节点,就能起到删除文件的作用。

  2. 移动文件或重命名文件,只是改变文件名,不影响inode号码。

  3. 打开一个文件以后,系统就以inode号码来识别这个文件,不再考虑文件名。因此,通常来说,系统无法从inode号码得知文件名。

第3点使得软件更新变得简单,可以在不关闭软件的情况下进行更新,不需要重启。因为系统通过inode号码,识别运行中的文件,不通过文件名。更新的时候,新版文件以同样的文件名,生成一个新的inode,不会影响到运行中的文件。等到下一次运行这个软件的时候,文件名就自动指向新版文件,旧版文件的inode则被回收。

(完)

文档信息

2011年12月1日星期四

阮一峰的网络日志

阮一峰的网络日志


济慈的《夜莺颂》

Posted: 01 Dec 2011 01:20 AM PST

1.

昨天,上海开始大幅降温。

到了晚上,寒冷的北风夹带着雨点,从街道的一头刮到另一头,行人匆匆。昏黄的路灯下只听到风声,以及雨水溅落在路面的声音。

结束了一整天忙碌的工作,我站在窗前,想歇口气,但是只觉得寒风一阵阵往脖子里钻,令人簌簌发抖。

突然之间,一个词冒了出来:"温柔的夜色"。此时此刻,我多么期待"温柔的夜色"啊!

2.

这个词是如此熟悉,不加思索就出现了,似乎在哪里见过。

想了半天,我恍然大悟,原来这是菲茨杰拉德的一篇小说的名字:《夜色温柔》(Tender is the Night)。

好多年前,我读《了不起的盖茨比》入了迷,觉得原著的语言太优美了。读完以后,就想看菲茨杰拉德的其他作品,首当其冲的就是堪称《了不起的盖茨比》的续集《夜色温柔》。虽然后来终究没有读,但是对这本书始终没有忘记。

菲茨杰拉德是20世纪美国文坛的大才子,海明威的密友。他出身普通家庭,但是爱上了富家小姐塞尔达,经过苦苦追求,两人终于结婚。他最有名的作品,内容都与这场婚姻有关。

婚后,两人移居巴黎。塞尔达富家小姐的习性暴露无遗:喜欢喝酒、讲究排场、挥霍无度。菲茨杰拉德为了维持婚姻,伤透了脑筋,最终身心俱疲,不仅写不出小说,身体也垮了,1940年死于酗酒后的心脏病。他死后,患了精神分裂症的塞尔达被送入精神病院,1948年死于一场火灾。

不知为什么,这个寒冷的夜晚,我又想起了菲茨杰拉德的悲剧人生,以及他笔下死于心碎的盖茨比。

3.

我找到了小说《夜色温柔》,想让菲茨杰拉德抒情忧伤的笔触,伴随我度过这个夜晚。

翻开第一页,意外发现有一段题诗。

这段诗是这样的:

  Already with thee! tender is the night...
  ...But here there is no light,
  Save what from heaven is with the breezes blown
  Through verdurous glooms and winding mossy ways.

    -- Ode to a Nightingale

译成中文就是(本文所有译文都是我的翻译):

  你已经在我身边!夜色如此温柔......
  ......但是没有一丝光亮,
  四周只有微风吹来
  穿过阴暗的绿茵,以及长满青苔的小径。

多美的诗句啊!而且与菲茨杰拉德想在小说中表达的情感,异常贴切:找到了自己所爱的人,"你已经在我身边",就连夜色也"如此温柔";但是,这段感情没有希望,看不见"一丝光亮";黑暗中"微风吹来",四周是"阴暗的"环境,以及"长满青苔的"弯曲道路,暗示内心的寂寞,以及前途未卜的未来。

难怪这本小说取名为《夜色温柔》。作者借此表达心中的忧伤,也就是后面三句。

4.

这一段诗,出自19世纪英国诗人济慈的名作《夜莺颂》(Ode to a Nightingale)。

我这才想到,菲茨杰拉德引用济慈的作品不是偶然的,他们两人的人生非常相似,同样的悲惨。

济慈堪称是英国历史上出身最卑微的大诗人。父亲是马厩工人,在他8岁时去世,母亲也在几年后死亡,他依靠兄弟姐妹互相支持长大。20岁以后,他的唯一经济来源就是写诗,终生拮据。1820年,他患上了肺结核,第二年逝世,享年只有26岁。

根据记载,1819年5月,他住在伦敦一个朋友家里。一个黄昏,听到花园的树上有夜莺啼叫,他心有所感,就写下了《夜莺颂》。

当时,他正与邻家女孩Fanny Brawne恋爱。他很珍惜这段感情,但是一想到自己的身体和经济状况,就不胜烦恼,觉得前途渺茫。盼望逃避现实,像夜莺一样飞走,但是又不可能实现。

网上的材料这样介绍两人的爱情:

"1820年的初春,济慈去了伦敦城,那天他没有穿大衣。回来的时候为了省钱,他坐在马车的外面,结果全身都被雨淋透了。等他回到家里,他的恋人范妮为他打开门,他几乎是倒在范妮的怀中。

那天晚上,他开始咳血。他知道自己得了肺结核,并从此不让范妮再接近他。他每天坐在窗前,看着范妮在院子里玩耍,他每天给范妮写信,尽管她就住在自己的隔壁。

到秋天的时候,医生建议他必须住到比较温暖的地方去。他在友人的陪伴下,来到罗马。1821年2月,济慈在罗马病逝。

消息传回伦敦,范妮悲痛欲绝,她那个时候只有19岁。"

5.

《夜莺颂》不长,只有80行,但是用词华丽、语法古老,不易读。好在全诗的结构,以及想要表达的情绪,还是很清楚的。

开头先说,作者羡慕夜莺的欢乐。

  'Tis not through envy of thy happy lot,
  But being too happy in thine happiness --

  我并不感到妒忌,
  反而因为你的欢乐,我也产生欢乐。

接着说,希望追随夜莺而去。

  ...leave the world unseen
  And with thee fade away into the forest dim.

  将这个世界抛在身后
  与你一起,隐入幽静的森林。

因为这个世界实在太悲伤了。

  Here, where men sit and hear each other groan;
  Where palsy shakes a few, sad, last grey hairs.

  这里,人们只能坐着互相叹气;
  在岁月的麻木中,听任白发飘零

但是,四周是一片黑夜,终究无力飞翔。

  ...But here there is no light,
  Save what from heaven is with the breezes blown

  ......没有一丝光亮,
  四周只有微风吹来

作者感叹身边的花草是如此葱郁,

  I cannot see what flowers are at my feet,
  Nor what soft incense hangs upon the boughs,

  我不知道脚边是什么花,
  也不知道枝头的清香从何而来,

若能就此死去,也算是美好。

  Darkling I listen; and for many a time
  I have been half in love with easeful Death

  许多次,我在黑暗中这样倾听
  几乎爱上这种安详的死亡

但是,夜莺的歌声是不死的。

  Thou wast not born for death, immortal Bird!
  No hungry generations tread thee down;

  永生的鸟儿,你不会死
  人类世代的悲伤,无法令你停止歌唱。

只可惜现在它飞走了,只把我一个留在这里。

  Was is a vision, or a waking dream?
  Fled is that music -- Do I wake or sleep?

  这是幻觉,还是白日梦?
  歌声消逝,我到底算醒着还是在做梦?

6.

此诗有查良铮(穆旦)的中译。我认为,他的译文质量很好,基本上把原诗的意思翻译出来了,没有错译。但是还不算完美,有些段落他的译文与原诗的意境差别比较大,而且没有注解,典故的处理不理想。

我个人特别喜欢全诗的第三大段,那一段描写现实世界的悲凉,我想很多中国人会深有同感。

  Here, where men sit and hear each other groan;
  这里,人们只能坐着互相叹气,

  Where palsy shakes a few, sad, last grey hairs.
  在岁月的麻木中,听任白发飘零。

  Where youth grows pale, and spectre-thin, and dies;
  这里,青年逐渐变得苍白、消瘦,直至死亡;

  Where but to think is to be full of sorrow
  这里,稍一思考,就充满悲伤

  And leaden-eyed despairs;
  黯淡的眼神中只有绝望;

  Where Beauty cannot keep her lustrous eyes,
  这里,美神无法保住她的姿色,

  Or new Love pine at them beyond to-morrow.
  新生的爱情第二天就消亡。

(完)

文档信息

2011年11月26日星期六

阮一峰的网络日志

阮一峰的网络日志


骰子作画的算法

Posted: 26 Nov 2011 05:28 AM PST

程序员Scott MacDonald做了一个很有趣的项目----骰子作画

他用黑底白点的骰子。

模拟出一张人像照片。

把图像放大,就可以看得更清楚。

他一共用了2500多颗骰子。

最后的成品就是这样。

任何一张图片都可以用骰子模拟出来,算法非常简单:将图片分成若干个区域,每个区域经过计算以后,用1-6之间的一个整数表示,代表骰子的一个面。这种将连续的量转化成不连续的整数的算法,属于vector quantization(矢量量化)的一个应用。

具体来说,

第一步,将图片分割成16像素x16像素的小方块。

  for (int i=0; i < (pic_width/16); ++i) {

    for (int j=0; j < (pic_height/16); ++j) {

      patch = cropped_img.get(i*16, j*16, 16, 16);

    }

  }

第二步,每个小方块内共有256个像素,将每个像素点的灰度值,存入一个数组。

  for (int k=0; k < patch.pixels.length; ++k) {

   x[k] = rgb2gray(patch.pixels[k]);

  }

  int rgb2gray(int argb) {

    int _alpha = (argb >> 24) & 0xFF;

    int _red = (argb >> 16) & 0xFF;

    int _green = (argb >> 8 ) & 0xFF;

    int _blue = (argb) & 0xFF;

    return int(0.3*_red + 0.59*_green + 0.11*_blue);

  }

第三步,计算该数组的平均值,并用1-6之间的一个整数来表示。

  int dice_num = six_step_gray(mean(x));

  int mean(int[] x) {

    float m = 0;

    for (int i=0; i < x.length; ++i) {

      m += x[i];

    }

    m = m/x.length;

    return int(m);

  }

  int six_step_gray(int x) {

    if (0 <= x && x <= 41) return 1;

    if (41 < x && x <= 83) return 2;

    if (83 < x && x <= 124) return 3;

    if (124 < x && x <= 165) return 4;

    if (165 < x && x <= 206) return 5;

    if (x < 206 && x <= 247) return 6;

    else return 6;
  }

整数1,表示骰子朝上的一面有1个白点;整数2,表示有2个白点;以此类推。白点越少,表示这个区域越接近全黑;白点越多,表示越接近全白。根据白点值,将骰子依次放入,就能模拟出全图。

这种算法早在1981年就有人提出,当时用的是1~9个白点的多米诺骨牌。

如果区域划分得越小,模拟图的生成效果就越好。

此外,不用编程,使用Photoshop也可以得到类似效果。

(完)

文档信息

2011年11月24日星期四

阮一峰的网络日志

阮一峰的网络日志


珠海印象

Posted: 23 Nov 2011 07:54 PM PST

过去一周,我在珠海旅行。

这是我第一次去珠海,写一些感想。

动身之前,我心目中的珠海,是一个美丽的、现代化的、宜居的海滨城市。可是,身临其境,我发现并非如此。

虽然临海,但是除了一条狭长的情侣路,完全看不出这是一个海滨城市;市内的旅游景点非常少,几乎没有什么值得去的地方;市区与国内其他大城市一样,高楼林立,五光十色,交通拥堵,铺天盖地的喧哗和广告;马路很宽,路灯昏暗,凡是行人稀少的路段,车速都飞快,一个红灯长达100秒;路上很难拦到出租车,而且由于主干道不设自行车道,即使拦到,路边停车也很困难。

我感觉,珠海的城市规划很成问题。一个典型的例子就是情侣路。这是珠海的名片,外地游客必到之处,但是完全没有开发好。整个情侣路长达十多公里,但是一路上几乎没有任何商业设施,买瓶水都很麻烦。情侣路旁边居然是一条宽阔的高速干道,车速极快,使得游客无法自由通行,仿佛这条路存在的主要目的,就是为了行车,而不是为了游览。

海滨浴场是整条路上唯一的沙滩,设施破败陈旧,垃圾遍地,显然缺乏管理和资金投入。很难想象,市政当局居然会对这样一个重要的城市休闲地标放任不管。倒是房地产商充分利用了这个地段,海滨浴场周围都是密密麻麻的楼盘,把整个沙滩像山谷一样围了起来,这块理应属于全体市民的海滩,实际上成了业主们的后花园。

总之,珠海这个城市缺乏特色。拱北口岸附近,是全市最热闹的地方,即使半夜12点,通关的人流都络绎不绝。给我的感觉是,这个城市的主要功能,就是通往澳门的通道。

但是,珠海其实拥有很好的自然条件,如果好好规划,完全不会是现在这个样子。要是将来有人收集中国城市规划的失败案例,珠海可以算一个。

旅行的最后两天,我去了珠江口的外伶仃岛。岛上环境优美,海水清澈,生活宁静安逸,各种设施齐全,更重要的是网络全覆盖。如果以后有空,我会考虑在岛上租一间房,休假一个月。

(完)

文档信息

2011年11月12日星期六

阮一峰的网络日志

阮一峰的网络日志


EOF是什么?

Posted: 12 Nov 2011 03:42 AM PST

我学习C语言的时候,遇到的一个问题就是EOF

它是end of file的缩写,表示"文字流"(stream)的结尾。这里的"文字流",可以是文件(file),也可以是标准输入(stdin)。

比如,下面这段代码就表示,如果不是文件结尾,就把文件的内容复制到屏幕上。

  int c;

  while ((c = fgetc(fp)) != EOF) {

    putchar (c);

  }

很自然地,我就以为,每个文件的结尾处,有一个叫做EOF的特殊字符,读取到这个字符,操作系统就认为文件结束了。

但是,后来我发现,EOF不是特殊字符,而是一个定义在头文件stdio.h的常量,一般等于-1。

  #define EOF (-1)

于是,我就困惑了。

如果EOF是一个特殊字符,那么假定每个文本文件的结尾都有一个EOF(也就是-1),还是可以做到的,因为文本对应的ASCII码都是正值,不可能有负值。但是,二进制文件怎么办呢?怎么处理文件内部包含的-1呢?

这个问题让我想了很久,后来查了资料才知道,在Linux系统之中,EOF根本不是一个字符,而是当系统读取到文件结尾,所返回的一个信号值(也就是-1)。至于系统怎么知道文件的结尾,资料上说是通过比较文件的长度。

所以,处理文件可以写成下面这样:

  int c;

  while ((c = fgetc(fp)) != EOF) {

    do something

  }

这样写有一个问题。fgetc()不仅是遇到文件结尾时返回EOF,而且当发生错误时,也会返回EOF。因此,C语言又提供了feof()函数,用来保证确实是到了文件结尾。上面的代码feof()版本的写法就是:

  int c;

  while (!feof(fp)) {

    c = fgetc(fp);

    do something;

  }

但是,这样写也有问题。fgetc()读取文件的最后一个字符以后,C语言的feof()函数依然返回0,表明没有到达文件结尾;只有当fgetc()向后再读取一个字符(即越过最后一个字符),feof()才会返回一个非零值,表示到达文件结尾。

所以,按照上面这样写法,如果一个文件含有n个字符,那么while循环的内部操作会运行n+1次。所以,最保险的写法是像下面这样:

  int c = fgetc(fp);

  while (c != EOF) {

    do something;

    c = fgetc(fp);

  }

  if (feof(fp)) {

    printf("\n End of file reached.");

  } else {

    printf("\n Something went wrong.");

  }

除了表示文件结尾,EOF还可以表示标准输入的结尾。

  int c;

  while ((c = getchar()) != EOF) {

    putchar(c);

  }

但是,标准输入与文件不一样,无法事先知道输入的长度,必须手动输入一个字符,表示到达EOF。

Linux中,在新的一行的开头,按下Ctrl-D,就代表EOF(如果在一行的中间按下Ctrl-D,则表示输出"标准输入"的缓存区,所以这时必须按两次Ctrl-D);Windows中,Ctrl-Z表示EOF。(顺便提一句,Linux中按下Ctrl-Z,表示将该进程中断,在后台挂起,用fg命令可以重新切回到前台;按下Ctrl-C表示终止该进程。)

那么,如果真的想输入Ctrl-D怎么办?这时必须先按下Ctrl-V,然后就可以输入Ctrl-D,系统就不会认为这是EOF信号。Ctrl-V表示按"字面含义"解读下一个输入,要是想按"字面含义"输入Ctrl-V,连续输入两次就行了。

(完)

文档信息