1. 程式人生 > >Linux塊設備IO子系統(一) _驅動模型(轉載)

Linux塊設備IO子系統(一) _驅動模型(轉載)

validate per ring span () VC isp 設備 思路

  塊設備是Linux三大設備之一,其驅動模型主要針對磁盤,Flash等存儲類設備,塊設備(blockdevice)是一種具有一定結構的隨機存取設備,對這種設備的讀寫是按(所以叫塊設備)進行的,他使用緩沖區來存放暫時的數據,待條件成熟後,從緩存一次性寫入設備或者從設備一次性讀到緩沖區。作為存儲設備,塊設備驅動的核心問題就是哪些page->segment->block->sector與哪些sector有數據交互,本文以3.14為藍本,探討內核中的塊設備驅動模型。

框架

  下圖是Linux中的塊設備模型示意圖,應用層程序有兩種方式訪問一個塊設備:/dev和文件系統掛載點,前者和字符設備一樣,通常用於配置,後者就是我們mount之後通過文件系統直接訪問一個塊設備了。

  1. read()系統調用最終會調用一個適當的VFS函數(read()-->sys_read()-->vfs_read()),將文件描述符fd和文件內的偏移量offset傳遞給它。
  2. VFS會判斷這個SCI的處理方式,如果訪問的內容已經被緩存在RAM中(磁盤高速緩存機制),就直接訪問,否則從磁盤中讀取
  3. 為了從物理磁盤中讀取,內核依賴映射層mapping layer,即上圖中的磁盤文件系統
  4. 確定該文件所在文件系統的塊的大小,並根據文件塊的大小計算所請求數據的長度。本質上,文件被拆成很多塊,因此內核需要確定請求數據所在的塊
  5. 映射層調用一個具體的文件系統的函數,這個層的函數會訪問文件的磁盤節點,然後根據邏輯塊號確定所請求數據在磁盤上的位置。
  6. 內核利用通用塊層(generic block layer)啟動IO操作來傳達所請求的數據,通常,一個IO操作只針對磁盤上一組連續的塊。
  7. IO調度程序根據預先定義的內核策略將待處理的IO進行重排和合並
  8. 塊設備驅動程序向磁盤控制器硬件接口發送適當的指令,進行實際的數據操作

技術分享圖片

塊設備 VS 字符設備

  

作為一種存儲設備,和字符設備相比,塊設備有以下幾種不同:

字符設備塊設備
1byte 塊,硬件塊各有不同,但是內核都使用512byte描述
順序訪問 隨機訪問
沒有緩存,實時操作 有緩存,不是實時操作
一般提供接口給應用層 塊設備一般提供接口給文件系統
是被用戶程序調用 由文件系統程序調用

此外,大多數情況下,磁盤控制器都是直接使用DMA方式進行數據傳送。

IO調度

  就是電梯算法。我們知道,磁盤是的讀寫是通過機械性的移動磁頭來實現讀寫的,理論上磁盤設備滿足塊設備的隨機讀寫的要求,但是出於節約磁盤,提高效率的考慮,我們希望當磁頭處於某一個位置的時候,一起將最近需要寫在附近的數據寫入,而不是這寫一下,那寫一下然後再回來,IO調度就是將上層發下來的IO請求的順序進行重新排序以及對多個請求進行合並,這樣就可以實現上述的提高效率、節約磁盤的目的。這種解決問題的思路使用電梯算法,一個運行中的電梯,一個人20樓->1樓,另外一個人15->5樓,電梯不會先將第一個人送到1樓再去15樓接第二個人將其送到5樓,而是從20樓下來,到15樓的時候停下接人,到5樓將第二個放下,最後到達1樓,一句話,電梯算法最終服務的優先順序並不按照按按鈕的先後順序。Linux內核中提供了下面的幾種電梯算法來實現IO調度:

  • No-op I/O scheduler只實現了簡單的FIFO的,只進行最簡單的合並,比較適合基於Flash的存儲
  • Anticipatory I/O scheduler推遲IO請求(大約幾個微秒),以期能對他們進行排序,獲得更高效率
  • Deadline I/O scheduler試圖把每次請求的延遲降到最低,同時也會對BIO重新排序,特別適用於讀取較多的場合,比如數據庫
  • CFQ I/O scheduler為系統內所有的任務分配均勻的IO帶寬,提供一個公平的工作環境,在多媒體環境中,能保證音視頻及時從磁盤中讀取數據,是當前內核默認的調度器

