1. 程式人生 > >嵌入式Linux——塊裝置驅動

嵌入式Linux——塊裝置驅動

        宣告:本文章是看韋東山老師的教學視訊後並閱讀了一些部落格後所寫的塊裝置的驅動程式,其中包括一些對程式的分析,如果文中的分析與您的文章相同敬請提出,我會做相應的修改或刪除。同時如果我的文章對你有所幫助那是我的幸運。

       說起塊裝置驅動,我們就會想我們為什麼要學習塊裝置驅動啊?我們不是已經學了字元裝置驅動了嗎?我們可以用字元驅動程式去寫塊裝置驅動嗎?

        要回答上面的問題,我們就要試著將字元裝置驅動用到塊裝置中,看他是否可以良好的執行。同時我們也可以複習一下字元裝置驅動程式。我們寫字元裝置驅動的步驟是:

  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 的分析要看文章:

下面我們將對核心的結構和核心的方法進行分析:

這三篇文章都對這一方面做了詳細的介紹,我這裡同樣做一些介紹幫助記憶。

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)             

而下面我們將開始通過對:

drivers\block\xd.c ,drivers\block\z2ram.c 的分析來確定編寫塊裝置驅動的步驟:

我們先分析drivers\block\z2ram.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\n",
				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,
};

下面我們分析drivers\block\xd.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\n", 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\n");
			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 <linux/module.h>
#include <linux/errno.h>
#include <linux/interrupt.h>
#include <linux/mm.h>
#include <linux/fs.h>
#include <linux/kernel.h>
#include <linux/timer.h>
#include <linux/genhd.h>
#include <linux/hdreg.h>
#include <linux/ioport.h>
#include <linux/init.h>
#include <linux/wait.h>
#include <linux/blkdev.h>
#include <linux/blkpg.h>
#include <linux/delay.h>
#include <linux/io.h>

#include <asm/system.h>
#include <asm/uaccess.h>
#include <asm/dma.h>

#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 \n",++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 \n",++r_cnt);
			memcpy(req->buffer,ramblock_buf+offset,len);
		}else{
			//printk("write %d \n",++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");




而這個程式的測試方法在下面文章中詳細說明:

這裡只是簡要的介紹:

一,再開發板上操作

    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 */

同時我在文章的末尾列出幾個我覺得比較好的文章,希望對你也有幫助: