天天看點

Linux裝置驅動--塊裝置(三)之程式設計

 塊裝置驅動注冊與登出

塊裝置驅動中的第1個工作通常是注冊它們自己到核心,完成這個任務的函數是 register_blkdev(),其原型為:

int register_blkdev(unsigned int major, const char *name);

major 參數是塊裝置要使用的主裝置号,name為裝置名,它會在/proc/devices中被顯示。 如果major為0,核心會自動配置設定一個新的主裝置号register_blkdev()函數的傳回值就是這個主裝置号。如果傳回1個負值,表明發生了一個錯誤。

與register_blkdev()對應的登出函數是unregister_blkdev(),其原型為:

int unregister_blkdev(unsigned int major, const char *name);

這裡,傳遞給register_blkdev()的參數必須與傳遞給register_blkdev()的參數比對,否則這個函數傳回-EINVAL。

塊裝置的請求隊列操作

标準的請求處理程式能排序請求,并合并相鄰的請求,如果一個塊裝置希望使用标準的請求處理程式,那它必須調用函數blk_init_queue來初始化請求隊列。當處理在隊列上的請求時,必須持有隊列自旋鎖。初始化請求隊列

request_queue_t *blk_init_queue(request_fn_proc *rfn, spinlock_t *lock);

該函數的第1個參數是請求處理函數的指針,第2個參數是控制通路隊列權限的自旋鎖,這個函數會發生記憶體配置設定的行為,故它可能會失敗,函數調用成

功時,它傳回指向初始化請求隊列的指針,否則,傳回NULL。這個函數一般在塊裝置驅動的子產品加載函數中調用。清除請求隊列

void blk_cleanup_queue(request_queue_t * q);

這個函數完成将請求隊列傳回給系統的任務,一般在塊裝置驅動子產品解除安裝函數中調用。

提取請求

struct request *elv_next_request(request_queue_t *queue);

上述函數用于傳回下一個要處理的請求(由 I/O 排程器決定),如果沒有請求則傳回NULL。

去除請求

void blkdev_dequeue_request(struct request *req);

上述函數從隊列中去除1個請求。如果驅動中同時從同一個隊列中操作了多個請求,它必須以這樣的方式将它們從隊列中去除。

配置設定“請求隊列”

request_queue_t *blk_alloc_queue(int gfp_mask);

對于FLASH、RAM盤等完全随機通路的非機械裝置,并不需要進行複雜的I/O排程,這個時候,應該使用上述函數配置設定1個“請求隊列”,并使用如下函數來綁定“請求隊列”和“制造請求”函數。

void blk_queue_make_request(request_queue_t * q,

make_request_fn * mfn);

void blk_queue_hardsect_size(request_queue_t *queue,

unsigned short max);

該函數用于告知核心塊裝置硬體扇區的大小,所有由核心産生的請求都是這個大小的倍數并且被正确對界。但是,核心塊裝置層和驅動之間的通信還是以512位元組扇區為機關進行。

步驟:

在塊裝置驅動的子產品加載函數中通常需要完成如下工作:

① 配置設定、初始化請求隊列,綁定請求隊列和請求函數。

② 配置設定、初始化gendisk,給gendisk的major、fops、queue等成

員指派,最後添加gendisk。

③ 注冊塊裝置驅動。

在塊裝置驅動的子產品解除安裝函數中通常需要與子產品加載函數相反的工作:

① 清除請求隊列。

② 删除gendisk和對gendisk的引用。

③ 删除對塊裝置的引用,登出塊裝置驅動。

總結:

塊裝置的I/O操作方式與字元裝置存在較大的不同,因而引入了

request_queue、request、bio等一系列資料結構。在整個塊裝置的I/O操作中,貫穿于始終的就是“請求”,字元裝置的I/O操作則是直接進行不繞彎,

塊裝置的I/O操作會排隊和整合。

驅動的任務是處理請求,對請求的排隊和整合由I/O排程算法解決,是以,塊裝置驅動的核心就是請求處理函數或“制造請求”函數。

