当前位置:首页>Linux>《Linux-0.12 源码篇》- 06 文件系统

《Linux-0.12 源码篇》- 06 文件系统

  • 2026-06-25 14:58:33
《Linux-0.12 源码篇》- 06 文件系统

字数 9116,阅读大约需 46 分钟

第六章:文件系统

文件系统是操作系统为用户提供的持久化数据存储机制,它将磁盘上的数据块组织成目录和文件的层次结构,为用户程序提供简单一致的文件访问接口。Linux 0.12采用了Minix文件系统,这是一个简单而经典的文件系统实现,包含了现代文件系统的核心要素:超级块、i节点、目录项、数据块、以及缓冲区高速缓存机制。通过文件系统,程序可以用文件名而非磁盘块号来访问数据,极大地简化了数据管理。

6.1 Minix文件系统布局

Minix文件系统将磁盘分区划分为几个区域:引导块、超级块、i节点位图、块位图、i节点区和数据区。引导块位于第0块,用于存放引导程序。超级块位于第1块,保存文件系统的元信息,包括总i节点数、总数据块数、空闲i节点数、空闲块数等关键统计信息。

i节点位图和块位图各占若干块,用位图方式记录i节点和数据块的分配情况。每个位对应一个i节点或数据块,位值为1表示已分配,0表示空闲。通过扫描位图可以快速找到空闲的i节点或数据块。i节点区包含所有i节点,每个i节点32字节,记录文件的元数据(文件类型、权限、大小、修改时间、数据块索引等)。数据区存放文件和目录的实际内容。

MINIX 文件系统

每个文件或目录都对应一个i节点。i节点包含9个块号数组:前7个是直接块号,直接指向文件的前7KB数据块;第8个是一次间接块号,指向一个索引块,该索引块包含512个块号,可索引512KB数据;第9个是二次间接块号,指向一个二级索引块,可索引256MB数据。这种混合索引结构既能高效处理小文件,也支持较大文件。

超级块结构定义

超级块(super_block)保存文件系统的全局元数据,位于磁盘块1:



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41

/* include/linux/fs.h - 超级块结构 */
struct super_block {
    unsigned short s_ninodes;        /* i节点总数
                                      * 文件系统最多支持多少个文件/目录 */
    unsigned short s_nzones;         /* 逻辑块总数(数据区块数)
                                      * 文件系统总容量 = s_nzones * 1024 字节 */
    unsigned short s_imap_blocks;    /* i节点位图占用的块数
                                      * 每块1024字节=8192位,可管理8192个i节点 */
    unsigned short s_zmap_blocks;    /* 块位图占用的块数
                                      * 每块可管理8192个数据块 */
    unsigned short s_firstdatazone;  /* 第一个数据块号
                                      * 数据区起始位置 = 1+1+s_imap_blocks+
                                      *   s_zmap_blocks+i节点区块数 */
    unsigned short s_log_zone_size;  /* log2(块大小/1024)
                                      * Linux 0.12中块大小固定为1024,所以=0 */
    unsigned long  s_max_size;       /* 文件最大长度,受i节点索引结构限制
                                      * 直接块7KB+一次间接512KB+二次间接256MB */
    unsigned short s_magic;          /* 文件系统魔数,Minix: 0x137F
                                      * 用于识别文件系统类型 */

    /* 以下字段仅存在于内存中的超级块,不写入磁盘 */
struct buffer_head * s_imap[8];  /* i节点位图缓冲块指针数组(最多8块)
                                      * 指向缓冲区中的i节点位图 */
struct buffer_head * s_zmap[8];  /* 块位图缓冲块指针数组(最多8块)
                                      * 指向缓冲区中的块位图 */
    unsigned short s_dev;            /* 设备号,例如 0x301 表示/dev/hda1
                                      * 高8位主设备号,低8位次设备号 */
struct m_inode * s_isup;         /* 被挂载的i节点(挂载点)
                                      * NULL表示根文件系统 */
struct m_inode * s_imount;       /* 挂载到此设备的i节点
                                      * 指向挂载的文件系统根i节点 */
    unsigned long s_time;            /* 修改时间(Unix时间戳) */
struct task_struct * s_wait;     /* 等待此超级块的进程队列
                                      * 用于锁机制 */
    unsigned char s_lock;            /* 锁标志: 1=已锁定,0=未锁定
                                      * 防止并发修改超级块 */
    unsigned char s_rd_only;         /* 只读标志: 1=只读,0=可写
                                      * 只读文件系统不允许写操作 */
    unsigned char s_dirt;            /* 修改标志: 1=已修改,0=未修改
                                      * 已修改的超级块需要写回磁盘 */
};


磁盘i节点结构

磁盘上的i节点(d_inode)占32字节,保存文件元数据:



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

/* include/linux/fs.h - 磁盘i节点,32字节 */
struct d_inode {
    unsigned short i_mode;     /* 文件类型和权限
                                * 位15-12: 文件类型
                                *   0100(0x4000) 目录
                                *   0010(0x2000) 字符设备
                                *   0110(0x6000) 块设备
                                *   0010(0x8000) 普通文件
                                * 位11-9: 特殊权限(setuid/setgid/sticky)
                                * 位8-6: 属主权限(rwx)
                                * 位5-3: 组权限(rwx)
                                * 位2-0: 其他用户权限(rwx) */
    unsigned short i_uid;      /* 属主用户ID */
    unsigned long  i_size;     /* 文件大小(字节)
                                * 对于目录是目录项总长度 */
    unsigned long  i_time;     /* 修改时间(Unix时间戳,从1970-01-01起的秒数) */
    unsigned char  i_gid;      /* 属主组ID */
    unsigned char  i_nlinks;   /* 链接计数(硬链接数)
                                * 当降为0时文件被删除 */
    unsigned short i_zone[9];  /* 数据块索引数组(块号)
                                * i_zone[0-6]: 直接块号,每块1KB,共7KB
                                * i_zone[7]: 一次间接块号
                                *   指向索引块,索引块包含512个块号(512KB)
                                * i_zone[8]: 二次间接块号
                                *   指向二级索引,每个二级索引512个块号
                                *   512*512*1KB = 256MB
                                * 最大文件大小: 7KB+512KB+256MB ≈ 256MB */
};


内存i节点结构

内存中的i节点(m_inode)扩展了磁盘i节点,增加了管理字段:



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

/* include/linux/fs.h - 内存i节点结构 */
struct m_inode {
    unsigned short i_mode;     /* 同d_inode */
    unsigned short i_uid;      /* 同d_inode */
    unsigned long  i_size;     /* 同d_inode */
    unsigned long  i_mtime;    /* 修改时间 */
    unsigned char  i_gid;      /* 同d_inode */
    unsigned char  i_nlinks;   /* 同d_inode */
    unsigned short i_zone[9];  /* 同d_inode */

