Linux块设备驱动(三)程序设计

本节我们利用前两节所总结的内容设计一个简单的块设备驱动程序,分配一块内存作为磁盘实现块设备的功能。

首先是一些宏定义和全局变量

#define RAMDISK_SIZE		(1024*1024)
#define SECTOR_SIZE			512
static int major;

struct ramdisk_dev {                   
	unsigned char *buffer;                       /* The data array */
	spinlock_t lock;                /* For mutual exclusion */
	struct gendisk *gd;             /* The gendisk structure */
};

struct ramdisk_dev *ramdisk;

我们定义了一个名为ramdisk_dev的结构描述该块设备,buffer指向存取数据内存,lock是控制访问队列的自旋锁,gd就是核心的gendisk结构体。宏定义RAMDISK_SIZE为整个磁盘的容量,SECTOR_SIZE为每个扇区的大小。

入口和出口函数

static struct block_device_operations ramdisk_fops = {
	.getgeo          = ramdisk_getgeo,
};

static int __init ramdisk_init(void)
{
	
	major = register_blkdev(major, "ramdisk");
	if (major <= 0) {
		printk("ramdisk: unable to get major number\n");
		return -EBUSY;
	}

	ramdisk = kmalloc(sizeof(struct ramdisk_dev), GFP_KERNEL);
	if (!ramdisk)
		goto unregister;
	
	memset(ramdisk, 0, sizeof(struct ramdisk_dev));
	
	ramdisk->buffer = vmalloc(RAMDISK_SIZE);
	if (ramdisk->buffer == NULL)
		goto unregister;
	
	spin_lock_init(&ramdisk->lock);
	
	ramdisk->gd = alloc_disk(16);
	if (ramdisk->gd == NULL)
		goto vfree;
	
	ramdisk->gd->major = major;
	ramdisk->gd->first_minor = 0;
	ramdisk->gd->fops = &ramdisk_fops;
	ramdisk->gd->queue = blk_init_queue(ramdisk_request, &ramdisk->lock);
	if (ramdisk->gd->queue == NULL)
		goto vfree;
	sprintf(ramdisk->gd->disk_name, "ramdisk0");
	set_capacity(ramdisk->gd, RAMDISK_SIZE / 512);
	
	add_disk(ramdisk->gd);
	
	return 0;
	
vfree:	
	vfree(ramdisk->buffer);
unregister:
	unregister_blkdev(major, "sbd");
	return -ENOMEM;
}
module_init(ramdisk_init);

static void vmem_disk_exit(void)
{
	if (ramdisk->gd->queue)
		blk_cleanup_queue(ramdisk->gd->queue);
			
	if (ramdisk->gd) {
		del_gendisk(ramdisk->gd);
		put_disk(ramdisk->gd);
	}
					
	if (ramdisk->buffer)
		vfree(ramdisk->buffer);
	
	unregister_blkdev(major, "ramdisk");
	kfree(ramdisk);
}
module_exit(vmem_disk_exit);

入口函数初始化一个ramdisk_dev,并初始化其中的gendisk结构体,出口函数释放分配的资源。

block_device_operations中成员函数的实现,这里只实现getgeo函数,设置磁头,磁柱,扇区信息

static int ramdisk_getgeo(struct block_device *bdev, struct hd_geometry *geo)
{
	/* 容量=heads*cylinders*sectors*512 */
	geo->heads     = 2;
	geo->cylinders = 32;
	geo->sectors   = RAMDISK_SIZE/2/32/512;
	return 0;
}

最后就是处理请求函数的实现了

static void ramdisk_request(struct request_queue *q)
{
	struct request *req;
	struct bio *bio;
	struct bio_vec bvec;
	struct bvec_iter iter;
	sector_t sector; 
	unsigned long offset;
	unsigned long nbytes;
	
	while ((req = blk_peek_request(q)) != NULL) {	//each request 

		blk_start_request(req);
		
			__rq_for_each_bio(bio, req) {			//each bio
				
				sector = bio->bi_iter.bi_sector;
				bio_for_each_segment(bvec, bio, iter) {		//each bio_vec
					
					char *buffer = __bio_kmap_atomic(bio, iter);
					
					offset = sector*SECTOR_SIZE;
					nbytes = bio_cur_bytes(bio);
					
					if (bio_data_dir(bio))					//write 
						memcpy(ramdisk->buffer + offset, buffer, nbytes);
					else									//read 
						memcpy(buffer, ramdisk->buffer + offset, nbytes);
					
					sector += bio_cur_bytes(bio) >> 9;
					
					__bio_kunmap_atomic(buffer);
				}
			}
			
		__blk_end_request_all(req, 0);
	}
}

请求处理函数提取出每个请求,再遍历每个请求的每个bio,再遍历每个bio的bio_vec。

处理每个bio的过程: 

通过bio->bvec-iter->bi_sector得到要操作哪个扇区;

提取每个bio_vec;

处理每个bio_vec: 需要三个要素,请求的缓存地址,块设备的读写起始地址,传输数据的字节数

请求的缓存地址:用__bio_kmap_atomic()得到请求的缓存,如果是写操作,将该缓存内容复制到块设备,如果是读,则将块设备内容复制到该缓存。处理结束后要使用__bio_kunmap_atomic()解除对该缓存的映射。

块设备的读写起始地址:用扇区编号乘上扇区容量获得;

传输数据的字节数:用bio_cur_bytes()得到。

接下来就是复制数据了,最后算出下一个bio_vec在哪个扇区,用传输数据的字节数除以扇区容量,表示这次操作占了几个扇区,再累加到当前扇区编号就得到下次操作所在的扇区。

 编译通过后加载驱动,可以在/dev目录下看到这个块设备:

 

再将其格式化为ext2文件系统:mkfs.ext2 /dev/ramdisk0

Linux块设备驱动(三)程序设计_第1张图片

接下来就可以挂载使用了,我们先建立一个ramdisk目录,将其挂载到该目录上:

使用df指令查看:

可以看到这个磁盘已经成功挂载可以使用了。 

你可能感兴趣的:(嵌入式Linux驱动)