盡管在塊裝置驅動中仍然存在block_device_operations結構體及其成員函數,但其不再包含讀寫一類的成員函數,而隻是包含打開、釋放及I/O控制等

與具體讀寫無關的函數。塊裝置驅動的結構相當複雜的,但幸運的是,塊裝置不像字元裝置那麼包羅萬象,它通常就是儲存設備,而且驅動的主體已經

由Linux核心提供,針對一個特定的硬體系統,驅動工程師所涉及到的工作往往隻是編寫少量的與硬體直接互動的代碼。

#include <linux/init.h>  
#include <linux/module.h>  
#include <linux/kernel.h>  
#include <linux/fs.h>
#include <asm/uaccess.h>
#include <linux/spinlock.h>
#include <linux/sched.h>
#include <linux/types.h>
#include <linux/fcntl.h>
#include <linux/hdreg.h>
#include <linux/genhd.h>
#include <linux/blkdev.h>

#define MAXBUF 1024	


#define BLK_MAJOR 253

char blk_dev_name[]="blk_dev";
static char flash[1024*16];


int major;
spinlock_t lock;
struct gendisk *gd;



/*塊裝置資料傳輸*/
static void blk_transfer(unsigned long sector, unsigned long nsect, char *buffer, int write)
{
	int read = !write;
	if(read)
	{
		memcpy(buffer, flash+sector*512, nsect*512);
	}
	else
	{
		memcpy(flash+sector*512, buffer, nsect*512);
	}
}

/*塊裝置請求處理函數*/
static void blk_request_func(struct request_queue *q)
{
	struct request *req;
	while((req = elv_next_request(q)) != NULL)  
	{
		if(!blk_fs_request(req))
		{
			end_request(req, 0);
			continue;
		}
		
		blk_transfer(req->sector, req->current_nr_sectors, req->buffer, rq_data_dir(req));
		/*rq_data_dir從request獲得資料傳送的方向*/
		/*req->current_nr_sectors 在目前段中将完成的扇區數*/
		/*req->sector 将送出的下一個扇區*/
		end_request(req, 1);
	}
}

/*strcut block_device_operations*/
static  int blk_ioctl(struct block_device *dev, fmode_t no, unsigned cmd, unsigned long arg)
{
       return -ENOTTY;
}

static int blk_open (struct block_device *dev , fmode_t no)
{
	printk("blk mount succeed\n");
	return 0;
}
static int blk_release(struct gendisk *gd , fmode_t no)
{
	printk("blk umount succeed\n");
	return 0;
}
struct block_device_operations blk_ops=
{
	.owner = THIS_MODULE,
	.open = blk_open,
	.release = blk_release,
    .ioctl = blk_ioctl,
};

//-----------------------------------------------

static int __init block_module_init(void)
{
	
	
	if(!register_blkdev(BLK_MAJOR, blk_dev_name)) //注冊一個塊裝置
	{
		major = BLK_MAJOR;	
		printk("regiser blk dev succeed\n");
	}
	else
	{
		return -EBUSY;
	}
	gd = alloc_disk(1);  //配置設定一個gendisk,分區是一個
	spin_lock_init(&lock); //初始化一個自旋鎖
	gd->major = major;
	gd->first_minor = 0;   //第一個次裝置号
	gd->fops = &blk_ops;   //關聯操作函數

	gd->queue = blk_init_queue(blk_request_func, &lock); //初始化請求隊列并關聯到gendisk

	snprintf(gd->disk_name, 32, "blk%c", 'a');  
	blk_queue_hardsect_size(gd->queue, 512);  //設定扇區大小512位元組
	set_capacity(gd, 32);  //設定塊裝置大小 512*32=16K
	add_disk(gd);
	printk("gendisk init success!\n");
	return 0;
}
static void __exit block_module_exit(void)
{
	blk_cleanup_queue(gd->queue);
	del_gendisk(gd); 
	unregister_blkdev(BLK_MAJOR, blk_dev_name);
	printk("block module exit succeed!\n");
}

module_init(block_module_init);
module_exit(block_module_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("gec");
//------------------------------------------------------------------------------
           

繼續閱讀