    /* 以下字段仅在内存中,用于管理i节点缓存 */
struct task_struct * i_wait;  /* 等待此i节点的进程队列
                                   * 用于i节点锁机制 */
    unsigned long i_atime;        /* 最后访问时间 */
    unsigned long i_ctime;        /* i节点改变时间(元数据修改时间) */
    unsigned short i_dev;         /* 设备号(所在设备) */
    unsigned short i_num;         /* i节点号(1-based,i节点0未使用)
                                   * 在设备上的i节点索引 */
    unsigned short i_count;       /* 引用计数
                                   * 多个文件描述符/进程可能引用同一i节点
                                   * 降为0时才能释放 */
    unsigned char i_lock;         /* 锁标志: 1=已锁定,0=未锁定
                                   * 防止并发修改i节点 */
    unsigned char i_dirt;         /* 修改标志: 1=已修改,需要写回磁盘 */
    unsigned char i_pipe;         /* 管道标志: 1=这是管道,0=普通文件
                                   * 管道使用i节点但无磁盘数据块 */
    unsigned char i_mount;        /* 挂载标志: 1=挂载点,0=普通文件
                                   * 该i节点是其他文件系统的挂载点 */
    unsigned char i_seek;         /* 搜索标志(用于lseek) */
    unsigned char i_update;       /* 更新标志: 需要更新i_atime或i_ctime */
};


目录在Minix文件系统中是特殊的文件,其数据块存放的是目录项数组。每个目录项包含i节点号和文件名(最长14字符)。读取目录就是读取其数据块,解析目录项结构。查找文件时,从根目录的i节点开始,逐层解析路径中的每一级目录,最终找到目标文件的i节点号。

6.2 缓冲区高速缓存

缓冲区高速缓存(buffer cache)是文件系统性能的关键。所有对块设备的读写操作都通过缓冲区进行,缓存命中时可以避免慢速的磁盘访问。buffer_head结构描述一个缓冲块,包含设备号、块号、数据指针、锁标志、修改标志、更新标志等。

buffer_init()在系统初始化时构建缓冲区。它从物理内存高端向下分配空间,每个buffer_head管理1KB数据缓冲区。所有buffer_head通过双向链表组织成free_list空闲链表,还通过hash_table哈希表按(设备号,块号)进行索引,以加速查找。

缓冲区高速缓存 (Buffer Cache) 内存布局

getblk()函数是缓冲区管理的核心。它根据设备号和块号查找缓冲块:首先在hash_table中查找,如果找到就等待其解锁并返回;如果未找到就从free_list分配一个空闲缓冲块,将其插入hash_table并返回。如果缓冲块是脏的(b_dirt=1),getblk()会先将其写回磁盘再重用。这种LRU策略保证了热点数据留在缓存中。

bread()函数读取指定块到缓冲区。它调用getblk()获取缓冲块,如果缓冲块的b_uptodate标志为0(数据无效),就发起磁盘读操作ll_rw_block(READ, bh),等待读完成后返回。breada()实现预读优化,一次读取多个连续块,提高顺序访问性能。bwrite()将缓冲块标记为脏(b_dirt=1)并加入写队列,由后台定期将脏块写入磁盘。

buffer_head缓冲块头结构

每个缓冲块由buffer_head结构管理:



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

/* include/linux/fs.h - 缓冲块头结构 */
struct buffer_head {
    char * b_data;                   /* 指向实际数据缓冲区(1KB)
                                      * 从内存高端向下分配 */
    unsigned long b_blocknr;         /* 逻辑块号(在设备上的块号)
                                      * 与b_dev一起唯一标识一个磁盘块 */
    unsigned short b_dev;            /* 设备号(0x0300=hda, 0x0301=hda1等)
                                      * 高8位主设备号,低8位次设备号 */
    unsigned char b_uptodate;        /* 数据有效标志
                                      * 1=缓冲区数据与磁盘同步,可直接使用
                                      * 0=数据无效,需要从磁盘读取 */
    unsigned char b_dirt;            /* 脏标志
                                      * 1=数据已修改,需要写回磁盘
                                      * 0=数据未修改或已写回 */
    unsigned char b_count;           /* 引用计数
                                      * 正在使用此缓冲块的进程数
                                      * >0时不能被重用 */
    unsigned char b_lock;            /* 锁标志
                                      * 1=缓冲块被锁定(正在进行I/O操作)
                                      * 0=未锁定,可以访问 */
struct task_struct * b_wait;     /* 等待此缓冲块解锁的进程队列
                                      * 当b_lock=1时,其他进程在此等待 */
struct buffer_head * b_prev;     /* 空闲链表中的前一个缓冲块
                                      * free_list双向链表指针 */
struct buffer_head * b_next;     /* 空闲链表中的下一个缓冲块 */
struct buffer_head * b_prev_free;/* hash队列中的前一个缓冲块
                                      * hash_table用于快速查找 */
struct buffer_head * b_next_free;/* hash队列中的下一个缓冲块 */
};


buffer_init初始化缓冲区

系统初始化时,buffer_init()从内存高端向下分配缓冲区:



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49

/* fs/buffer.c - 缓冲区初始化 */
#define NR_BUFFERS nr_buffers          /* 缓冲块数量,根据内存大小计算 */
#define HASH_TABLE_SIZE 307            /* 哈希表大小(素数,减少冲突) */

struct buffer_head * start_buffer;     /* 缓冲块头数组起始地址 */
struct buffer_head * hash_table[HASH_TABLE_SIZE];  /* 哈希表 */
staticstruct buffer_head * free_list; /* 空闲链表头 */
static int nr_buffers = 0;             /* 缓冲块总数 */

void buffer_init(long buffer_end)
{
struct buffer_head * h = start_buffer;
    void * b = (void *) buffer_end;    /* 从内存高端向下分配数据缓冲区 */
    int i;

    /* 如果buffer_end在16MB以上,只使用到16MB为止 */
    if (buffer_end == 1 << 20)
        b = (void *) (640 * 1024);     /* 640KB以下是传统内存 */
    else
        b = (void *) buffer_end;

    /* 循环分配缓冲块,直到空间不足 */
    while ((b -= BLOCK_SIZE) >= ((void *) (h + 1))) {
        h->b_dev = 0;                  /* 设备号0表示空闲 */
        h->b_dirt = 0;                 /* 未修改 */
        h->b_count = 0;                /* 无引用 */
        h->b_lock = 0;                 /* 未锁定 */
        h->b_uptodate = 0;             /* 数据无效 */
        h->b_wait = NULL;              /* 无等待进程 */
        h->b_next = NULL;
        h->b_prev = NULL;
        h->b_data = (char *) b;        /* 指向1KB数据缓冲区 */
        h->b_prev_free = h - 1;        /* 构建双向链表 */
        h->b_next_free = h + 1;
        h++;                           /* 下一个buffer_head */
        nr_buffers++;                  /* 缓冲块计数+1 */
        if (b == (void *) 0x100000)    /* 跳过1MB边界 */
            b = (void *) 0xA0000;      /* 640KB-1MB是系统保留区 */
    }

    h--;                               /* 最后一个buffer_head */
    free_list = start_buffer;          /* 空闲链表头 */
    free_list->b_prev_free = h;        /* 构成循环链表 */
    h->b_next_free = free_list;

    /* 初始化哈希表为空 */
    for (i = 0; i < HASH_TABLE_SIZE; i++)
        hash_table[i] = NULL;
}


getblk获取缓冲块

getblk()是缓冲区管理的核心函数,实现查找或分配缓冲块:



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61

/* fs/buffer.c - 获取指定设备和块号的缓冲块 */
struct buffer_head * getblk(int dev, int block)
{
struct buffer_head * bh;

    /*=== 步骤1: 在哈希表中查找缓冲块 ===*/
repeat:
    if ((bh = get_hash_table(dev, block))) {
        /* 找到了,等待解锁 */
        wait_on_buffer(bh);            /* 如果b_lock=1则睡眠等待 */
        if (bh->b_dev == dev && bh->b_blocknr == block)
            return bh;                 /* 返回缓冲块 */
        goto repeat;                   /* 可能在等待期间被换出,重新查找 */
    }

