娇小w搡bbbb搡bbb,《第一次の人妻》,中国成熟妇女毛茸茸,边啃奶头边躁狠狠躁视频免费观看

本節目的:


通過分析2.6內核下的塊設備驅動框架,知道如何來寫驅動


1、之前我們學的都是字符設備驅動,先來回憶一下


字符設備驅動:


當我們的應用層讀寫(read()/write())字符設備驅動時,是按字節/字符來讀寫數據的,期間沒有任何緩存區,因為數據量小,不能隨機讀取數據,例如:按鍵、LED、鼠標、鍵盤等。


2、接下來本屆開始學習塊設備驅動


塊設備:


塊設備是i/o設備中的一類,當我們的應用層對該設備讀寫時,是按扇區大小來讀寫數據,若讀寫的數據小于扇區的大小,就會需要緩存區,可以隨機讀寫設備的任意位置處的數據,例如 普通文件(*txt,*.c等),硬盤,U盤,SD卡


3、塊設備結構:


段(Segments):由若干個塊組成。是Linux內存管理機制中一個內存頁或者內存頁的一部分

塊(Blocks):由Linux制定對內核或文件系統等數據處理的基本單位。通常由1個或多個扇區組成。(對Linux操作系統而言)

扇區(Sectors):塊設備的基本單位。通常在512字節到32768字節之間,默認512字節。

 

4、我們以txt文件為例,來簡要分析一下塊設備流程:


比如:當我們要寫一個很小的數據到txt文件某個位置時,由于塊設備寫的數據是按扇區為單位,但又不能破快txt文件里其他位置,那么就引入了一個“緩存區”,將所有數據讀到緩存區里,然后修改緩存數據,再將整個數據放入txt文件對應的某個扇區中,當我們對txt文件多次寫入很小的數據的話,那么就會重復不斷地對扇區讀出,寫入,這樣會浪費很多時間在讀/寫硬盤上,所以內核提供了一個隊列的機制,在沒有關閉txt文件之前,會將讀寫請求進行優化,排序,合并等操作,從而提高訪問硬盤的效率


(PS:內核中是通過elv_merge()函數實現將隊列優化,排序,合并,后面會分析到)


5、接下來開始分析塊設備框架


當我們對一個*.txt寫入數據時,文件系統會轉換為對塊設備上扇區的訪問,也就是調用ll_rw_block()函數,從這個函數開始進入了設備層。


5.1 先來分析ll_rw_block()函數(/fs/buffer.c)


void ll_rw_block(int rw, int nr, struct buffer_head *bhs[])

//rw:讀寫標志位, nr:bhs[]長度, bhs[]:要讀寫的數據數組

{

int i;

 

for (i = 0; i < nr; i++) {

struct buffer_head *bh = bhs[i];    //獲取nr個buffer_head

... ...

if (rw == WRITE || rw == SWRITE) {

if (test_clear_buffer_dirty(bh)) {

... ...

submit_bh(WRITE, bh);    //提交WRITE寫標志的buffer_head

continue;

}

} else {

if (!buffer_uptodate(bh)) {

... ...

submit_bh(rw, bh);    //提交其它標志的buffer_head

continue;

}

}

unlock_buffer(bh);

}

}

 

其中buffer_head結構體,就是我們的緩沖區描述符,存放緩存區的各種信息,結構體如下所示:


struct buffer_head {

unsigned long b_state;         //緩沖區狀態標識

struct buffer_head *b_this_page;    //頁面中的緩沖區

struct page *b_page;         //存儲緩沖區位于哪個頁面

sector_t b_blocknr;             //邏輯塊號

size_t b_size;             //塊大小

char *b_data;             //頁面中的緩沖區

 

struct block_device *b_bdev;        //塊設備,來表示一個獨立的磁盤設備

 

bh_end_io_t *b_end_io;         //I/O完成方法

 

  void *b_private;             //完成方法數據

 

struct list_head b_assoc_buffers;   //相關映射鏈表

 

struct address_space *b_assoc_map;

 

atomic_t b_count;             //緩沖區使用計數

};

 

5.2 然后進入submit_bd()中,submit_bh()函數如下:


int submit_bh(int rw, struct buffer_head * bh)

{

struct bio *bio;    //定義一個bit(block input output),也就是塊設備i/o

... ...

bio = bio_alloc(GFP_NOIO, 1);    //分配bio

    /* 根據buffer_head(bh)構造bio */

bio->bi_sector = bh->b_blocknr * (bh->b_size >> 9);   //存放邏輯塊號

bio->bi_bdev = bh->b_bdev;                            //存放對應的塊設備

bio->bi_io_vec[0].bv_page = bh->b_page;               //存放緩沖區所在的物理頁面

bio->bi_io_vec[0].bv_len = bh->b_size;                //存放扇區的大小

bio->bi_io_vec[0].bv_offset = bh_offset(bh);         //存放扇區中以字節為單位的偏移量

 

bio->bi_vcnt = 1;                                    //計數值

bio->bi_idx = 0;                                     //索引值

bio->bi_size = bh->b_size;                           //存放扇區的大小

 

bio->bi_end_io = end_bio_bh_io_sync;                 //設置i/o回調函數

bio->bi_private = bh;                                //指向哪個緩沖區

 

... ...

submit_bio(rw, bio);                                 //提交bio

    ... ...

}

