程序项目代做,有需求私信(vue、React、Java、爬虫、电路板设计、嵌入式linux等)

linux驱动移植-linux块设备驱动z2ram

----------------------------------------------------------------------------------------------------------------------------

内核版本:linux 5.2.8
根文件系统:busybox 1.25.0
u-boot:2016.05
----------------------------------------------------------------------------------------------------------------------------

  linux内核将块设备相关的驱动放在drivers/block路径下:

root@zhengyang:/work/sambashare/linux-5.2.8# ls drivers/block/
amiflop.c  built-in.a    loop.c    nbd.c             pktcdvd.c    rsxx         swim_asm.S  virtio_blk.c    zram
aoe        cryptoloop.c  loop.h    null_blk.h        ps3disk.c    skd_main.c   swim.c      xen-blkback
ataflop.c  drbd          loop.o    null_blk_main.c   ps3vram.c    skd_s1120.h  sx8.c       xen-blkfront.c
brd.c      floppy.c      Makefile  null_blk_zoned.c  rbd.c        sunvdc.c     umem.c      xsysace.c
brd.o      Kconfig       mtip32xx  paride            rbd_types.h  swim3.c      umem.h      z2ram.c

这里我们以块设备驱动z2ram为例进行讲解分析。z2ram驱动是实用RAM模拟一段块设备,也就是ramdisk。

一、Kconfig文件

1.1 AMIGA_Z2RAM

我们首先查看一下drivers/block/Kconfig文件配置,找到Z2RAM相关配置:

config AMIGA_Z2RAM
        tristate "Amiga Zorro II ramdisk support"
        depends on ZORRO
        help
          This enables support for using Chip RAM and Zorro II RAM as a
          ramdisk or as a swap partition. Say Y if you want to include this
          driver in the kernel.

          To compile this driver as a module, choose M here: the
          module will be called z2ram.

可以看到如果我们配置了AMIGA_Z2RAM,这支持使用芯片RAM和Zero II RAM作为ramdisk或者交换分区,此外AMIGA_Z2RAM还依赖于模块ZORRO。

ZORRO配置在板载配置文件arch/m68k/configs/amiga_defconfig中定义为y,而我们使用的Mini2440开发板并不支持该配置:

root@zhengyang:/work/sambashare/linux-5.2.8# grep "CONFIG_ZORRO" * -nR
arch/m68k/amiga/config.c:174:#ifdef CONFIG_ZORRO
arch/m68k/amiga/config.c:185:#endif /* CONFIG_ZORRO */
arch/m68k/amiga/config.c:844:#ifdef CONFIG_ZORRO
arch/m68k/amiga/config.c:850:#endif /* CONFIG_ZORRO */
arch/m68k/amiga/platform.c:19:#ifdef CONFIG_ZORRO
arch/m68k/amiga/platform.c:80:#else /* !CONFIG_ZORRO */
arch/m68k/amiga/platform.c:84:#endif /* !CONFIG_ZORRO */
arch/m68k/configs/amiga_defconfig:22:CONFIG_ZORRO=y
arch/m68k/configs/amiga_defconfig:24:CONFIG_ZORRO_NAMES=y
arch/m68k/configs/amiga_defconfig:398:CONFIG_ZORRO8390=y
arch/m68k/configs/multi_defconfig:32:CONFIG_ZORRO=y
arch/m68k/configs/multi_defconfig:35:CONFIG_ZORRO_NAMES=y
arch/m68k/configs/multi_defconfig:447:CONFIG_ZORRO8390=y
drivers/zorro/zorro.h:3:#ifdef CONFIG_ZORRO_NAMES
drivers/zorro/Makefile:6:obj-$(CONFIG_ZORRO)    += zorro.o zorro-driver.o zorro-sysfs.o
drivers/zorro/Makefile:8:obj-$(CONFIG_ZORRO_NAMES) +=  names.o
drivers/video/fbdev/cirrusfb.c:47:#ifdef CONFIG_ZORRO
drivers/video/fbdev/cirrusfb.c:276:#ifdef CONFIG_ZORRO
drivers/video/fbdev/cirrusfb.c:345:#endif /* CONFIG_ZORRO */
drivers/video/fbdev/cirrusfb.c:1053:#ifdef CONFIG_ZORRO
drivers/video/fbdev/cirrusfb.c:1133:#elif defined(CONFIG_ZORRO)
drivers/video/fbdev/cirrusfb.c:1134:            /* FIXME: CONFIG_PCI and CONFIG_ZORRO may be defined both */
drivers/video/fbdev/cirrusfb.c:1535:#ifdef CONFIG_ZORRO
drivers/video/fbdev/cirrusfb.c:1670:#ifdef CONFIG_ZORRO /* only works on Zorro boards */
drivers/video/fbdev/cirrusfb.c:1712:#endif /* CONFIG_ZORRO */
drivers/video/fbdev/cirrusfb.c:1943:#ifdef CONFIG_ZORRO
drivers/video/fbdev/cirrusfb.c:1956:#endif /* CONFIG_ZORRO */
drivers/video/fbdev/cirrusfb.c:2197:#ifdef CONFIG_ZORRO
drivers/video/fbdev/cirrusfb.c:2327:#endif /* CONFIG_ZORRO */
drivers/video/fbdev/cirrusfb.c:2372:#ifdef CONFIG_ZORRO
drivers/video/fbdev/cirrusfb.c:2386:#ifdef CONFIG_ZORRO
drivers/video/fbdev/cirrusfb.c:2511:#ifdef CONFIG_ZORRO
drivers/video/fbdev/cirrusfb.c:2521:#ifdef CONFIG_ZORRO
drivers/Makefile:99:obj-$(CONFIG_ZORRO)         += zorro/
drivers/net/ethernet/8390/Makefile:20:obj-$(CONFIG_ZORRO8390) += zorro8390.o

如果定义了CONFIG_ZORRO将会编译ZORRO模块,关于zooro相关文件定义:

root@zhengyang:/work/sambashare/linux-5.2.8# find -name zorro.*
./arch/m68k/include/asm/zorro.h
./include/uapi/linux/zorro.h
./include/linux/zorro.h
./drivers/zorro/zorro.h
./drivers/zorro/zorro.c
./drivers/zorro/zorro.ids
./Documentation/zorro.txt

这里我们粗略看一下drivers/zorro/zorro.c文件,咱么也不做过多的解读,因为这不是这一节的重点,了解即可。

1.2 zorro.c

/*
 *    Zorro Bus Services
 *
 *    Copyright (C) 1995-2003 Geert Uytterhoeven
 *
 *    This file is subject to the terms and conditions of the GNU General Public
 *    License.  See the file COPYING in the main directory of this archive
 *    for more details.
 */

#include <linux/module.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/zorro.h>
#include <linux/bitops.h>
#include <linux/string.h>
#include <linux/platform_device.h>
#include <linux/dma-mapping.h>
#include <linux/slab.h>

#include <asm/byteorder.h>
#include <asm/setup.h>
#include <asm/amigahw.h>

#include "zorro.h"


    /*
     *  Zorro Expansion Devices
     */

unsigned int zorro_num_autocon;
struct zorro_dev_init zorro_autocon_init[ZORRO_NUM_AUTO] __initdata;
struct zorro_dev *zorro_autocon;


    /*
     *  Zorro bus
     */

struct zorro_bus {
        struct device dev;
        struct zorro_dev devices[0];
};
    /*
     *  Find Zorro Devices
     */

struct zorro_dev *zorro_find_device(zorro_id id, struct zorro_dev *from)
{
        struct zorro_dev *z;

        if (!zorro_num_autocon)
                return NULL;

        for (z = from ? from+1 : &zorro_autocon[0];
             z < zorro_autocon+zorro_num_autocon;
             z++)
                if (id == ZORRO_WILDCARD || id == z->id)
                        return z;
        return NULL;
}
EXPORT_SYMBOL(zorro_find_device);


    /*
     *  Bitmask indicating portions of available Zorro II RAM that are unused
     *  by the system. Every bit represents a 64K chunk, for a maximum of 8MB
     *  (128 chunks, physical 0x00200000-0x009fffff).
     *
     *  If you want to use (= allocate) portions of this RAM, you should clear
     *  the corresponding bits.
     *
     *  Possible uses:
     *      - z2ram device
     *      - SCSI DMA bounce buffers
     *
     *  FIXME: use the normal resource management
     */