    /*=== 步骤2: 未找到,从空闲链表分配 ===*/
    /* 扫描空闲链表,寻找空闲缓冲块(b_count=0) */
    bh = free_list;
    do {
        if (bh->b_count)               /* 引用计数>0,正在使用,跳过 */
            continue;
        if (!bh->b_dirt && !bh->b_lock) /* 找到干净且未锁定的块 */
            break;                     /* 最佳选择,立即使用 */
        /* 否则继续寻找更好的候选 */
    } while ((bh = bh->b_next_free) != free_list);

    /*=== 步骤3: 如果分配的块是脏的,先写回磁盘 ===*/
    if (bh->b_dirt) {
        sync_dev(bh->b_dev);           /* 同步该设备的所有脏块 */
        goto repeat;                   /* 写回后重新查找 */
    }

    /*=== 步骤4: 如果缓冲块在哈希表中,先移除 ===*/
    if (bh->b_dev)
        remove_from_queues(bh);        /* 从哈希队列移除 */

    /*=== 步骤5: 初始化缓冲块并插入哈希表 ===*/
    bh->b_dev = dev;                   /* 设置设备号 */
    bh->b_blocknr = block;             /* 设置块号 */
    bh->b_dirt = 0;                    /* 标记为干净 */
    bh->b_uptodate = 0;                /* 数据无效(稍后需要读取) */
    bh->b_count = 1;                   /* 引用计数设为1 */
    remove_from_queues(bh);            /* 从空闲链表移除 */
    insert_into_queues(bh);            /* 插入哈希表 */
    return bh;                         /* 返回缓冲块 */
}

/* 辅助函数: 在哈希表中查找缓冲块 */
static struct buffer_head * get_hash_table(int dev, int block)
{
struct buffer_head * bh;

    /* 计算哈希值: (dev ^ block) % HASH_TABLE_SIZE */
    for (bh = hash_table[_hashfn(dev, block)]; bh != NULL; bh = bh->b_next)
        if (bh->b_dev == dev && bh->b_blocknr == block)
            return bh;                 /* 找到匹配的缓冲块 */
    return NULL;                       /* 未找到 */
}

/* 哈希函数 */
#define _hashfn(dev, block) (((dev) ^ (block)) % HASH_TABLE_SIZE)


bread读取磁盘块

bread()读取指定块到缓冲区,如果缓存未命中则从磁盘读取:



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44

/* fs/buffer.c - 读取磁盘块到缓冲区 */
struct buffer_head * bread(int dev, int block)
{
struct buffer_head * bh;

    /*=== 步骤1: 调用getblk获取缓冲块 ===*/
    if (!(bh = getblk(dev, block))) {
        printk("bread: getblk failed\n");
        return NULL;                   /* 内存不足,分配失败 */
    }

    /*=== 步骤2: 检查数据是否有效 ===*/
    if (bh->b_uptodate)                /* 数据有效,缓存命中 */
        return bh;                     /* 直接返回,无需读盘 */

    /*=== 步骤3: 数据无效,从磁盘读取 ===*/
    ll_rw_block(READ, bh);             /* 调用底层块设备驱动读取
                                        * READ=0, 读操作
                                        * 驱动会填充bh->b_data */
    wait_on_buffer(bh);                /* 等待I/O完成
                                        * I/O完成后中断处理程序会:
                                        * 1. 设置b_uptodate=1
                                        * 2. 设置b_lock=0
                                        * 3. 唤醒等待进程 */

    /*=== 步骤4: 检查读取结果 ===*/
    if (bh->b_uptodate)                /* 读取成功 */
        return bh;

    /* 读取失败,释放缓冲块 */
    brelse(bh);                        /* 减少引用计数 */
    return NULL;
}

/* 释放缓冲块(减少引用计数) */
void brelse(struct buffer_head * bh)
{
    if (!bh)
        return;
    wait_on_buffer(bh);                /* 等待解锁 */
    if (!(bh->b_count--))
        panic("Trying to free free buffer");
    wake_up(&buffer_wait);             /* 唤醒等待空闲缓冲块的进程 */
}


sync()系统调用将所有脏缓冲块写回磁盘,保证数据持久化。它先调用sync_inodes()写i节点,再扫描所有缓冲块,对脏块调用ll_rw_block(WRITE)。sync_dev(dev)只同步指定设备的缓冲块。系统定期(通常每30秒)调用sync(),以防止系统崩溃时丢失过多数据。

6.3 i节点管理

内存中维护一个i节点表inode_table(64项),缓存最近访问的i节点。m_inode结构是内存i节点,包含磁盘i节点的所有字段,还增加了引用计数、锁、修改标志等字段。i节点在内存和磁盘之间同步,修改后的i节点最终写回磁盘。

iget(dev, nr)函数获取指定设备的i节点。它首先在inode_table中查找,如果找到就增加引用计数并返回;如果未找到就分配一个空闲i节点项,调用read_inode()从磁盘读取i节点内容到内存。iput(inode)释放i节点,将引用计数减1,如果降为0且i节点已修改,则调用write_inode()写回磁盘,如果i节点的链接数为0(文件被删除),则释放其数据块和i节点本身。

i节点索引结构示意图

bmap(inode, block)函数将文件的逻辑块号映射为磁盘块号。它根据block值选择使用直接块、一次间接块或二次间接块。如果对应块号为0(块未分配),且create标志置位,bmap()会调用new_block()分配新块。new_block()扫描块位图找空闲块,设置位图并更新超级块,返回新块号。free_block()释放块,清除位图位。

iget获取i节点

iget()从内存缓存或磁盘获取i节点:



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108

/* fs/inode.c - i节点表,64项 */
struct m_inode inode_table[NR_INODE];  /* NR_INODE=64 */

/* 获取指定设备和i节点号的i节点 */
struct m_inode * iget(int dev, int nr)
{
struct m_inode * inode, * empty;

    if (!dev)
        panic("iget with dev==0");

    empty = NULL;
    /*=== 步骤1: 在inode_table中查找匹配的i节点 ===*/
    inode = inode_table + 0;           /* 从inode_table[0]开始 */
repeat:
    for (; inode < inode_table + NR_INODE; inode++) {
        if (inode->i_dev != dev || inode->i_num != nr) {
            /* 不匹配,记录第一个空闲项 */
            if (!inode->i_count && !empty)
                empty = inode;
            continue;
        }

        /*=== 找到匹配的i节点 ===*/
        wait_on_inode(inode);          /* 等待i节点解锁 */

        /* 等待期间可能被修改,重新验证 */
        if (inode->i_dev != dev || inode->i_num != nr) {
            inode = inode_table + 0;   /* 从头开始搜索 */
            goto repeat;
        }

        inode->i_count++;              /* 引用计数+1 */

        /* 如果是挂载点,跟随i节点到被挂载的文件系统根目录 */
        if (inode->i_mount) {
            int i;
            /* 搜索super_block表,找到挂载的文件系统 */
            for (i = 0; i < NR_SUPER; i++)
                if (super_block[i].s_imount == inode)
                    break;
            if (i >= NR_SUPER) {
                printk("Mounted inode hasn't got sb\n");
                return inode;
            }
            /* 返回被挂载文件系统的根i节点(1号) */
            iput(inode);               /* 释放当前i节点 */
            dev = super_block[i].s_dev;
            nr = ROOT_INO;             /* ROOT_INO=1 */
            inode = inode_table + 0;
            goto repeat;
        }
        return inode;                  /* 返回找到的i节点 */
    }

