Mr Dk.'s BlogMr Dk.'s Blog
  • 🦆 About Me
  • ⛏️ Technology Stack
  • 🔗 Links
  • 🗒️ About Blog
  • Algorithm
  • C++
  • Compiler
  • Cryptography
  • DevOps
  • Docker
  • Git
  • Java
  • Linux
  • MS Office
  • MySQL
  • Network
  • Operating System
  • Performance
  • PostgreSQL
  • Productivity
  • Solidity
  • Vue.js
  • Web
  • Wireless
  • 🐧 How Linux Works (notes)
  • 🐧 Linux Kernel Comments (notes)
  • 🐧 Linux Kernel Development (notes)
  • 🐤 μc/OS-II Source Code (notes)
  • ☕ Understanding the JVM (notes)
  • ⛸️ Redis Implementation (notes)
  • 🗜️ Understanding Nginx (notes)
  • ⚙️ Netty in Action (notes)
  • ☁️ Spring Microservices (notes)
  • ⚒️ The Annotated STL Sources (notes)
  • ☕ Java Development Kit 8
GitHub
  • 🦆 About Me
  • ⛏️ Technology Stack
  • 🔗 Links
  • 🗒️ About Blog
  • Algorithm
  • C++
  • Compiler
  • Cryptography
  • DevOps
  • Docker
  • Git
  • Java
  • Linux
  • MS Office
  • MySQL
  • Network
  • Operating System
  • Performance
  • PostgreSQL
  • Productivity
  • Solidity
  • Vue.js
  • Web
  • Wireless
  • 🐧 How Linux Works (notes)
  • 🐧 Linux Kernel Comments (notes)
  • 🐧 Linux Kernel Development (notes)
  • 🐤 μc/OS-II Source Code (notes)
  • ☕ Understanding the JVM (notes)
  • ⛸️ Redis Implementation (notes)
  • 🗜️ Understanding Nginx (notes)
  • ⚙️ Netty in Action (notes)
  • ☁️ Spring Microservices (notes)
  • ⚒️ The Annotated STL Sources (notes)
  • ☕ Java Development Kit 8
