LINUX block device driver development

1. Introduction to block device driver

  • Block device drivers provide access to block oriented devices
  • Block device drivers generally transmit data in a random manner, and the data always has a fixed size block.
  • A typical block device is a disk drive device.

Introduction to block device driver

  • The block device driver interface is relatively complex, and the character device interface is simple.
  • Block device driver has a great impact on the performance of the whole system. Speed and efficiency are the key issues to be considered in the design of block device driver.
  • The system uses the optimized management (merging and reordering) of buffer and access request to improve the system performance.
  • Block device drivers cannot be accessed directly through nodes.
  • It needs to be mounted to a directory through the file system. open when mount is executed. release when Umount

Difference between block device and character device

LINUX MTD system hierarchy


2. I/O operation characteristics of block equipment

  • Block devices can only accept input and return output in blocks. Character devices are in bytes.
  • The block device has a corresponding buffer for I/O requests. Character devices read and write directly.
  • Block devices can be accessed randomly. Character devices can only read and write sequentially.

3. Structure of block device drive

3.1block_device_operations structure

In the block device driver, there is one similar to that in the character device driver file_operations Structural block_device_operations Structure is a collection of operations on block devices:
struct block_device_operations
{
	int(*open)(struct inode *, struct file*);  //open
	int(*release)(struct inode *, struct file*);  //release
	int(*ioctl)(struct inode *, struct file *, unsigned, unsignedlong);  //ioctl
	int(*media_changed)(struct gendisk*);  //Media changed
	int(*revalidate_disk)(struct gendisk*);  //Make media valid
	int(*getgeo)(struct block_device *, struct hd_geometry*);//Populate drive information
	struct module *owner; //Module owner
	......
};

Analysis block_ device_ Main members of operations structure:

1,Opening and releasing
int(*open)(struct inode *, struct file*);  //open
int(*release)(struct inode *, struct file*);  //release

2,I/O control
int(*ioctl)(struct inode *, struct file *, unsigned cmd, unsigned long);
  
3,Medium change(For mobile devices)
int(*media_changed)(struct gendisk*);

4,Make media valid
int(*revalidate_disk)(struct gendisk*);
   Called in response to a media change, it gives the driver an opportunity to do the necessary work to prepare the new media.

5,Get drive information
int(*getgeo)(struct block_device *, struct hd_geometry*);
    This function populates an array based on the geometric information of the driver hd_geometry Structure, hd_geometry The structure contains information such as magnetic head, sector and cylinder.

6,Module pointer
struct module *owner;
Usually initialized to THIS_MODULE

The above 3-5 are standard requests for block devices, which are processed by the LINUX block device layer.

3.2 gendisk structure

In the Linux kernel, the gendisk structure is used to represent an independent disk device (or partition)

struct gendisk
{
	int major; /* Main equipment No */
	int first_minor;  /*1st secondary equipment No*/
	int minors; /* The maximum number of secondary devices. If it cannot be partitioned, it is 1*/
	char disk_name[32]; /* Equipment name */
	struct block_device_operations *fops; /*Block device operation structure*/
	struct request_queue *queue;  /*Request queue*/
	void *private_data;  /*Private data*/
	sector_t capacity; /*Number of sectors, 512 bytes is one sector*/
	...
}

Linux provides a set of functions to operate gendisk:

1,distribution gendisk
struct gendisk *alloc_disk(int minors);
parameter minors Is the number of partitions

2,increase gendisk
void add_disk(struct gendisk *gd);
gendisk After the structure is allocated, the system cannot use the disk. You need to call the above function to register the disk device.
Special attention should be paid to the right add_disk()The call of must occur after the initialization of the driver is completed and can respond to the request of the disk.

3,release gendisk
 When a disk is no longer needed, it should be released using the following function gendisk. 
void del_gendisk(struct gendisk *gd);

4,set up gendisk Capacity of
void set_capacity(struct gendisk *disk,sector_t size);

Regardless of the real sector size of the physical device, the sectors in which the kernel interacts with the block device driver are 512 bytes.

##3.3 request and bio structure
Sbh(p280)
Sbh(285)
1. Request queue: struct request_queue. A block device corresponds to a request queue. All IO requests of the block device will be added to the request queue.
2. Request: struct request. An IO request is pending.
3. Block IO:struct bio. Usually, one bio corresponds to one IO request, and the IO scheduling algorithm can combine consecutive BIOS into one request, so one request can contain multiple BIOS
4,bio_ Operation of VEC data block. A bio can contain one data block or several data blocks. Members of bio can be_ VEC traversal: bio_for_each_segment(bvec, bio, i)

4. Registration and cancellation of block device driver

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