submit_hd()函數就是通過bh來構造bio,然后調用submit_bio()提交bio。


5.3 submit_bio()函數如下:


void submit_bio(int rw, struct bio *bio)

{

... ...

generic_make_request(bio);

}

最終調用generic_make_request();把bio數據提交到相應塊設備的請求隊列中,generic_make_request()函數主要是實現對bio的提交處理


5.4 generic_make_request()函數如下所示:


void generic_make_request(struct bio *bio)

{

if (current->bio_tail) {    //current->bio_tail不為空,表示有bio正在提交

*(current->bio_tail) = bio;    //將當前的bio放到之前的bio->bi_next里面

bio->bi_next = NULL;        //更新bio->bi_next=0

current->bio_tail = &bio->bi_next;    //然后將當前的bio->bi_next放到current->bio_tail里,使下次的bio就會放到當前bio->bi_next里面了

return;

}

BUG_ON(bio->bi_next);

do {

current->bio_list = bio->bi_next;

if (bio->bi_next == NULL)

current->bio_tail = ¤t->bio_list;

else

bio->bi_next = NULL;

__generic_make_request(bio);    //提交bio

bio = current->bio_list;

} while (bio);

current->bio_tail = NULL; /* deactivate */

}

從上面的注釋和代碼分析到,只有當第一次進入generic_make_request()時,current->bio_tail為NULL,才能調用__generic_make_request()。


__generic_make_request()首先由bio對應的block_device獲取申請隊列q,然后要檢查對應的設備是不是分區,如果是分區的話要將扇區地址進行重新計算,最后調用q的成員函數make_request_fn完成bio的遞交。


5.5 __generic_make_request()函數如下所示:


static inline void __generic_make_request(struct bio *bio)

{

request_queue_t *q;

    ... ...

int ret;

... ...

do {

... ...

q = bdev_get_queue(bio->bi_bdev);//通過bio->bi_dev獲取申請隊列q

... ...

ret = q->make_request_fn(q, bio);//提交申請隊列q和bio

} while (ret);

}

這個q->make_request_fn()又是什么函數?到底做了什么,我們搜索一下它在哪里被初始化的,如下圖,搜索make_request_fn,它在blk_queue_make_request()函數中被初始化mfn這個參數

繼續搜索blk_queue_make_request,找到它被調用,賦入的mfn參數是什么?


如下圖,找到它在blk_init_queue_node()函數中被調用

最終q->make_request_fn()執行的是__make_request()函數


5.6 我們來看看__make_request()函數,對提交的申請隊列q和bio做了什么


static int __make_request(request_queue_t *q, struct bio *bio)

{

struct request *req;    //塊設備本身的隊列

    ... ...

    //(1)將之前的申請隊列q和傳入bio,通過排序,合并在本身的req隊列中

el_ret = elv_merge(q, &req, bio);

... ...

init_request_from_bio(req, bio);    //合并失敗,單獨將bio放入req隊列

 

add_request(q, req);                //單獨將之前的申請隊列q放入req隊列

    ... ...

__generic_unplug_device(q);        //(2)執行申請隊列的處理函數

}

1)上面的elv_merge()函數,就是內核中的電梯算法(elevator merge),它就類似我們坐的電梯,通過一個標志,向上或者向下


比如申請隊列中有以下6個申請:


4(in),2(out),5(in),3(out),6(in),1(out)  //其中in:寫出隊列到扇區,out:讀入隊列


最后執行下來,就會排序合并,先寫出4,5,6隊列,再讀入1,2,3隊列


2)上面的__generic_unplug_device()函數如下:

 

void __generic_unplug_device(request_queue_t *q)

{

if (unlikely(blk_queue_stopped(q)))

return;

 

if (!blk_remove_plug(q))

return;

 

q->request_fn(q);

}

最終執行q的成員request_fn()函數,執行申請隊列的處理函數


6、本節框架分析總結,如下圖所示:

7、其中q->request_fn是一個request_fn_proc結構體,如下圖所示:

7.1 這個申請隊列q->request_fn又是怎么來的?


我們參考自帶的塊設備驅動程序driversblockxd.c


在入口函數中發現有這么一句:


static struct request_queue *xd_queue;    //定義一個申請隊列xd_queue


xd_queue = blk_init_queue(do_xd_request, &xd_lock);    //分配一個申請隊列

 