GitHub
  • 🐧 Linux Kernel Comments
    • Chapter 2 - 微型计算机组成结构

      • Chapter 2 - 微型计算机组成结构
    • Chapter 3 - 内核编程语言和环境

      • Chapter 3 - 内核编程语言和环境
    • Chapter 4 - 80X86 保护模式及其编程

      • Chapter 4.1-4.2 - 80X86 系统寄存器和系统指令 & 保护模式内存管理
      • Chapter 4.3 - 分段机制
      • Chapter 4.4 - 分页机制
      • Chapter 4.5 - 保护
      • Chapter 4.6 - 中断和异常处理
      • Chapter 4.7 - 任务管理
      • Chapter 4.8 - 保护模式编程初始化
      • Chapter 4.9 - 一个简单的多任务内核实例
    • Chapter 5 - Linux 内核体系结构

      • Chapter 5.1-5.2 - Linux 内核模式 & 体系结构
      • Chapter 5.3 - Linux 内核对内存的管理和使用
      • Chapter 5.4-5.6 - 中断机制 & 系统调用 & 系统时间和定时
      • Chapter 5.7-5.9 - Linux 进程控制 & 堆栈使用 & 文件系统
    • Chapter 6 - 引导启动程序 (boot)

      • Chapter 6 - 引导启动程序 (boot)
    • Chapter 7 - 初始化程序 (init)

      • Chapter 7 - 初始化程序 (init)
    • Chapter 8 - 内核代码

      • Chapter 8.1 - 内核代码总体功能
      • Chapter 8.2 - asm.s 程序
      • Chapter 8.3 - traps.c 程序
      • Chapter 8.4 - sys_call.s 程序
      • Chapter 8.5 - mktime.c 程序
      • Chapter 8.6 - sched.c 程序
      • Chapter 8.7 - signal.c 程序
      • Chapter 8.8 - exit.c 程序
      • Chapter 8.9 - fork.c 程序
      • Chapter 8.10 - sys.c 程序
    • Chapter 9 - 块设备驱动程序

      • Chapter 9.1 - 块设备驱动程序 总体功能
      • Chapter 9.2 - blk.h 文件
      • Chapter 9.3 - hd.c 程序
      • Chapter 9.4 - ll_rw_blk.c 程序
      • Chapter 9.5 - ramdisk.c 程序
    • Chapter 10 - 字符设备驱动程序

      • Chapter 10.1 - 字符设备驱动程序 总体功能
      • Chapter 10.2 - keyboard.S 程序
      • Chapter 10.3 - console.c 程序
      • Chapter 10.4 - serial.c 程序
      • Chapter 10.5 - rs_io.s 程序
      • Chapter 10.6 - tty_io.c 程序
      • Chapter 10.7 - tty_ioctl.c 程序
    • Chapter 12 - 文件系统

      • Chapter 12.1 - 文件系统 总体功能
      • Chapter 12.2 - buffer.c 程序
      • Chapter 12.3 - bitmap.c 程序
      • Chapter 12.4 - truncate.c 程序
      • Chapter 12.5 - inode.c 程序
      • Chapter 12.6 - super.c 程序
      • Chapter 12.7 - namei.c 程序
      • Chapter 12.9 - block_dev.c 程序
      • Chapter 12.10 - file_dev.c 程序
      • Chapter 12.11 - pipe.c 程序
      • Chapter 12.12 - char_dev.c 程序
      • Chapter 12.13 - read_write.c 程序
      • Chapter 12.14 - open.c 程序
      • Chapter 12.15 - exec.c 程序
      • Chapter 12.16 - stat.c 程序
      • Chapter 12.17 - fcntl.c 程序
      • Chapter 12.18 - ioctl.c 程序
      • Chapter 12.19 - select.c 程序

Chapter 12.5 - inode.c 程序

Created by : Mr Dk.

2019 / 09 / 05 16:10

Nanjing, Jiangsu, China


12.5 inode.c 程序

12.5.1 功能描述

iget() 函数

  • 从设备 dev 上读取指定结点号 nr 的 inode
  • 并把 inode 的引用计数字段值 i_count 增 1

iput() 函数

  • 把 inode 引用计数值减 1
  • 若 inode 的链接计数为 0,则释放 inode 占用的所有磁盘逻辑块

在某时刻,进程不需要持续使用一个 inode 时就应当调用 iput() 函数。内核代码通常调用 iput() 的时机:

  • inode 的引用计数字段增 1
  • 调用了 namei()、dir_namei() 或 open_namei() 函数
  • 调用了 iget()、new_inode() 或 get_empty_inode() 函数
  • 关闭文件,且没有其它进程使用该文件
  • 卸载文件系统

bmap() 函数用于把文件数据块映射到对应的盘块上,参数为 inode 指针和文件中的数据块号。在对应文件数据块不存在的情况下,根据 create 标志判断是否需要在盘上建立盘块,并返回在对应设备上的盘块号。函数主要对 inode 中的 i_zone[] 进行处理,根据 i_zone[] 中设置的盘块号,设置逻辑块 bitmap 的占用情况。

12.5.2 代码注释

inode 的数据结构

磁盘上的 inode (32B)

struct d_inode {
    unsigned short i_mode; // 文件类型和属性
    unsigned short i_uid; // 用户 id
    unsigned long i_size; // 文件大小
    unsigned long i_time; // 修改时间
    unsigned char i_gid; // 组 id
    unsigned char i_nlinks; // 链接计数 (多少个目录指向该 inode)
    unsigned short i_zone[9]; // 逻辑块号 (直接、一次间接、二次间接)
};

内存中的 inode - 其中前 7 项与 d_node 完全一样。