    /*=== 步骤2: 未找到,使用空闲i节点项 ===*/
    if (!empty)
        return NULL;                   /* 无空闲项,返回失败 */

    inode = empty;                     /* 使用空闲项 */
    inode->i_dev = dev;                /* 设置设备号 */
    inode->i_num = nr;                 /* 设置i节点号 */

    /*=== 步骤3: 从磁盘读取i节点 ===*/
    read_inode(inode);                 /* 读取磁盘i节点到内存 */
    return inode;
}

/* 从磁盘读取i节点 */
static void read_inode(struct m_inode * inode)
{
struct super_block * sb;
struct buffer_head * bh;
    int block;

    /*=== 步骤1: 计算i节点所在的磁盘块号 ===*/
    lock_inode(inode);                 /* 锁定i节点 */
    sb = get_super(inode->i_dev);      /* 获取超级块 */

    /* i节点区起始块 = 2 + s_imap_blocks + s_zmap_blocks
     * 每块1024字节 / 32字节每个i节点 = 32个i节点
     * i节点nr所在块 = i节点区起始 + (nr-1)/32 */
    block = 2 + sb->s_imap_blocks + sb->s_zmap_blocks +
            (inode->i_num - 1) / INODES_PER_BLOCK;

    /*=== 步骤2: 读取包含i节点的磁盘块 ===*/
    if (!(bh = bread(inode->i_dev, block))) {
        panic("unable to read i-node block");
    }

    /*=== 步骤3: 从缓冲块中复制i节点数据 ===*/
    /* 计算i节点在块内的偏移: (nr-1) % 32 */
    *((struct d_inode *) inode) =
        ((struct d_inode *) bh->b_data)[(inode->i_num - 1) % INODES_PER_BLOCK];

    brelse(bh);                        /* 释放缓冲块 */

    /*=== 步骤4: 初始化其他字段 ===*/
    inode->i_count = 1;                /* 引用计数=1 */
    inode->i_lock = 0;                 /* 解锁 */
    inode->i_dirt = 0;                 /* 未修改 */
    inode->i_pipe = 0;                 /* 非管道 */
    inode->i_mount = 0;                /* 非挂载点 */
    inode->i_seek = 0;
    inode->i_update = 0;

    unlock_inode(inode);               /* 解锁i节点 */
}


iput释放i节点

iput()减少i节点引用计数,必要时写回磁盘或释放资源:



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84

/* fs/inode.c - 释放i节点 */
void iput(struct m_inode * inode)
{
    if (!inode)
        return;

    wait_on_inode(inode);              /* 等待i节点解锁 */

    if (!inode->i_count)
        panic("iput: trying to free free inode");

    /*=== 步骤1: 处理管道i节点 ===*/
    if (inode->i_pipe) {
        wake_up(&inode->i_wait);       /* 唤醒等待管道的进程 */
        if (--inode->i_count)
            return;                    /* 还有其他引用 */
        /* 最后一个引用,释放管道缓冲区 */
        free_page(inode->i_size);      /* i_size存放管道缓冲区地址 */
        inode->i_count = 0;            /* 清零引用计数 */
        inode->i_dirt = 0;
        inode->i_pipe = 0;
        return;
    }

    /*=== 步骤2: 处理块设备i节点 ===*/
    if (!inode->i_dev) {
        inode->i_count--;              /* 无设备号,直接减引用计数 */
        return;
    }

repeat:
    /*=== 步骤3: 处理被删除的文件(链接数=0) ===*/
    if (inode->i_count > 1) {
        inode->i_count--;              /* 还有其他引用 */
        return;
    }

    /* 现在i_count=1,即将清零 */
    if (!inode->i_nlinks) {
        /* 文件链接数=0,文件已被删除 */
        truncate(inode);               /* 释放所有数据块 */
        free_inode(inode);             /* 释放i节点,清除i节点位图 */
        return;                        /* 注意:不减少i_count */
    }

    /*=== 步骤4: 将修改的i节点写回磁盘 ===*/
    if (inode->i_dirt) {
        write_inode(inode);            /* 同步i节点到磁盘 */
        wait_on_inode(inode);          /* 等待写入完成 */
        goto repeat;                   /* 重新检查 */
    }

    inode->i_count--;                  /* 减少引用计数 */
}

/* 将内存i节点写回磁盘 */
static void write_inode(struct m_inode * inode)
{
struct super_block * sb;
struct buffer_head * bh;
    int block;

    lock_inode(inode);                 /* 锁定i节点 */

    /* 计算i节点所在磁盘块 */
    sb = get_super(inode->i_dev);
    block = 2 + sb->s_imap_blocks + sb->s_zmap_blocks +
            (inode->i_num - 1) / INODES_PER_BLOCK;

    /* 读取磁盘块 */
    if (!(bh = bread(inode->i_dev, block))) {
        panic("unable to read i-node block");
    }

    /* 将内存i节点复制到缓冲块 */
    ((struct d_inode *) bh->b_data)[(inode->i_num - 1) % INODES_PER_BLOCK] =
        *((struct d_inode *) inode);

    bh->b_dirt = 1;                    /* 标记为脏,稍后写回 */
    inode->i_dirt = 0;                 /* 清除脏标志 */
    brelse(bh);                        /* 释放缓冲块 */

    unlock_inode(inode);               /* 解锁 */
}


bmap映射逻辑块到磁盘块

bmap()将文件的逻辑块号转换为磁盘物理块号:



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97

/* fs/inode.c - 块号映射 */
int bmap(struct m_inode * inode, int block)
{
    int i;

    if (block < 0)
        panic("bmap: block < 0");

    /*=== 情凵1: 直接块(0-6),共7KB ===*/
    if (block < 7) {
        if (!inode->i_zone[block]) {   /* 块未分配 */
            /* 分配新块 */
            if (!(inode->i_zone[block] = new_block(inode->i_dev)))
                return 0;              /* 分配失败 */
            inode->i_ctime = CURRENT_TIME;
            inode->i_dirt = 1;         /* 标记i节点修改 */
        }
        return inode->i_zone[block];   /* 返回磁盘块号 */
    }

    /*=== 情凵2: 一次间接块(7-518),共512KB ===*/
    block -= 7;                        /* 减去直接块 */
    if (block < 512) {
        /* 检查一次间接块是否分配 */
        if (!inode->i_zone[7]) {
            if (!(inode->i_zone[7] = new_block(inode->i_dev)))
                return 0;
            inode->i_dirt = 1;
            inode->i_ctime = CURRENT_TIME;
        }

        /* 读取一次间接块(索引块) */
struct buffer_head * bh;
        if (!(bh = bread(inode->i_dev, inode->i_zone[7])))
            return 0;

        /* 索引块包含512个块号(unsigned short) */
        i = ((unsigned short *) (bh->b_data))[block];
        if (!i) {                      /* 该块未分配 */
            if ((i = new_block(inode->i_dev))) {
                ((unsigned short *) (bh->b_data))[block] = i;
                bh->b_dirt = 1;        /* 标记索引块修改 */
            }
        }
        brelse(bh);                    /* 释放索引块 */
        return i;                      /* 返回磁盘块号 */
    }

    /*=== 情凵3: 二次间接块(519-262,658),共256MB ===*/
    block -= 512;                      /* 减去一次间接 */
    if (block < 512 * 512) {
        /* 检查二次间接块是否分配 */
        if (!inode->i_zone[8]) {
            if (!(inode->i_zone[8] = new_block(inode->i_dev)))
                return 0;
            inode->i_dirt = 1;
            inode->i_ctime = CURRENT_TIME;
        }

        /* 读取二级索引块 */
struct buffer_head * bh;
        if (!(bh = bread(inode->i_dev, inode->i_zone[8])))
            return 0;

        /* 计算一级索引块的位置: block / 512 */
        i = ((unsigned short *) bh->b_data)[block >> 9];
        if (!i) {                      /* 一级索引块未分配 */
            if ((i = new_block(inode->i_dev))) {
                ((unsigned short *) (bh->b_data))[block >> 9] = i;
                bh->b_dirt = 1;
            }
        }
        brelse(bh);

        if (!i)
            return 0;

        /* 读取一级索引块 */
        if (!(bh = bread(inode->i_dev, i)))
            return 0;

        /* 计算最终数据块的位置: block % 512 */
        i = ((unsigned short *) bh->b_data)[block & 511];
        if (!i) {                      /* 数据块未分配 */
            if ((i = new_block(inode->i_dev))) {
                ((unsigned short *) (bh->b_data))[block & 511] = i;
                bh->b_dirt = 1;
            }
        }
        brelse(bh);
        return i;                      /* 返回磁盘块号 */
    }

    /* 超出文件大小限制 */
    panic("bmap: block > 7+512+512*512");
    return 0;
}