其中blk_init_queue()函數原型如下所示:


request_queue_t *blk_init_queue(request_fn_proc *rfn, spinlock_t *lock)

// *rfn:request_fn_proc結構體,用來執行申請隊列中的處理函數

// *lock:隊列訪問權限的自旋鎖(spinlock),該鎖需要通過DEFINE_SPINLOCK()函數來定義

顯然就是do_xd_request()掛到xd_queue->request_fn里。然后返回這個request隊列


7.2 我們再看看申請隊列的處理函數do_xd_request()是如何處理的,函數如下:


static void do_xd_request (request_queue_t * q)

{

struct request *req;

 

if (xdc_busy)

return;

 

while ((req = elv_next_request(q)) != NULL) {  //(1)while獲取申請隊列中的需要處理的申請

int res = 0;

... ...

for (retry = 0; (retry < XD_RETRIES) && !res; retry++)

res = xd_readwrite(rw, disk, req->buffer, block, count);

                      //將獲取申請req的buffer成員 讀寫到disk扇區中,當讀寫失敗返回0,成功返回1

end_request(req, res); //申請隊列中的申請已處理結束,當res=0,表示讀寫失敗

}

}

(1)為什么要while一直獲取?


因為這個q是個申請隊列,里面會有多個申請,之前是使用電梯算法elv_merge()函數合并的,所以獲取也要通過電梯算法elv_next_request()函數獲取。


通過上面代碼和注釋,內核中的申請隊列q最終都是交給驅動處理,由驅動來對扇區讀寫


8、接下來我們就看看driversblockxd.c的入口函數大概流程,是如何創建塊設備驅動的


static DEFINE_SPINLOCK(xd_lock);    //定義一個自旋鎖,用到申請隊列中

static struct request_queue *xd_queue;    //定義一個申請隊列xd_queue

static int __init xd_init(void)    //入口函數

{

if (register_blkdev(XT_DISK_MAJOR, "xd"))  //1.創建一個塊設備,保存在/proc/devices中

goto out1;

    ... ...

    //2.分配一個申請隊列,后面會賦給gendisk結構體的queue成員

xd_queue = blk_init_queue(do_xd_request, &xd_lock);

... ...

for (i = 0; i < xd_drives; i++) {

... ...

        //3.分配一個gendisk結構體,64:次設備號個數,也成為分區個數

struct gendisk *disk = alloc_disk(64);

... ...

        //4.接下來設備gendisk結構體

disk->major = XT_DISK_MAJOR;    //設備主設備號

disk->first_minor = i<<6;       //設置次設備號

... ...

disk->fops = &xd_fops;          //設置塊設備驅動的操作函數

... ...

disk->queue = xd_queue;         //設置queue申請隊列,用于管理該沈北IO申請隊列

... ...

xd_gendisk[i] = disk;

}

    ... ...

for (i = 0; i < xd_drives; i++)

add_disk(xd_gendisk[i]);        //5.注冊gendisk結構體

    ... ...

}

 


其中gendisk(通用磁盤)結構體是用來存儲該設備的硬盤信息,包括請求隊列、分區鏈表和塊設備操作函數集等,結構體如下所示:


struct gendisk {

int major; //設備主設備號

int first_minor;    //起始次設備號

int minors;         //次設備號的數量,也成為分區數量,如果改值為1,表示無法分區

char disk_name[32]; //設備名稱

struct hd_struct **part; //分區表的信息

int part_uevent_suppress;

struct block_device_operations *fops;    //塊設備操作集合

struct request_queue *queue;            //申請隊列,用于管理該設備IO申請隊列的指針

void *private_data;                    //私有數據

sector_t capacity;                    //扇區數,512字節為1個扇區,描述設備容量

    ... ....

};

 

9、所以注冊一個塊設備驅動,需要以下步驟:


1 創建一個塊設備


2 分配一個申請隊列


3 分配一個gendisk結構體


4 設置gendisk結構體的成員


5 注冊gendisk結構體

關鍵字:S3C2440  塊設備驅動  框架 引用地址:S3C2440 塊設備驅動之框架詳細分析

上一篇:S3C2440的Linux啟動過程分析(一)——SC2440處理器結構
下一篇:01-S3C2440學習入門概念+環境搭建

推薦閱讀