struct m_inode {
    unsigned short i_mode; // 文件类型和属性
    unsigned short i_uid; // 用户 id
    unsigned long i_size; // 文件大小
    unsigned long i_time; // 修改时间
    unsigned char i_gid; // 组 id
    unsigned char i_nlinks; // 链接计数 (多少个目录指向该 inode)
    unsigned short i_zone[9]; // 逻辑块号 (直接、一次间接、二次间接)
    
    struct task_struct * i_wait; // 等待 inode 的进程
    struct task_struct * i_wait2; // for pipes
    unsigned long i_atime; // 最后访问时间
    unsigned long i_ctime; // 修改时间
    unsigned short i_dev; // inode 所在设备号
    unsigned short i_num; // inode 号
    unsigned short i_count; // inode 被使用次数 (0 为空闲)
    unsigned char i_lock; // 锁定标志
    unsigned char i_dirt; // 修改标志
    unsigned char i_pipe; // 管道标志
    unsigned char i_mount; // 安装标志
    unsigned char i_seek; // 搜寻标志
    unsigned char i_update; // 更新标志
};

内存中的 inode 表:

struct m_inode inode_table[NR_INODE] = { { 0, }, }; // 32 项

wait_on_inode() - 等待 inode 可用

static inline void wait_on_buffer(struct m_inode * inode)
{
    cli();
    while (inode->i_lock)
        sleep_on(&inode->i_wait);
    sti();
}

lock_inode() - 对 inode 上锁

static inline void lock_inode(struct m_inode * inode)
{
    cli();
    while (inode->i_lock)
        sleep_on(&inode->i_wait);
    inode->i_lock = 1;
    sti();
}

unlock_inode() - 对 inode 解锁

static inline void unlock_inode(struct m_inode * inode)
{
    inode->i_lock = 0;
    wake_up(&inode->i_wait);
}

invalidate_inodes() - 释放设备 dev 在内存 inode 表中的所有 inode

void invalidate_inodes(int dev)
{
    int i;
    struct m_inode * inode;
    
    // 扫描内存 inode 表中每一项
    inode = 0 + inode_table;
    for (i = 0; i < NR_INODE; i++, inode++) {
        wait_on_inode(inode); // 等待 inode 解锁
        if (inode->i_dev == dev) {
            if (inode->i_count)
                printk("inode in use on removed disk\n\r");
            inode->i_dev = inode->i_dirt = 0; // 释放 inode
        }
    }
}

sync_inodes() - 同步 inode

将内存 inode 表中所有的 inode 与设备上的 inode 作同步操作。

void sync_inodes(void)
{
    int i;
    struct m_inode * inode;
    
    // 扫描内存 inode 表
    inode = 0 + inode_table;
    for (i = 0; i < NR_INODE; i++, inode++) {
        wait_on_inode(inode);
        if (inode->i_dirt && !inode->i_pipe)
            // inode 已修改,且不是管道结点
            write_inode(inode); // 写入缓冲区
    }
}

_bmap() - 文件数据块与盘块的映射

将指定的文件数据块对应到设备的逻辑块上,并返回逻辑块号:

  • 如果创建标志置位,那么对应逻辑块不存在时就申请新逻辑块
  • 返回文件数据块对应在设备上的逻辑块号
