块设备文件通常是指以块的方式写入的设备。如硬盘、光驱等。块设备驱动与字符设备驱动有很大差别。
块设备驱动程序描述符blk_dev_struct是定义如下:
<linux/blkdev.h>
struct blk_dev_struct {
request_queue_t request_queue; //请求队列
queue_proc *queue; //函数指针queue
void *date; //保存一些私有数据
};
块设备驱动编写包括注册和使用两类。其中request是请求队列,它是当内核安排一次数据传输时在列表中的请求队列。
块设备驱动程序绝大部分与设备无关,内核把相同的代码放在
中简化驱动程序的代码,所以每个块设备驱动程序都必须包含这个头文件。
驱动程序要用的数据结构定义:
struct device_struct {
const char *name;
struct file_operations *chops;
};
static struct device_struct blkdevs[MAX_BLKDEV];
struct sbull_dev {
void **data;
int quantum;// the current quantum size
int qset;// the current array size
unsigned long size;
unsigned int access_key;// used by sbulluid and sbullpriv
unsigned int usage;// lock the device while using it
unsigned int new_msg;
struct sbull_dev *next;// next listitem
};
与字符设备驱动程序一样,块设备驱动程序也包含一个 file_operation 结构,其结构定义如下:
struct file_operation blk_fops = {
NULL,//seek
block_read,//内核函数
block_write,//内核函数
NULL,//readdir
NULL,//poll
sbull_ioctl,// ioctl
NULL,//mmap
sbull_open,//open
NULL,//flush
sbull_release,//release
block_fsync,//内核函数
NULL,//fasync
sbull_check_media_change,//check media change
NULL,//revalidate
NULL,//lock
};
从上面结构中可以看出,所有的块驱动程序都调用内核函数 :
block_read()
block_write()
block_fsync()
函数,所以在块设备驱动程序入口中不包含这些函数,只需包括:
ioctl()
open()
release()
函数即可。
块设备驱动要完成引导内核完成一定工作,还需要再内核编译时增加一些内容。块设备驱动程序初始化时,由驱动程序init()
完成。
块设备驱动程序初始化的工作主要包括:
· 检查硬件是否存在;
· 登记主设备号;
· 将 fops 结构的指针传递给内核;
· 利用 register_blkdev()函数对设备进行注册
register_blkdev注册详细如下:
if(register_blkdev(sbull_MAJOR,“sbull”,&sbull_fops)) {
printk(“Registering block device major:%d failed\n”,sbull_MAJOR);
return-EIO;
};
将块设备驱动程序的数据容量传递给缓冲区:
#define sbull_HARDS_SIZE 512
#define sbull_BLOCK_SIZE 1024
static int sbull_hard = sbull_HARDS_SIZE;
static int sbull_soft = sbull_BLOCK_SIZE;
hardsect_size[sbull_MAJOR] = &sbull_hard;
blksize_size[sbull_MAJOR] = &sbull_soft;
在块设备驱动程序内核编译时,应把下列宏加到 blk.h 文件中:
#define MAJOR_NR sbull_MAJOR
#define DEVICE_NAME “sbull”
#define DEVICE_REQUEST sbull_request
#define DEVICE_NR(device) (MINOR(device))
#define DEVICE_ON(device)
#define DEVICE_OFF(device)
request操作涉及一个重要的数据结构:
struct request {
kdev_t rq_dev;
int cmd; // 读或写
int errors;
unsigned long sector;
char *buffer;
struct request *next;
};
设备的读写操作都是由request()
函数完成。所有的读写请求都存储在 request 结构的链表中。request()函数利用 CURRENT 宏检查当前的请求,如下:
#define CURRENT (blk_dev[MAJOR_NR].current_request)
接下来看一看 sbull_request 的具体使用:
extern struct request *CURRENT;
void sbull_request(void) {
unsigned long offset,total;
Begin:
INIT_REQUEST:
offset = CURRENT -> sector * sbull_hard;
total = CURRENT -> current_nr_sectors * sbull_hard;
/*超出设备的边界*/
if(total + offset > sbull_size * 1024) {
/*请求错误*/
end_request(0);
goto Begin;
}
if(CURRENT -> cmd == READ) {
memcpy(CURRENT -> buffer,sbull_storage + offset,total);
}
else if(CURRENT -> cmd == WRITE) {
memcpy(sbull_storage + offset,CURRENT -> buffer,total);
}
else {
end_request(0);
}
/*成功*/
end_request(1);
/*当请求做完时让 INIT_REQUEST 返回*/
goto Begin;
}
假定队列中至少有一个请求,request()函数现在应处理队列中的第一个请求,当处理完
请求后,request()
函数将调用 end_request()函数。如果成功地完成了读写操作,那么应该用参
数值 1 调用 end_request()
函数;如果读写操作不成功,那么以参数值 0 调用 end_request()函数。
如果队列中还有其他请求,那么将 CURRENT 指针设为指向下一个请求。执行 end_request()
函数后,request()函数回到循环的起点,对下一个请求重复上面的处理过程。
int sbull_open(struct inode *inode,struct file *filp) {
int num = MINOR(inode -> i_rdev);
if(num >= sbull -> size)
return ENODEV;
sbull -> size = sbull -> size + num;
if(!sbull -> usage) {
check_disk_change(inode -> i_rdev);
if(!*(sbull -> data))
return -ENOMEM;
}
sbull -> usage++;
MOD_INC_USE_COUNT;
return 0;
}
void sbull_release(struct inode *inode,struct file *filp) {
sbull -> size = sbull -> size + MINOR(inode -> i_rdev);
sbull -> usage--;
MOD_DEC_USE_COUNT;
printk("This blkdev is in release!\n");
return 0;
}
#include
#include
int sbull_ioctl(struct inode *inode,struct file *filp,unsigned int cmd,
unsigned long arg) {
int err;
struct hd_geometry *geo = (struct hd_geometry *)arg;
PDEBUG(“ioctl 0x%x 0x%lx\n”,cmd,arg);
switch(cmd) {
case BLKGETSIZE:
/ *返回设备大小*/
if(!arg)
return –EINVAL; // NULL pointer:not valid
err = verify_area(VERIFY_WRITE,(long *)arg,sizeof(long));
if(err)
return err;
put_user(1024*sbull_sizes[MINOR(inode -> i_rdev)/sbull_hardsects
[MINOR(inode -> i_rdev)],(long*)arg];
return 0;
case BLKFLSBUF: // flush
if(!suser())
return –EACCES; // only root
fsync_dev(inode -> i_rdev);
return 0;
case BLKRRPART: // re-read partition table: can’t do it
return –EINVAL;
RO_IOCTLS(inode -> i_rdev,arg);
// the default RO operations,宏 RO_IOCTLS(kdev_t dev,unsigned long where)
在 blk.h 中定义
}
return –EINVAL; // unknown command
}
感谢阅读,祝君成功!
-by aiziyou