嵌入式Linux——块设备驱动

2019-07-12 19:26发布

        声明:本文章是看韦东山老师的教学视频后并阅读了一些博客后所写的块设备的驱动程序,其中包括一些对程序的分析,如果文中的分析与您的文章相同敬请提出,我会做相应的修改或删除。同时如果我的文章对你有所帮助那是我的幸运。        说起块设备驱动,我们就会想我们为什么要学习块设备驱动啊?我们不是已经学了字符设备驱动了吗?我们可以用字符驱动程序去写块设备驱动吗?         要回答上面的问题,我们就要试着将字符设备驱动用到块设备中,看他是否可以良好的运行。同时我们也可以复习一下字符设备驱动程序。我们写字符设备驱动的步骤是:
  1.     为该字符设备确定一个主设备号(用于对这个设备的识别)
  2.     完善file_operations结构体    
  3.     登记register_chrdev(主设备号,file_operations,设备名)
  4.     入口出口函数
  5.     修饰入口出口函数,并添加协议
上面就是字符驱动的步骤,而如果上面的步骤用到块设备中,就不一定可行了。我们知道不管是磁盘硬盘还是flash设备,他的基本操作单位是扇区,如果当你想要对硬盘或者flash中的一个文件进行操作时,他的基本步骤为:
  1. 将扇区中的文件读出到缓存区buffer中,
  2. 修改buffer中的文件
  3. 擦除读出的扇区
  4. 将修改的文件写回到扇区中
经过上面的步骤可以对扇区中的文件进行修改,而当进行读写操作时,会在不同的扇区间进行寻址跳转,这会浪费很大的资源。所以使用字符设备驱动的程序去写块设备是很费资源的。所以我们在这里要学习块设备的驱动。而下面是块设备和字符设备的对比:

块设备 VS 字符设备