static int _bmap(struct m_inode * inode, int block, int create)
{
    struct buffer_head * bh;
    int i;
    
    // 判断参数有效性
    if (block < 0)
        // 文件数据块号小于 0
        panic("_bmap: block<0");
    if (block >= 7 + 512 + 512 * 512)
        // 文件数据块号大于文件系统表示范围
        panic("_bmap: block>big");
    
    // 直接块
    if (block < 7) {
        if (create && !inode->i_zone[block])
            // 创建标志置位
            // 数据块对应的逻辑块为空
            // 向设备申请一块磁盘块
            if (inode->i_zone[block] = new_block(inode->i_dev)) {
                inode->i_ctime = CURRENT_TIME;
                inode->i_dirt = 1; // inode 已被修改
            }
        return inode->i_zone[block];
    }
    
    // 一次间接块
    block -= 7;
    if (block < 512) {
        if (create && !inode->i_zone[7])
            // 创建标志置位
            // 一次间接块对应逻辑块为空
            if (inode->i_zone[7] = new_block(inode->i_dev)) {
                inode->i_dirt = 1;
                inode->i_ctime = CURRENT_TIME;
            }
        if (!inode->i_zone[7])
            // 申请磁盘块失败
            // 或 inode 没有一次间接块,映射失败
            return 0;
        // 将一次间接块读入缓冲区
        if (!(bh = bread(inode->i_dev, inode->i_zone[7])))
            return 0;
        // 一次间接块中,文件数据块对应的逻辑块
        i = ((unsigned short *) (bh->b_data)) [block];
        if (create && !i)
            // 创建标志置位 && 文件数据块没有对应逻辑块
            if (i = new_block(inode->i_dev)) {
                // 申请逻辑块,将逻辑块号写入缓冲区中的一次间接块中
                // 缓冲区修改标志置位
                ((unsigned short *) (bh->b_data)) [block] = i;
                bh->b_dirt = 1;
            }
        brelse(bh); // 释放缓冲区
        return i;
    }
    
    // 二次间接块
    block -= 512;
    if (create && !inode->i_zone[8])
        // 二次间接块对应逻辑块为空
        if (inode->i_zone[8] = new_block(inode->i_dev)) {
            // 申请二次间接块
            inode->i_dirt = 1;
            inode->i_ctime = CURRENT_TIME;
        }
    if (!inode->i_zone[8])
        // 申请失败 || 二次间接块本来就为空
        return 0;
    if (!(bh = bread(inode->i_dev, inode->i_zone[8])))
        // 为二次间接块申请缓冲区
        return 0;
    i = ((unsigned short *) bh->b_data) [block >> 9]; // 第 block / 512 项 (第 block/512 个一次间接块)
    if (create && !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;
    i = ((unsigned short *) bh->b_data) [block & 511]; // 逻辑块在一次间接块中的位置
    if (create && !i)
        // 逻辑块不存在,申请逻辑块
        if (i = new_block(inode->i_dev)) {
            // 一次间接块对应的缓冲区被修改
            ((unsigned short *) (bh->b_data)) [block & 511] = i;
            bh->b_dirt = 1;
        }
    brelse(bh); // 释放一次间接块对应的缓冲区
    return i;
}

bmap() - 取文件数据块在设备上的对应逻辑块号

int bmap(struct m_inode * inode, int block)
{
    return _bmap(inode, block, 0); // 逻辑块不存在时,不申请
}

create_block() - 取文件数据块在设备上对应的逻辑块号,若不存在就创建一块

int create_block(struct m_inode * inode, int block)
{
    return _bmap(inode, block, 1);
}

iput() - 放回一个 inode

将 inode 的引用计数递减。

  • 管道 inode:唤醒等待的进程
  • 块设备 inode:刷新设备
void iput(struct m_inode * inode)
{
    if (!inode)
        return;
    wait_on_inode(inode);
    if (!inode->i_count)
        panic("iput: trying to free free inode");
    if (inode->i_pipe) {
        wake_up(&inode->i_wait);
        wake_up(&inode->i_wait2);
        if (--inode->i_count)
            // inode 还有引用,则直接返回
            return;
        // inode 已无更多引用
        // 释放管道占用的内存页面
        free_page(inode->i_size);
        // inode 变为空闲
        inode->i_count = 0;
        inode->i_dirt = 0;
        inode->i_pipe = 0;
        return;
    }
    if (!inode->i_dev) {
        // 设备号为 0
        inode->i_count--;
        return;
    }
    if (S_ISBLK(inode->i_mode)) {
        // 块设备
        sync_dev(inode->i_zone[0]); // 设备号
        wait_on_inode(inode);
    }
    
repeat:
    if (inode->i_count > 1) {
        // inode 还有人再用,不能释放
        inode->i_count--;
        return;
    }
    // i_count == 1
    if (!inode->i_nlinks) {
        // inode 链接数为 0,对应的文件被删除
        // 释放该 inode 的所有的逻辑块
        truncate(inode);
        free_inode(inode);
        return;
    }
    if (inode->i_dirt) {
        write_inode(inode);
        wait_on_inode(inode);
        goto repeat; // 因为睡眠了,所以要重复判断
    }
    
    inode->i_count--; // == 0,已释放
    return;
}

get_empty_inode() - 从 inode 表中获取一个空闲 inode 项

寻找引用计数 count 为 0 的 inode,将其写盘后清零,引用计数被置 1。

struct m_inode * get_empty_inode(void)
{
    struct m_inode * inode;
    static struct m_inode * last_inode = inode_table;
    int i;
    
    do {
        inode = NULL;
        for (i = NR_INODE; i; i--) {
            if (++last_inode >= inode_table + NR_INODE)
                last_inode = inode_table; // 返回 inode 表头
            if (!last_inode->i_count) {
                inode = last_inode;
                if (!inode->i_dirt && !inode->i_lock)
                    // 可以使用该 inode
                    break;
            }
        }
        if (!inode) {
            // 没有找到空闲 inode 结点
            // 打印 inode 表供调试使用,停机
            for (i = 0; i < NR_INODE; i++)
                printk("%04x: %6d\t", inode_table[i].i_dev, inode_table[i].i_num);
            panic("No free inodes in mem");
        }
        wait_on_inode(inode); // 等待该 inode 解锁
        while (inode->i_dirt) {
            write_inode(inode); // 同步 inode
            wait_on_inode(inode); // 等待解锁
        }
    } while (inode->i_count); // 如果 inode 又被占用,则又要重新寻找
    
    memset(inode, 0, sizeof(*inode)); // inode 清零
    inode->i_count = 1;
    return inode;
}

get_pipe_inode() - 获取管道 inode

扫描 inode 表,取得一个空闲 inode。取得一页空闲内存供管道使用,将得到 inode 的引用计数置位 2 (Reader + Writer)。

struct m_inode * get_pipe_inode(void)
{
    struct m_inode * inode;
    
    if (!(inode = get_empty_inode()))
        return NULL;
    if (!(inode->i_size = get_free_page())) {
        // i_size 字段指向缓冲区
        inode->i_count = 0;
        return NULL;
    }
    inode->i_count = 2;
    PIPE_HEAD(*inode) = PIPE_TAIL(*inode) = 0; // 管道头尾指针
    inode->i_pipe = 1; // 管道标志
    return inode;
}

iget() - 取得一个 inode

从设备读取指定编号的 inode 到内存 inode 表中,返回该 inode 的指针。

struct m_inode * iget(int dev, int nr)
{
    struct m_inode * inode, * empty;
    
    if (!dev)
        panic("iget with dev==0");
    empty = get_empty_inode();
    
    // inode 可能已在 inode 表中
    inode = inode_table;
    while (inode < NR_INODE + inode_table) {
        if (inode->i_dev != dev || inode->i_num != nr) {
            inode++;
            continue;
        }
        wait_on_inode(inode); // 等待 inode 解锁
        if (inode->i_dev != dev || inode->i_num != nr) {
            inode = inode_table; // 重头扫描
            continue;
        }
        inode->i_count++;
        if (inode->i_mount) {
            int i;
            
            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");
                if (empty)
                    iput(empty);
                return inode;
            }
            iput(inode);
            dev = super_block[i].s_dev;
            nr = ROOT_INO;
            inode = inode_table;
            continue;
        }
        
        if (empty)
            iput(empty);
        return inode;
    }
    
    // 在 inode 表中没有找到指定的 inode
    // 利用之前申请的空闲 inode 在 inode 表中建立该 inode
    if (!empty)
        return NULL;
    
    inode = empty;
    inode->i_dev = dev;
    inode->i_num = nr;
    read_inode(inode);
    return inode;
}