我們可以通過內核傳參的方式指定使用的調度算法

kernel elevator=deadline

或者,使用如下命令改變內核調度算法

echo SCHEDULER > /sys/block/DEVICE/queue/scheduler

Page->Segment->Block->Sector VS Sector

VS左面(Page->Segment->Block->Sector)是數據交互中的內存部分,Page就是內存映射的最小單位; Segment就是一個Page中我們要操作的一部分,由若幹個相鄰的塊組成; Block(塊)是邏輯上的進行數據存取的最小單位,是文件系統的抽象,邏輯塊的大小是在格式化的時候確定的, 一個 Block 最多僅能容納一個文件(即不存在多個文件同一個block的情況)。如果一個文件比block小,他也會占用一個block,因而block中空余的空間會浪費掉。而一個大文件,可以占多個甚至數十個成百上千萬的block。Linux內核要求 Block_Size = Sector_Size * (2的n次方),並且Block_Size <= 內存的Page_Size(頁大小), 如ext2 fs的block缺省是4k。若block太大,則存取小文件時,有空間浪費的問題;若block太小,則硬盤的 Block 數目會大增,而造成 inode 在指向 block 的時候的一些搜尋時間的增加,又會造成大文件讀寫方面的效率較差,block是VFS和文件系統傳送數據的基本單位。block對應磁盤上的一個或多個相鄰的扇區,而VFS將其看成是一個單一的數據單元,塊設備的block的大小不是唯一的,創建一個磁盤文件系統時,管理員可以選擇合適的扇區的大小,同一個磁盤的幾個分區可以使用不同的塊大小。此外,對塊設備文件的每次讀或寫操作是一種"原始"訪問,因為它繞過了磁盤文件系統,內核通過使用最大的塊(4096)執行該操作。Linux對內存中的block會被進一步劃分為Sector,Sector是硬件設備傳送數據的基本單位,這個Sector就是512byte,和物理設備上的概念不一樣,如果實際的設備的sector不是512byte,而是4096byte(eg SSD),那麽只需要將多個內核sector對應一個設備sector即可

VS右邊是物理上的概念,磁盤中一個Sector是512Byte,SSD中一個Sector是4K

核心結構與方法簡述

核心結構

  • gendisk是一個物理磁盤或分區在內核中的描述
  • block_device_operations描述磁盤的操作方法集,block_device_operations之於gendisk,類似於file_operations之於cdev
  • request_queue對象表示針對一個gendisk對象的所有請求的隊列,是相應gendisk對象的一個域
  • request表示經過IO調度之後的針對一個gendisk(磁盤)的一個"請求",是request_queue的一個節點。多個request構成了一個request_queue
  • bio表示應用程序對一個gendisk(磁盤)原始的訪問請求,一個bio由多個bio_vec,多個bio經過IO調度和合並之後可以形成一個request。
  • bio_vec描述的應用層準備讀寫一個gendisk(磁盤)時需要使用的內存頁page的一部分,即上文中的"段",多個bio_vec和bio_iter形成一個bio
  • bvec_iter描述一個bio_vec中的一個sector信息

技術分享圖片