DECLARE_BITMAP(zorro_unused_z2ram, 128);
EXPORT_SYMBOL(zorro_unused_z2ram);
static void __init mark_region(unsigned long start, unsigned long end,
                               int flag)
{
        if (flag)
                start += Z2RAM_CHUNKMASK;
        else
                end += Z2RAM_CHUNKMASK;
        start &= ~Z2RAM_CHUNKMASK;
        end &= ~Z2RAM_CHUNKMASK;

        if (end <= Z2RAM_START || start >= Z2RAM_END)
                return;
        start = start < Z2RAM_START ? 0x00000000 : start-Z2RAM_START;
        end = end > Z2RAM_END ? Z2RAM_SIZE : end-Z2RAM_START;
        while (start < end) {
                u32 chunk = start>>Z2RAM_CHUNKSHIFT;

                if (flag)
                        set_bit(chunk, zorro_unused_z2ram);
                else
                        clear_bit(chunk, zorro_unused_z2ram);
                start += Z2RAM_CHUNKSIZE;
        }
}


static struct resource __init *zorro_find_parent_resource(
        struct platform_device *bridge, struct zorro_dev *z)
{
        int i;

        for (i = 0; i < bridge->num_resources; i++) {
                struct resource *r = &bridge->resource[i];

                if (zorro_resource_start(z) >= r->start &&
                    zorro_resource_end(z) <= r->end)
                        return r;
        }
        return &iomem_resource;
}
static int __init amiga_zorro_probe(struct platform_device *pdev)
{
        struct zorro_bus *bus;
        struct zorro_dev_init *zi;
        struct zorro_dev *z;
        struct resource *r;
        unsigned int i;
        int error;

        /* Initialize the Zorro bus */
        bus = kzalloc(struct_size(bus, devices, zorro_num_autocon),
                      GFP_KERNEL);
        if (!bus)
                return -ENOMEM;

        zorro_autocon = bus->devices;
        bus->dev.parent = &pdev->dev;
        dev_set_name(&bus->dev, zorro_bus_type.name);
        error = device_register(&bus->dev);
        if (error) {
                pr_err("Zorro: Error registering zorro_bus\n");
                put_device(&bus->dev);
                kfree(bus);
                return error;
        }
        platform_set_drvdata(pdev, bus);

        pr_info("Zorro: Probing AutoConfig expansion devices: %u device%s\n",
                 zorro_num_autocon, zorro_num_autocon == 1 ? "" : "s");

        /* First identify all devices ... */
        for (i = 0; i < zorro_num_autocon; i++) {
                zi = &zorro_autocon_init[i];
                z = &zorro_autocon[i];

                z->rom = zi->rom;
                z->id = (be16_to_cpu(z->rom.er_Manufacturer) << 16) |
                        (z->rom.er_Product << 8);
                if (z->id == ZORRO_PROD_GVP_EPC_BASE) {
                        /* GVP quirk */
                        unsigned long magic = zi->boardaddr + 0x8000;

                        z->id |= *(u16 *)ZTWO_VADDR(magic) & GVP_PRODMASK;
                }
                z->slotaddr = zi->slotaddr;
                z->slotsize = zi->slotsize;
                sprintf(z->name, "Zorro device %08x", z->id);
                zorro_name_device(z);
                z->resource.start = zi->boardaddr;
                z->resource.end = zi->boardaddr + zi->boardsize - 1;
                z->resource.name = z->name;
                r = zorro_find_parent_resource(pdev, z);
                error = request_resource(r, &z->resource);
                if (error)
                        dev_err(&bus->dev,
                                "Address space collision on device %s %pR\n",
                                z->name, &z->resource);
                z->dev.parent = &bus->dev;
                z->dev.bus = &zorro_bus_type;
                z->dev.id = i;
                switch (z->rom.er_Type & ERT_TYPEMASK) {
                case ERT_ZORROIII:
                        z->dev.coherent_dma_mask = DMA_BIT_MASK(32);
                        break;

                case ERT_ZORROII:
                default:
                        z->dev.coherent_dma_mask = DMA_BIT_MASK(24);
                        break;
                }
                z->dev.dma_mask = &z->dev.coherent_dma_mask;
        }

        /* ... then register them */
        for (i = 0; i < zorro_num_autocon; i++) {
                z = &zorro_autocon[i];
                error = device_register(&z->dev);
                if (error) {
                        dev_err(&bus->dev, "Error registering device %s\n",
                                z->name);
                        put_device(&z->dev);
                        continue;
                }
        }

        /* Mark all available Zorro II memory */
        zorro_for_each_dev(z) {
                if (z->rom.er_Type & ERTF_MEMLIST)
                        mark_region(zorro_resource_start(z),
                                    zorro_resource_end(z)+1, 1);
        }

        /* Unmark all used Zorro II memory */
        for (i = 0; i < m68k_num_memory; i++)
                if (m68k_memory[i].addr < 16*1024*1024)
                        mark_region(m68k_memory[i].addr,
                                    m68k_memory[i].addr+m68k_memory[i].size,
                                    0);

        return 0;
}

static struct platform_driver amiga_zorro_driver = {
        .driver   = {
                .name   = "amiga-zorro",
        },
};

static int __init amiga_zorro_init(void)
{
        return platform_driver_probe(&amiga_zorro_driver, amiga_zorro_probe);
}

module_init(amiga_zorro_init);
View Code

从上面代码我们不难看出这里定义了一个platform驱动,用于匹配名称为amiga-zorro的platform设备匹配。关于plaftrom设备的定义是在arch/m68k/amiga/platform.c中定义:

#ifdef CONFIG_ZORRO

static const struct resource zorro_resources[] __initconst = {
        /* Zorro II regions (on Zorro II/III) */
        {
                .name   = "Zorro II exp",
                .start  = 0x00e80000,
                .end    = 0x00efffff,
                .flags  = IORESOURCE_MEM,
        }, {
                .name   = "Zorro II mem",
                .start  = 0x00200000,
                .end    = 0x009fffff,
                .flags  = IORESOURCE_MEM,
        },
        /* Zorro III regions (on Zorro III only) */
        {
                .name   = "Zorro III exp",
                .start  = 0xff000000,
                .end    = 0xffffffff,
                .flags  = IORESOURCE_MEM,
        }, {
                .name   = "Zorro III cfg",
                .start  = 0x40000000,
                .end    = 0x7fffffff,
                .flags  = IORESOURCE_MEM,
        }
};


static int __init amiga_init_bus(void)
{
        struct platform_device *pdev;
        unsigned int n;

        if (!MACH_IS_AMIGA || !AMIGAHW_PRESENT(ZORRO))
                return -ENODEV;

        n = AMIGAHW_PRESENT(ZORRO3) ? 4 : 2;
        pdev = platform_device_register_simple("amiga-zorro", -1,
                                               zorro_resources, n);
        return PTR_ERR_OR_ZERO(pdev);
}

subsys_initcall(amiga_init_bus);


static int __init z_dev_present(zorro_id id)
{
        unsigned int i;

        for (i = 0; i < zorro_num_autocon; i++) {
                const struct ExpansionRom *rom = &zorro_autocon_init[i].rom;
                if (be16_to_cpu(rom->er_Manufacturer) == ZORRO_MANUF(id) &&
                    rom->er_Product == ZORRO_PROD(id))
                        return 1;
        }

        return 0;
}

#else /* !CONFIG_ZORRO */

static inline int z_dev_present(zorro_id id) { return 0; }

#endif /* !CONFIG_ZORRO */

当platform设备和驱动匹配时,将会执行probe函数amiga_zorro_probe,在这个函数中初始化了zorro_unused_z2ram,这后面会介绍。

下面我们进入正题,开始介绍z2ram块设备驱动。

二、全局变量

