天天看點

SSD卡驅動中trim指令的實作原理

有關trim指令的簡介 可以看下http://blog.csdn.net/yuzhihui_no1/article/details/46519701

這裡就大概的說下驅動中對trim指令的實作吧,由于對公司代碼的保密性,這裡就不沾貼代碼了,就大概的說下實作原理;

條理會有點亂,也借此機會複習整理下一些相關知識點;

首先是塊裝置的基本架構:隊列 queue和綁定隊列的函數

        先說說一般的塊裝置架構:

        dev->queue = blk_init_queue(request,  &dev->lock);

        每個塊裝置都需要一個請求隊列(後面會講有的不需要隊列),這是因為對塊裝置請求資料的傳人和輸出發生的時間,與核心請求的時間相差太大了,是以需要隊列來對一些請求做處理。比如合并req、調整req的請求順序之類的(後面有時間會注重分析io請求的合并和排序)。

        從這裡就可以看出,為什麼要有隊列?就是因為請求的傳人和輸出時間與核心的請求時間相差太大。反之,如果塊裝置請求資料的傳入和傳出時間,與核心請求的時間相差不大,是不是就意味着可以不用隊列?

        答案是肯定的,SSD的驅動就是不需要隊列的。

        原因:1、SSD裝置的響應時間和請求時間相差不大(其實對CPU來說還是比較大的);

                    2、也是最主要的,或者說最本質的,最根本的,就是SSD是電子裝置,而普通盤是機械裝置。普通硬碟讀寫速度之是以慢的原因是機械臂的移動耗費的時間,是以就有電梯算法之類的,來減少機械臂的移動。而SSD卡是電子裝置,不存在尋址(機械臂移動就是為了尋址)耗費時間,可以類比下記憶體,也可以類比下hash算法,或者再類比下查字典,其實原理大緻一樣的,不需要順序的一個一個的去尋址;

        介于上面的原因,就不需要對io請求進行排序,或者合并之類的(驅動中請求合并還是會有的,但不是因為尋址的原因,而是為了提高讀性能,和記憶體管理中的預讀頁是一樣的原理)

         下面接着說普通塊裝置的隊列和請求函數,當請求隊列生成的時候,請求函數就已經和隊列綁定了。而且還贈送了一個把自旋鎖。當request()函數被調用時,這把鎖會由核心來控制,也就是說request()函數是在原子上下文中運作的(是以定義request函數時,要牢記遵守原子上下文的規則)。

         request()函數有自旋鎖時,可以防止核心再給他安排其他的請求;request()函數内要開鎖時,一定要記得先禁止其他線程對隊列和包含資料的通路,而request()函數傳回時,必須要得到該鎖。

         對普通塊裝置來說request()函數就承載了,裝置的讀寫請求的處理了。線上程調用該函數傳回前,不需要把所有的請求都執行完,但request函數必須有傳回響應,而且還得保證能完成所有的請求;

        接下來說說SSD驅動中使用的無請求隊列的塊裝置架構:

        首先還是自己建立一個隊列:queue = blk_alloc_queue(GFP_NOIO);//該函數會告訴塊裝置子系統,驅動使用定制的make_request函數。該隊列不儲存請求;

        然後還是綁定請求函數:blk_queue_make_request(queue,  make_request);

        最後是構造一個請求函數:make_request(q, bio);

       從上層一直往下走的話,到block層會有__generic_make_request(struct bio *bio)函數,該函數有兩行代碼可以引出塊裝置驅動的運作;

       q = bdev_get_queue(bio->bi_bdev);//從塊裝置結構體中擷取到queue

       ret = q->make_request_fn(q, bio);//開始執行隊列綁定的處理函數了

現在來說說trim指令了,在驅動中首先會判斷這個bio請求是否是丢棄的bio,看bio->bi_rw辨別 ,如果是丢棄的,就是trim指令了。

        接着就調用get_bi_sector(bio)來擷取到bio->bi_sector第一個請求的sector,再根據bio->bi_size 來得到最後一個請求的sector,其中的一些對齊轉換,根據不同需求再做決定。最後就是實際的丢棄操作了;

        目前已經得到了要丢棄的資料範圍,循環去執行丢棄操作,分析丢棄一個lba的動作,其他的循環丢棄就可以了;

        丢棄一個lba動作:首先擷取到ftl映射表中對應pba位址,如果是空的,表示該位置上實際就沒有資料,那就不需要操作,直接傳回;

        如果lba對應的ftl映射表pba位址,已經存在,則修改映射表,使lba對應的pba為空,接着就需要把實際的pba(開始映射表對應的pba)設定為無效。各個驅動設計的不一樣,但總的思路就是把pba标記為無效,然後再根據該pba所在的sb是什麼狀态,再做一些狀态的調整和處理。

        總之,最後就會把該sb從其他連結清單中拿出來,挂載到待擦除連結清單上,接着就會喚醒gc線程去做gc工作。而gc線程做的工作是搜尋需要回收的sb,還有就是回收sb。從連結清單上的sb中去判斷每個pba是否有效,如果有效就讀取pba上的資料,然後再寫入到新的位址上。一直循環,直到把sb上的所有有效的pba資料搬運完,然後就開始真正的擦除該sb,擦除後再把該sb挂到free_block連結清單上。

         最後就調用下 bio_end(bio, bio->bi_size, 0)函數傳回,trim指令就這麼愉快的結束了,但背景gc線程還在跑。

         轉載位址:http://blog.csdn.net/yuzhihui_no1/article/details/51325360