磁盘i节点管理文件系统进程磁盘i节点管理文件系统进程alt[在内存中][不在内存中]alt[引用计数=0且已修改]open函数调用解析路径 查找i节点号iget函数调用查找inode_table引用计数+1read_inode读取i节点返回i节点数据填充m_inode结构返回i节点指针返回文件描述符read函数调用bmap函数调用计算磁盘块号返回块号bread读取数据块返回缓冲块复制数据到用户缓冲区close(fd)iput(inode)引用计数-1write_inode写回磁盘

6.4 文件操作

open()系统调用打开文件。sys_open()首先调用open_namei()根据路径名查找i节点,这个过程从根目录开始,逐层解析每个目录分量。找到i节点后,分配一个file结构和文件描述符,将file->f_inode指向i节点,file->f_pos设为0(文件位置),将文件描述符返回给用户。

read()系统调用读取文件内容。sys_read()根据文件描述符找到file结构和i节点,调用file_read()逐块读取数据。file_read()循环调用bmap()获取文件逻辑块对应的磁盘块号,然后用bread()读取块到缓冲区,将数据从内核缓冲区复制到用户缓冲区,更新文件位置f_pos。对于小于1KB的读取,只复制部分块内容。

write()系统调用写入文件。sys_write()调用file_write(),它与file_read()类似,但是将用户数据复制到缓冲区,并标记缓冲块为脏。如果写入位置超过文件末尾,file_write()会调用bmap()分配新块,并更新i节点的i_size字段。close()关闭文件,减少file和i节点的引用计数,如果i节点引用计数降为0则可能写回磁盘。

file结构和文件描述符

每个打开的文件都有一个file结构:



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

/* include/linux/fs.h - 文件结构 */
struct file {
    unsigned short f_mode;       /* 文件模式
                                  * 1: 只读(O_RDONLY)
                                  * 2: 只写(O_WRONLY)
                                  * 3: 读写(O_RDWR) */
    unsigned short f_flags;      /* 文件标志
                                  * O_CREAT, O_TRUNC, O_APPEND等 */
    unsigned short f_count;      /* 引用计数
                                  * 多个fd可能指向同一file(如fork后) */
struct m_inode * f_inode;    /* 指向i节点 */
    off_t f_pos;                 /* 文件当前位置(字节偏移)
                                  * read/write会更新此值 */
};

/* 全局文件表,最多64个打开文件 */
struct file file_table[NR_FILE];   /* NR_FILE=64 */

/* 每个进程的文件描述符表 */
struct task_struct {
    ...
struct file * filp[NR_OPEN];  /* NR_OPEN=20,每个进程最多20个fd */
    ...
};


sys_open打开文件

sys_open()实现文件打开:



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53

/* fs/open.c - open系统调用 */
int sys_open(const char * filename, int flag, int mode)
{
struct m_inode * inode;
struct file * f;
    int i, fd;

    /*=== 步骤1: 检查模式标志 ===*/
    mode &= 0777 & ~current->umask;    /* 应用umask */

    /*=== 步骤2: 解析路径名,获取i节点 ===*/
    if (!(inode = open_namei(filename, flag, mode)))
        return -1;                     /* 文件不存在或无权限 */

    /*=== 步骤3: 分配文件描述符fd ===*/
    for (fd = 0; fd < NR_OPEN; fd++) {
        if (!current->filp[fd])        /* 找到空闲fd */
            break;
    }
    if (fd >= NR_OPEN) {               /* 进程打开文件太多 */
        iput(inode);
        return -1;
    }

    /*=== 步骤4: 从全局file_table中分配file结构 ===*/
    for (f = file_table + 0; f < file_table + NR_FILE; f++) {
        if (!f->f_count) {             /* 找到空闲file */
            f->f_count = 1;            /* 引用计数=1 */
            break;
        }
    }
    if (f >= file_table + NR_FILE) {   /* 系统打开文件太多 */
        iput(inode);
        return -1;
    }

    /*=== 步骤5: 初始化file结构 ===*/
    current->filp[fd] = f;             /* 关联fd和file */
    f->f_mode = inode->i_mode;         /* 设置文件模式 */
    f->f_flags = flag;                 /* 设置标志 */
    f->f_inode = inode;                /* 指向i节点 */
    f->f_pos = 0;                      /* 文件位置=0 */

    /*=== 步骤6: 处理O_TRUNC标志 ===*/
    if (flag & O_TRUNC) {              /* 截断文件到长度0 */
        truncate(inode);               /* 释放所有数据块 */
        inode->i_size = 0;             /* 文件大小=0 */
        inode->i_dirt = 1;             /* 标记i节点修改 */
        inode->i_mtime = CURRENT_TIME;
    }

    return fd;                         /* 返回文件描述符 */
}


file_read读取文件

file_read()从文件读取数据:



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57