核心方法

  • set_capacity()設置gendisk對應的磁盤的物理參數
  • blk_init_queue()分配+初始化+綁定一個有IO調度的gendisk的requst_queue,處理函數是**void (request_fn_proc) (struct request_queue *q);**類型
  • blk_alloc_queue() 分配+初始化一個沒有IO調度的gendisk的request_queue,
  • blk_queue_make_request()綁定處理函數到一個沒有IO調度的request_queue,處理函數函數是void (make_request_fn) (struct request_queue q, struct bio bio);類型
  • __rq_for_each_bio()遍歷一個request中的所有的bio
  • bio_for_each_segment()遍歷一個bio中所有的segment
  • rq_for_each_segment()遍歷一個request中的所有的bio中的所有的segment

  最後三個遍歷算法都是用在request_queue綁定的處理函數中,這個函數負責對上層請求的處理。

核心結構與方法詳述

gendisk

  同樣是面向對象的設計方法,Linux內核使用gendisk對象描述一個系統的中的塊設備,類似於Windows系統中的磁盤分區和物理磁盤的關系,OS眼中的磁盤都是邏輯磁盤,也就是一個磁盤分區,一個物理磁盤可以對應多個磁盤分區,在Linux中,這個gendisk就是用來描述一個邏輯磁盤,也就是一個磁盤分區。

技術分享圖片
 1 struct gendisk {
 2     /* major, first_minor and minors are input parameters only,
 3      * don‘t use directly.  Use disk_devt() and disk_max_parts().
 4      */
 5     int major;            /* major number of driver */
 6     int first_minor;
 7     int minors;                     /* maximum number of minors, =1 for
 8                                          * disks that can‘t be partitioned. */
 9 
10     char disk_name[DISK_NAME_LEN];    /* name of major driver */
11     char *(*devnode)(struct gendisk *gd, umode_t *mode);
12 
13     unsigned int events;        /* supported events */
14     unsigned int async_events;    /* async events, subset of all */
15 
16     /* Array of pointers to partitions indexed by partno.
17      * Protected with matching bdev lock but stat and other
18      * non-critical accesses use RCU.  Always access through
19      * helpers.
20      */
21     struct disk_part_tbl __rcu *part_tbl;
22     struct hd_struct part0;
23 
24     const struct block_device_operations *fops;
25     struct request_queue *queue;
26     void *private_data;
27 
28     int flags;
29     struct device *driverfs_dev;  // FIXME: remove
30     struct kobject *slave_dir;
31 
32     struct timer_rand_state *random;
33     atomic_t sync_io;        /* RAID */
34     struct disk_events *ev;
35 #ifdef  CONFIG_BLK_DEV_INTEGRITY
36     struct blk_integrity *integrity;
37 #endif
38     int node_id;
39 };
linux-4.0\include\linux\genhd.h

--major: 驅動的主設備號
--first_minor: 第一個次設備號
--minors: 次設備號的數量,即允許的最大分區的數量,1表示不允許分區
--disk_name: 設備名稱
--part_tbl: 分區表數組首地址
--part0: 第一個分區,相當於part_tbl->part[0]
--fops: 操作方法集指針
--queue: 請求對象指針
--private_data: 私有數據指針
--driverfs_dev: 表示這是一個設備

gendisk是一個動態分配的結構體,所以不要自己手動來分配,而是使用內核相應的API來分配,其中會做一些初始化的工作

struct gendisk *alloc_disk(int minors);

//註冊gendisk類型對象到內核
void add_disk(struct gendisk *disk);

//從內核註銷gendisk對象
void del_gendisk(struct gendisk *gp);

上面幾個API是一個塊設備驅動中必不可少的部分,下面的兩個主要是用來內核對於設備管理用的,通常不要驅動來實現

//對gendisk的引用計數+1
struct kobject *get_disk(struct gendisk *disk);

//對gendisk的引用計數-1
void put_disk(struct gendisk *disk);

這兩個API最終回調用kobject *get_disk() 和kobject_put()來實現對設備的引用計數

block_device_operations