read_inode() - 读取指定的 inode 信息

从设备上读取含有指定 inode 信息的盘块,读入缓冲区。从缓冲区复制到指定的 inode 结构中。

static void read_inode(struct m_inode * inode)
{
    struct super_block * sb;
    struct buffer_head * bh;
    int block;
    
    lock_inode(inode);
    // 取得设备 super block
    if (!(sb = get_super(inode->i_dev)))
        panic("trying to read inode without dev");
    // 取得 inode 编号对应的逻辑块号
    // 启动块 + 超级块 + inode bitmap 占用块 + 逻辑块 bitmap 占用块
    // + (inode_num - 1) / 每块含有的 inode 号
    block = 2 + sb->s_imap_blocks + sb->s_zmap_blocks + 
        (inode->i_num-1)/INODE_PER_BLOCK;
    // 取得对应缓冲区
    if (!(bh = bread(inode->i_dev, block)))
        panic("unable to read i-node block");
    // 从缓冲区指定项中复制数据
    *(struct d_inode *) inode = ((struct d_inode *) bh->b_data) [(inode->i_num-1) % INODES_PER_BLOCK];
    brelse(bh); // 释放缓冲区
    if (S_ISBLK(inode->i_mode)) {
        // 块设备文件
        int i = inode->i_zone[0]; // 设备号
        if (blk_size[MAJOR(i)])
            inode->i_size = 1024 * blk_size[MAJOR(i)][MINOR(i)];
        else
            inode->i_size = 0x7fffffff;
    }
    
    unlock_inode(inode);
}

