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.6 - super.c 程序

Created by : Mr Dk.

2019 / 09 / 06 14:03

Nanjing, Jiangsu, China


12.6 super.c 程序

12.6.1 功能描述

对文件系统中的超级块进行操作的函数。此外还有文件系统加载 (mount) / 卸载 (unmount) 的系统调用。

12.6.2 代码注释

Super block 的数据结构定义

磁盘上的 super block:

struct d_super_block {
    unsigned short s_ninodes; // inode 数量
    unsigned short s_nzones; // 逻辑块数
    unsigned short s_imap_blocks; // inode bitmap 所占的数据块数
    unsigned short s_zmap_blocks; // 逻辑块 bitmap 所占的数据块数
    unsigned short s_firstdatazone; // 第一个数据逻辑块
    unsigned short s_log_zone_size; // 每个逻辑块包含的数据块数
    unsigned long s_max_size; // 文件最大长度
    unsigned short s_magic; // 文件系统 magic
};

内存中的 super block:(前面的部分和磁盘中的 super block 完全相同)

struct super_block {
    unsigned short s_ninodes; // inode 数量
    unsigned short s_nzones; // 逻辑块数
    unsigned short s_imap_blocks; // inode bitmap 所占的数据块数
    unsigned short s_zmap_blocks; // 逻辑块 bitmap 所占的数据块数
    unsigned short s_firstdatazone; // 第一个数据逻辑块
    unsigned short s_log_zone_size; // 每个逻辑块包含的数据块数
    unsigned long s_max_size; // 文件最大长度
    unsigned short s_magic; // 文件系统 magic
    
    struct buffer_head * s_imap[8]; // inode bitmap 所在的缓冲块指针,8 块
    struct buffer_head * s_zmap[8]; // 逻辑块 bitmap 所在的缓冲块指针,8 块
    unsigned short s_dev; // 超级块所在的设备号
    struct m_inode * s_isup; // 被安装到的文件系统根目录的 inode
    struct m_inode * s_imount; // 被安装到的 inode
    unsigned long s_time; // 修改时间
    struct task_struct * s_wait; // 等待该 super block 的进程
    unsigned char s_lock; // 被锁定标志
    unsigned char s_rd_only; // 只读标志
    unsigned char s_dirt; // 已修改标志
};
struct super_block super_block[NR_SUPER]; // 8 项

测试指定位偏移处 bit 位的值

  • 仅测试并返回 bit 的值
  • 不对 bit 进行任何改动
#define set_bit(bitnr, addr) ({ \
register int __res __asm__("ax"); \
__asm__("bt %2,%3;setb %%al":"=a"(__res):"a"(0),"r"(bitnr),"m"(*addr)); \
__res; })

lock_super() - 锁定超级块

若超级块被锁定,则将当前任务置为不可中断的等待状态,直到超级块解锁,任务被明确唤醒。

static void lock_super(struct super_block * sb)
{
    cli();
    while (sb->s_lock)
        sleep_on(&(sb->s_wait));
    sb->s_lock = 1;
    sti();
}

free_super() - 对指定超级块解锁

static void free_super(struct super_block * sb)
{
    cli();
    sb->s_lock = 0;
    wake_up(&(sb->s_wait));
    sti();
}

wait_on_super() - 睡眠等待超级块解锁

static void wait_on_super(struct super_block * sb)
{
    cli();
    while (sb->s_lock)
        sleep_on(&(sb->s_wait));
    sti();
}

get_super() - 取指定设备的超级块

在内存的超级块表中搜索指定设备 dev 的超级块结构体:

struct super_block * get_super(int dev)
{
    struct super_block * s;
    
    if (!dev)
        return NULL;
    
    s = 0 + super_block;
    while (s < super_block + NR_SUPER)
        if (s->s_dev == dev) {
            wait_on_super(s); // 等待超级块解锁
            // 睡眠后需要重新判断
            if (s->s_dev == dev)
                return s;
            s = 0 + super_block; // 从头扫描
        } else
            s++;
    return NULL;
}

put_super() - 放回指定设备的超级块

  1. 释放设备使用的超级块数组项 - s_dev 置为 0
  2. 释放设备的 inode bitmap 和逻辑块 bitmap 占用的高速缓冲。如果超级块对应的是根文件系统,或其某个 inode 上已经安装了其它文件系统,则不能释放。
void put_super(int dev)
{
    struct super_block * sb;
    int i;
    
    if (dev == ROOT_DEV) {
        // 根文件系统设备
        printk("root diskette changed: prepare for armageddon\n\r");
        return;
    }
    if (!(sb = get_super(dev)))
        return;
    if (sb->s_imount) {
        printk("Mounted disk changed - tssk, tssk\n\r");
        return;
    }
    
    // 找到指定设备的超级块了
    lock_super(sb); // 锁定该超级块
    sb->s_dev = 0; // 释放超级块表项
    // 释放 bitmap 缓冲区
    for (i = 0; i < I_MAP_SLOTS; i++)
        brelse(sb->s_imap[i]);
    for (i = 0; i < Z_MAP_SLOTS; i++)
        brelse(sb->s_zmap[i]);
    free_super(sb); // 超级块解锁
    return;
}

