Linux內核中的塊設備驅動
在現代計算機系統中,塊設備驅動程序是操作系統內核中一個重要的組成部分,它用于管理硬盤、閃存等存儲設備。Linux內核是一個開源、自由的操作系統內核,驅動程序源代碼公開可用,可以幫助開發人員更好地了解Linux內核塊設備驅動的工作原理。
一、塊設備驅動程序的基礎概念
1、塊設備驅動程序的作用
塊設備驅動程序是一種負責管理塊設備(如硬盤、NVMe快閃存儲器等)的軟件組件,它負責實現塊設備的讀寫操作、磁盤緩存的管理等。塊設備驅動程序使得操作系統內核和各種應用程序都可以通過標準的接口訪問塊設備。
2、Linux內核塊設備驅動程序中的主要數據結構
在Linux內核中,塊設備驅動程序主要包含以下數據結構:
(1)bio
I/O操作描述符(I/O descriptor,簡稱bio)是Linux內核中塊設備驅動程序中最基本的數據結構。它描述了一個塊設備操作的所有細節和參數,包括讀寫操作的塊數、讀寫的數據指針、物理地址、緩沖區大小、操作類型等。bio數據結構的重要性在于它是向塊設備發送I/O操作的載體。
(2)request_queue
request_queue是塊設備驅動程序中的另一個重要的數據結構,它管理著一組bio數據結構。request_queue中可以管理多個bio請求,并且可以高效地組織和處理這些請求。當一個新的I/O請求到來時,request_queue會將其和之前未完成的請求進行合并,以提高I/O操作的效率。
(3)gendisk設備結構
gendisk設備結構是Linux內核中塊設備驅動程序的子結構之一,它是塊設備驅動程序和塊設備層之間的接口數據結構。每個塊設備(如硬盤、固態硬盤等)都對應著一個gendisk設備結構,以便于被塊設備驅動程序和塊設備層管理和訪問。gendisk設備結構中包括了塊設備的主要屬性,例如塊大小、可訪問的扇區數、分區信息、分塊信息等。
3、Linux內核塊設備驅動程序的主要工作流程
Linux內核中塊設備驅動程序的主要工作流程如下:
(1)初始化塊設備驅動程序
在Linux內核中,塊設備驅動程序的初始化通常是在模塊加載時完成的(即init函數中完成初始化)。塊設備驅動程序的初始化包括注冊塊設備驅動程序、創建gendisk設備結構以及建立request_queue等。
(2)接受并處理I/O請求
塊設備驅動程序通常是被塊設備層調用的,以提供塊設備的讀寫服務,塊設備層會將I/O請求通過request_queue發送給塊設備驅動程序,塊設備驅動程序會在這里接收到并處理I/O請求。
(3)處理I/O請求
塊設備驅動程序主要實現I/O請求的處理,其處理流程通常包括以下幾個步驟:
- 將待處理的I/O請求從request_queue中取出。
- 將請求轉化為通用的bio數據結構。
- 將bio數據結構添加到硬件設備的操作隊列中。
- 等待硬件設備完成I/O請求。
- 在I/O請求完成后,將bio數據結構從硬件設備的操作隊列中移除,并修改I/O請求的狀態。
(4)釋放塊設備驅動程序資源
在Linux內核中,塊設備驅動程序資源的釋放實際上是由模塊卸載時完成的(即exit函數中完成資源釋放)。在資源釋放時,塊設備驅動程序需要注銷注冊設備、刪除gendisk設備結構以及銷毀request_queue等。
二、塊設備驅動程序源代碼分析
1、塊設備驅動程序的編寫
Linux內核中塊設備驅動程序涉及到很多I/O操作,因此需要仔細編寫。下面分別簡要介紹塊設備驅動程序的讀、寫和I/O請求處理函數的編寫方法。
(1)塊設備驅動程序的讀函數編寫
塊設備驅動程序的讀函數(read函數)通常是異步的,即它不會等待傳輸完成。當執行一個讀請求時,驅動程序中的read函數會創建一個讀取請求,并將其添加到request_queue隊列中等待處理。一旦請求被添加到request_queue中,驅動程序就會返回給調用者一個代表讀請求正在處理的值。
(2)塊設備驅動程序的寫函數編寫
塊設備驅動程序的寫函數(write函數)與讀函數類似,也是異步的,與讀函數不同的是它需要等待寫操作完成。當執行一個寫請求時,驅動程序中的write函數會創建一個寫請求,并將其添加到request_queue隊列中等待處理。一旦請求被添加到request_queue中,驅動程序就會等待寫操作完成后將控制權限返回給調用者。
(3)塊設備驅動程序的I/O請求處理函數編寫
塊設備驅動程序中最重要的函數是I/O請求處理函數,它被用來接收和處理所有接收到的I/O請求。當新的I/O請求到來時,塊設備層會將請求通過request_queue發送給塊設備驅動程序中的I/O請求處理函數進行處理。
I/O請求處理函數主要包括以下幾個步驟:
- 判斷請求類型,并將其應用到相應的數據結構中。
- 將請求轉化為通用的bio數據結構。
- 將bio數據結構添加到硬件設備的操作隊列中。
- 等待硬件設備完成I/O請求。
- 在I/O請求完成后,將bio數據結構從硬件設備的操作隊列中移除,并修改I/O請求的狀態。
2、塊設備驅動程序源代碼
下面為讀者介紹Linux內核塊設備驅動程序的一個例子(內核版本為4.19.0),該程序負責管理SATA磁盤設備的讀寫操作。
(1)塊設備驅動程序的頭文件
#include
#include
#include
#include
(2)塊設備驅動程序的聲明
/* Major number */
static int dev_major = 0;
/* Number of volumes to manage */
static int volumes_count = 3;
/* Default block size for devices (2^logical) */
static int block_size = 512;
/* Sector size of the devices */
static int sector_size = 512;
static int mydrv_open(struct block_device *bdev, fmode_t mode);
static void mydrv_release(struct gendisk *gd, fmode_t mode);
static int mydrv_ioctl(struct block_device *bdev, fmode_t mode,
unsigned int cmd, unsigned long arg);
static int mydrv_getgeo(struct block_device *bdev, struct hd_geometry *geo);
static struct block_device_operations mydrv_ops = {
.owner = THIS_MODULE,
.open = mydrv_open,
.release = mydrv_release,
.ioctl = mydrv_ioctl,
.getgeo = mydrv_getgeo,
};
static struct request_queue *mydrv_queue = NULL;
/* Function to handle requests */
static void mydrv_request(struct request_queue *q);
(3)塊設備驅動程序的模塊加載及卸載函數
/* Module initialization */
static int __init mydrv_init(void)
{
int ret = -1;
struct gendisk *disk = NULL;
/* Register block device */
dev_major = register_blkdev(dev_major, "mydrv");
if (dev_major <= 0) {
pr_err("mydrv: block device registration failed\n");
goto err_reg;
}
/* Create request queue */
mydrv_queue = blk_alloc_queue(GFP_KERNEL);
if (!mydrv_queue) {
pr_err("mydrv: request queue creation failed\n");
goto err_queue;
}
/* Set request function */
blk_queue_make_request(mydrv_queue, mydrv_request);
/* Initialize volumes */
if (!init_volumes(&mydrv_queue, &disk)) {
pr_err("mydrv: volume initialization failed\n");
goto err_vols;
}
/* Create block device */
if (!add_disk(disk)) { pr_err("mydrv: disk registration failed\n");
goto err_disk;
}
/* Set block device operations */
disk->fops = &mydrv_ops;
/* Success */
return 0;
/* Error handling */
err_disk:
if (disk) {
del_gendisk(disk);
put_disk(disk);
}
err_vols:
blk_cleanup_queue(mydrv_queue);
err_queue:
unregister_blkdev(dev_major, "mydrv");
err_reg:
return ret;
}
/* Module exit */
static void __exit mydrv_exit(void)
{
unregister_blkdev(dev_major, "mydrv");
blk_cleanup_queue(mydrv_queue);
cleanup_volumes();
}
(4)塊設備驅動程序的操作函數
及請求處理函數。
```c
/* Open operation */
static int mydrv_open(struct block_device *bdev, fmode_t mode)
{
return 0;
}
/* Release operation */
static void mydrv_release(struct gendisk *gd, fmode_t mode)
{
}
/* Control operation */
static int mydrv_ioctl(struct block_device *bdev, fmode_t mode,
unsigned int cmd, unsigned long arg)
{
return -ENOTTY;
}
/* Geometry function */
static int mydrv_getgeo(struct block_device *bdev, struct hd_geometry *geo)
{
struct mydrv_volume *vol = NULL;
int ret = -1;
/* Get volume information */
vol = bdev->bd_disk->private_data;
if (!vol) {
pr_err("mydrv: invalid volume information\n");
goto out;
}
/* Set geometry */
geo->heads = vol->heads;
geo->sectors = vol->sectors;
geo->cylinders = vol->cylinders;
ret = 0;
out:
return ret;
}
/* Request function */
static void mydrv_request(struct request_queue *q)
{
struct request *req = NULL;
struct bio *bio = NULL;
struct mydrv_volume *vol = NULL;
/* Process all requests in the queue */
while ((req = blk_fetch_request(q)) != NULL) {
/* Check request type */
if (req->cmd_type != REQ_TYPE_FS) {
pr_err("mydrv: wrong request type\n");
__blk_end_request_all(req, -EIO);
continue;
}
/* Process all bio requests */
__rq_for_each_bio(bio, req) {
/* Get volume */
vol = bio->bi_bdev->bd_disk->private_data;
if (!vol) {
pr_err("mydrv: invalid volume information\n");
__blk_end_request_all(req, -EIO);
continue;
}
/* Process bio request */
switch (bio_rw(bio)) {
case READ:
mydrv_read(vol, bio);
break;
case WRITE:
mydrv_write(vol, bio);
break;
default:
pr_err("mydrv: wrong I/O operation\n");
__blk_end_request_all(req, -EIO);
break;
}
}
/* End request */
__blk_end_request_all(req, 0);
}
}
三、總結
塊設備驅動程序是Linux內核中非常重要的組件之一,它負責處理塊設備的讀寫操作,為操作系統內核和各種應用程序提供標準接口。在塊設備驅動程序的編寫過程中,需要仔細處理讀、寫和I/O請求處理函數的實現,以實現塊設備的最佳操作效率。
希望大家通過閱讀本文,了解和掌握塊設備驅動程序的工作原理,進一步提高對Linux內核的理解和認知。