和字符設備一樣,如果使用/dev接口訪問塊設備,最終就會回調這個操作方法集的註冊函數

 1 //linux-4.0\include\linux\blkdev.h
 2 struct block_device_operations {
 3     int (*open) (struct block_device *, fmode_t);
 4     void (*release) (struct gendisk *, fmode_t);
 5     int (*rw_page)(struct block_device *, sector_t, struct page *, int rw);
 6     int (*ioctl) (struct block_device *, fmode_t, unsigned, unsigned long);
 7     int (*compat_ioctl) (struct block_device *, fmode_t, unsigned, unsigned long);
 8     long (*direct_access)(struct block_device *, sector_t,void **, unsigned long *pfn, long size);
 9     unsigned int (*check_events) (struct gendisk *disk,unsigned int clearing);
10         /* ->media_changed() is DEPRECATED, use ->check_events() instead */
11     int (*media_changed) (struct gendisk *);
12     void (*unlock_native_capacity) (struct gendisk *);
13     int (*revalidate_disk) (struct gendisk *);
14     int (*getgeo)(struct block_device *, struct hd_geometry *);
15     /* this callback is with swap_lock and sometimes page table lock held */
16     void (*swap_slot_free_notify) (struct block_device *, unsigned long);
17     struct module *owner;
18 };

struct block_device_operations
--open: 當應用層打開一個塊設備的時候被回調
--release: 當應用層關閉一個塊設備的時候被回調
--compat_ioctl: 相當於file_operations裏的compat_ioctl,不過塊設備的ioctl包含大量的標準操作,所以在這個接口實現的操作很少
--direct_access:在移動塊設備中測試介質是否改變的方法,已經過時,同樣的功能被check_event()實現 (熱插拔)
--getgeo: 即get geometry,獲取驅動器的幾何信息,獲取到的信息會被填充在一個hd_geometry結構中
--owner: 模塊所屬,通常填THIS_MODULE

request_queue

  每一個gendisk對象都有一個request_queue對象,前文說過,塊設備有兩種訪問接口,一種是/dev下,一種是通過文件系統,後者經過IO調度在這個gendisk->request_queue上增加請求,最終回調與request_queue綁定的處理函數,將這些請求向下變成具體的硬件操作

 1 struct request_queue {
 2     /*
 3      * Together with queue_head for cacheline sharing
 4      */
 5     struct list_head    queue_head;
 6     struct request        *last_merge;
 7     struct elevator_queue    *elevator;
 8     int            nr_rqs[2];    /* # allocated [a]sync rqs */
 9     int            nr_rqs_elvpriv;    /* # allocated rqs w/ elvpriv */
10 
11        .............................
12 };

  --queue_head:請求隊列的鏈表頭

  --elevator:請求隊列使用的IO調度算法,通過內核啟動參數選擇:kernel elevator=deadline

request_queue_t和gendisk一樣需要使用內核API來分配並初始化,裏面大量的成員不要直接操作, 此外, 請求隊列如果要正常工作還需要綁定到一個處理函數中, 當請求隊列不為空時, 處理函數會被回調, 這就是塊設備驅動中處理請求的核心部分!

從驅動模型的角度來說, 塊設備主要分為兩類需要IO調度的和不需要IO調度的, 前者包括磁盤, 光盤等, 後者包括Flash, SD卡等, 為了保證模型的統一性 , Linux中對這兩種使用同樣的模型但是通過不同的API來完成上述的初始化綁定

有IO調度類設備API

//初始化+綁定
struct request_queue *blk_init_queue(request_fn_proc *rfn, spinlock_t *lock)  

無IO調度類設備API

//初始化
struct request_queue *blk_alloc_queue(gfp_t gfp_mask) 

//綁定
 void blk_queue_make_request(struct request_queue *q, make_request_fn *mfn)

共用API

針對請求隊列的操作是塊設備的一個核心任務, 其實質就是對請求隊列操作函數的編寫, 這個函數的主要功能就是從請求隊列中獲取請求並根據請求進行相應的操作 內核中已經提供了大量的API供該函數使用

//清除請求隊列, 通常在卸載函數中使用
void blk_cleanup_queue(struct request_queue *q)  

//從隊列中去除請求
blkdev_dequeue_request(struct request *rq)

//提取請求
struct request *blk_fetch_request(struct request_queue *q)