read_super() - 读取指定设备的超级块

  1. 如果设备 dev 上的文件系统超级块已经在超级块表中,则直接返回超级块项的指针
  2. 否则从设备 dev 上读取超级块到缓冲区,再从缓冲区复制到超级块数组中:
static struct super_block * read_super(int dev)
{
    struct super_block * s;
    struct buffer_head * bh;
    int i, block;
    
    if (!dev)
        return NULL;
    check_disk_change(dev);
    
    if (s = get_super(dev))
        // 超级块已在超级块表中
        return s;
    // 否则在超级块表中寻找一个空闲项
    for (s = 0 + super_block; ; s++) {
        if (s >= NR_SUPER + super_block)
            return NULL; // 没找到空闲项
        if (!s->s_dev)
            break; // s_dev == 0
    }
    
    // 该超级块被用于设备 dev 上的文件系统
    s->s_dev = dev;
    s->s_isup = NULL;
    s->s_imount = NULL;
    s->s_time = 0;
    s->s_rd_only = 0;
    s->s_dirt = 0;
    
    lock_super(s); // 锁定超级块
    if (!(bh = bread(dev, 1))) {
        // 读取超级块到缓冲区中
        // 失败,则释放
        s->s_dev = 0;
        free_super(s);
        return NULL;
    }
    // 从缓冲区复制到内存中的超级块表中
    *((struct d_super_block *) s) = *((struct d_super_block *) bh->b_data);
    brelse(bh);
    
    // 检查超级块的有效性
    if (s->s_magic != SUPER_MAGIC) {
        // 不是正确的文件系统
        s->s_dev = 0;
        free_super(s);
        return NULL;
    }
    
    // 初始化内存超级块结构的 bitmap 空间
    for (i = 0; i < I_MAP_SLOTS; i++)
        s->s_imap[i] = NULL;
    for (i = 0; i < Z_MAP_SLOTS; i++)
        s->s_zmap[i] = NULL;
    // inode bitmap 保存在 2 号块开始的逻辑块中
    block = 2;
    // 读取 inode bitmap 到缓冲区中
    for (i = 0; i < s->s_imap_blocks; i++)
        if (s->s_imap[i] = bread(dev, block))
            block++;
        else
            break;
    // 读取逻辑块 bitmap 到缓冲区中
    for (i = 0; i < s->s_zmap_blocks; i++)
        if (s->s_zmap[i] = bread(dev, block))
            block++;
        else
            break;
    
    if (block != 2 + s->s_imap_blocks + s->s_zmap_blocks) {
        // 读出的 bitmap 块数不等于位图该有的逻辑块数
        // 释放所有缓冲块
        for (i = 0; i < I_MAP_SLOTS; i++)
            brelse(s->s_imap[i]);
        for (i = 0; i < Z_MAP_SLOTS; i++)
            brelse(s->s_zmap[i]);
        s->s_dev = 0; // 释放数组项
        free_super(s); // 解锁
        return NULL;
    }
    
    // 将 bitmap 的第 0 位置 1
    s->s_imap[0]->b_data[0] |= 1;
    s->s_zmap[0]->b_data[0] |= 1;
    free_super(s);
    return s;
}

sys_umount() - 系统调用 - 卸载文件系统

  1. 根据块设备文件名,找到对应的 inode,从而获得其设备号
  2. 复位文件系统超级块的相应字段,释放 bitmap 占用的缓冲块
  3. 最后执行高速缓冲与设备上的同步操作
int sys_umount(char * dev_name)
{
    struct m_inode * inode;
    struct super_block * sb;
    int dev;
    
    // 找设备对应的 inode
    if (!(inode = namei(dev_name)))
        return -ENOENT;
    dev = inode->i_zone[0]; // 设备号
    if (!(S_ISBLK(inode->i_mode))) {
        // 不是块设备
        iput(inode); // 放回 inode
        return -ENOTBLK;
    }
    
    // 已经取得设备号了,所以可以将 inode 放回
    iput(inode);
    // 检查卸载条件
    if (dev == ROOT_DEV)
        // 根文件系统不能被卸载
        return -EBUSY;
    if (!(sb = get_super(dev)) || !(sb->s_imount))
        // 超级块表中没有该超级块
        // 文件系统没有被安装
        return -ENOENT;
    if (!sb->s_imount->i_mount)
        // 安装标志未置位
        printk("Mounted inode has i_mount=0\n");
    // 扫描内存 inode 表
    for (inode = inode_table + 0; inode < inode_table + NR_INODE; inode++)
        if (inode->i_dev == dev && inode->i_count)
            // 有进程正在使用该设备上的文件
            return -EBUSY;
    
    // 卸载条件已满足
    sb->s_imount->i_mount = 0; // 被安装的 inode 复位安装标志
    iput(sb->s_imount); // 释放被安装的 inode
    sb->i_imount = NULL;
    iput(sb->s_isup); // 释放 root inode
    sb->s_isup = NULL;
    
    put_super(dev); // 释放设备上的超级块,以及 bitmap 占用的高速缓冲块
    sync_dev(dev); // 高速缓冲同步到设备
    return 0;
}

