注册块IO设备
为了注册块IO设备,register_blkdev()被使用。取消注册时使用unregister_blkdev()方法。自从4.9版本的内核开始,对register_blkdev()的调用是可选的。
下面是一个典型的场景。
#include <linux/fs.h>
#define MY_BLOCK_MAJOR 240
#define MY_BLKDEV_NAME "mybdev"
static int my_block_init(void)
{
int status;
status = register_blkdev(MY_BLOCK_MAJOR, MY_BLKDEV_NAME);
if (status < 0) {
printk(KERN_ERR "unable to register mybdev block device\n");
return -EBUSY;
}
//...
}
static void my_block_exit(void)
{
//...
unregister_blkdev(MY_BLOCK_MAJOR, MY_BLKDEV_NAME);
}
注册一个磁盘
尽管register_blkdev()方法获取了major,但是它没有为系统提供设备(磁盘)。alloc_disk()方法用于分配磁盘,del_gendisk()用于取消分配。使用add_disk()来添加磁盘。
alloc_disk()和add_disk()被用于模块的初始化。
#include <linux/fs.h>
#include <linux/genhd.h>
#define MY_BLOCK_MINORS 1
static struct my_block_dev {
struct gendisk *gd;
//...
} dev;
static int create_block_device(struct my_block_dev *dev)
{
dev->gd = alloc_disk(MY_BLOCK_MINORS);
//...
add_disk(dev->gd);
}
static int my_block_init(void)
{
//...
create_block_device(&dev);
}
static void delete_block_device(struct my_block_dev *dev)
{
if (dev->gd)
del_gendisk(dev->gd);
//...
}
static void my_block_exit(void)
{
delete_block_device(&dev);
//...
}
就想是字符设备一样,我们建议使用my_block_dev结构来存储描述块设备的重要元素。
It can be noticed that the basic structure in working with block devices (disks) is the struct gendisk structure.
struct gendisk structure
struct gendisk存储着有关磁盘的信息。这个结构来自于alloc_disk()。在调用add_disk()之前,各个字段被填充了。
struct gendisk结构拥有下面几个重要的字段
- major, first_minor, minor, describing the identifiers used by the disk; a disk must have at least one minor; if the disk allows the partitioning operation, a minor must be allocated for each possible partition
- disk_name, which represents the disk name as it appears in /proc/partitions and in sysfs (/sys/block)
- fops, representing operations associated with the disk
- queue, which represents the queue of requests
- capacity, which is disk capacity in 512 byte sectors; it is initialized using the set_capacity() function
- private_data, which is a pointer to private data
struct gendisk结构如下所示
#include <linux/genhd.h>
#include <linux/fs.h>
#include <linux/blkdev.h>
#define NR_SECTORS 1024
#define KERNEL_SECTOR_SIZE 512
static struct my_block_dev {
//...
spinlock_t lock; /* For mutual exclusion */
struct request_queue *queue; /* The device request queue */
struct gendisk *gd; /* The gendisk structure */
//...
} dev;
static int create_block_device(struct my_block_dev *dev)
{
...
/* Initialize the gendisk structure */
dev->gd = alloc_disk(MY_BLOCK_MINORS);
if (!dev->gd) {
printk (KERN_NOTICE "alloc_disk failure\n");
return -ENOMEM;
}
dev->gd->major = MY_BLOCK_MAJOR;
dev->gd->first_minor = 0;
dev->gd->fops = &my_block_ops;
dev->gd->queue = dev->queue;
dev->gd->private_data = dev;
snprintf (dev->gd->disk_name, 32, "myblock");
set_capacity(dev->gd, NR_SECTORS);
add_disk(dev->gd);
return 0;
}
static int my_block_init(void)
{
int status;
//...
status = create_block_device(&dev);
if (status < 0)
return status;
//...
}
static void delete_block_device(struct my_block_dev *dev)
{
if (dev->gd) {
del_gendisk(dev->gd);
}
//...
}
static void my_block_exit(void)
{
delete_block_device(&dev);
//...
}
就像之前所说的那样,内核把磁盘看作是512字节大小扇区的向量。现实当中,设备可能具有不同大小的扇区。通过内核产生的所有请求将会是扇区大小的倍数并且会被对齐。
struct block_device_operations structure
如果只是字符设备,那么struct file_operations 的操作是完整的,对于块设备而言,struct block_device_operations的操作是完整的。
struct block_device_operations结构如下所示
struct block_device_operations {
int (*open) (struct block_device *, fmode_t);
int (*release) (struct gendisk *, fmode_t);
int (*locked_ioctl) (struct block_device *, fmode_t, unsigned,
unsigned long);
int (*ioctl) (struct block_device *, fmode_t, unsigned, unsigned long);
int (*compat_ioctl) (struct block_device *, fmode_t, unsigned,
unsigned long);
int (*direct_access) (struct block_device *, sector_t,
void **, unsigned long *);
int (*media_changed) (struct gendisk *);
int (*revalidate_disk) (struct gendisk *);
int (*getgeo)(struct block_device *, struct hd_geometry *);
struct module *owner;
}
open()和release()操作被直接从用户空间调用。可以分区,文件系统的创建,文件系统的验证。
#include <linux/fs.h>
#include <linux/genhd.h>
static struct my_block_dev {
//...
struct gendisk * gd;
//...
} dev;
static int my_block_open(struct block_device *bdev, fmode_t mode)
{
//...
return 0;
}
static int my_block_release(struct gendisk *gd, fmode_t mode)
{
//...
return 0;
}
struct block_device_operations my_block_ops = {
.owner = THIS_MODULE,
.open = my_block_open,
.release = my_block_release
};
static int create_block_device(struct my_block_dev *dev)
{
//....
dev->gd->fops = &my_block_ops;
dev->gd->private_data = dev;
//...
}
请求队列
块设备的驱动使用的是队列来存储块IO请求。请求队列通过双链表链接在一起,其中还包含着控制信息。请求队列当中的每一个请求都是struct request结构。
创建和删除请求队列
A request queue is created with the blk_init_queue() function and is deleted using the blk_cleanup_queue() function.
#include <linux/fs.h>
#include <linux/genhd.h>
#include <linux/blkdev.h>
static struct my_block_dev {
//...
struct request_queue *queue;
//...
} dev;
static void my_block_request(struct request_queue *q);
//...
static int create_block_device(struct my_block_dev *dev)
{
/* Initialize the I/O queue */
spin_lock_init(&dev->lock);
dev->queue = blk_init_queue(my_block_request, &dev->lock);
if (dev->queue == NULL)
goto out_err;
blk_queue_logical_block_size(dev->queue, KERNEL_SECTOR_SIZE);
dev->queue->queuedata = dev;
//...
out_err:
return -ENOMEM;
}
static int my_block_init(void)
{
int status;
//...
status = create_block_device(&dev);
if (status < 0)
return status;
//...
}
static void delete_block_device(struct block_dev *dev)
{
//...
if (dev->queue)
blk_cleanup_queue(dev->queue);
}
static void my_block_exit(void)
{
delete_block_device(&dev);
//...
}
The blk_init_queue() function receives as first argument a pointer to the function which processes the requests for the device (of type request_fn_proc). In the example above, the function is my_block_request(). The lock parameter is a spinlock (initialized by the driver) that the kernel holds during the request() function call to ensure exclusive access to the queue. This spinlock can also be used in other driver functions to protect access to shared data with the request() function.
As part of the request queue initialization, you can configure the queuedata field, which is equivalent to the private_data field in other structures.
处理请求队列的有用的功能
The function of type request_fn_proc is used to handle requests for working with the block device. This function is the equivalent of read and write functions encountered on character devices. The function receives the request queue associated with the device as an argument and can use various functions for processing the requests from the request queue.
The functions used to process the requests from the request queue are described below:
- blk_peek_request() - retrieves a reference to the first request from the queue; the respective request must be started using blk_start_request();
- blk_start_request() - extracts the request from the queue and starts it for processing; in general, the function receives as a reference a pointer to a request returned by blk_peek_request();
- blk_fetch_request() - retrieves the first request from the queue (using blk_peek_request()) and starts it (using blk_start_request());
- blk_requeue_request() - to re-enter queue.
请求块设备
A request for a block device is described by struct request structure.
The fields of struct request structure include:
- cmd_flags: a series of flags including direction (reading or writing); to find out the direction, the macrodefinition rq_data_dir is used, which returns 0 for a read request and 1 for a write request on the device;
- __sector: the first sector of the transfer request; if the device sector has a different size, the appropriate conversion should be done. To access this field, use the blk_rq_pos macro;
- __data_len: the total number of bytes to be transferred; to access this field the blk_rq_bytes macro is used;
- generally, data from the current struct bio will be transferred; the data size is obtained using the blk_rq_cur_bytes macro;
- bio, a dynamic list of struct bio structures that is a set of buffers associated to the request; this field is accessed by macrodefinition rq_for_each_segment if there are multiple buffers, or by bio_data macrodefinition in case there is only one associated buffer;
- bio_data: the address of the buffer associated to the request
- about the struct bio structure and its associated operations will be discussed in the bio_structure section;
创建请求
Read /write requests are created by code layers superior to the kernel I/O subsystem. Typically, the subsystem that creates requests for block devices is the file management subsystem. The I/O subsystem acts as an interface between the file management subsystem and the block device driver. The main operations under the responsibility of the I/O subsystem are adding requests to the queue of the specific block device and sorting and merging requests according to performance considerations.
结束请求
When the driver has finished transferring all the sectors of a request to /from the device, it must inform the I/O subsystem by calling the blk_end_request() function. If the lock associated to the request queue is already acquired, the __blk_end_request() function can be used.
If the driver wants to close the request even if it did not transfer all the related sectors, it can call the blk_end_request_all() or __blk_end_request_all() function. The __blk_end_request_all() function is called if the lock associated to the request queue is already acquired.
处理请求
The central part of a block device driver is the request_fn_proc function type. In previous examples, the function that fulfilled this role was my_block_request(). As stated in the Create and delete a request queue section, this function is associated to the driver by calling blk_init_queue() function.
This function is called when the kernel considers that the driver should process I/O requests. The function must start processing the requests from the queue, but it is not mandatory to finish them, as requests may be finished by other parts of the driver.
The lock parameter, sent when creating a request queue, is a spinlock that the kernel holds when executing the request method. For this reason, the request function runs in an atomic context and must follow the rules for atomic code (it does not need to call functions that can cause sleep, etc.). This lock also ensures that no other requests for the device will be added to the queue while the request function is running.
Calling the function that processes the request queue is asynchronous relative to the actions of any userspace process and no assumptions about the process in which the respective function is running should be made. Also, it should not be assumed that the buffer provided by a request is from kernel space or user space, any operation that accesses the userspace being erroneous.
Calling the function that processes the request queue is asynchronous relative to the actions of any userspace process and no assumptions about the process in which the respective function is running should be made. Also, it should not be assumed that the buffer provided by a request is from kernel space or user space, any operation that accesses the userspace being erroneous.
Below is presented one of the simplest function of type request_fn_proc:
static void my_block_request(struct request_queue *q)
{
struct request *rq;
struct my_block_dev *dev = q->queuedata;
while (1) {
rq = blk_fetch_request(q);
if (rq == NULL)
break;
if (blk_rq_is_passthrough(rq)) {
printk (KERN_NOTICE "Skip non-fs request\n");
__blk_end_request_all(rq, -EIO);
continue;
}
/* do work */
...
__blk_end_request_all(rq, 0);
}
}
The my_block_request() function contains a while() loop for iterating through the request queue sent as argument. The operations performed within this loop are:
- Read the first request from the queue using blk_fetch_request(). As described in Useful functions for processing request queues section, the blk_fetch_request() function retrieves the first item from the request queue and starts the request.
- If the function returns NULL, it has reached the end of the request queue (there is no remaining request to be processed) and exits my_block_request().
- A block device can receive calls which do not transfer data blocks (e.g. low level operations on the disk, instructions referring to special ways of accessing the device). Most drivers do not know how to handle these requests and return an error.
- To return an error, __blk_end_request_all() function is called, -EIO being the second argument.
- The request is processed according to the needs of the associated device.
- The request ends. In this case, __blk_end_request_all() function is called in order to complete the request entirely. If all request sectors have been processed, the __blk_end_request() function is used.
struct bio 结构
Each struct request structure is an I/O block request, but may come from combining more independent requests from a higher level. The sectors to be transferred for a request can be scattered into the main memory but they always correspond to a set of consecutive sectors on the device. The request is represented as a series of segments, each corresponding to a buffer in memory. The kernel can combine requests that refer to adjacent sectors but will not combine write requests with read requests into a single struct request structure.
A struct request structure is implemented as a linked list of struct bio structures together with information that allows the driver to retain its current position while processing the request.
The struct bio structure is a low-level description of a portion of a block I/O request.
struct bio {
//...
struct gendisk *bi_disk;
unsigned int bi_opf; /* bottom bits req flags, top bits REQ_OP. Use accessors. */
//...
struct bio_vec *bi_io_vec; /* the actual vec list */
//...
struct bvec_iter bi_iter;
/...
void *bi_private;
//...
};
In turn, the struct bio structure contains a bi_io_vec vector of struct bio_vec structures. It consists of the individual pages in the physical memory to be transferred, the offset within the page and the size of the buffer. To iterate through a struct bio structure, we need to iterate through the vector of struct bio_vec and transfer the data from every physical page. To simplify vector iteration, the struct bvec_iter structure is used. This structure maintains information about how many buffers and sectors were consumed during the iteration. The request type is encoded in the bi_opf field; to determine it, use the bio_data_dir() function.
创建struct bio结构
Two functions can be used to create a struct bio structure:
- bio_alloc(): allocates space for a new structure; the structure must be initialized;
- bio_clone(): makes a copy of an existing struct bio structure; the newly obtained structure is initialized with the values of the cloned structure fields;the buffers are shared with the struct bio structurethat has been cloned so that access to the buffers has to be done carefully to avoid access to the same memory area from the two clones;
Both functions return a new struct bio structure.
提交struct bio结构
Usually, a struct bio structure is created by the higher levels of the kernel (usually the file system). A structure thus created is then transmitted to the I/O subsystem that gathers more struct bio structures into a request.
For submitting a struct bio structure to the associated I/O device driver, the submit_bio() function is used. The function receives as argument an initialized struct bio structure that will be added to a request from the request queue of an I/O device. From that queue, it can be processed by the I/O device driver using a specialized function.
等待struct bio结构的完成
Submitting a struct bio structure to a driver has the effect of adding it to a request from the request queue from where it will be further processed. Thus, when the submit_bio() function returns, it is not guaranteed that the processing of the structure has finished. If you want to wait for the processing of the request to be finished, use the submit_bio_wait() function.
To be notified when the processing of a struct bio structure ends (when we do not use submit_bio_wait() function), the bi_end_io field of the structure should be used. This field specifies the function that will be called at the end of the struct bio structure processing. You can use the bi_private field of the structure to pass information to the function.
初始化struct bio结构
Once a struct bio structure has been allocated and before being transmitted, it must be initialized.
Initializing the structure involves filling in its important fields. As mentioned above, the bi_end_io field is used to specify the function called when the processing of the structure is finished. The bi_private field is used to store useful data that can be accessed in the function pointed by bi_end_io.
The bi_opf field specifies the type of operation. Use the bio_set_op_attrs to initialize the type of operation.
struct bio *bio = bio_alloc(GFP_NOIO, 1);
//...
bio->bi_disk = bdev->bd_disk;
bio->bi_iter.bi_sector = sector;
bio_set_op_attrs(bio, REQ_OP_READ, 0);
bio_add_page(bio, page, size, offset);
//...
In the code snippet above we specified the block device to which we sent the following: struct bio structure, startup sector, operation (REQ_OP_READ or REQ_OP_WRITE) and content. The content of a struct bio structure is a buffer described by: a physical page, the offset in the page and the size of the bufer. A page can be assigned using the alloc_page() call.
如何使用struct bio结构的内容
To use the content of a struct bio structure, the structure’s support pages must be mapped to the kernel address space from where they can be accessed. For mapping /unmapping, use the kmap_atomic and the kunmap_atomic macros.
static void my_block_transfer(struct my_block_dev *dev, size_t start,
size_t len, char *buffer, int dir);
static int my_xfer_bio(struct my_block_dev *dev, struct bio *bio)
{
int i;
struct bio_vec bvec;
struct bvec_iter i;
int dir = bio_data_dir(bio);
/* Do each segment independently. */
bio_for_each_segment(bvec, bio, i) {
sector_t sector = i.bi_sector;
char *buffer = kmap_atomic(bvec.bv_page);
unsigned long offset = bvec.bv_offset;
size_t len = bvec.bv_len;
/* process mapped buffer */
my_block_transfer(dev, sector, len, buffer + offset, dir);
kunmap_atomic(buffer);
}
return 0;
}
As it can be seen from the example above, iterating through a struct bio requires iterating through all of its segments. A segment (struct bio_vec) is defined by the physical address page, the offset in the page and its size.
To simplify the processing of a struct bio, use the bio_for_each_segment macrodefinition. It will iterate through all segments, and will also update global information stored in an iterator (struct bvec_iter) such as the current sector as well as other internal information (segment vector index, number of bytes left to be processed, etc.) .
You can store information in the mapped buffer, or extract information.
In case request queues are used and you needed to process the requests at struct bio level, use the rq_for_each_segment macrodefinition instead of the bio_for_each_segment macrodefinition. This macrodefinition iterates through each segment of each struct bio structure of a struct request structure and updates a struct req_iterator structure. The struct req_iterator contains the current struct bio structure and the iterator that traverses its segments.
struct bio_vec bvec;
struct req_iterator iter;
rq_for_each_segment(bvec, req, iter) {
sector_t sector = iter.iter.bi_sector;
char *buffer = kmap_atomic(bvec.bv_page);
unsigned long offset = bvec.bv_offset;
size_t len = bvec.bv_len;
int dir = bio_data_dir(iter.bio);
my_block_transfer(dev, sector, len, buffer + offset, dir);
kunmap_atomic(buffer);
}
在bio等级设置请求队列
The function blk_init_queue() may specify a function to be used to process requests sent to the driver. The function receives as argument the request queue as queries and carries out processing at struct request level.
If, for flexibility reasons, it is needed to specify a function that carries out processing at struct bio structure level, the function blk_queue_make_request() in conjunction with the blk_alloc_queue() function should be used.
Below is a typical example of initializing a function that carries out processing at struct bio structure level:
// the declaration of the function that carries out processing
// :c:type:`struct bio` structures
static void my_make_request(struct request_queue *q, struct bio *bio);
// ...
// queue creation
dev->queue = blk_alloc_queue (GFP_KERNEL);
if (dev->queue == NULL) {
printk(KERN_ERR "cannot allocate block device queue\n");
return -ENOMEM;
}
// the registration of the function that carries out processing
// :c:type:`struct bio` structures
blk_queue_make_request(dev->queue, my_make_request);
dev->queue->queuedata = dev;
/*
* SO2 - Block device drivers lab (#7)
* Linux - Exercise #1, #2, #3, #6 (RAM Disk)
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/genhd.h>
#include <linux/fs.h>
#include <linux/blkdev.h>
#include <linux/bio.h>
#include <linux/vmalloc.h>
MODULE_DESCRIPTION("Simple RAM Disk");
MODULE_AUTHOR("SO2");
MODULE_LICENSE("GPL");
#define KERN_LOG_LEVEL KERN_ALERT
#define MY_BLOCK_MAJOR 240
#define MY_BLKDEV_NAME "mybdev"
#define MY_BLOCK_MINORS 1
#define NR_SECTORS 128
#define KERNEL_SECTOR_SIZE 512
/* TODO 6/0: use bios for read/write requests */
#define USE_BIO_TRANSFER 0
static struct my_block_dev {
spinlock_t lock;
struct request_queue *queue;
struct gendisk *gd;
u8 *data;
size_t size;
} g_dev;
static int my_block_open(struct block_device *bdev, fmode_t mode)
{
return 0;
}
static void my_block_release(struct gendisk *gd, fmode_t mode)
{
}
static const struct block_device_operations my_block_ops = {
.owner = THIS_MODULE,
.open = my_block_open,
.release = my_block_release
};
static void my_block_transfer(struct my_block_dev *dev, sector_t sector,
unsigned long len, char *buffer, int dir)
{
unsigned long offset = sector * KERNEL_SECTOR_SIZE;
/* check for read/write beyond end of block device */
if ((offset + len) > dev->size)
return;
/* TODO 3/4: read/write to dev buffer depending on dir */
if (dir == 1) /* write */
memcpy(dev->data + offset, buffer, len);
else
memcpy(buffer, dev->data + offset, len);
}
/* to transfer data using bio structures enable USE_BIO_TRANFER */
#if USE_BIO_TRANSFER == 1
static void my_xfer_request(struct my_block_dev *dev, struct request *req)
{
/* TODO 6/10: iterate segments */
struct bio_vec bvec;
struct req_iterator iter;
rq_for_each_segment(bvec, req, iter) {
sector_t sector = iter.iter.bi_sector;
unsigned long offset = bvec.bv_offset;
size_t len = bvec.bv_len;
int dir = bio_data_dir(iter.bio);
char *buffer = kmap_atomic(bvec.bv_page);
printk(KERN_LOG_LEVEL "%s: buf %8p offset %lu len %u dir %d\n", __func__, buffer, offset, len, dir);
/* TODO 6/3: copy bio data to device buffer */
my_block_transfer(dev, sector, len, buffer + offset, dir);
kunmap_atomic(buffer);
}
}
#endif
static void my_block_request(struct request_queue *q)
{
struct request *rq;
struct my_block_dev *dev = q->queuedata;
while (1) {
/* TODO 2/3: fetch request */
rq = blk_fetch_request(q);
if (rq == NULL)
break;
/* TODO 2/5: check fs request */
if (blk_rq_is_passthrough(rq)) {
printk(KERN_NOTICE "Skip non-fs request\n");
__blk_end_request_all(rq, -EIO);
continue;
}
/* TODO 2/6: print request information */
printk(KERN_LOG_LEVEL
"request received: pos=%llu bytes=%u "
"cur_bytes=%u dir=%c\n",
(unsigned long long) blk_rq_pos(rq),
blk_rq_bytes(rq), blk_rq_cur_bytes(rq),
rq_data_dir(rq) ? 'W' : 'R');
#if USE_BIO_TRANSFER == 1
/* TODO 6/1: process the request by calling my_xfer_request */
my_xfer_request(dev, rq);
#else
/* TODO 3/3: process the request by calling my_block_transfer */
my_block_transfer(dev, blk_rq_pos(rq),
blk_rq_bytes(rq),
bio_data(rq->bio), rq_data_dir(rq));
#endif
/* TODO 2/1: end request successfully */
__blk_end_request_all(rq, 0);
}
}
static int create_block_device(struct my_block_dev *dev)
{
int err;
dev->size = NR_SECTORS * KERNEL_SECTOR_SIZE;
dev->data = vmalloc(dev->size);
if (dev->data == NULL) {
printk(KERN_ERR "vmalloc: out of memory\n");
err = -ENOMEM;
goto out_vmalloc;
}
/* initialize the I/O queue */
spin_lock_init(&dev->lock);
dev->queue = blk_init_queue(my_block_request, &dev->lock);
if (dev->queue == NULL) {
printk(KERN_ERR "blk_init_queue: out of memory\n");
err = -ENOMEM;
goto out_blk_init;
}
blk_queue_logical_block_size(dev->queue, KERNEL_SECTOR_SIZE);
dev->queue->queuedata = dev;
/* initialize the gendisk structure */
dev->gd = alloc_disk(MY_BLOCK_MINORS);
if (!dev->gd) {
printk(KERN_ERR "alloc_disk: failure\n");
err = -ENOMEM;
goto out_alloc_disk;
}
dev->gd->major = MY_BLOCK_MAJOR;
dev->gd->first_minor = 0;
dev->gd->fops = &my_block_ops;
dev->gd->queue = dev->queue;
dev->gd->private_data = dev;
snprintf(dev->gd->disk_name, DISK_NAME_LEN, "myblock");
set_capacity(dev->gd, NR_SECTORS);
add_disk(dev->gd);
return 0;
out_alloc_disk:
blk_cleanup_queue(dev->queue);
out_blk_init:
vfree(dev->data);
out_vmalloc:
return err;
}
static int __init my_block_init(void)
{
int err = 0;
/* TODO 1/5: register block device */
err = register_blkdev(MY_BLOCK_MAJOR, MY_BLKDEV_NAME);
if (err < 0) {
printk(KERN_ERR "register_blkdev: unable to register\n");
return err;
}
/* TODO 2/3: create block device using create_block_device */
err = create_block_device(&g_dev);
if (err < 0)
goto out;
return 0;
out:
/* TODO 1/1: unregister block device in case of an error */
unregister_blkdev(MY_BLOCK_MAJOR, MY_BLKDEV_NAME);
return err;
}
static void delete_block_device(struct my_block_dev *dev)
{
if (dev->gd) {
del_gendisk(dev->gd);
put_disk(dev->gd);
}
if (dev->queue)
blk_cleanup_queue(dev->queue);
if (dev->data)
vfree(dev->data);
}
static void __exit my_block_exit(void)
{
/* TODO 2/1: cleanup block device using delete_block_device */
delete_block_device(&g_dev);
/* TODO 1/1: unregister block device */
unregister_blkdev(MY_BLOCK_MAJOR, MY_BLKDEV_NAME);
}
module_init(my_block_init);
module_exit(my_block_exit);
/*
* SO2 - Block device driver (#8)
* Test suite for exercise #3 (RAM Disk)
*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <fcntl.h>
#include <time.h>
#include <errno.h>
#define NR_SECTORS 128
#define SECTOR_SIZE 512
#define DEVICE_NAME "/dev/myblock"
#define MODULE_NAME "ram-disk"
#define MY_BLOCK_MAJOR "240"
#define MY_BLOCK_MINOR "0"
#define max_elem_value(elem) \
(1 << 8*sizeof(elem))
static unsigned char buffer[SECTOR_SIZE];
static unsigned char buffer_copy[SECTOR_SIZE];
static void test_sector(int fd, size_t sector)
{
int i;
for (i = 0; i < sizeof(buffer) / sizeof(buffer[0]); i++)
buffer[i] = rand() % max_elem_value(buffer[0]);
lseek(fd, sector * SECTOR_SIZE, SEEK_SET);
write(fd, buffer, sizeof(buffer));
fsync(fd);
lseek(fd, sector * SECTOR_SIZE, SEEK_SET);
read(fd, buffer_copy, sizeof(buffer_copy));
printf("test sector %3d ... ", sector);
if (memcmp(buffer, buffer_copy, sizeof(buffer_copy)) == 0)
printf("passed\n");
else
printf("failed\n");
}
int main(void)
{
int fd;
size_t i;
int back_errno;
printf("insmod ../kernel/" MODULE_NAME ".ko\n");
system("insmod ../kernel/" MODULE_NAME ".ko\n");
sleep(1);
printf("mknod " DEVICE_NAME " b " MY_BLOCK_MAJOR " " MY_BLOCK_MINOR "\n");
system("mknod " DEVICE_NAME " b " MY_BLOCK_MAJOR " " MY_BLOCK_MINOR "\n");
sleep(1);
fd = open(DEVICE_NAME, O_RDWR);
if (fd < 0) {
back_errno = errno;
perror("open");
fprintf(stderr, "errno is %d\n", back_errno);
exit(EXIT_FAILURE);
}
srand(time(NULL));
for (i = 0; i < NR_SECTORS; i++)
test_sector(fd, i);
close(fd);
sleep(1);
printf("rmmod " MODULE_NAME "\n");
system("rmmod " MODULE_NAME "\n");
return 0;
}
/*
* SO2 Lab - Block device drivers (#7)
* Linux - Exercise #4, #5 (Relay disk - bio)
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include <linux/genhd.h>
#include <linux/blkdev.h>
MODULE_AUTHOR("SO2");
MODULE_DESCRIPTION("Relay disk");
MODULE_LICENSE("GPL");
#define KERN_LOG_LEVEL KERN_ALERT
#define PHYSICAL_DISK_NAME "/dev/vdb"
#define KERNEL_SECTOR_SIZE 512
#define BIO_WRITE_MESSAGE "def"
/* pointer to physical device structure */
static struct block_device *phys_bdev;
static void send_test_bio(struct block_device *bdev, int dir)
{
struct bio *bio = bio_alloc(GFP_NOIO, 1);
struct page *page;
char *buf;
/* TODO 4/3: fill bio (bdev, sector, direction) */
bio->bi_disk = bdev->bd_disk;
bio->bi_iter.bi_sector = 0;
bio_set_op_attrs(bio, dir, 0);
page = alloc_page(GFP_NOIO);
bio_add_page(bio, page, KERNEL_SECTOR_SIZE, 0);
/* TODO 5/5: write message to bio buffer if direction is write */
if (dir == REQ_OP_WRITE) {
buf = kmap_atomic(page);
memcpy(buf, BIO_WRITE_MESSAGE, strlen(BIO_WRITE_MESSAGE));
kunmap_atomic(buf);
}
/* TODO 4/3: submit bio and wait for completion */
printk(KERN_LOG_LEVEL "[send_test_bio] Submiting bio\n");
submit_bio_wait(bio);
printk(KERN_LOG_LEVEL "[send_test_bio] Done bio\n");
/* TODO 4/3: read data (first 3 bytes) from bio buffer and print it */
buf = kmap_atomic(page);
printk(KERN_LOG_LEVEL "read %02x %02x %02x\n", buf[0], buf[1], buf[2]);
kunmap_atomic(buf);
bio_put(bio);
__free_page(page);
}
static struct block_device *open_disk(char *name)
{
struct block_device *bdev;
/* TODO 4/5: get block device in exclusive mode */
bdev = blkdev_get_by_path(name, FMODE_READ | FMODE_WRITE | FMODE_EXCL, THIS_MODULE);
if (IS_ERR(bdev)) {
printk(KERN_ERR "blkdev_get_by_path\n");
return NULL;
}
return bdev;
}
static int __init relay_init(void)
{
phys_bdev = open_disk(PHYSICAL_DISK_NAME);
if (phys_bdev == NULL) {
printk(KERN_ERR "[relay_init] No such device\n");
return -EINVAL;
}
send_test_bio(phys_bdev, REQ_OP_READ);
return 0;
}
static void close_disk(struct block_device *bdev)
{
/* TODO 4/1: put block device */
blkdev_put(bdev, FMODE_READ | FMODE_WRITE | FMODE_EXCL);
}
static void __exit relay_exit(void)
{
/* TODO 5/1: send test write bio */
send_test_bio(phys_bdev, REQ_OP_WRITE);
close_disk(phys_bdev);
}
module_init(relay_init);
module_exit(relay_exit);
来源:CSDN
作者:久许
链接:https://blog.csdn.net/jiuweideqixu/article/details/104147914