Blog

索引节点和目录项和磁盘

Content #

Linux 文件系统为每个文件都分配两个数据结构,索引节点(index node)和目录项(directory entry)。它们主要用来记录文件的元信息和目录结构。

  1. 索引节点,简称为 inode 用来记录文件的元数据,比如 inode 编号、文件大小、访问权限、修改日期、数据的位置等。索引节点和文件一一对应,它跟文件内容一样,都会被持久化存储到磁盘中。所以记住,索引节点同样占用磁盘空间。

  2. 目录项,简称为 dentry 用来记录文件的名字、索引节点指针以及与其他目录项的关联关系。多个关联的目录项,就构成了文件系统的目录结构。不过,不同于索引节点,目录项是由内核维护的一个内存数据结构,所以通常也被叫做目录项缓存。

换句话说,索引节点是每个文件的唯一标志,而目录项维护的正是文件系统的树状结构。目录项和索引节点的关系是多对一,比如,通过硬链接为文件创建的别名,就会对应不同的目录项,不过这些目录项本质上还是链接同一个文件,所以,它们的索引节点相同。

磁盘读写的最小单位是扇区,然而扇区只有 512B 大小,如果每次都读写这么小的单位,效率一定很低。所以,文件系统又把连续的扇区组成了逻辑块,然后每次都以逻辑块为最小单元,来管理数据。常见的逻辑块大小为 4KB,也就是由连续的 8 个扇区组成。

这里有两点需要你注意。

第一,目录项本身就是一个内存缓存,而索引节点则是存储在磁盘中的数据。在前面的 Buffer 和 Cache 原理中,我曾经提到过,为了协调慢速磁盘与快速 CPU 的性能差异,文件内容会缓存到页缓存 Cache 中。

那么,你应该想到,这些索引节点自然也会缓存到内存中,加速文件的访问。

第二,磁盘在执行文件系统格式化时,会被分成三个存储区域,超级块、索引节点区和数据块区。其中,

  1. 超级块,存储整个文件系统的状态。

  2. 索引节点区,用来存储索引节点。

  3. 数据块区,则用来存储文件数据。

Viewpoints #

From #

23 | 基础篇:Linux 文件系统是怎么工作的?

kernel build system

Content #

使用 INSTALL_MOD_STRIP #

sudo INSTALL_MOD_STRIP=1 make modules_install

From #

perf分析CPU性能的两种方法

perf top #

类似于 top,它能够实时显示占用 CPU 时钟最多的函数或者指令,因此可以用来查找热点函数,使用界面如下所示:

$ perf top
Samples: 833  of event 'cpu-clock', Event count (approx.): 97742399
Overhead  Shared Object       Symbol
   7.28%  perf                [.] 0x00000000001f78a4
   4.72%  [kernel]            [k] vsnprintf
   4.32%  [kernel]            [k] module_get_kallsym
   3.65%  [kernel]            [k] _raw_spin_unlock_irqrestore
...

输出结果中,第一行包含三个数据,分别是采样数(Samples)、事件类型(event)和事件总数量(Event count)。比如这个例子中,perf 总共采集了 833 个 CPU 时钟事件,而总事件数则为 97742399。

如果采样数过少(比如只有十几个),那下面的排序和百分比就没什么实际参考价值了。

再往下看是一个表格式样的数据,每一行包含四列,分别是:

  1. 第一列 Overhead ,是该符号的性能事件在所有采样中的比例,用百分比来表示。

  2. 第二列 Shared ,是该函数或指令所在的动态共享对象(Dynamic Shared Object),如内核、进程名、动态链接库名、内核模块名等。

  3. 第三列 Object ,是动态共享对象的类型。比如 [.] 表示用户空间的可执行程序、或者动态链接库,而 [k] 则表示内核空间。

  4. 最后一列 Symbol 是符号名,也就是函数名。当函数名未知时,用十六进制的地址来表示。

还是以上面的输出为例,我们可以看到,占用 CPU 时钟最多的是 perf 工具自身,不过它的比例也只有 7.28%,说明系统并没有 CPU 性能问题。

...

cook:dpkg

dpkg #

dpkg的功能可分为四部分:

  1. 管理DEB(dpkg-deb)
  2. 安装包
  3. 查询包管理数据库(dpkg-query)
  4. 移除包

管理DEB(dpkg-deb) #

dpkg-deb --info postfix_2.1.5-1_i386.deb
dpkg-deb --field postfix_2.1.5-1_i386.deb Version
dpkg-deb --field postfix_2.1.5-1_i386.deb Recommends Suggests
dpkg-deb --contents postfix_2.1.5-1_i386.deb
dpkg-deb --control postfix_2.1.5-1_i386.deb
dpkg-deb --extract postfix_2.1.5-1_i386.deb .

安装DEB #

dpkg --install ./postfix_2.1.5-1_i386.deb
dpkg --unpack postfix_2.1.5-1_i386.deb

dpkg在安装时会把包的信息放在/var/lib/dpkg/info/目录之下。

dpkg --configure postfix

移除rc标识的包 #

dpkg-query的man文档中有各个状态字符的说明。

dpkg --list | grep ˆrc | awk ’ { print $2 } ’ | xargs dpkg -P