作为一种存储设备,和字符设备相比,块设备有以下几种不同: 字符设备 块设备 1byte 块,硬件块各有不同,但是内核都使用512byte描述 顺序访问 随机访问 没有缓存,实时操作 有缓存,不是实时操作 一般提供接口给应用层 块设备一般提供接口给文件系统 是被用户程序调用 由文件系统程序调用 此外,大多数情况下,磁盘控制器都是直接使用DMA方式进行数据传送。 而在介绍块设备之前要先介绍一下电梯调度算法。因为电梯调度算法可以帮助我们对块设备去操作进行调整(排序或者整合)。这些方法可以使操作的效率大大提高。同时也可以节省很多资源。下面我们以一张图来详细的介绍电梯调度算法: 假设1,3,5,7楼的人想上到八楼,而2,4,6,8楼的人想下到1楼,那么如果以字符设备的方法我们就要上下8次如下图的正常方法,使用电梯调度算法只要两次就可以了,即将1,3,5,7楼的人一起都运到八楼而在这个过程中不会下,在将8,6,4,2楼的人一起运到一楼而不上去。我们可以明显的看出这样的方法及节约资源更提高了效率。 下面就让我们对块设备的框架进行分析: 我们会发现当一个应用程序对一个文件例如“1.txt”进行读写操作时,通过文件系统会将这个对文件的读写操作转化为对扇区的读写操作,而ll_rw_block函数又是将扇区的读写通过块设备作用到硬件上,而ll_rw_block(lowlevel_readwrite_block)函数就是将读写放入队列,并调用队列的处理函数实现优化(调顺序/合并)。ll_rw_block的分析: 分析ll_rw_block for (i = 0; i < nr; i++) { struct buffer_head *bh = bhs[i]; submit_bh(rw, bh); struct bio *bio; // 使用bh来构造bio (block input/output) submit_bio(rw, bio); // 通用的构造请求: 使用bio来构造请求(request) generic_make_request(bio); __generic_make_request(bio); request_queue_t *q = bdev_get_queue(bio->bi_bdev); // 找到队列 // 调用队列的"构造请求函数" ret = q->make_request_fn(q, bio); // 默认的函数是__make_request __make_request // 先尝试合并 elv_merge(q, &req, bio); // 如果合并不成,使用bio构造请求 init_request_from_bio(req, bio); // 把请求放入队列 add_request(q, req); // 执行队列 __generic_unplug_device(q); // 调用队列的"处理函数" q->request_fn(q); submit_bio(rw, bio); // 通用的构造请求: 使用bio来构造请求(request) generic_make_request(bio); __generic_make_request(bio); request_queue_t *q = bdev_get_queue(bio->bi_bdev); // 找到队列 // 调用队列的"构造请求函数" ret = q->make_request_fn(q, bio); // 默认的函数是__make_request __make_request // 先尝试合并 elv_merge(q, &req, bio); // 如果合并不成,使用bio构造请求 init_request_from_bio(req, bio); // 把请求放入队列 add_request(q, req); // 执行队列 __generic_unplug_device(q); // 调用队列的"处理函数" q->request_fn(q); 而详细的对于ll_rw_block 的分析要看文章: 22.Linux-块设备驱动之框架详细分析(详解)23.Linux-块设备驱动(详解) 则对核心的结构和方法做了介绍。 下面我们将对核心的结构和核心的方法进行分析: 而23.Linux-块设备驱动(详解) ,

Linux块设备IO子系统(一) _驱动模型

Linux块设备驱动

这三篇文章都对这一方面做了详细的介绍,我这里同样做一些介绍帮助记忆。   gendisk结构体 使用gendisk结果提来描述一个独立的磁盘设备或分区. struct gendisk { int major; /* major number of driver (主设备号)*/ int first_minor; /* 第一个次设备号 */ int minors; /* maximum number of minors, =1 for * disks that can't be partitioned.(最大次设备号,当为1时这个盘不可分) */ char disk_name[32]; /* name of major driver (盘的名字)*/ struct hd_struct **part; /* [indexed by minor] (磁盘分区信息)*/ int part_uevent_suppress; struct block_device_operations *fops; /* 块设备操作函数 */ struct request_queue *queue; /* 请求队列 */ void *private_data; sector_t capacity; /* 扇区容量 */ };     而gendisk结构体对应的方法有: struct gendisk *alloc_disk(int minors) /* 动态分配一个gendisk结构体,次设备号个数=分区个数+1 */ void del_gendisk(struct gendisk *disk) /* 释放一个gendisk结构体 */ /** * add_disk - add partitioning information to kernel list * @disk: per-device partitioning information * * This function registers the partitioning information in @disk * with the kernel. */ void add_disk(struct gendisk *disk) /* 注册gendisk结构体到内核,或将部分gendisk的信息告知内核 */ static inline void set_capacity(struct gendisk *disk, sector_t size) /*设置磁盘中扇区的个数,因为是扇区的个数,所以要用中的字节数除以512*/ request结构体和request_queue结构体       在Linux中用request结构体来表示等待进行的IO请求,而用request_queue表示IO请求的序列。     request结构体: /* * try to put the fields that are referenced together in the same cacheline */ struct request { unsigned int cmd_flags; /* 命令标志位,有READ=0和WRITE=1 */ enum rq_cmd_type_bits cmd_type; /* Maintain bio traversal state for part by part I/O submission. * hard_* are block layer internals, no driver should touch them!(hard_*为block层的内部构件,没有驱动可以动他们) */ sector_t sector; /* next sector to submit (下一个要传输的扇区)*/ sector_t hard_sector; /* next sector to complete (下一个完成的扇区)*/ unsigned long nr_sectors; /* no. of sectors left to submit (剩下要传输的扇区个数)*/ unsigned long hard_nr_sectors; /* no. of sectors left to complete */ /* no. of sectors left to submit in the current segment (当前段内剩余要传输的扇区个数)*/ unsigned int current_nr_sectors; /* no. of sectors left to complete in the current segment */ unsigned int hard_cur_sectors; char *buffer; /* 缓存区, */ }; struct request_queue { /* * Together with queue_head for cacheline sharing */ struct list_head queue_head; /* 请求队列的链表头 */ struct request *last_merge; elevator_t *elevator; /* 请求队列的IO调度算法 */ /* * the queue request freelist, one for reads and one for writes */ struct request_list rq; request_fn_proc *request_fn; /* 队列请求函数 */ /* * Auto-unplugging state */ struct timer_list unplug_timer; /* 自动非阻塞时间列表 */ int unplug_thresh; /* After this many requests */ unsigned long unplug_delay; /* After this many jiffies */ /* * protects queue structures from reentrancy. ->__queue_lock should * _never_ be used directly, it is queue private. always use * ->queue_lock. */ spinlock_t __queue_lock; /* 自旋锁 */ spinlock_t *queue_lock; /* * queue settings */ unsigned long nr_requests; /* Max # of requests (最大请求个数)*/ unsigned int max_sectors; unsigned int max_hw_sectors; unsigned short max_phys_segments; unsigned short max_hw_segments; unsigned short hardsect_size; unsigned int max_segment_size; };     request结构体和request_queue结构体的使用方法 /** * blk_init_queue - prepare a request queue for use with a block device * @rfn: The function to be called to process requests that have been * placed on the queue. * @lock: Request queue spin lock * * Description: * If a block device wishes to use the standard request handling procedures, * which sorts requests and coalesces adjacent requests, then it must * call blk_init_queue(). The function @rfn will be called when there * are requests on the queue that need to be processed. If the device * supports plugging, then @rfn may not be called immediately when requests * are available on the queue, but may be called at some time later instead. * Plugged queues are generally unplugged when a buffer belonging to one * of the requests on the queue is needed, or due to memory pressure. * * @rfn is not required, or even expected, to remove all requests off the * queue, but only as many as it can handle at a time. If it does leave * requests on the queue, it is responsible for arranging that the requests * get dealt with eventually. * * The queue spin lock must be held while manipulating the requests on the * request queue; this lock will be taken also from interrupt context, so irq * disabling is needed for it. * * Function returns a pointer to the initialized request queue, or NULL if * it didn't succeed. * * Note: * blk_init_queue() must be paired with a blk_cleanup_queue() call * when the block device is deactivated (such as at module unload).(blk_init_queue()要和blk_cleanup_queue()成对使用 ) **/ request_queue_t *blk_init_queue(request_fn_proc *rfn, spinlock_t *lock) /* 初始化一个请求队列 */ 其中blk_init_queue函数有两个参数分别为:队列的操作函数和队列的自旋锁,而自旋锁的定义为: static DEFINE_SPINLOCK(ramblock_lock); void blk_cleanup_queue(request_queue_t * q) /* 清除请求队列 */ struct request *elv_next_request(request_queue_t *q) /* 使用电梯调度算法调用请求队列中下一个未完成的请求 */ void end_request(struct request *req, int uptodate) /* 结束请求 当uptodate==0,表示使用该申请读写扇区失败, uptodate==1,表示成功 */ 当uptodate==0,表示使用该申请读写扇区失败, uptodate==1,表示成功 */ 块IO,这里对块IO不做详细的介绍,用一个图进行说明: 块驱动注册 int register_blkdev(unsigned int major, const char *name) /* 注册块设备到内核,当第一个参数主设备号为0时,返回的就是内核分配的主设备号 */ 块驱动注销 int unregister_blkdev(unsigned int major, const char *name) /* 从内核中注销块设备 */ 动态内存的分配 static inline void *kzalloc(size_t size, gfp_t flags) /* 将分配的size尺寸的内存设置为0,并分配 */ 动态内存的释放 void kfree(const void *block) 而下面我们将开始通过对: driverslockxd.c ,driverslockz2ram.c 的分析来确定编写块设备驱动的步骤: 我们先分析driverslockz2ram.c(用内存模拟磁盘),同样分析一个驱动程序要从他的入口函数进行分析(由于要分析,所以将一些没必要的程序删除): z2_init(void) { int ret; if (register_blkdev(Z2RAM_MAJOR, DEVICE_NAME)) /* 将块设备注册到内核中,此时块设备的主设备号为:Z2RAM_MAJOR */ goto err; z2ram_gendisk = alloc_disk(1); /* 分配gendisk结构体*/ if (!z2ram_gendisk) goto out_disk; z2_queue = blk_init_queue(do_z2_request, &z2ram_lock); /* 初始化请求队列,而do_z2_request为请求处理函数,而z2ram_lock自旋锁 */ if (!z2_queue) goto out_queue; z2ram_gendisk->major = Z2RAM_MAJOR; /* 设置gendisk的主设备号 */ z2ram_gendisk->first_minor = 0; /* 设置gendisk的首次设备号 */ z2ram_gendisk->fops = &z2_fops; /* 设置gendisk的操作函数,其中z2_fops为block_device_operations结构体 */ sprintf(z2ram_gendisk->disk_name, "z2ram"); /* 设置gendisk的名字 */ z2ram_gendisk->queue = z2_queue; /* 设置gendisk的请求队列,而这个队列在前面以定义 */ add_disk(z2ram_gendisk); /* 注册gendisk到内核 */ blk_register_region(MKDEV(Z2RAM_MAJOR, 0), Z2MINOR_COUNT, THIS_MODULE, z2_find, NULL, NULL); return 0; out_queue: put_disk(z2ram_gendisk); /* 注销gendisk */ out_disk: unregister_blkdev(Z2RAM_MAJOR, DEVICE_NAME); /* 从内核中注销块设备 */ err: return ret; } 从上面的分析中我们知道一个块设备驱动的步骤为:
  1. register_blkdev,将块设备注册到内核中,此时块设备的主设备号为:Z2RAM_MAJOR
  2.  alloc_disk,分配gendisk结构体
  3. blk_init_queue初始化请求队列
  4. 设置gendisk的主设备号,次设备号,操作函数,名字,请求队列
  5. add_disk,注册gendisk到内核
而在blk_init_queue初始化请求列表中回调的请求处理函数 static void do_z2_request(request_queue_t *q) { struct request *req; /* 定义请求 */ while ((req = elv_next_request(q)) != NULL) { /* 电梯调度算法调用队列中下一个未处理的请求 */ unsigned long start = req->sector << 9; /* 要处理的扇区的始地址 */ unsigned long len = req->current_nr_sectors << 9; /* 传输数据长度 */ if (start + len > z2ram_size) { /* 如果始地址加传输的数的长度大于总的内存,则出错 */ printk( KERN_ERR DEVICE_NAME ": bad access: block=%lu, count=%u ", req->sector, req->current_nr_sectors); end_request(req, 0); continue; } while (len) { unsigned long addr = start & Z2RAM_CHUNKMASK; unsigned long size = Z2RAM_CHUNKSIZE - addr; if (len < size) size = len; addr += z2ram_map[ start >> Z2RAM_CHUNKSHIFT ]; if (rq_data_dir(req) == READ) /* 如果是读 */ memcpy(req->buffer, (char *)addr, size); else                                               /* 否则是写 */ memcpy((char *)addr, req->buffer, size); start += size; len -= size; } end_request(req, 1); /* 结束请求 */ } }   而gendisk的操作函数为:   static struct block_device_operations z2_fops = { .owner = THIS_MODULE, .open = z2_open, .release = z2_release, }; 下面我们分析driverslockxd.c 驱动程序: /* xd_init: register the block device number and set up pointer tables */ static int __init xd_init(void) { if (register_blkdev(XT_DISK_MAJOR, "xd")) /* 将主设备号为XT_DISK_MAJOR的块设备注册到内核 */ goto out1; err = -ENOMEM; xd_queue = blk_init_queue(do_xd_request, &xd_lock); /* 初始化请求队列,其中请求处理函数为do_xd_request */ if (!xd_queue) goto out1a; for (i = 0; i < xd_drives; i++) { XD_INFO *p = &xd_info[i]; struct gendisk *disk = alloc_disk(64); /* 初始化gendisk结构体,其中64为有63个次设备号 */ if (!disk) goto Enomem; p->unit = i; disk->major = XT_DISK_MAJOR; /* 设置gendisk的主设备号 */ disk->first_minor = i<<6; /* 设置gendisk的首次设备号 */  sprintf(disk->disk_name, "xd%c", i+'a');  /* 设置gendisk的名字 */ disk->fops = &xd_fops;  /* 设置gendisk的操作函数,其中xd_fops为block_device_operations结构体 */   disk->private_data = p; disk->queue = xd_queue; /* 设置gendisk的请求队列,而这个队列在前面以定义 */ set_capacity(disk, p->heads * p->cylinders * p->sectors); /* 设置gendisk的容量 */ printk(" %s: CHS=%d/%d/%d ", disk->disk_name, p->cylinders, p->heads, p->sectors); xd_gendisk[i] = disk; } /* xd_maxsectors depends on controller - so set after detection */ blk_queue_max_sectors(xd_queue, xd_maxsectors); for (i = 0; i < xd_drives; i++) add_disk(xd_gendisk[i]); /* 注册gendisk到内核 */ return 0; out5: free_irq(xd_irq, NULL); out4: for (i = 0; i < xd_drives; i++) put_disk(xd_gendisk[i]); /* 注销gendisk结构体 */ out3: release_region(xd_iobase,4); out2: blk_cleanup_queue(xd_queue); /* 清除请求队列 */ out1a: unregister_blkdev(XT_DISK_MAJOR, "xd"); /* 注销块设备 */ out1: if (xd_dma_buffer) xd_dma_mem_free((unsigned long)xd_dma_buffer, xd_maxsectors * 0x200); return err; Enomem: err = -ENOMEM; while (i--) put_disk(xd_gendisk[i]); goto out3; }   通过上面的分析它的步骤为:  
  1.  register_blkdev,将主设备号为XT_DISK_MAJOR的块设备注册到内核 
  2. blk_init_queue, 初始化请求队列,其中请求处理函数为do_xd_request 
  3. alloc_disk, 初始化gendisk结构体,其中64为有63个次设备号
  4. 设置gendisk的主设备号,首次设备号,操作函数,名字,请求队列,容量
  5. add_disk,注册gendisk到内核
而blk_init_queue初始化请求队列的请求处理函数为: /* do_xd_request: handle an incoming request */ static void do_xd_request (request_queue_t * q) { struct request *req; /* 定义请求 */ while ((req = elv_next_request(q)) != NULL) {  /* 电梯调度算法调用队列中下一个未处理的请求 */ unsigned block = req->sector; /* 要处理的扇区的始地址 */ unsigned count = req->nr_sectors;  /* 传输数据长度 */ int rw = rq_data_dir(req); /* 传输的方向,是读还是写 */ XD_INFO *disk = req->rq_disk->private_data; int res = 0; int retry; if (!blk_fs_request(req)) { end_request(req, 0); /* 结束请求 */ continue; } if (block + count > get_capacity(req->rq_disk)) { /* 如果始地址加传输的数的长度大于总的内存,则出错 */ end_request(req, 0); /* 结束请求 */ continue; } if (rw != READ && rw != WRITE) { printk("do_xd_request: unknown request "); end_request(req, 0); /* 结束请求 */ continue; } for (retry = 0; (retry < XD_RETRIES) && !res; retry++) res = xd_readwrite(rw, disk, req->buffer, block, count); end_request(req, res); /* wrap up, 0 = fail, 1 = success */ } }   而gendisk的操作函数为:   static struct block_device_operations xd_fops = { .owner = THIS_MODULE, .ioctl = xd_ioctl, .getgeo = xd_getgeo, /* 获得几何属性 */ }; /* 获得几何属性 */ }; 其中xd_getgeo用于确定磁头,柱面,扇区的大小,其中总的内存容量=heads*sectors*cylinders*512,其中512为扇区大小: static int xd_getgeo(struct block_device *bdev, struct hd_geometry *geo) { XD_INFO *p = bdev->bd_disk->private_data; geo->heads = p->heads; /* 磁头的大小 */ geo->sectors = p->sectors; /* 扇区的大小 */ geo->cylinders = p->cylinders; /* 柱面的大小 */ return 0; } /* 磁头的大小 */ geo->sectors = p->sectors; /* 扇区的大小 */ geo->cylinders = p->cylinders; /* 柱面的大小 */ return 0; } 通过分析上面两个程序我们知道,写块驱动程序的步骤为:  
  1.  register_blkdev,将主设备号为XXX_MAJOR的块设备注册到内核 
  2. blk_init_queue, 初始化请求队列,其中请求处理函数为XXX_request 
  3. alloc_disk, 初始化gendisk结构体,其中XX为有XX-1个次设备号
  4. 设置gendisk的主设备号,首次设备号,操作函数,名字,请求队列,容量
  5. add_disk,注册gendisk到内核