/* fs/file_dev.c - 文件读取 */
int file_read(struct m_inode * inode, struct file * filp, char * buf, int count)
{
    int left, chars, nr;
struct buffer_head * bh;

    /*=== 步骤1: 计算实际可读字节数 ===*/
    if ((left = count) <= 0)
        return 0;

    /* 不能超过文件大小 */
    if (filp->f_pos >= inode->i_size)
        return 0;                      /* 已到文件末尾 */

    if (filp->f_pos + left > inode->i_size)
        left = inode->i_size - filp->f_pos;

    /*=== 步骤2: 循环读取各个数据块 ===*/
    while (left > 0) {
        /* 计算当前位置所在的逻辑块号 */
        nr = filp->f_pos / BLOCK_SIZE;   /* 逻辑块号 = 位置/1024 */

        /*=== 步骤2.1: 将逻辑块映射为磁盘块 ===*/
        nr = bmap(inode, nr);          /* 调用bmap转换为磁盘块号 */
        if (!nr)
            break;                     /* 没有分配块,读取结束 */

        /*=== 步骤2.2: 读取磁盘块到缓冲区 ===*/
        if (!(bh = bread(inode->i_dev, nr)))
            break;                     /* 读取失败 */

        /*=== 步骤2.3: 计算本次复制的字节数 ===*/
        /* 块内偏移量 */
        nr = filp->f_pos % BLOCK_SIZE;
        /* 本块剩余可读字节 */
        chars = MIN(BLOCK_SIZE - nr, left);

        /*=== 步骤2.4: 从内核缓冲区复制到用户空间 ===*/
        /* put_fs_byte: 将内核空间字节写入用户空间
         * 从 bh->b_data + nr 处开始复制 chars 字节到 buf */
        char * p = bh->b_data + nr;
        while (chars-- > 0)
            put_fs_byte(*(p++), buf++);  /* 逐字节复制 */

        brelse(bh);                    /* 释放缓冲块 */

        /*=== 步骤2.5: 更新文件位置和剩余字节数 ===*/
        filp->f_pos += chars;          /* 移动文件指针 */
        left -= chars;                 /* 减少剩余字节数 */
    }

    /*=== 步骤3: 更新i节点访问时间 ===*/
    inode->i_atime = CURRENT_TIME;

    /* 返回实际读取的字节数 */
    return count - left;
}


file_write写入文件

file_write()向文件写入数据:



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62

/* fs/file_dev.c - 文件写入 */
int file_write(struct m_inode * inode, struct file * filp, char * buf, int count)
{
    off_t pos;
    int block, c;
struct buffer_head * bh;
    char * p;
    int i = 0;

    /*=== 步骤1: 处理O_APPEND模式 ===*/
    if (filp->f_flags & O_APPEND)
        pos = inode->i_size;           /* 附加模式,从文件末尾开始 */
    else
        pos = filp->f_pos;             /* 否则从当前位置开始 */

    /*=== 步骤2: 循环写入各个数据块 ===*/
    while (i < count) {
        /* 计算目标位置的逻辑块号 */
        block = pos / BLOCK_SIZE;

        /*=== 步骤2.1: 映射逻辑块为磁盘块,必要时分配 ===*/
        if (!(block = bmap(inode, block))) {
            /* bmap会自动分配新块 */
            break;                     /* 分配失败,停止写入 */
        }

        /*=== 步骤2.2: 读取目标块到缓冲区 ===*/
        if (!(bh = bread(inode->i_dev, block)))
            break;                     /* 读取失败 */

        /*=== 步骤2.3: 计算本次写入的字节数 ===*/
        c = pos % BLOCK_SIZE;          /* 块内偏移量 */
        p = bh->b_data + c;            /* 写入位置 */
        c = BLOCK_SIZE - c;            /* 本块剩余空间 */
        if (c > count - i)
            c = count - i;             /* 不超过剩余字节数 */

        /*=== 步骤2.4: 从用户空间复制到内核缓冲区 ===*/
        /* get_fs_byte: 从用户空间读取字节到内核空间 */
        pos += c;
        i += c;
        while (c-- > 0)
            *(p++) = get_fs_byte(buf++);  /* 逐字节复制 */

        /*=== 步骤2.5: 标记缓冲块为脏 ===*/
        bh->b_dirt = 1;                /* 脏块,稍后写回磁盘 */
        brelse(bh);                    /* 释放缓冲块 */
    }

    /*=== 步骤3: 更新i节点信息 ===*/
    inode->i_mtime = CURRENT_TIME;     /* 修改时间 */
    inode->i_ctime = CURRENT_TIME;     /* 改变时间 */

    /* 如果写入超过原文件大小,更新i_size */
    if (pos > inode->i_size) {
        inode->i_size = pos;           /* 扩大文件 */
        inode->i_dirt = 1;             /* 标记i节点修改 */
    }

    filp->f_pos = pos;                 /* 更新文件位置 */
    return i;                          /* 返回实际写入字节数 */
}


lseek()系统调用改变文件位置。它修改file->f_pos,支持从文件开头、当前位置或文件末尾偏移。stat()获取文件状态信息(i节点中的元数据),填充stat结构返回给用户。unlink()删除文件,它减少i节点的链接计数,如果降为0则在iput()时释放i节点和数据块。link()创建硬链接,增加i节点链接计数。

6.5 目录操作与路径解析

目录的数据块存放目录项数组,每个目录鞘16字节:2字节i节点号+14字节文件名。查找文件时,从根i节点(inode 1)开始,读取根目录的数据块,在目录项中查找路径的第一个分量,找到后获取其i节点,重复此过程直到解析完整路径。

namei()函数实现路径解析,返回路径对应的i节点。它处理绝对路径(从根目录开始)和相对路径(从当前工作目录开始),支持符号链接(跟随链接继续解析)。open_namei()是namei()的扩展,支持创建不存在的文件(当O_CREAT标志置位时)。

mkdir()系统调用创建目录。它分配新i节点,设置为目录类型,分配数据块,写入两个特殊目录项:"."指向自己,".."指向父目录,然后在父目录中添加新目录的目录项。rmdir()删除空目录,检查目录只包含"."和".."后删除i节点。

目录项结构

目录文件的数据块包含目录项数组:



1
2
3
4
5
6
7
8
9
10
11
12

/* include/linux/fs.h - 目录项结构,16字节 */
struct dir_entry {
    unsigned short inode;        /* i节点号(0表示目录项未使用/已删除)
                                  * 1=根目录,2及以上是普通文件/目录 */
    char name[NAME_LEN];         /* 文件名,NAME_LEN=14字节
                                  * 不足的用'\0'填充,无结尾\0 */
};

/* 特殊目录项:
 * "."  - 指向当前目录自身的i节点
 * ".." - 指向父目录的i节点
 */


namei路径解析

namei()将路径名解析为i节点:



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100

/* fs/namei.c - 路径名解析 */
struct m_inode * namei(const char * pathname)
{
    const char * basename;
    int namelen;
struct m_inode * dir, * inode;
struct buffer_head * bh;
struct dir_entry * de;

    /*=== 步骤1: 确定起始目录 ===*/
    if (*pathname == '/') {
        /* 绝对路径,从根目录开始 */
        dir = current->root;       /* 根目录i节点 */
        pathname++;                /* 跳过开头的'/' */
    } else {
        /* 相对路径,从当前工作目录开始 */
        dir = current->pwd;        /* 当前工作目录i节点 */
    }
    dir->i_count++;                /* 增加引用计数 */

    /*=== 步骤2: 逐层解析路径各个分量 ===*/
    while (1) {
        /* 跳过多余的'/' */
        while (*pathname == '/')
            pathname++;

        /* 如果到达路径末尾,返回当前目录i节点 */
        if (!*pathname)
            return dir;

        /*=== 步骤2.1: 提取下一个路径分量 ===*/
        basename = pathname;
        namelen = 0;
        while (*pathname && *pathname != '/') {
            pathname++;
            namelen++;
        }
        if (namelen > NAME_LEN)    /* 文件名过长 */
            namelen = NAME_LEN;

        /*=== 步骤2.2: 在当前目录中查找该分量 ===*/
        inode = find_entry(&dir, basename, namelen, &de);
        if (!inode) {              /* 未找到 */
            iput(dir);
            return NULL;
        }

        /*=== 步骤2.3: 检查是否为目录(如果还有下一级) ===*/
        if (*pathname) {           /* 路径未结束 */
            if (!S_ISDIR(inode->i_mode)) {
                /* 不是目录,无法继续解析 */
                iput(inode);
                iput(dir);
                return NULL;
            }
        }

        /*=== 步骤2.4: 释放旧目录,移动到下一级 ===*/
        iput(dir);                 /* 释放父目录i节点 */
        dir = inode;               /* 当前目录移动到下一级 */
    }
}