不过上面的命令可能会出错,因为dpkg的输出中包名的长度是固定值,因此有的包可能无法完整显示名称,这时再用dpkg -P命令删除时就会出错,解决办法是使用COLUMNS环境变量。

COLUMNS=1000 dpkg --list | grep ˆrc | awk ’ { print $2 } ’ | xargs dpkg -P

当然,使用dpkg-query也能很好地完成任务。

...

函数getpeername的安全漏洞

Content #

2002年,FreeBSD实现的getpeername存在安全漏洞,代码的简化版本如下:

/* Declaration of library function memcpy */
void *memcpy(void *dest, void *src, size_t n);

/* Kernel memory region holding user-accessible data */
#define KSIZE 1024
char kbuf[KSIZE];

/* Copy at most maxlen bytes from kernel region to user buffer */
int copy_from_kernel(void *user_dest, int maxlen) {
        /* Byte count len is minimum of buffer size and maxlen */
        int len = KSIZE < maxlen ? KSIZE : maxlen;
        memcpy(user_dest, kbuf, len);
        return len;
}

如果maxlen使用了负数,那么len也会得到负数。然后len会作为参数n传给 memcpy。由于n被声明为size_t,是无符号的,memcpy接收到的就会是一个很大的正整数,程序就可能读到没有被授权的内核内存区域。

...

有符号数隐式转换为无符号数导致的错误

Content #

C语言同时包含有符号数与无符号数,当执行一个运算时,如果参与运算的数,一个是有符号的,另一个是无符号的,那么C语言会隐式地将有符号的数转换成无符号的数,然后执行运算。

下面的代码要求数组所有元素的和:

/* WARNING: This is buggy code */
float sum_elements(float a[], unsigned length) {
    int i;
    float result = 0;

    for (i = 0; i <= length1; i++)
        result += a[i];
    return result;
}

当参数length为0时,由于length声明为无符号数,0-1会得到UMax,<=总会是真,代码会试图越界访问数组的元素。解决办法一,将length声明为int。解决办法二,使用<号代替<=。

下面的代码要用库函数strlen比较两个字符的长度:

/* Prototype for library function strlen */
size_t strlen(const char *s);

/* Determine whether string s is longer than string t */
/* WARNING: This function is buggy */
int strlonger(char *s, char *t) {
    return strlen(s) - strlen(t) > 0;
}

当s比t短时,strlen(s)-strlen(t)的结果会是负的,但会变成一个很大的无符号数,始终会大于0。解决办法:

return strlen(s) > strlen(t);

From #

思维的语言是心语

Content #

思想真的必须依靠语言吗?人们真的是在用英语、彻罗基语、奇温久语或者2050 年的新话来思考的吗?抑或我们的思想原本是以无声的形式栖息于大脑之中,即所谓的思维语言,或者说“心语”(mentalese),而只有在我们需要与他人交流时,才临时披上了一件语言的外衣?在语言本能的探讨中,这是一个至为关键的问题。

人们很容易高估语言的能力,以为语言决定 着我们的思维。实际上,语言不是思维的唯一方式。心智计算理论是认知科学的基础,无论是英语还是其他任何自然语言,都不能用作心智计算的介质。心语,才是思维的语言。

From #

语言具有普遍复杂性的原因

Content #

语言之所以具有普遍的复杂性,是因为孩子们在不断地对其进行改造,一代又一代,皆是如此。而这并不是因为别人的教导,也不是因为他们个个都头脑聪明,更不是因为这会对他们有所帮助,而是因为他们情不自禁,别无选择。

From #

皮钦语转化为克里奥尔语

Content #

大西洋的奴隶贸易和南太平洋的卖身苦力。或许是受了巴别塔的警示,一些种植园主故意将不同语言背景的奴隶和劳力混编在一起。当然,也有些人喜欢某一特定种族的劳工,但因条件有限,只能接受不同种族的劳力。这些言语不通的人需要相互交流才能完成工作,但却没有机会去学习彼此的语言,因此,他们会发展出一种临时用语,即“皮钦语”(Pidgin)。皮钦语吸收了殖民者或种植园主所用语言中的大量词语,语序排列变化多端,缺乏一定的语法规则。有时,皮钦语可以成为一种通用语,并且经过几十年的发展,其形式也变得日益复杂,例如现在南太平洋地区所使用的“皮钦英语”(Pidgin English)。当年菲利普王子(Prince Philip)访问新几内亚时,发现自己被当地人称为“那个属于女王的小伙”(fella belong Mrs. Queen),这让他很是欣喜。

不过,语言学家德里克·比克顿(Derek Bickerton)证明说,在许多情况下,皮钦语也可以在很短的时间内转化为一种完整、复杂的语言,只要将一群正开始学习母语的孩子放到皮钦语的环境中就行。比克顿指出,这种情况往往发生在从小离开父母、并由使用皮钦语的人照顾的孩子中。这些孩子并不满足于简单地重复这种支离破碎、片断式的语言形式,他们往往会注入前所未有的复杂语法,从而创造出一种具有丰富表现力的崭新语言。这种语言被称为“克里奥尔语”(Creole),它是以皮钦语为母语的孩子发展出来的一种语言。

From #