//從隊列中去除請求
struct request *blk_peek_request(struct request_queue *q)

//啟停請求隊列, 當設備進入到不能處理請求隊列的狀態時,應通知通用塊層
void blk_stop_queue(struct request_queue *q) 
void blk_start_queue(struct request_queue *q) 

request

技術分享圖片
 1 struct request {
 2     struct list_head queuelist;
 3     union {
 4         struct call_single_data csd;
 5         unsigned long fifo_time;
 6     };
 7 
 8     struct request_queue *q;
 9     struct blk_mq_ctx *mq_ctx;
10 
11     u64 cmd_flags;
12     enum rq_cmd_type_bits cmd_type;
13     unsigned long atomic_flags;
14 
15     int cpu;
16 
17     /* the following two fields are internal, NEVER access directly */
18     unsigned int __data_len;    /* total data len */
19     sector_t __sector;        /* sector cursor */
20 
21     struct bio *bio;
22     struct bio *biotail;
23 
24     /*
25      * The hash is used inside the scheduler, and killed once the
26      * request reaches the dispatch list. The ipi_list is only used
27      * to queue the request for softirq completion, which is long
28      * after the request has been unhashed (and even removed from
29      * the dispatch list).
30      */
31     union {
32         struct hlist_node hash;    /* merge hash */
33         struct list_head ipi_list;
34     };
35 
36     /*
37      * The rb_node is only used inside the io scheduler, requests
38      * are pruned when moved to the dispatch queue. So let the
39      * completion_data share space with the rb_node.
40      */
41     union {
42         struct rb_node rb_node;    /* sort/lookup */
43         void *completion_data;
44     };
45     
46     /*
47      * Three pointers are available for the IO schedulers, if they need
48      * more they have to dynamically allocate it.  Flush requests are
49      * never put on the IO scheduler. So let the flush fields share
50      * space with the elevator data.
51      */
52     union {
53         struct {
54             struct io_cq        *icq;
55             void            *priv[2];
56         } elv;
57 
58         struct {
59             unsigned int        seq;
60             struct list_head    list;
61             rq_end_io_fn        *saved_end_io;
62         } flush;
63     };     
64      
65     struct gendisk *rq_disk;
66     struct hd_struct *part;
67     unsigned long start_time;
68     .............................
69 };
View Code

  queuelist:將這個request掛接到鏈表的節點

  q:這個request從屬的request_queue

  bio:組成這個request的bio鏈表頭指針

  biotail:組成這個request的bio鏈表的為指針

  hash:內核hash表頭指針

bio

  bio用來描述單一的I/O請求,它記錄了一次I/O操作所必需的相關信息,如用於I/O操作的數據緩存位置,,I/O操作的塊設備起始扇區,是讀操作還是寫操作等等

  下列結構體定義並未完整定義。

 1 struct bio {
 2      struct bio              *bi_next;       /* request queue link */
 3      struct block_device     *bi_bdev;
 4      unsigned long           bi_flags;       /* status, command, etc */
 5      unsigned long           bi_rw;          /* bottom bits READ/WRITE,
 6                                               * top bits priority
 7                                               */
 8      struct bvec_iter        bi_iter;
 9      unsigned int            bi_phys_segments;
10      unsigned int            bi_seg_front_size;
11      unsigned int            bi_seg_back_size;
12      atomic_t                bi_remaining;
13      bio_end_io_t            *bi_end_io;
14      void                    *bi_private;
15      unsigned short          bi_vcnt;        /* how many bio_vec‘s */
16      unsigned short          bi_max_vecs;    /* max bvl_vecs we can hold */
17      struct bio_vec        *bi_io_vec;    /* the actual vec list */
18      struct bio_vec          bi_inline_vecs[0];
19 };

bi_next:指向鏈表中下一個bio的指針bi_next
bi_rw:低位表示讀寫READ/WRITE, 高位表示優先級
bi_vcnt:bio對象包含bio_vec對象的數目
bi_max_vecs:這個bio能承載的最大的io_vec的數目
bi_io_vec:實際的vec列表
bi_inline_vecs[0]:表示這個bio包含的bio_vec變量的數組,即這個bio對應的某一個page中的一"段"內存