z2ram块设备驱动的实现位于drivers/block/z2ram.c,在文件的开始定义了全局变量:

#define DEVICE_NAME "Z2RAM"

#include <linux/major.h>
#include <linux/vmalloc.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/blk-mq.h>
#include <linux/bitops.h>
#include <linux/mutex.h>
#include <linux/slab.h>

#include <asm/setup.h>
#include <asm/amigahw.h>
#include <asm/pgtable.h>

#include <linux/zorro.h>


#define Z2MINOR_COMBINED      (0)
#define Z2MINOR_Z2ONLY        (1)
#define Z2MINOR_CHIPONLY      (2)
#define Z2MINOR_MEMLIST1      (4)
#define Z2MINOR_MEMLIST2      (5)
#define Z2MINOR_MEMLIST3      (6)
#define Z2MINOR_MEMLIST4      (7)
#define Z2MINOR_COUNT         (8) /* Move this down when adding a new minor */

#define Z2RAM_CHUNK1024       ( Z2RAM_CHUNKSIZE >> 10 )   // 0x0000ffff >> 10 = 64  每个切片按照1KB分割,一共有64个

static DEFINE_MUTEX(z2ram_mutex);
static u_long *z2ram_map    = NULL;   // 使用内存模拟块设备,指向一个一维数组,每个成员都是ulong类型,存放的是每一个切片的起始地址
static u_long z2ram_size    = 0;      // 模拟的块设备大小,单位字节
static int z2_count         = 0;      // 在Zorro II RAM中分配的切片个数
static int chip_count       = 0;      // 在Chip RAM中分配的切片个数
static int list_count       = 0;
static int current_device   = -1;     // 次设备号,标识当前操作的块设备

static DEFINE_SPINLOCK(z2ram_lock);

static struct gendisk *z2ram_gendisk;

static struct request_queue *z2_queue;
static struct blk_mq_tag_set tag_set;

static const struct blk_mq_ops z2_mq_ops = {
        .queue_rq       = z2_queue_rq,
};

Z2RAM_CHUNKSIZE定义为0x0000ffff,即切片大小为64kb。

Z2RAM_SIZE定位为0x00800000,即RAM模拟的块设备大小为8MB。

这里采用RAM内存模拟的块设备大小为8MB,然后把块设备按照切片大小64kb进行分割,一共有128个切片。其中z2ram_map用于保存每个切片的起始地址。

三、块设备操作集

在drivers/block/z2ram.c文件中定义了块设备的操作:

static const struct block_device_operations z2_fops =
{
        .owner          = THIS_MODULE,
        .open           = z2_open,
        .release        = z2_release,
};

即打开块设备的的时候回调z2_open函数,关闭块设备的时候回调z2_release函数。

3.1 z2_open

static int z2_open(struct block_device *bdev, fmode_t mode)
{
    int device;
    int max_z2_map = ( Z2RAM_SIZE / Z2RAM_CHUNKSIZE ) *    // 8MB / 64KB * 4 (32位机器 4字节)
        sizeof( z2ram_map[0] );
    int max_chip_map = ( amiga_chip_size / Z2RAM_CHUNKSIZE ) *  // amiga_chip_size大小是多少,我没找到我们姑且当做0吧
        sizeof( z2ram_map[0] );
    int rc = -ENOMEM;

    device = MINOR(bdev->bd_dev);  // 获取次设备号,实际为0

    mutex_lock(&z2ram_mutex);     // 互斥锁
    if ( current_device != -1 && current_device != device )
    {
        rc = -EBUSY;
        goto err_out;
    }

    if ( current_device == -1 )  // 初始化时为-1,成立
    {
        z2_count   = 0;
        chip_count = 0;
        list_count = 0;
        z2ram_size = 0;

        /* Use a specific list entry. */
        if (device >= Z2MINOR_MEMLIST1 && device <= Z2MINOR_MEMLIST4) {  // 次设备号在4~7之间,由于minors=1,所以次设备号为0,不会进入这里
                int index = device - Z2MINOR_MEMLIST1 + 1;   // 相对Z2MINOR_MEMLIST1的起始偏移
                unsigned long size, paddr, vaddr;

                if (index >= m68k_realnum_memory) {
                        printk( KERN_ERR DEVICE_NAME
                                ": no such entry in z2ram_map\n" );
                        goto err_out;
                }

                paddr = m68k_memory[index].addr;                        // 获取地址
                size = m68k_memory[index].size & ~(Z2RAM_CHUNKSIZE-1);  // 获取大小 
                vaddr = (unsigned long)z_remap_nocache_nonser(paddr, size);
                z2ram_map =
                        kmalloc_array(size / Z2RAM_CHUNKSIZE,
                                      sizeof(z2ram_map[0]),
                                      GFP_KERNEL);
                if ( z2ram_map == NULL )
                {
                    printk( KERN_ERR DEVICE_NAME
                        ": cannot get mem for z2ram_map\n" );
                    goto err_out;
                }

                while (size) {
                        z2ram_map[ z2ram_size++ ] = vaddr;
                        size -= Z2RAM_CHUNKSIZE;
                        vaddr += Z2RAM_CHUNKSIZE;
                        list_count++;
                }
                if ( z2ram_size != 0 )
                    printk( KERN_INFO DEVICE_NAME
                        ": using %iK List Entry %d Memory\n",
                        list_count * Z2RAM_CHUNK1024, index );
        } else

        switch ( device )
        {
            case Z2MINOR_COMBINED:  // 0  实际走这里

                z2ram_map = kmalloc( max_z2_map + max_chip_map, GFP_KERNEL );  // 分配一段内存,初始化z2ram_map
                if ( z2ram_map == NULL )
                {
                    printk( KERN_ERR DEVICE_NAME
                        ": cannot get mem for z2ram_map\n" );
                    goto err_out;
                }

                get_z2ram();       //  计算每个切片的起始地址
                get_chipram();    // 为每个切片分配64kb内存

                if ( z2ram_size != 0 )  // 128
                    printk( KERN_INFO DEVICE_NAME
                        ": using %iK Zorro II RAM and %iK Chip RAM (Total %dK)\n",
                        z2_count * Z2RAM_CHUNK1024,                  // 128 * 64
                        chip_count * Z2RAM_CHUNK1024,                // 0 * 64
                        ( z2_count + chip_count ) * Z2RAM_CHUNK1024 );  // 大小为多少kb

            break;

            case Z2MINOR_Z2ONLY:  // 1
                z2ram_map = kmalloc( max_z2_map, GFP_KERNEL );
                if ( z2ram_map == NULL )
                {
                    printk( KERN_ERR DEVICE_NAME
                        ": cannot get mem for z2ram_map\n" );
                    goto err_out;
                }

                get_z2ram();

                if ( z2ram_size != 0 )
                    printk( KERN_INFO DEVICE_NAME
                        ": using %iK of Zorro II RAM\n",
                        z2_count * Z2RAM_CHUNK1024 );

            break;

            case Z2MINOR_CHIPONLY:  // 2
                z2ram_map = kmalloc( max_chip_map, GFP_KERNEL );
                if ( z2ram_map == NULL )
                {
                    printk( KERN_ERR DEVICE_NAME
                        ": cannot get mem for z2ram_map\n" );
                    goto err_out;
                }

                get_chipram();

                if ( z2ram_size != 0 )
                    printk( KERN_INFO DEVICE_NAME
                        ": using %iK Chip RAM\n",
                        chip_count * Z2RAM_CHUNK1024 );

            break;

            default:
                rc = -ENODEV;
                goto err_out;

            break;
        }

        if ( z2ram_size == 0 )
        {
            printk( KERN_NOTICE DEVICE_NAME
                ": no unused ZII/Chip RAM found\n" );
            goto err_out_kfree;
        }

        current_device = device;             // 设置当前选择的块设备 次设备号
        z2ram_size <<= Z2RAM_CHUNKSHIFT;     // z2ram << 16为,ram空间大小,单位字节
        set_capacity(z2ram_gendisk, z2ram_size >> 9);  // 设置块设备容量,单位为扇区个数
    }

    mutex_unlock(&z2ram_mutex); // 解锁
    return 0;

err_out_kfree:
    kfree(z2ram_map);
err_out:
    mutex_unlock(&z2ram_mutex);
    return rc;
}

