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);
从上面代码我们不难看出这里定义了一个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);
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]
参考文章