Major is the primary device number. If it is 0, it will be dynamically allocated, and the return value is the primary device number. If registration fails, a negative value will be returned. Integer numbers with major other than 0 are registered statically.

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

5. Loading and unloading functions of block device driver

Load function to complete:
1. Register block device driver
2. Allocate and initialize request queue, bind request queue and request function.
3. Allocate and initialize gendisk, assign values to the major, fops, queue and other members of gendisk, and finally add gendisk

Uninstall function to complete:
1. Clear the request queue.
2. Delete gendisk and references to gendisk
3. Delete the reference to the block device and unregister the block device driver.

6. Opening and releasing of block equipment

A simple block device driver may not provide the open() and release() functions.
When a node references a block device, inode - > I_ bdev->bd_ Disk contains a pointer to the associated gendisk structure. So the private of gendisk_ Private assigned to file by data_ data, private_data also preferably points to the device structure XXX describing the device_ Pointer to dev, as shown in the code listing on the following page:
The open() and release() methods should also set the state of the driver and hardware. These tasks may include starting and stopping the disk, locking a removable device, allocating DMA buffer, etc.

static int xxx_open(struct inode *inode, struct file *filp)
{
	struct xxx_dev *dev = inode->i_bdev->bd_disk->private_data;
	filp->private_data = dev; //Assign private to file_ data
	...
	return 0;
}

7. ioctl function driven by block device

Like character device drivers, block devices can include an ioctl() function to provide I/O control over the device. In fact, the high-level block device layer code processes the vast majority of ioctl(). Therefore, there is usually no need to implement many ioctl commands in specific block device drivers, and the implementation method is the same as that of character devices.

int xxx_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg) {
	switch (cmd) {
		case HDIO_GETGEO:
		  ...... 
	}
}

8. Block device driven I/O request processing

There are two ways to process I/O requests driven by block devices:
1) Using a request queue - the driver must provide a request function - for devices that are not randomly accessible, such as mechanical disks
2) Do not use request queue - the driver must provide a manufacturing request function - for devices that can be accessed randomly, such as Ram disk, FLASH, SD card, etc

8.1 use request queue for mechanical disk devices,

The prototype of block device driver request function is:

 int request(struct request_queue_t *queue );

This function cannot be called by the driver itself. It will call this function only when the kernel thinks it is time for the driver to handle operations such as reading and writing to the device.

static int xxx_request(struct request_queue_t *queue )
{
	(1)Gets the first outstanding request in the queue
	(2)If not a file system request,Direct clear 
	(3)To process this request, read and write the device
	(4)Clear this request.
	(5)Notify the object waiting for this request,This request has been completed
}

##8.2 do not use request queue
The driver must provide a "manufacturing request" function (Note: This is not a request function). The prototype of the "manufacturing request" function is:

typedef int (make_request_fn) (request_queue_t* q, struct bio* bio);

The first parameter is "request queue". The block corresponding to the request for the required operation is bio. bio represents one or more buffers to be transmitted
This function either transfers directly or redirects the request to another device
After processing, bio should be used_ Endio() notifies the end of processing

void bio_endio(struct bio* bio, unsigned int byetes, int error);

Bytes is the number of bytes that have been transferred. If the request cannot be completed, assign the error code to the error parameter. This function returns 0 whether the IO is processed successfully or not. If a non-zero value is returned, the bio will be submitted again.

static int xxx_make_request(request_queue_t* q, struct bio* bio)
{
     1)Get this bio The starting position of the request in the block device memory 
      2)Start traversing this bio Each of the bio_vec And map its virtual address
      3)judge bio Direction of request processing ,And read and write according to the direction
      4)Finish each bio_vec Should be kmap The mapped address is cancelled 
      5)Report processing ends after processing 
}

#Summary
The I/O operation mode of block device is quite different from that of character device, so request is introduced_ Queue, request, bio and other data structures. In the I/O operation of the whole block device, the "request" runs through all the time. The I/O operation of the character device is carried out directly without detours. The I/O operation of the block device will be queued and integrated. The task of driver is to process requests, and the queuing and integration of requests are solved by I/O scheduling algorithm. Therefore, the core of block device driver is request processing function or "manufacturing request" function. Although block still exists in the block device driver_ device_ Operations structure and its member functions, but it no longer contains member functions such as read-write, but only functions unrelated to specific read-write, such as open, release and I/O control. The structure of the block device driver is quite complex, but fortunately, the block device is not as comprehensive as the character device. It is usually a storage device, and the main body of the driver has been provided by the Linux kernel. For a specific hardware system, the work involved by the driver engineer is often only to write a small amount of code that directly interacts with the hardware.

Keywords: Linux

Added by UKlee on Tue, 07 Dec 2021 17:18:26 +0200