write_inode() - 将 inode 写入缓冲区中

static void write_inode(struct m_inode * inode)
{
    struct super_block * sb;
    struct buffer_head * bh;
    int block;
    
    lock_inode(inode); // 锁定 inode
    if (!inode->i_dirt || !inode->i_dev) {
        // inode 未修改 || inode 设备号为 0
        // 不需修改,直接退出
        unlock_inode(inode);
        return;
    }
    if (!(sb = get_super(inode->i_dev)))
        panic("trying to write inode without device");
    // 计算 inode 对应的逻辑块号
    block = 2 + sb->s_imap_blocks + sb->s_zmap_blocks + (inode->i_num-1)/INODES_PER_BLOCK;
    // 将 inode 对应的逻辑块读入缓冲区
    if (!(bh = bread(inode->i_dev, block)))
        panic("unable to read i-node block");
    // 复制内存 inode 中的信息到缓冲区中
    ((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; // inode 已同步到缓冲区中
    brelse(bh); // 释放缓冲区
    unlock_inode(inode);
}

Summary

之前一直没弄清楚的是磁盘、缓冲区、内存三个位置的数据结构之间的关系,这三个地方的数据是独立的。

inode 表位于内存中,这个源代码文件中,基本上是对内存中的 inode 表进行操作;而 inode 实际上位于磁盘。在读取 inode 时,需要先将 inode 所在的磁盘逻辑块读进缓冲区,然后再从缓冲区中将数据拷贝到内存的 inode 表中。

而写入 inode 时,需要将内存 inode 表中的数据拷贝到对应的缓冲区中,再从缓冲区同步到磁盘上。对于内存中的 inode 来说,写入 inode 时,只需要同步到缓冲区中,即可认为同步完成。从缓冲区到磁盘的同步由 缓冲区管理机制 负责完成。

Edit this page on GitHub
Prev
Chapter 12.4 - truncate.c 程序
Next
Chapter 12.6 - super.c 程序