bio_vec

  描述指定page中的一塊連續的區域,在bio中描述的就是一個page中的一個"段"(segment)

1 struct bio_vec {
2     struct page    *bv_page;//描述的page
3     unsigned int    bv_len;//描述的長度
4     unsigned int    bv_offset;//描述的起始地址偏移量
5 };

bio_iter

  用於記錄當前bvec被處理的情況,用於遍歷bio

1 struct bvec_iter {
2     sector_t        bi_sector;    /* device address in 512 byte sectors */
3     unsigned int    bi_size;    /* residual I/O count */
4     unsigned int    bi_idx;        /* current index into bvl_vec */
5     unsigned int    bi_bvec_done;    /* number of bytes completed in current bvec */
6 };

__rq_for_each_bio()

  遍歷一個request的每一個bio

1 #define __rq_for_each_bio(_bio, rq)    2     if ((rq->bio))            3         for (_bio = (rq)->bio; _bio; _bio = _bio->bi_next)

bio_for_each_segment()

  遍歷一個bio中的每一個segment

1 #define bio_for_each_segment(bvl, bio, iter)                2     __bio_for_each_segment(bvl, bio, iter, (bio)->bi_iter)

rq_for_each_segment()

  遍歷一個request中的每一個segment

1 #define rq_for_each_segment(bvl, _rq, _iter)            2     __rq_for_each_bio(_iter.bio, _rq)            3         bio_for_each_segment(bvl, _iter.bio, _iter.iter)

小結

  遍歷request_queue,綁定函數的一個必要的工作就是將request_queue中的數據取出, 所以遍歷是必不可少的, 針對有IO調度的設備, 我們需要從中提取請求再繼續操作, 對於沒有IO調度的設備, 我們可以直接從request_queue中提取bio進行操作, 這兩種處理函數的接口就不一樣,下面的例子是對LDD3中的代碼進行了修剪而來的,相應的API使用的是3.14版本,可以看出這兩種模式的使用方法的不同。