/* 在目录中查找文件名 */
static struct m_inode * find_entry(struct m_inode ** dir,
        const char * name, int namelen,struct dir_entry ** res_dir)
{
    int entries, i;
struct buffer_head * bh;
struct dir_entry * de;

    /*=== 步骤1: 计算目录项数量 ===*/
    entries = (*dir)->i_size / sizeof(struct dir_entry);

    /*=== 步骤2: 遍历目录的每个数据块 ===*/
    for (i = 0; i < entries; i++) {
        /* 计算i目录项所在的块号 */
        int block = i * sizeof(struct dir_entry) / BLOCK_SIZE;
        int offset = i * sizeof(struct dir_entry) % BLOCK_SIZE;

        /* 读取目录数据块 */
        if (!(block = bmap(*dir, block))) continue;
        if (!(bh = bread((*dir)->i_dev, block))) continue;

        /* 获取目录项 */
        de = (struct dir_entry *) (bh->b_data + offset);

        /*=== 步骤3: 比较文件名 ===*/
        if (de->inode && namelen == strlen(de->name) &&
            strncmp(name, de->name, namelen) == 0) {
            /* 找到匹配的目录项 */
            *res_dir = de;         /* 返回目录项指针 */
            return iget((*dir)->i_dev, de->inode);  /* 返回i节点 */
        }

        brelse(bh);                /* 未找到,释放缓冲块 */
    }

    return NULL;                   /* 未找到 */
}


6.6 管道机制

管道是进程间通信的一种机制。pipe()系统调用创建管道,分配一个i节点(i_pipe标志置位),分配一个物理页面作为管道缓冲区,返回两个文件描述符:读端和写端。写进程向写端写数据,数据保存在管道缓冲区;读进程从读端读数据,从缓冲区取出数据。

管道读写使用pipe_read()和pipe_write()函数。写进程将数据复制到管道缓冲区,如果缓冲区满则睡眠等待读进程读取腾出空间。读进程从缓冲区读取数据,如果缓冲区空则睡眠等待写进程写入数据。管道使用环形缓冲区,通过头尾指针管理。当所有写端关闭时,读进程读到EOF;当所有读端关闭时,写进程收到SIGPIPE信号。

管道通信 Pipe

通过本章的学习,我们深入理解了Linux 0.12的文件系统实现:从Minix文件系统的磁盘布局到缓冲区高速缓存机制,从i节点管理到文件操作,从路径解析到目录操作,以及管道通信机制。文件系统为用户程序提供了统一的数据存储接口,屏蔽了底层硬件细节,是操作系统的重要组成部分。

6.7 参考资料

本章内容基于以下资料编写:

Minix文件系统规范

  • • Minix File System - 详细描述了Minix文件系统的磁盘布局和数据结构

Linux 0.12源代码文件

  • • fs/buffer.c - 缓冲区管理、getblk、bread、bwrite、sync
  • • fs/inode.c - i节点管理、iget、iput、bmap、read_inode、write_inode
  • • fs/namei.c - 路径解析、namei、open_namei、mkdir、rmdir、link、unlink
  • • fs/file_dev.c - file_read、file_write文件读写
  • • fs/read_write.c - sys_read、sys_write、sys_lseek系统调用
  • • fs/open.c - sys_open、sys_close、sys_creat系统调用
  • • fs/pipe.c - pipe_read、pipe_write、sys_pipe管道机制
  • • fs/super.c - 超级块管理、mount、umount
  • • fs/bitmap.c - new_block、free_block、new_inode、free_inode位图操作
  • • include/linux/fs.h - file、inode、buffer_head等结构定义

"以铜为镜,可以正衣冠;以古为镜,可以知兴替;以人为镜,可以明得失。" —— 《旧唐书》

最新文章

随机文章

