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。
一、全局变量
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 ) static DEFINE_MUTEX(z2ram_mutex); static u_long *z2ram_map = NULL; static u_long z2ram_size = 0; static int z2_count = 0; static int chip_count = 0; 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, };
二、块设备操作集
在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函数。
2.1 z2_open
static int z2_open(struct block_device *bdev, fmode_t mode) { int device; int max_z2_map = ( Z2RAM_SIZE / Z2RAM_CHUNKSIZE ) * sizeof( z2ram_map[0] ); int max_chip_map = ( amiga_chip_size / Z2RAM_CHUNKSIZE ) * sizeof( z2ram_map[0] ); int rc = -ENOMEM; device = MINOR(bdev->bd_dev); mutex_lock(&z2ram_mutex); if ( current_device != -1 && current_device != device ) { rc = -EBUSY; goto err_out; } if ( current_device == -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) { int index = device - Z2MINOR_MEMLIST1 + 1; 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); #ifdef __powerpc__ /* FIXME: ioremap doesn't build correct memory tables. */ { vfree(vmalloc (size)); } vaddr = (unsigned long)ioremap_wt(paddr, size); #else vaddr = (unsigned long)z_remap_nocache_nonser(paddr, size); #endif 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: z2ram_map = kmalloc( max_z2_map + 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_z2ram(); get_chipram(); if ( z2ram_size != 0 ) printk( KERN_INFO DEVICE_NAME ": using %iK Zorro II RAM and %iK Chip RAM (Total %dK)\n", z2_count * Z2RAM_CHUNK1024, chip_count * Z2RAM_CHUNK1024, ( z2_count + chip_count ) * Z2RAM_CHUNK1024 ); break; case Z2MINOR_Z2ONLY: 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: 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; 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; }
2.1.1 get_z2ram
static void get_z2ram( void ) { int i; for ( i = 0; i < Z2RAM_SIZE / Z2RAM_CHUNKSIZE; i++ ) { if ( test_bit( i, zorro_unused_z2ram ) ) { z2_count++; z2ram_map[z2ram_size++] = (unsigned long)ZTWO_VADDR(Z2RAM_START) + (i << Z2RAM_CHUNKSHIFT); clear_bit( i, zorro_unused_z2ram ); } } return; }
2.1.2 get_chipram
static void get_chipram( void ) { while ( amiga_chip_avail() > ( Z2RAM_CHUNKSIZE * 4 ) ) { chip_count++; z2ram_map[ z2ram_size ] = (u_long)amiga_chip_alloc( Z2RAM_CHUNKSIZE, "z2ram" ); if ( z2ram_map[ z2ram_size ] == 0 ) { break; } z2ram_size++; } return; }
2.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个分区(除了代表磁盘的0号分区外);
- 使用blk_mq_init_sq_queue初始化一个请求队列;
- 设置gendisk结构体的成员:
- 设置成员参数major、first_minor、disk_name、fops;
- 设置请求队列queue,等于之前初始化的请求队列;
- 使用add_disk注册gendisk;
- 使用blk_register_region建立磁盘设备号和gendisk的映射关系(这一步是不是多余的);
3.1 z2_find
static struct kobject *z2_find(dev_t dev, int *part, void *data) { *part = 0; return get_disk_and_module(z2ram_gendisk); }
3.2 z2_mq_ops
z2_mq_ops为为块设备驱动行为的回调函数,z2_mq_ops定义为:
static const struct blk_mq_ops z2_mq_ops = { .queue_rq = z2_queue_rq, };
一旦I/O请求就绪,并且I/O调度器不想再把请求保持在队列上来排序或扩展,I/O调度器就会调用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; unsigned long start = blk_rq_pos(req) << 9; unsigned long len = blk_rq_cur_bytes(req); 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; unsigned long size = Z2RAM_CHUNKSIZE - addr; void *buffer = bio_data(req->bio); if (len < size) size = len; addr += z2ram_map[ start >> Z2RAM_CHUNKSHIFT ]; if (rq_data_dir(req) == READ) memcpy(buffer, (char *)addr, size); else 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; }
四、模块出口函数
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;
参考文章
标签:map,Z2RAM,queue,z2ram,linux,驱动,z2,size From: https://www.cnblogs.com/zyly/p/16704365.html