m68k_memory定义在arch/m68k/kernel/setup_mm.c文件中:

struct m68k_mem_info m68k_memory[NUM_MEMINFO];  // NUM_MEMINFO=4

m68k_mem_info定义在arch/m68k/include/asm/setup.h:

struct m68k_mem_info {
        unsigned long addr;             /* physical address of memory chunk */
        unsigned long size;             /* length of memory chunk (in bytes) */
};

m68k_realnum_memory定义在arch/m68k/kernel/setup_mm.c文件中:

int m68k_realnum_memory;
3.1.1 get_z2ram

get_z2ram用于计算每个切片的起始地址,并保存在z2ram_map中:

static void
get_z2ram( void )
{
    int i;

    for ( i = 0; i < Z2RAM_SIZE / Z2RAM_CHUNKSIZE; i++ )  //  0x00800000 / 0x0000ffff = 128
    {
        if ( test_bit( i, zorro_unused_z2ram ) )   //DECLARE_BITMAP(zorro_unused_z2ram,128),定义一个无符号长整型数组, 
//并且在mark_region函数中set_bit每一位为1,名字为zorro_unused_z2ram, test_bit判断第i位是否为1
{ z2_count
++; // 切片个数+1 z2ram_map[z2ram_size++] = (unsigned long)ZTWO_VADDR(Z2RAM_START) + // 0x002000000 + 0x80000000 + i << 16(1个切片大小) (i << Z2RAM_CHUNKSHIFT); clear_bit( i, zorro_unused_z2ram ); } } return; }

其中Z2RAM_SIZE定义如下:

include/linux/zorro.h:143:#define Z2RAM_START           (0x00200000)

其中ZTWO_VADDR宏定义如下:

arch/m68k/include/asm/amigahw.h:245:#define ZTWO_VADDR(x) ((void __iomem *)(((unsigned long)(x))+zTwoBase))   // zTwoBase = 0x80000000

在内存中开辟一段RAM空间来模拟磁盘,基地址为0x80000000,Z2RAM起始地址为0x00200000:

3.1.2 get_chipram

和get_z2ram类似,这里我们姑且把amiga_chip_avail返回大小当做为0;

static void
get_chipram( void )
{

    while ( amiga_chip_avail() > ( Z2RAM_CHUNKSIZE * 4 ) )   // 64kb * 4
    {
        chip_count++;  // 切片个数+1
        z2ram_map[ z2ram_size ] =
            (u_long)amiga_chip_alloc( Z2RAM_CHUNKSIZE, "z2ram" );

        if ( z2ram_map[ z2ram_size ] == 0 )
        {
            break;
        }

        z2ram_size++;
    }

    return;
}

3.2 z2_release

static void
z2_release(struct gendisk *disk, fmode_t mode)
{
    mutex_lock(&z2ram_mutex);
    if ( current_device == -1 ) {
        mutex_unlock(&z2ram_mutex);
        return;
    }
    mutex_unlock(&z2ram_mutex);
    /*
     * FIXME: unmap memory
     */
}

四、模块入口函数

module_init(z2_init);

我们定位到模块入口函数z2_init:

static int __init
z2_init(void)
{
    int ret;

    if (!MACH_IS_AMIGA)
        return -ENODEV;

    ret = -EBUSY;
    if (register_blkdev(Z2RAM_MAJOR, DEVICE_NAME))  
        goto err;

    ret = -ENOMEM;
    z2ram_gendisk = alloc_disk(1);
    if (!z2ram_gendisk)
        goto out_disk;

    z2_queue = blk_mq_init_sq_queue(&tag_set, &z2_mq_ops, 16,
                                        BLK_MQ_F_SHOULD_MERGE);
    if (IS_ERR(z2_queue)) {
        ret = PTR_ERR(z2_queue);
        z2_queue = NULL;
        goto out_queue;
    }

    z2ram_gendisk->major = Z2RAM_MAJOR;
    z2ram_gendisk->first_minor = 0;
    z2ram_gendisk->fops = &z2_fops;
    sprintf(z2ram_gendisk->disk_name, "z2ram");

    z2ram_gendisk->queue = z2_queue;
    add_disk(z2ram_gendisk);
    blk_register_region(MKDEV(Z2RAM_MAJOR, 0), Z2MINOR_COUNT, THIS_MODULE,
                                z2_find, NULL, NULL);

    return 0;

out_queue:
    put_disk(z2ram_gendisk);
out_disk:
    unregister_blkdev(Z2RAM_MAJOR, DEVICE_NAME);
err:
    return ret;
}

在模块的入口函数中,主要执行了如下操作:

  • 调用register_blkdev注册块设备主设备号,这里主设备号定义为Z2RAM_MAJOR=37,块设备名称为DEVICE_NAME="Z2RAM";
  • 使用alloc_disk申请一个通用磁盘对象gendisk,参数为1,表示磁盘不分区;
  • 使用blk_mq_init_sq_queue初始化一个请求队列
  • 设置gendisk结构体的成员:
    • 设置成员参数major、first_minor、disk_name、fops;
    • 设置请求队列queue,等于之前初始化的请求队列;
  • 使用add_disk注册gendisk;
  • 使用blk_register_region建立磁盘设备号和gendisk的映射关系(这一步是不是多余的);

4.1 z2_find

static struct kobject *z2_find(dev_t dev, int *part, void *data)
{
        *part = 0;
        return get_disk_and_module(z2ram_gendisk);
}

4.2 z2_mq_ops

z2_mq_ops为块设备驱动mq的操作集合,z2_mq_ops定义为:

static const struct blk_mq_ops z2_mq_ops = {
        .queue_rq       = z2_queue_rq,
};

在上一节分析我们已经知道将request请求派发给块设备驱动的时候会被调用queue_rq函数,该函数本质上就是进行磁盘和内存之间的数据交互操作。比如将内存数据写入磁盘、或者从磁盘读取数据到内存等。

z2_queue_rq函数定义为:

static blk_status_t z2_queue_rq(struct blk_mq_hw_ctx *hctx,
                                const struct blk_mq_queue_data *bd)
{
        struct request *req = bd->rq;                 // 获取当前request
        unsigned long start = blk_rq_pos(req) << 9;   // rq->__sector << 9,blk_rq_pos获取到的是块设备的起始扇区地址,左移9位转换为字节地址
        unsigned long len  = blk_rq_cur_bytes(req);   // 当前bio段需要传输的字节大小 

        blk_mq_start_request(req);

        if (start + len > z2ram_size) {
                pr_err(DEVICE_NAME ": bad access: block=%llu, "
                       "count=%u\n",
                       (unsigned long long)blk_rq_pos(req),
                       blk_rq_cur_sectors(req));
                return BLK_STS_IOERR;
        }

        spin_lock_irq(&z2ram_lock);

        while (len) {   
                unsigned long addr = start & Z2RAM_CHUNKMASK;       // 获取块设备当前切片起始地址   
//(z2ram模拟的块设备每一个切片大小为64kb,Z2RAM_CHUNKMASK=0x0000FFFF)
unsigned
long size = Z2RAM_CHUNKSIZE - addr; // 当前切片剩余大小
                void *buffer = bio_data(req->bio);   // 获取要读写的数据在内存中的地址

                if (len < size)                      // 设置需要写入的字节大小
                        size = len;         
                addr += z2ram_map[ start >> Z2RAM_CHUNKSHIFT ];    // 获取第 start >> 16个 块设备切片的 地址
                if (rq_data_dir(req) == READ)                      // 读操作,addr -> buffer,从块设备读数据存到内存,每次传输size大小字节
                        memcpy(buffer, (char *)addr, size);
                else                                        // 写操作,buffer -> addr,将内存数据写入开设备,每次传输size大小字节
                        memcpy((char *)addr, buffer, size);
                start += size;    // 下一次传输的起始地址 
                len -= size;      // 剩余需要传输的字节
        }

        spin_unlock_irq(&z2ram_lock);
        blk_mq_end_request(req, BLK_STS_OK);
        return BLK_STS_OK;
}
4.2.1 bio_data
/*
 * Check whether this bio carries any data or not. A NULL bio is allowed.
 */
static inline bool bio_has_data(struct bio *bio)
{
        if (bio &&
            bio->bi_iter.bi_size &&
            bio_op(bio) != REQ_OP_DISCARD &&
            bio_op(bio) != REQ_OP_SECURE_ERASE &&
            bio_op(bio) != REQ_OP_WRITE_ZEROES)
                return true;

        return false;
}


static inline unsigned int bio_cur_bytes(struct bio *bio)
{
        if (bio_has_data(bio))
                return bio_iovec(bio).bv_len;   //  宏展开后等价bio->bi_io_vec[bio->bi_iter.bi_idx].lv_len,取自当前bio段
        else /* dataless requests such as discard */
                return bio->bi_iter.bi_size;     // 块设备读/写的字节大小
}

static inline void *bio_data(struct bio *bio)
{
        if (bio_has_data(bio)) return page_address(bio_page(bio)) + bio_offset(bio);  // 获取要读写的数据在内存中的起始地址

        return NULL;
}

五、模块出口函数

module_exit(z2_exit);

我们定位到模块入口函数z2_exit:

static void __exit z2_exit(void)
{
    int i, j;
    blk_unregister_region(MKDEV(Z2RAM_MAJOR, 0), Z2MINOR_COUNT);
    unregister_blkdev(Z2RAM_MAJOR, DEVICE_NAME);
    del_gendisk(z2ram_gendisk);
    put_disk(z2ram_gendisk);
    blk_cleanup_queue(z2_queue);
    blk_mq_free_tag_set(&tag_set);

    if ( current_device != -1 )
    {
        i = 0;

        for ( j = 0 ; j < z2_count; j++ )
        {
            set_bit( i++, zorro_unused_z2ram );
        }

        for ( j = 0 ; j < chip_count; j++ )
        {
            if ( z2ram_map[ i ] )
            {
                amiga_chip_free( (void *) z2ram_map[ i++ ] );
            }
        }

        if ( z2ram_map != NULL )
        {
            kfree( z2ram_map );
        }
    }

    return;
}

在模块的出口函数中,主要执行了如下操作:

  • 调用blk_unregister_region取消磁盘设备号和gendisk的映射关系(这一步是不是多余的);
  • 调用unregister_blkdev取消块设备主设备号注册;
  • 调用del__gendisk释放磁盘gendisk;
  • 调用put_disk注销磁盘gendisk;
  • 调用blk_cleanup_queue清除内核中的请求队列request_queue;
  • 调用blk_mq_free_tag_set释放 blk_mq_alloc_tag_set申请的标签集tag_set;

六、编写RAM驱动

我们参考z2ram.c,通过内存来模拟块设备驱动。我们在/work/sambashare/drivers路径下创建项目16.ramdisk_dev, 然后将drivers/block/z2ram.c文件拷贝到项目路径下,命名为ramdisk_dev.c

6.1 ramdisk_dev.c

#include <linux/major.h>
#include <linux/vmalloc.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/blk-mq.h>
#include <linux/mutex.h>
#include <linux/hdreg.h>


// 定义磁盘设备大小 
#define RAMDISK_SIZE  (2*1024*1024)
// 每个切片位偏移  4kb
#define RAMDISK_CHUNKSHIFT 12 
// 磁盘分片,每片大小1kb
#define RAMDISK_CHUNKSIZE (1<<RAMDISK_CHUNKSHIFT)
// 磁盘切片mask
#define RAMDISK_CHUNKMASK (RAMDISK_CHUNKSIZE-1) 
// 块设备名称
#define DEVICE_NAME  "ramdisk"
// 主设备号
#define DEVICE_MAJOR 37



// 定义互斥锁
static DEFINE_MUTEX(ramdisk_mutex);
// 指针数组,每一个元素都指向一个磁盘切片
static u_long ** ramdisk_map;
// 定义自旋锁
static DEFINE_SPINLOCK(ramdisk_lock);
// 通用磁盘
static struct gendisk *ram_gendisk;

static int current_device   = -1;     // 次设备号,用于标识当前被占用的块设备分区,同一时间是可以打开一个block_device(一个磁盘可以包含多个分区,即多个block_device)

/*
 * 对块设备进行读写操作
 */
static blk_status_t blk_mq_queue_rq(struct blk_mq_hw_ctx *hctx,const struct blk_mq_queue_data *bd)
{
    struct request *req = bd->rq;                   // 获取当前request
    unsigned long start = blk_rq_pos(req) << 9;     // 起始扇区地址,转为字节地址
    unsigned long len  = blk_rq_cur_bytes(req);     // 当前bio段需要传输的字节大小,如果一个请求包含多个bio段,这里只处理了第request->bio->bi_iter->bi_idx个bio段
    blk_mq_start_request(req);

    if (start + len > RAMDISK_SIZE) 
    {
        pr_err(DEVICE_NAME ": bad access: block=%llu, "
               "count=%u\n",
               (unsigned long long)blk_rq_pos(req),   // 扇区地址,转位字节地址
               blk_rq_cur_sectors(req));              // 当前扇区 
        return BLK_STS_IOERR;
    }

    spin_lock_irq(&ramdisk_lock);

    while (len) 
    {        

        unsigned char *addr;                              // 获取块设备当前切片起始地址 
        unsigned long size = RAMDISK_CHUNKSIZE - (start & RAMDISK_CHUNKMASK);    // 当前切片剩余大小
        void *buffer = bio_data(req->bio);                // 获取要读写的数据在内存中的地址

        if (len < size)                                   // 设置当前切片可以写入的字节大小
            size = len;
        addr = (char *)ramdisk_map[ start >> RAMDISK_CHUNKSHIFT ] + (start & RAMDISK_CHUNKMASK);  //  获取第start>>16个块设备切片的地址+读取偏移
        if (rq_data_dir(req) == READ)                     // 读操作,addr -> buffer,从块设备读数据存到内存,每次传输size大小字节
        {
            printk("read from disk address %lu,length %lu,bio segment count %d;disk mapping ram %px\n",
                start,
                len,
                req->bio->bi_vcnt,
                addr);
            memcpy(buffer, addr , size);     
        }else{                                               // 写操作,buffer -> addr,将内存数据写入块设备,每次传输size大小字节
            printk("write to disk address %lu,length %lu,bio segment count %d;disk mapping ram %px\n",
                start,
                len,
                req->bio->bi_vcnt,
                addr);
            memcpy(addr, buffer, size);
        }
        start += size;    // 下一次传输的起始地址
        len -= size;      // 剩余需要传输的字节
    }

    spin_unlock_irq(&ramdisk_lock);
    blk_mq_end_request(req, BLK_STS_OK);
    return BLK_STS_OK;
}


/*
 * 打开一个 block_device设备时调用
 */
static int block_device_open(struct block_device *bdev, fmode_t mode)
{
   int major,minor;
   int rc = 0;

   // 获取次设备号,实际为0
   minor = MINOR(bdev->bd_dev);
   // 获取主设备号
   major = MAJOR(bdev->bd_dev);  
    
   printk("device major %d, minor %d\n",major,minor);    
   
   mutex_lock(&ramdisk_mutex);     // 互斥锁
   // 当前块设备已经被某个应用占用
   if ( current_device != -1 && current_device != minor )
   {
      rc = -EBUSY;       
      printk("open block device fail\n");
   }else{
      // 设置当前选择的块设备 次设备号   
      current_device = minor;
      printk("open block device success\n");
   }
   
   mutex_unlock(&ramdisk_mutex); // 解锁 
   return rc;
}

/*
 * 关闭一个block_device设备时调用
 */
static void block_device_release(struct gendisk *disk, fmode_t mode)
{    
    printk("release block device success\n");
}

/*
 * 获取驱动器的集合信息,获取到的信息会被填充在一个hd_geometry结构中
 */
static int ramdisk_getgeo(struct block_device *bdev, struct hd_geometry *geo)
{   
    geo->heads =2;                                        // 2个磁头
    geo->cylinders = 32;                                  // 32个磁道(柱面),具有相同编号的磁号形成一个圆柱,称之为磁盘的柱面
    geo->sectors = RAMDISK_SIZE/(2*32*SECTOR_SIZE);       //一个磁道有多少个扇区    
    return 0;
}

/*
 * 块设备驱动操作参数
 */
static struct block_device_operations block_device_fops =
{
    .owner        = THIS_MODULE,
    .open        = block_device_open,
    .release    = block_device_release,
    .getgeo     = ramdisk_getgeo,                //几何,保存磁盘的信息(柱头,柱面,扇区)
};

static struct request_queue *ramdisk_queue;
static struct blk_mq_tag_set tag_set;

/*
 * 块设备驱动mq操作函数
 */
static struct blk_mq_ops ramdisk_mq_ops = 
{
    .queue_rq    = blk_mq_queue_rq,
};


static void get_ramdisk( void )
{
    int i;

    for ( i = 0; i < RAMDISK_SIZE / RAMDISK_CHUNKSIZE; i++ )
    {
        // 初始化第i个元素 -> 指向一个磁盘切片 
        ramdisk_map[i] = kmalloc(RAMDISK_CHUNKSIZE,GFP_KERNEL);
        if ( ramdisk_map[i] == NULL )
        {
            printk( KERN_ERR DEVICE_NAME": cannot get mem for ramdisk_map\n" );                
        }

    }

    return;
}

/*
 * RAM模拟磁盘
 */
static int ramdisk_alloc(void)
{
    int max_ramdisk_map = ( RAMDISK_SIZE / RAMDISK_CHUNKSIZE ) *  sizeof(u_long *);
    printk("every chunk size %d,chunk count %d", RAMDISK_CHUNKSIZE, max_ramdisk_map/sizeof( u_long *));
    int rc = -ENOMEM;

    ramdisk_map = kmalloc(max_ramdisk_map, GFP_KERNEL);
    if ( ramdisk_map == NULL )
    {
        printk( KERN_ERR DEVICE_NAME": cannot get mem for ramdisk_map\n" );
        goto err_out;
    }

    // 动态申请内存
    get_ramdisk();
    
    set_capacity(ram_gendisk, RAMDISK_SIZE >> 9);   // 设置块设备容量,单位为扇区个数

    return 0;

err_out:
    return rc;
}
 

/*
 * 块设备驱动入口函数
 */
static int ramdisk_init(void)
{
    int ret;

    ret = -EBUSY;
    // 注册块设备主设备号
    if (register_blkdev(DEVICE_MAJOR, DEVICE_NAME))
        goto err;

    ret = -ENOMEM;
    // 申请一个通用磁盘对象gendisk,参数为1,表示磁盘不分区,0号分区表示的是整个磁盘
    ram_gendisk = alloc_disk(1);
    if (!ram_gendisk)
        goto out_disk;

    // 初始化一个请求队列
    ramdisk_queue = blk_mq_init_sq_queue(&tag_set, &ramdisk_mq_ops, 16,BLK_MQ_F_SHOULD_MERGE);
    if (IS_ERR(ramdisk_queue)) 
    {
        ret = PTR_ERR(ramdisk_queue);
        ramdisk_queue = NULL;
        goto out_queue;
    }

    ram_gendisk->major = DEVICE_MAJOR;
    ram_gendisk->first_minor = 0;
    ram_gendisk->fops = &block_device_fops;
    sprintf(ram_gendisk->disk_name, DEVICE_NAME);

    ram_gendisk->queue = ramdisk_queue;
    
    // 注册gendisk
    add_disk(ram_gendisk);
    
    // 申请内存作为磁盘
    ret = ramdisk_alloc();
    if(IS_ERR(ret))
    {
        goto out_ram;
    }

    return 0;
out_ram:
    // 释放磁盘gendisk
    del_gendisk(ram_gendisk);
    // 清除内核中的请求队列request_queue
    blk_cleanup_queue(ramdisk_queue);
    // 释放 blk_mq_alloc_tag_set申请的标签集tag_set
    blk_mq_free_tag_set(&tag_set);
out_queue:
    // 注销磁盘gendisk
    put_disk(ram_gendisk);
out_disk:
    // 取消块设备主设备号注册
    unregister_blkdev(DEVICE_MAJOR, DEVICE_NAME);
err:
    return ret;
}

/*
 * 块设备驱动出口函数
 */
static void ramdisk_exit(void)
{
    int i;
    // 取消块设备主设备号注册
    unregister_blkdev(DEVICE_MAJOR, DEVICE_NAME);

    // 释放磁盘gendisk
    del_gendisk(ram_gendisk);
    // 注销磁盘gendisk
    put_disk(ram_gendisk);
    // 清除内核中的请求队列request_queue
    blk_cleanup_queue(ramdisk_queue);

    // 释放 blk_mq_alloc_tag_set申请的标签集tag_set
    blk_mq_free_tag_set(&tag_set);
    
    for ( i = 0; i < RAMDISK_SIZE / RAMDISK_CHUNKSIZE; i++ )
    {
       kfree(ramdisk_map[i]);
    }
    kfree(ramdisk_map);
    return;
} 

module_init(ramdisk_init);
module_exit(ramdisk_exit);
View Code

6.2 Makefile

KERN_DIR :=/work/sambashare/linux-5.2.8
all:
        make -C $(KERN_DIR) M=`pwd` modules
clean:
        make -C $(KERN_DIR) M=`pwd` modules clean
        rm -rf modules.order

obj-m += ramdisk_dev.o

七、测试运行

7.1 编译块设备驱动

在16.ramdisk_dev路径下编译:

root@zhengyang:/work/sambashare/drivers/16.ramdisk_dev# cd /work/sambashare/drivers/16.ramdisk_dev/
root@zhengyang:/work/sambashare/drivers/16.ramdisk_dev# make

拷贝驱动文件到nfs文件系统:

root@zhengyang:/work/sambashare/drivers/16.ramdisk_dev# cp /work/sambashare/drivers/16.ramdisk_dev/ramdisk_dev.ko /work/nfs_root/rootfs/

7.2 安装驱动

重启开发板,加载驱动,挂载块设备到/tmp目录,执行如下命令:

insmod ramdisk_dev.ko            //加载ramdisk块设备
mkdosfs /dev/ramdisk             //将ramdisk块设备格式化为dos磁盘类型
mount /dev/ramdisk /tmp/         //挂载块设备到/tmp目录下

执行完输出如下,从执行后的输出信息我们实际上可以看到mkdosfs命令会向磁盘的前几个扇区写入4096个字节的数据,不难猜测,磁盘的前几个扇区保存的就是磁盘分区等信息。

[root@zy:/]# insmod ramdisk_dev.ko
ramdisk_dev: loading out-of-tree module taints kernel.
ramdisk_dev: module license 'unspecified' taints kernel.
Disabling lock debugging due to kernel taint
[root@zy:/]# mkdosfs /dev/ramdisk   // 将ramdisk块设备格式化为dos磁盘类型
every chunk size 4096,chunk count 512
device major 37, minor 0
open block device success
read from disk address 0,length 4096,bio segment count 1;disk mapping ram c30b1000
read from disk address 4096,length 4096,bio segment count 1;disk mapping ram c30b0000
read from disk address 8192,length 4096,bio segment count 1;disk mapping ram c30ef000
read from disk address 12288,length 4096,bio segment count 1;disk mapping ram c30ee000
read from disk address 16384,length 4096,bio segment count 1;disk mapping ram c30ed000
read from disk address 20480,length 4096,bio segment count 1;disk mapping ram c30ec000
read from disk address 24576,length 4096,bio segment count 1;disk mapping ram c30eb000
read from disk address 28672,length 4096,bio segment count 1;disk mapping ram c30ea000
read from disk address 32768,length 4096,bio segment count 1;disk mapping ram c30fd000
write to disk address 0,length 4096,bio segment count 1;disk mapping ram c30b1000  // 看这里
release block device success
[root@zy:/]# mount /dev/ramdisk /tmp/       // 挂载
device major 37, minor 0
open block device success
read from disk address 1024,length 1024,bio segment count 1;disk mapping ram c30b1400
release block device success
device major 37, minor 0
open block device success
read from disk address 1024,length 1024,bio segment count 1;disk mapping ram c30b1400
release block device success
device major 37, minor 0
open block device success
read from disk address 1024,length 1024,bio segment count 1;disk mapping ram c30b1400
release block device success
device major 37, minor 0
open block device success
read from disk address 0,length 4096,bio segment count 1;disk mapping ram c30b1000
read from disk address 4096,length 4096,bio segment count 1;disk mapping ram c30b0000
read from disk address 8192,length 4096,bio segment count 1;disk mapping ram c30ef000
read from disk address 12288,length 4096,bio segment count 1;disk mapping ram c30ee000
release block device success
device major 37, minor 0
open block device success
read from disk address 0,length 512,bio segment count 1;disk mapping ram c30b1000
read from disk address 512,length 512,bio segment count 1;disk mapping ram c30b1200
read from disk address 3072,length 512,bio segment count 1;disk mapping ram c30b1c00
read from disk address 35840,length 512,bio segment count 1;disk mapping ram c30fdc00
write to disk address 0,length 512,bio segment count 1;disk mapping ram c30b1000 // 挂载的时候也会执行写操作,第一个扇区写入

查看设备节点文件:

[root@zy:/]# ls /dev/ramdisk -l
total 0
brw-rw----    1 0        0          37,  0 Jan  1 00:00 1 /dev/ramdisk

7.3 测试运行

接下来向/tmp目录下1.txt文件中写入数据,最终都会保存在/dev/ramdisk块设备里面。

echo "hello.world" > /tmp/1.txt   //创建1.txt文件,并写入hello,world
cat /tmp/1.txt             //查看文件内容
ls -l /tmp/1.txt           //查看文件

执行完以上命令输出如下:

[root@zy:/]# echo "hello,world" > /tmp/1.txt
write to disk address 3072,length 512,bio segment count 1;disk mapping ram c30b1c00
write to disk address 19456,length 512,bio segment count 1;disk mapping ram c30edc00
write to disk address 36352,length 512,bio segment count 1;disk mapping ram c30fde00write to disk address 512,length 512,bio segment count 1;disk mapping ram c30b1200
write to disk address 36352,length 512,bio segment count 1;disk mapping ram c30fde00
[root@zy:/]# cat /tmp/1.txt
hello,world
[root@zy:/]# ls -l /tmp/1.txt-rwxr-xr-x    1 0        0               12 Jan  1 00:06 /tmp/1.txt

执行卸载块设备命令,可以看到1.txt文件已经不存在了:

[root@zy:/]# umount /tmp/
device major 37, minor 0
open block device success
release block device success
[root@zy:/]# cat /tmp/1.txt
cat: can't open '/tmp/1.txt': No such file or directory

在/mnt目录下创建.bin文件,可以将块设备里面的内容写入到ramdisk.bin文件里面:

[root@zy:/]# cat /dev/ramdisk > /mnt/ramdisk.bin
device major 37, minor 0
open block device success
read from disk address 0,length 4096,bio segment count 0;disk mapping ram c30b1000
read from disk address 130560,length 512,bio segment count 19;disk mapping ram c3104e00
read from disk address 131072,length 4096,bio segment count 0;disk mapping ram c3105000
read from disk address 261632,length 512,bio segment count 4;disk mapping ram c3124e00
read from disk address 262144,length 4096,bio segment count 0;disk mapping ram c3125000
read from disk address 392704,length 512,bio segment count 1;disk mapping ram c3144e00
read from disk address 393216,length 4096,bio segment count 0;disk mapping ram c3145000
read from disk address 523776,length 512,bio segment count 1;disk mapping ram c3164e00
read from disk address 524288,length 4096,bio segment count 0;disk mapping ram c3165000
read from disk address 654848,length 512,bio segment count 1;disk mapping ram c3184e00
read from disk address 655360,length 4096,bio segment count 0;disk mapping ram c3185000
read from disk address 785920,length 512,bio segment count 1;disk mapping ram c31a4e00
read from disk address 786432,length 4096,bio segment count 0;disk mapping ram c31a5000
read from disk address 916992,length 512,bio segment count 1;disk mapping ram c31c4e00
read from disk address 917504,length 4096,bio segment count 0;disk mapping ram c31c5000
read from disk address 1048064,length 512,bio segment count 1;disk mapping ram c31e4e00
read from disk address 1048576,length 4096,bio segment count 0;disk mapping ram c31e5000
read from disk address 1179136,length 512,bio segment count 1;disk mapping ram c3204e00
read from disk address 1179648,length 4096,bio segment count 0;disk mapping ram c3205000
read from disk address 1310208,length 512,bio segment count 2;disk mapping ram c3224e00
read from disk address 1310720,length 4096,bio segment count 0;disk mapping ram c3225000
read from disk address 1441280,length 512,bio segment count 1;disk mapping ram c3244e00
read from disk address 1441792,length 4096,bio segment count 0;disk mapping ram c3245000
read from disk address 1572352,length 512,bio segment count 1;disk mapping ram c3264e00
read from disk address 1572864,length 4096,bio segment count 0;disk mapping ram c3265000
read from disk address 1703424,length 512,bio segment count 1;disk mapping ram c3284e00
read from disk address 1703936,length 4096,bio segment count 0;disk mapping ram c3285000
read from disk address 1834496,length 512,bio segment count 1;disk mapping ram c32a4e00
read from disk address 1835008,length 4096,bio segment count 0;disk mapping ram c32a5000
read from disk address 1965568,length 512,bio segment count 1;disk mapping ram c32c4e00
read from disk address 1966080,length 4096,bio segment count 0;disk mapping ram c32c5000
read from disk address 2096640,length 512,bio segment count 1;disk mapping ram c32e4e00
release block device success

八、使用fdisk对磁盘分区

8.1 fdisk命令

fdisk [块设备磁盘]:将一个块设备(磁盘)分成若干个块设备,并将分区的信息写进分区表。fdisk命令常用参数如下:

d 删除一个分区
n 新建一个分区
p 打印分区表
q 放弃不保存
t 改变分区类型
w 把分区写入分区表,保存并退出
l 列出已挂在的磁盘

8.2 实验

输入如下命令对ramdisk块设备进行分区:

fdisk /dev/ramdisk

(1) 输入n,  出现两个菜单e表示扩展分区,p表示主分区:

(2) 输入p,进入主分区,再输入1,表示第一个主分区:

为什么柱面数只有1~32?因为在程序中我们设置了该块设备的磁盘信息:

/*
 * 获取驱动器的集合信息,获取到的信息会被填充在一个hd_geometry结构中
 */
static int ramdisk_getgeo(struct block_device *bdev, struct hd_geometry *geo)
{   
    geo->heads =2;                                        // 2个磁头
    geo->cylinders = 32;                                  // 32个磁道(柱面),具有相同编号的磁号形成一个圆柱,称之为磁盘的柱面
    geo->sectors = RAMDISK_SIZE/(2*32*SECTOR_SIZE);       //一个磁道有多少个扇区    
    return 0;
}

因为geo->heads=2,所以最大只能创建2个分区,如果我们输入3,创建第3个主分区将会失败。

(3) 然后输入1,表示开始柱面 ,再输入5,表示结束柱面:

(4) 再次输入n,p,2,创建第2个分区,可以发现起始柱面就是从6开始的,因为1~5柱面被第一个分区占用了:

(5)第2个分区创建好了,输入p,打印分区表:

(6) 输入w,保存并退出。

 

发现出错,出现分区无法写入分区表。

这是因为我们在块设备驱动程序入口函数中,alloc_disk()分配一个gendisk,设置的只有1个分区:

    // 申请一个通用磁盘对象gendisk,参数为1,表示磁盘不分区,0号分区表示的是整个磁盘
    ram_gendisk = alloc_disk(1);

修改参数改为大于2的值即可,然后重新执行就没有问题了。

(7) ls /dev/ramdisk*  -l,就能看到分到的分区了,

[root@zy:/]# ls /dev/ramdisk* -l
brw-rw----    1 0        0          37,   0 Jan  1 00:03 /dev/ramdisk    // 表示整个磁盘  次设备号为0
brw-rw----    1 0        0          37,   1 Jan  1 00:03 /dev/ramdisk1   // 磁盘1号分区   次设备号为1
brw-rw----    1 0        0          37,   2 Jan  1 00:03 /dev/ramdisk2   // 磁盘2号分区   次设备号为2

8.3 fdisk -l

通过 fdisk -l 查看磁盘分区属性:

[root@zy:/]# fdisk -l
device major 37, minor 0
open block device success
release block device success
device major 37, minor 0
open block device success  
read from disk address 0,length 4096,bio segment count 4;disk mapping ram c30f4000  // 读取磁盘的前4096个字节,即前8个扇区
release block device success
device major 37, minor 0
open block device success
release block device success
device major 37, minor 0
open block device success
release block device success

Disk /dev/ramdisk: 2 MB, 2097152 bytes   // 磁盘大小2MB
2 heads, 64 sectors/track, 32 cylinders           // 2个磁头,一个磁头有32个柱面,每个柱面有64个扇区
Units = cylinders of 128 * 512 = 65536 bytes

Device Boot      Start         End      Blocks  Id System
/dev/ramdisk1      1           5         288  83 Linux                  
Partition 1 has different physical/logical endings:phys=(260, 1, 0) logical=(4, 1, 64)
Partition 1 does not end on cylinder boundary
/dev/ramdisk2      6          32        1728  83 Linux
Partition 2 has different physical/logical endings: phys=(287, 1, 0) logical=(31, 1, 64)
Partition 2 does not end on cylinder boundary

8.4 操作磁盘分区

创建了磁盘分区后,我们就可以操作每个磁盘分区:

[root@zy:/]# mkdosfs /dev/ramdisk1
device major 37, minor 0   
open block device success
read from disk address 32768,length 4096,bio segment count 1;disk mapping ram c30ec000
read from disk address 36864,length 4096,bio segment count 1;disk mapping ram c30e5000
read from disk address 40960,length 4096,bio segment count 1;disk mapping ram c309f000
write to disk address 32768,length 4096,bio segment count 1;disk mapping ram c30ec000
release block device success
[root@zy:/]# mkdosfs /dev/ramdisk2
device major 37, minor 0
open block device success
read from disk address 327680,length 4096,bio segment count 1;disk mapping ram c3138000
read from disk address 331776,length 4096,bio segment count 1;disk mapping ram c3139000
read from disk address 335872,length 4096,bio segment count 1;disk mapping ram c313a000
read from disk address 339968,length 4096,bio segment count 1;disk mapping ram c313b000
read from disk address 344064,length 4096,bio segment count 1;disk mapping ram c313c000
read from disk address 348160,length 4096,bio segment count 1;disk mapping ram c313d000
read from disk address 352256,length 4096,bio segment count 1;disk mapping ram c313e000
read from disk address 356352,length 4096,bio segment count 1;disk mapping ram c313f000
write to disk address 327680,length 4096,bio segment count 1;disk mapping ram c3138000
release block device success

挂载块设备/dev/ramdisk1到/tmp目录,执行如下命令:

[root@zy:/]# mount /dev/ramdisk1 /tmp/
device major 37, minor 0
open block device success
read from disk address 33792,length 1024,bio segment count 1;disk mapping ram c30ec400
release block device success
device major 37, minor 0
open block device success
read from disk address 33792,length 1024,bio segment count 1;disk mapping ram c30ec400
release block device success
device major 37, minor 0
open block device success
read from disk address 33792,length 1024,bio segment count 1;disk mapping ram c30ec400
release block device success
device major 37, minor 0
open block device success
read from disk address 32768,length 4096,bio segment count 1;disk mapping ram c30ec000
read from disk address 36864,length 4096,bio segment count 1;disk mapping ram c30e5000
read from disk address 40960,length 4096,bio segment count 1;disk mapping ram c309f000
read from disk address 45056,length 4096,bio segment count 1;disk mapping ram c3082000
release block device success
device major 37, minor 0
open block device success   // 打开块设备
read from disk address 32768,length 512,bio segment count 1;disk mapping ram c30ec000
read from disk address 33280,length 512,bio segment count 1;disk mapping ram c30ec200
read from disk address 35840,length 512,bio segment count 1;disk mapping ram c30ecc00
read from disk address 40960,length 512,bio segment count 1;disk mapping ram c309f000
write to disk address 32768,length 512,bio segment count 1;disk mapping ram c30ec000

从上面日志可以看到,设备挂载之后会执行打开块设备操作,但是并不会执行关闭块设备操作,如果我们执行umount操作,将会执行关闭块设备操作。

挂载块设备/dev/ramdisk2到/mnt目录,执行如下命令:

[root@zy:/]# mount /dev/ramdisk2 /mnt/
device major 37, minor 0
open block device success
read from disk address 328704,length 1024,bio segment count 1;disk mapping ram c30ec400
release block device success
device major 37, minor 0
open block device success
read from disk address 328704,length 1024,bio segment count 1;disk mapping ram c30ec400
release block device success
device major 37, minor 0
open block device success
read from disk address 328704,length 1024,bio segment count 1;disk mapping ram c30ec400
release block device success
device major 37, minor 0
open block device success
read from disk address 327680,length 4096,bio segment count 1;disk mapping ram c3138000
read from disk address 331776,length 4096,bio segment count 1;disk mapping ram c3139000
read from disk address 335872,length 4096,bio segment count 1;disk mapping ram c313a000
read from disk address 339968,length 4096,bio segment count 1;disk mapping ram c313b000
release block device success
device major 37, minor 0
open block device success
read from disk address 327680,length 512,bio segment count 1;disk mapping ram c3138000
read from disk address 328192,length 512,bio segment count 1;disk mapping ram c3138200
read from disk address 330752,length 512,bio segment count 1;disk mapping ram c3134c00
read from disk address 358400,length 512,bio segment count 1;disk mapping ram c30ecc00
write to disk address 327680,length 512,bio segment count 1;disk mapping ram c3138000

接下来向/tmp目录下1.txt文件中写入数据,最终都会保存在/dev/ramdisk1块设备里面。

[root@zy:/]# echo "hello,world" > /tmp/1.txt
write to disk address 35840,length 512,bio segment count 1;disk mapping ram c30ecc00
write to disk address 38400,length 512,bio segment count 1;disk mapping ram c30e5600
write to disk address 40960,length 512,bio segment count 1;disk mapping ram c309f000
write to disk address 33280,length 512,bio segment count 1;disk mapping ram c30ec200
[root@zy:/]# cat /tmp/1.txt
hello,world

接下来向/mnt目录下1.txt文件中写入数据,最终都会保存在/dev/ramdisk2块设备里面。

[root@zy:/]# echo "hello,world1" > /mnt/1.txt
write to disk address 330752,length 512,bio segment count 1;disk mapping ram c3134c00
write to disk address 344576,length 512,bio segment count 1;disk mapping ram c3138200
write to disk address 358912,length 512,bio segment count 1;disk mapping ram c313ba00
write to disk address 40960,length 512,bio segment count 1;disk mapping ram c309f000
[root@zy:/]# cat /mnt/1.txt
hello,world1

从上面的输出日志,我们发现,无论是读写磁盘分区/dev/ramdisk1(次设备号为1),还是磁盘分区/dev/ramdisk2(次设备号为2),打开块设备操作时,读写的都是整个磁盘/dev/ramdisk(次设备号0)。

device major 37, minor 0

九、代码下载

Young / s3c2440_project[drivers]

参考文章

[1]22.Linux-块设备驱动之框架详细分析(详解)

[2]23.Linux-块设备驱动(详解)

posted @ 2022-09-25 21:37  大奥特曼打小怪兽  阅读(287)  评论(0编辑  收藏  举报
如果有任何技术小问题,欢迎大家交流沟通,共同进步