sys_mount() - 系统调用 - 挂载文件系统

int sys_mount(char * dev_name, char * dir_name, int rw_flag)
{
    struct m_inode * dev_i, * dir_i;
    struct super_block * sb;
    int dev;
    
    // 根据设备名,找到设备 inode,取得设备号
    if (!(dev_i = namei(dev_name)))
        return -ENOENT;
    dev = dev_i->i_zone[0];
    if (!S_ISBLK(dev_i->i_mode)) {
        iput(dev_i);
        return -EPERM;
    }
    
    // 已经取得设备号了,设备 inode 可以被放回了
    iput(dev_i);
    
    // 找到挂载点目录的 inode
    if (!(dir_i = namei(dir_name)))
        return -ENOENT;
    if (dir_i->i_count != 1 || dir_i->i_num == ROOT_INO) {
        // 引用计数不为 1 (应当仅在此处被引用)
        // 根文件系统也安装在这个 inode 上
        iput(dir_i);
        return -EBUSY;
    }
    if (!S_ISDIR(dir_i->i_mode)) {
        // 挂载点不是一个目录 (应当是一个目录!)
        iput(dir_i);
        return -EPERM;
    }
    
    // 设备 inode、挂载点 inode 检查完毕
    // 读取文件系统的超级块信息
    if (!(sb = read_super(dev))) {
        // 读取超级块失败
        iput(dir_i);
        return -EBUSY;
    }
    if (sb->s_imount) {
        // 文件系统已挂载到了其它地方
        iput(dir_i);
        return -EBUSY;
    }
    if (dir_i->i_mount) {
        // 将要安装到的 inode 已挂载了文件系统
        iput(dir_i);
        return -EPERM;
    }
    
    sb->s_imount = dir_i; // 文件系统挂载点指向指定目录的 inode
    dir_i->i_mount = 1; // 该目录已挂载文件系统
    dir_i->i_dirt = 1; // 挂载点目录 inode 已被修改
    return 0;
}

mount_root() - 挂载根文件系统

在系统初始化时被调用:

  1. 初始化文件表 file_table[] 和超级块表
  2. 读取根文件系统的超级块,并取得 root inode
  3. 统计并显示根文件系统上的可用资源
void mount_root(void)
{
    int i, free;
    struct super_block * p;
    struct m_inode * mi;
    
    // 检查磁盘 inode 结构体大小
    if (32 != sizeof(struct d_inode))
        panic("bad i-node size");
    // 初始化文件表
    for (i = 0; i < NR_FILE; i++)
        file_table[i].f_count = 0;
    // 软盘......
    if (MAJOR(ROOT_DEV) == 2) {
        printk("Insert root floppy and press ENTER");
        wait_for_keypress();
    }
    // 初始化超级块表
    for (p = &super_block[0]; p < &super_block[NR_SUPER]; p++) {
        p->s_dev = 0;
        p->s_lock = 0;
        p->s_wait = NULL;
    }
    
    // 挂载根文件系统
    if (!(p = read_super(ROOT_DEV)))
        // 无法读取超级块
        panic("Unable to mount root");
    if (!(mi = iget(ROOT_DEV, ROOT_INO)))
        // 无法获取 root inode
        panic("Unable to read root i-node");
    
    mi->i_count += 3; // 被引用 4 次
    p->s_isup = p->s_imount = mi; // 超级块指向 root inode
    current->pwd = mi;
    current->root = mi; // 当前进程为 init 进程
    
    // 根文件系统的资源统计
    free = 0; // 空闲逻辑块
    i = p->s_nzones;
    while (--i >= 0)
        if (!set_bit(i & 8191, p->s_zmap[i >> 13]->b_data))
            // i 在 bitmap 中的位偏移
            // i>>13 表示 i/8192,即位于哪一个 bitmap 块中
            free++;
    printk("%d/%d free blocks\n\r", free, p->s_nzones);
    
    free = 0; // 空闲 inode
    i = p->s_ninodes + 1;
    while (--i >= 0)
        if (!set_bit(i & 8191, p->s_imap[i >> 13]->b_data))
            free++;
    printk("%d/%d free inodes\n\r", free, p->s_ninodes);
}

Summary

关于文件系统的挂载:首先需要根据挂载的设备名,找到设备 inode,从而取得设备号 → 找到文件系统的超级块。然后根据挂载点的目录名,找到挂载点目录的 inode;挂载后,文件系统超级块将指向挂载点的 inode,而挂载点的 inode 中则被设定为已经挂载了文件系统。

挂载根文件系统时,根据根设备号得到根设备的超级块,并且在初始化过程中确定了 root inode。将超级块设定为指向 root inode,也就是根目录,从而完成挂载。

Edit this page on GitHub
Prev
Chapter 12.5 - inode.c 程序
Next
Chapter 12.7 - namei.c 程序