油耗政策越來越嚴苛,到2020年,乘用車新車平均油耗要控制在5升/百公里以內。對于車企而言,節能減排,壓力很大。開發電動車是一種思路,因為電動車自帶的“零排放”光環可以稀釋掉不少燃油車的“超標油耗”。如果要在燃油車框架下“做文章”,混動是一劑救命良方,48V微混系統風頭正盛。謂48V系統,本質上是一種低混合動力系統,通俗來講,就是自動啟停系...
40臺由比亞迪印度工廠生產的電動巴士正式交付當地第六大城市海德拉巴。比亞迪電動巴士在印度的保有量提升至108臺,成為當地電動巴士數量最多的汽車品牌。 隨著版圖不斷擴大,比亞迪電動大巴在全球的保有量不斷提升,以比亞迪為代表的中國新能源客車不斷走向海外市場,成為我國電動汽車增長新引擎背后,國產新能源客車在海外市場的競爭力和發展機遇如何? ...
不知從何時開始,“S”對智能手機來說代表著“增強版”。但凡型號用上“S”,就意味著“外觀基本不變,配置進行升級”。上個月剛剛發布的華為Mate Xs折疊屏手機適用了這個定律,如今到了vivo NEX 3S 5G。NEX 3S 5G (左)和NEX 3 5G(右)在諸多旗艦手機中,vivo NEX系列算是我比較期待的產品。原因無它——每年vivo會在NEX系列上帶來一些讓...
3月12日,蓋世汽車于麥格納官方獲悉,其已于日前正式發布全新的eBeam電動車橋技術。該技術具有可擴展性,讓汽車制造商有能力在保證實用性和功能性的前提下實現卡車的電動化。eBeam集成了電池電動或混合動力系統,結構化的設計能滿足車輛的高載荷,同時可實現與之相匹配的持續性功率輸出。麥格納全新eBeam電動車橋據麥格納官介紹,eBeam專為皮卡和輕型商用...

史海拾趣

問答坊 | AI 解惑

關于USB轉并口

最近學習430,但筆記本沒有并口,想用USB轉個并口,然后再下載程序! 哪位有這方面的資料或有意愿做一個情聯系qq:413916089…

查看全部問答∨

燒寫2808的問題

燒寫2808時出現這個提示框,高手看看是怎么回事,謝謝 …

查看全部問答∨

基于Windows CEnet的車載平臺的定制開發.pdf

基于Windows CEnet的車載平臺的定制開發.pdf…

查看全部問答∨

電子電路設計基礎

本帖最后由 dontium 于 2015-1-23 11:38 編輯 電子電路設計基礎 …

查看全部問答∨

測試系統說明

注釋 – 全部時間建立在包括每條通道一項源和測量功能的基礎上。如果不含源或測量,開關時間會更快。   在此應用示例中,計算機通過GPIB總線或TSP-Link發送指令控制兩臺儀器。被測器件(DUT)為10顆LED。每顆LED由2602雙通道數字源表進行 ...…

查看全部問答∨

電源工程師必備常識——電容知識筆記

電容的特性(隔直通交)  電容器是一種能儲存電荷的容器。它是由兩片靠得較近的金屬片,中間再隔以絕緣物質而組成的。按絕緣材料不同,可制成各種各樣的電容器。如:云母、瓷介、紙介、電解電容器等。在構造上,又分為固定電容器和可變電容器。電 ...…

查看全部問答∨

請問該電路我該如何分析

本帖最后由 MONKEY666539 于 2014-6-30 17:14 編輯 D:\\111.jpg 如圖,該電路是準備將輸出電壓控制在[0.2V,2.0V],我不知道0.2V該怎么分析?…

查看全部問答∨

TM4C123的math.h庫問題

大家在使用TMC123時有沒有遇到一用math.c中的函數就出錯的情況,編譯環境keil4.7 …

查看全部問答∨

STM32F7DIS板子LCD顯示問題

STM32F7DIS板子LCD顯示問題,如何將攝像頭采集的數據顯示在LCD任意位置呢?那些函數咋使了。或者哪兒有資料可以參考呢? …

查看全部問答∨
小廣播
設計資源 培訓 開發板 精華推薦

最新單片機文章
何立民專欄 單片機及嵌入式寶典

北京航空航天大學教授,20余年來致力于單片機與嵌入式系統推廣工作。

 
EEWorld訂閱號

 
EEWorld服務號

 
汽車開發圈

 
機器人開發圈

電子工程世界版權所有 京ICP證060456號 京ICP備10001474號-1 電信業務審批[2006]字第258號函 京公網安備 11010802033920號 Copyright ? 2005-2025 EEWORLD.com.cn, Inc. All rights reserved
主站蜘蛛池模板: 峨边| 清流县| 竹山县| 安仁县| 将乐县| 新巴尔虎左旗| 荆州市| 喜德县| 建宁县| 茶陵县| 嘉义县| 枞阳县| 揭西县| 太仆寺旗| 亳州市| 三门峡市| 清远市| 文昌市| 佛山市| 阳江市| 崇左市| 咸宁市| 新宁县| 沂水县| 修武县| 罗田县| 阳山县| 黔西| 桦甸市| 石景山区| 普兰县| 云浮市| 双城市| 景宁| 贡嘎县| 项城市| 木兰县| 徐水县| 平谷区| 泽库县| 信宜市|