而我们要通过内存来模拟块设备写一个块设备的驱动,所以上面的步骤是不可少的: 下面我们来写自己的块驱动程序,他的步骤为: 1.分配gendisk结构体:alloc_disk 2.设置gendisk结构体     a.分配设置队列 blk_init_queue                                                                                        //它提供读写能力     b.设置gendisk的其他信息如:主设备号,首次设备号,操作函数,名字,请求队列,容量       // 它提供属性    3. 注册gendisk结构体:add_disk 他的实现代码为: #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define RAMBLOCK_SIZE (1024*1024) static struct gendisk *ramblock_disk; static request_queue_t *ramblock_queue; static DEFINE_SPINLOCK(ramblock_lock); static int auto_major; static unsigned char *ramblock_buf; static int ramblock_getgeo(struct block_device *bdev,struct hd_geometry *geo) { geo->heads = 2; geo->cylinders = 32; geo->sectors = RAMBLOCK_SIZE/512/2/32; return 0; } static void do_ramblock_request(request_queue_t *q) { struct request *req; //static int r_cnt = 0; //static int w_cnt = 0; #if 0 static int cnt = 0; printk("do_ramblock_request %d ",++cnt); #endif while((req=elv_next_request(q)) != NULL){ /* 数据传输三要素:源,目的,长度 */ unsigned long offset = req->sector * 512; //源的偏移值 unsigned long len = req->current_nr_sectors * 512; //长度 if(rq_data_dir(req) == READ){ //printk("read %d ",++r_cnt); memcpy(req->buffer,ramblock_buf+offset,len); }else{ //printk("write %d ",++w_cnt); memcpy(ramblock_buf+offset,req->buffer,len); } end_request(req,1); } } static struct block_device_operations ramblock_fops = { .owner = THIS_MODULE, .getgeo = ramblock_getgeo, }; static int ramblock_init(void) { /* 1. 分配一个gendisk结构体 */ ramblock_disk = alloc_disk(16); /* 次设备号个数=分区个数+1 */ /* 2. 设置gendisk */ /* 2.1 分配/设置请求队列:request_queue_t :他提供读写能力 */ ramblock_queue = blk_init_queue(do_ramblock_request, &ramblock_lock); /* 参数为处理函数和自旋锁 */ /* 2.2 设置gendisk其他信息 :他提供属性:比如容量 */ auto_major = register_blkdev(0,"ramblock"); ramblock_disk->major = auto_major; ramblock_disk->first_minor = 0; sprintf(ramblock_disk->disk_name,"ramblock"); ramblock_disk->fops = &ramblock_fops; ramblock_disk->queue = ramblock_queue; set_capacity(ramblock_disk,RAMBLOCK_SIZE/512); /* 3. 硬件相关的操作 */ ramblock_buf = kzalloc(RAMBLOCK_SIZE,GFP_KERNEL); //分配一块内存 /* 4. 注册gendisk */ add_disk(ramblock_disk); return 0; } static void ramblock_exit(void) { kfree(ramblock_buf); unregister_blkdev(auto_major,"ramblock"); del_gendisk(ramblock_disk); put_disk(ramblock_disk); blk_cleanup_queue(ramblock_queue); } module_init(ramblock_init); module_exit(ramblock_exit); MODULE_LICENSE("GPL");   而这个程序的测试方法在下面文章中详细说明: 11-S3C2440驱动学习(八)嵌入式linux-块设备驱动程序 这里只是简要的介绍: 一,再开发板上操作     1.insmod     ramblock.ko  /* 装载驱动 */     2.mkdosfs    /dev/ramblock  /* 格式化: 新的块设备接入系统时要格式化 */     3.mount    /dev/ramblock   /tmp     /* 挂接驱动设备到/tmp */     4. cd     /tmp      /* 进入/tmp文件,并在里面读写文件 */     5. cd /  ; umount     /tmp          /* 退出/tmp并卸载 */     6. mount    /dev/ramblock   /tmp     /* 重新挂接驱动设备到/tmp ,并检测前面读写的文件是否还在*/     7. cat     /dev/ramblock  > /mnt/ramblock.bin   /* 将整个磁盘印象到/mnt中 */ 二,在pc上查看ramblock.bin     1. cd     /work/nfs_root/first_fs/         /* 进入根文件系统 */     2. ls     ramblock.bin          3. sudo  mount  -o  loop  ramblock.bin   /mnt     /* 将ramblock.bin挂接到/mnt中,-o  loop :可以将一个普通文件当一个块设备来挂接 */     4. cd /mnt      /* 看在开发版时建立的文件是否还在 */     5. sudo  umount   /mnt    三,测试为块设备分区:     1. insmod  ramblock.ko     2. ls   /dev/ramblock*    /* 查看有几个分区,如果没分区,此时只有次设备号为0的一个块设备  */     3. fdisk  /dev/ramblock      /* 为/dev/ramblock分区 */     4.  m为寻求帮助,而n为添加新分区     5. 添加第一个分区:         a. n   /* 添加一个新分区 */         b.  p    /* p为主分区 */         c. 1    /* 分区号为1,次设备号为1 */         c. 1    /* 从第一个柱面开始 */         d. 5   /* 到第五个柱面结束 */         e. p    /* 查看分区情况 */     6.添加第二个分区:           a. n   /* 添加一个新分区 */         b.  p    /* p为主分区 */         c. 2    /* 分区号为2,次设备号为2 */         d.         /* 按回车,从默认的第六个柱面开始 */         d. 32  /* 到第32个柱面结束 */         e. p    /* 查看分区情况 */     7. w   /* 真正写入到分区表中 */     8. ls     /dev/ramblock*   -l         /* 查看块设备分区情况 */      9.分别操作各个分区         a.mkdosfs    /dev/ramblock1    /* 格式化分区1 */   同时我在文章的末尾列出几个我觉得比较好的文章,希望对你也有帮助: Linux块设备驱动详解(一) linux之块设备驱动  11-S3C2440驱动学习(八)嵌入式linux-块设备驱动程序  

Linux块设备驱动

Linux块设备IO子系统(一) _驱动模型

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