基本 文件 流程 错误 SQL 调试
  1. 请求信息 : 2026-07-03 16:38:05 HTTP/2.0 GET : https://f.mffb.com.cn/a/493368.html
  2. 运行时间 : 0.107349s [ 吞吐率:9.32req/s ] 内存消耗:5,020.49kb 文件加载:140
  3. 缓存信息 : 0 reads,0 writes
  4. 会话信息 : SESSION_ID=babbdade00de26dd8cbbea2e122caa9e
  1. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/public/index.php ( 0.79 KB )
  2. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/autoload.php ( 0.17 KB )
  3. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/composer/autoload_real.php ( 2.49 KB )
  4. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/composer/platform_check.php ( 0.90 KB )
  5. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/composer/ClassLoader.php ( 14.03 KB )
  6. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/composer/autoload_static.php ( 4.90 KB )
  7. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/helper.php ( 8.34 KB )
  8. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-validate/src/helper.php ( 2.19 KB )
  9. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/helper.php ( 1.47 KB )
  10. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/stubs/load_stubs.php ( 0.16 KB )
  11. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Exception.php ( 1.69 KB )
  12. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-container/src/Facade.php ( 2.71 KB )
  13. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/deprecation-contracts/function.php ( 0.99 KB )
  14. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/polyfill-mbstring/bootstrap.php ( 8.26 KB )
  15. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/polyfill-mbstring/bootstrap80.php ( 9.78 KB )
  16. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/var-dumper/Resources/functions/dump.php ( 1.49 KB )
  17. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-dumper/src/helper.php ( 0.18 KB )
  18. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/var-dumper/VarDumper.php ( 4.30 KB )
  19. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/App.php ( 15.30 KB )
  20. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-container/src/Container.php ( 15.76 KB )
  21. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/psr/container/src/ContainerInterface.php ( 1.02 KB )
  22. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/provider.php ( 0.19 KB )
  23. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Http.php ( 6.04 KB )
  24. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/helper/Str.php ( 7.29 KB )
  25. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Env.php ( 4.68 KB )
  26. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/common.php ( 0.03 KB )
  27. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/helper.php ( 18.78 KB )
  28. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Config.php ( 5.54 KB )
  29. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/app.php ( 0.95 KB )
  30. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/cache.php ( 0.78 KB )
  31. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/console.php ( 0.23 KB )
  32. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/cookie.php ( 0.56 KB )
  33. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/database.php ( 2.48 KB )
  34. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/facade/Env.php ( 1.67 KB )
  35. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/filesystem.php ( 0.61 KB )
  36. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/lang.php ( 0.91 KB )
  37. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/log.php ( 1.35 KB )
  38. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/middleware.php ( 0.19 KB )
  39. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/route.php ( 1.89 KB )
  40. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/session.php ( 0.57 KB )
  41. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/trace.php ( 0.34 KB )
  42. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/view.php ( 0.82 KB )
  43. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/event.php ( 0.25 KB )
  44. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Event.php ( 7.67 KB )
  45. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/service.php ( 0.13 KB )
  46. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/AppService.php ( 0.26 KB )
  47. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Service.php ( 1.64 KB )
  48. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Lang.php ( 7.35 KB )
  49. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/lang/zh-cn.php ( 13.70 KB )
  50. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/initializer/Error.php ( 3.31 KB )
  51. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/initializer/RegisterService.php ( 1.33 KB )
  52. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/services.php ( 0.14 KB )
  53. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/service/PaginatorService.php ( 1.52 KB )
  54. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/service/ValidateService.php ( 0.99 KB )
  55. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/service/ModelService.php ( 2.04 KB )
  56. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-trace/src/Service.php ( 0.77 KB )
  57. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Middleware.php ( 6.72 KB )
  58. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/initializer/BootService.php ( 0.77 KB )
  59. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/Paginator.php ( 11.86 KB )
  60. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-validate/src/Validate.php ( 63.20 KB )
  61. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/Model.php ( 23.55 KB )
  62. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/Attribute.php ( 21.05 KB )
  63. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/AutoWriteData.php ( 4.21 KB )
  64. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/Conversion.php ( 6.44 KB )
  65. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/DbConnect.php ( 5.16 KB )
  66. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/ModelEvent.php ( 2.33 KB )
  67. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/RelationShip.php ( 28.29 KB )
  68. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/contract/Arrayable.php ( 0.09 KB )
  69. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/contract/Jsonable.php ( 0.13 KB )
  70. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/contract/Modelable.php ( 0.09 KB )
  71. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Db.php ( 2.88 KB )
  72. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/DbManager.php ( 8.52 KB )
  73. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Log.php ( 6.28 KB )
  74. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Manager.php ( 3.92 KB )
  75. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/psr/log/src/LoggerTrait.php ( 2.69 KB )
  76. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/psr/log/src/LoggerInterface.php ( 2.71 KB )
  77. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Cache.php ( 4.92 KB )
  78. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/psr/simple-cache/src/CacheInterface.php ( 4.71 KB )
  79. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/helper/Arr.php ( 16.63 KB )
  80. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/cache/driver/File.php ( 7.84 KB )
  81. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/cache/Driver.php ( 9.03 KB )
  82. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/contract/CacheHandlerInterface.php ( 1.99 KB )
  83. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/Request.php ( 0.09 KB )
  84. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Request.php ( 55.78 KB )
  85. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/middleware.php ( 0.25 KB )
  86. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Pipeline.php ( 2.61 KB )
  87. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-trace/src/TraceDebug.php ( 3.40 KB )
  88. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/middleware/SessionInit.php ( 1.94 KB )
  89. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Session.php ( 1.80 KB )
  90. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/session/driver/File.php ( 6.27 KB )
  91. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/contract/SessionHandlerInterface.php ( 0.87 KB )
  92. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/session/Store.php ( 7.12 KB )
  93. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Route.php ( 23.73 KB )
  94. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/RuleName.php ( 5.75 KB )
  95. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/Domain.php ( 2.53 KB )
  96. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/RuleGroup.php ( 22.43 KB )
  97. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/Rule.php ( 26.95 KB )
  98. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/RuleItem.php ( 9.78 KB )
  99. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/route/app.php ( 1.72 KB )
  100. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/facade/Route.php ( 4.70 KB )
  101. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/dispatch/Controller.php ( 4.74 KB )
  102. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/Dispatch.php ( 10.44 KB )
  103. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/controller/Index.php ( 4.81 KB )
  104. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/BaseController.php ( 2.05 KB )
  105. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/facade/Db.php ( 0.93 KB )
  106. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/connector/Mysql.php ( 5.44 KB )
  107. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/PDOConnection.php ( 52.47 KB )
  108. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/Connection.php ( 8.39 KB )
  109. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/ConnectionInterface.php ( 4.57 KB )
  110. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/builder/Mysql.php ( 16.58 KB )
  111. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/Builder.php ( 24.06 KB )
  112. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/BaseBuilder.php ( 27.50 KB )
  113. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/Query.php ( 15.71 KB )
  114. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/BaseQuery.php ( 45.13 KB )
  115. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/TimeFieldQuery.php ( 7.43 KB )
  116. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/AggregateQuery.php ( 3.26 KB )
  117. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/ModelRelationQuery.php ( 20.07 KB )
  118. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/ParamsBind.php ( 3.66 KB )
  119. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/ResultOperation.php ( 7.01 KB )
  120. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/WhereQuery.php ( 19.37 KB )
  121. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/JoinAndViewQuery.php ( 7.11 KB )
  122. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/TableFieldInfo.php ( 2.63 KB )
  123. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/Transaction.php ( 2.77 KB )
  124. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/log/driver/File.php ( 5.96 KB )
  125. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/contract/LogHandlerInterface.php ( 0.86 KB )
  126. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/log/Channel.php ( 3.89 KB )
  127. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/event/LogRecord.php ( 1.02 KB )
  128. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/Collection.php ( 16.47 KB )
  129. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/facade/View.php ( 1.70 KB )
  130. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/View.php ( 4.39 KB )
  131. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Response.php ( 8.81 KB )
  132. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/response/View.php ( 3.29 KB )
  133. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Cookie.php ( 6.06 KB )
  134. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-view/src/Think.php ( 8.38 KB )
  135. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/contract/TemplateHandlerInterface.php ( 1.60 KB )
  136. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-template/src/Template.php ( 46.61 KB )
  137. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-template/src/template/driver/File.php ( 2.41 KB )
  138. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-template/src/template/contract/DriverInterface.php ( 0.86 KB )
  139. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/runtime/temp/067d451b9a0c665040f3f1bdd3293d68.php ( 11.98 KB )
  140. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-trace/src/Html.php ( 4.42 KB )
  1. CONNECT:[ UseTime:0.000609s ] mysql:host=127.0.0.1;port=3306;dbname=f_mffb;charset=utf8mb4
  2. SHOW FULL COLUMNS FROM `fenlei` [ RunTime:0.000794s ]
  3. SELECT * FROM `fenlei` WHERE `fid` = 0 [ RunTime:0.001995s ]
  4. SELECT * FROM `fenlei` WHERE `fid` = 63 [ RunTime:0.000291s ]
  5. SHOW FULL COLUMNS FROM `set` [ RunTime:0.000498s ]
  6. SELECT * FROM `set` [ RunTime:0.000203s ]
  7. SHOW FULL COLUMNS FROM `article` [ RunTime:0.000586s ]
  8. SELECT * FROM `article` WHERE `id` = 493368 LIMIT 1 [ RunTime:0.007701s ]
  9. UPDATE `article` SET `lasttime` = 1783067885 WHERE `id` = 493368 [ RunTime:0.005629s ]
  10. SELECT * FROM `fenlei` WHERE `id` = 67 LIMIT 1 [ RunTime:0.000240s ]
  11. SELECT * FROM `article` WHERE `id` < 493368 ORDER BY `id` DESC LIMIT 1 [ RunTime:0.000422s ]
  12. SELECT * FROM `article` WHERE `id` > 493368 ORDER BY `id` ASC LIMIT 1 [ RunTime:0.000379s ]
  13. SELECT * FROM `article` WHERE `id` < 493368 ORDER BY `id` DESC LIMIT 10 [ RunTime:0.001189s ]
  14. SELECT * FROM `article` WHERE `id` < 493368 ORDER BY `id` DESC LIMIT 10,10 [ RunTime:0.018803s ]
  15. SELECT * FROM `article` WHERE `id` < 493368 ORDER BY `id` DESC LIMIT 20,10 [ RunTime:0.001896s ]
0.108909s