sbull_init
└── setup_device
├──sbull_make_request
│ ├──sbull_xfer_bio
│ └──sbull_transfer
└──sbull_full_request
├──blk_fetch_request
└──sbull_xfer_request
├── __rq_for_each_bio
└── sbull_xfer_bio
└──sbull_transfer

  1 /*
  2  * Handle an I/O request.
  3  * 實現扇區的讀寫
  4  */
  5 static void sbull_transfer(struct sbull_dev *dev, unsigned long sector,unsigned long nsect, char *buffer, int write)
  6 {
  7     unsigned long offset = sector*KERNEL_SECTOR_SIZE;
  8     unsigned long nbytes = nsect*KERNEL_SECTOR_SIZE;
  9     if (write)
 10         memcpy(dev->data + offset, buffer, nbytes);
 11     else
 12         memcpy(buffer, dev->data + offset, nbytes);
 13 }
 14 /*
 15  * Transfer a single BIO.
 16  */
 17 static int sbull_xfer_bio(struct sbull_dev *dev, struct bio *bio)
 18 {
 19     struct bvec_iter i; //用來遍歷bio_vec對象
 20     struct bio_vec bvec;
 21     sector_t sector = bio->bi_iter.bi_sector;
 22     /* Do each segment independently. */
 23     bio_for_each_segment(bvec, bio, i) { //bvec會遍歷bio中每一個bio_vec對象 
 24         char *buffer = __bio_kmap_atomic(bio, i, KM_USER0);
 25         sbull_transfer(dev, sector, bio_cur_bytes(bio)>>9 ,buffer, bio_data_dir(bio) == WRITE);
 26         sector += bio_cur_bytes(bio)>>9;
 27         __bio_kunmap_atomic(bio, KM_USER0);
 28     }
 29     return 0; /* Always "succeed" */
 30 }
 31 
 32 /*
 33  * Transfer a full request.
 34  */
 35 static int sbull_xfer_request(struct sbull_dev *dev, struct request *req)
 36 {
 37     struct bio *bio;
 38     int nsect = 0;
 39     
 40     __rq_for_each_bio(bio, req) { 
 41         sbull_xfer_bio(dev, bio);
 42         nsect += bio->bi_size/KERNEL_SECTOR_SIZE;
 43     }
 44     return nsect;
 45 }
 46 
 47 /*
 48  * Smarter request function that "handles clustering".*/
 49 static void sbull_full_request(struct request_queue *q)
 50 {
 51     struct request *req;
 52     int nsect;
 53     struct sbull_dev *dev ;
 54     int i = 0;
 55     while ((req = blk_fetch_request(q)) != NULL) {
 56         dev = req->rq_disk->private_data;
 57         nsect = sbull_xfer_request(dev, req);
 58         __blk_end_request(req, 0, (nsect<<9)); 
 59         printk ("i = %d\n", ++i);
 60     }
 61 }
 62 
 63 //The direct make request version
 64 static void sbull_make_request(struct request_queue *q, struct bio *bio)
 65 {
 66     struct sbull_dev *dev = q->queuedata;
 67     int status;
 68 
 69     status = sbull_xfer_bio(dev, bio);
 70     bio_endio(bio, status);  
 71 
 72     return;
 73 }
 74 /*
 75  * The device operations structure.
 76  */
 77 static struct block_device_operations sbull_ops = {
 78     .owner  = THIS_MODULE,
 79     .open   = sbull_open,
 80     .release= sbull_release,
 81     .getgeo = sbull_getgeo,
 82 };
 83 
 84 /*
 85  * Set up our internal device.
 86  */
 87 static void setup_device(struct sbull_dev *dev, int which)
 88 {
 89     /*
 90      * Get some memory.
 91      */
 92     memset (dev, 0, sizeof (struct sbull_dev));
 93     dev->size = nsectors * hardsect_size;
 94     dev->data = vmalloc(dev->size);
 95     
 96         
 97     /*
 98      * The I/O queue, depending on whether we are using our own
 99      * make_request function or not.
100      */
101     switch (request_mode) {
102     case RM_NOQUEUE:
103         dev->queue = blk_alloc_queue(GFP_KERNEL);
104         blk_queue_make_request(dev->queue, sbull_make_request);
105         break;
106 
107     case RM_FULL:
108         dev->queue = blk_init_queue(sbull_full_request, &dev->lock);
109         break;
110     }
111     dev->queue->queuedata = dev;
112     /*
113      * And the gendisk structure.
114      */
115     dev->gd = alloc_disk(SBULL_MINORS);
116     dev->gd->major = sbull_major;
117     dev->gd->first_minor = which*SBULL_MINORS;
118     dev->gd->fops = &sbull_ops;
119     dev->gd->queue = dev->queue;
120     dev->gd->private_data = dev;
121     snprintf (dev->gd->disk_name, 32, "sbull%c", which + a);
122     set_capacity(dev->gd, nsectors*(hardsect_size/KERNEL_SECTOR_SIZE));
123     add_disk(dev->gd);
124     return;
125 }
126 
127 static int __init sbull_init(void)
128 {
129     int i;
130     /*
131      * Get registered.
132      */
133     sbull_major = register_blkdev(sbull_major, "sbull");
134     /*
135      * Allocate the device array, and initialize each one.
136      */
137     Devices = (struct sbull_dev *)kmalloc(ndevices*sizeof (struct sbull_dev), GFP_KERNEL);
138     for (i = 0; i < ndevices; i++) 
139         setup_device(Devices + i, i);
140     return 0;
141 }

原文鏈接:

Linux塊設備IO子系統(一) _驅動模型 - Abnor - 博客園 https://www.cnblogs.com/xiaojiang1025/p/6500557.html

Linux塊設備IO子系統(一) _驅動模型(轉載)