24小時學通Linux內核之如何處理輸入輸出操作


 

 

24小時學通Linux內核之如何處理輸入輸出操作

  真的是悲喜交加呀,本來這個寒假早上8點都去練車,兩個小時之后再來實驗室陪伴Linux內核,但是今天教練說沒名額考試了,好糾結,不過想想就可以睡懶覺了,哈哈,自從大三寒假以來還沒睡過懶覺呢,現在也有更多的時間來分享自己學習Linux內核的感受,前幾天覺得就是自己也有些不懂的,相信大家看了也是很模糊,以后我會標志出來自己不懂的,希望大神們指教,也希望大家多多指點,共同攻克Linux內核,今天將講到處理器是如何與其它設備進行交互的,內核又是如何響應和控制這些交互的,今天內容不多但是很關鍵,寫的不好希望大家批評,純手打。

  Linux內核是如何將軟硬件結合起來的呢?這里我們將一起探究內核與周圍硬件主要是文件IO和硬件設備之間的關系,來解釋這個問題。處理器與周圍設備的通信依賴於一系列的電路電線,總線就是具有類似功能的電線,設備與處理器通信主要是通過地址總線,數據總線,控制總線來實現,這里在學習單片機原理的時候也提到過,這里對系統的基本結構就不多說了,覺得更新快,不好講解,也沒什么好總結的,大家看看相關書籍就行。了解到設備可以當做文件系統中的文件來處理,其細節都可隱藏在內核中,而對應用程序員透明,當進程對設備文件應用某一系統調用的時候,只要將這一系統調用轉換成某種設備函數就足夠了,其中設備驅動程序定義了這些函數,接下來看看這些設備類型。其中應用層,文件系統層,通用塊設備層和設備驅動程序之間的關系如下圖,這里貼出來供大家了解一下。讀寫塊設備如下:

  先介紹塊設備,設備驅動在驅動程序初始化時為自己注冊,將這個驅動程序加入內核的驅動程序表中,並將設備號映射到數據結構block_device_operations中,數據結構block_device_operations包含了系統中啟動和停止給設定塊設備的函數(在include/linux/fs.h上。

struct block_operations{
    int (*open) (struct inode *,struct file *);
    int (*release) (struct inode *,struct file*);  //open()和release()都是同步的
    int (*ioctl) (struct node *,struct file *,unsigned, unsigned long);
    int (*media_changed) (struct gendisk *);
    int (*revalidate_disk) (struct gendisk *);
    struct module *owner;
};

  從處理器的角度來看,在適合的cidao4上定位磁頭並將磁盤轉到相應的塊要花費相當長的時間,這種延遲迫使內核實現了系統請求隊列,在Linux2.6中,每個塊設備都有自己的請求隊列,以便管理對該設備的IO口請求,進程只有在獲得請求隊列鎖之后才能 更新設備的請求隊列,讓我們先來看看request_queue結構(這些代碼都是自己敲出來的,然后分析,分析不好的請各位大神批評改正。)這些代碼都可以在include/linux/blkdev.h中查看。

struct request_queue
{
    struct list_head queue_head;  //指向請求隊列對首的指針
    struct request  *last_merge;  //加到請求隊列的最后一個請求
    elevator_t  elevator;      //這個不懂,求大神指struct request_list rq;     //由兩個wait_queue組成,分別用於塊設備讀請求隊列和寫請求隊列
...
    request_fn_proc *request_fn;
    merge_request_fn *back_merge_fn;
    merge_request_fn *front_merge_fn;
    merge_requests_fn *merge_requests_fn;
    make_request_fn *make_request_fn;
    prep_rq_fn *prep_rq_fn;
    unplug_fn *unplug_fn;
    merge_bvec_fn *merge_bvec_fn;
    activity_fn *activity_fn;      //定義調度程序相關函數來控制如何管理塊設備的請求
...
    struct time_list unplug_timer;
    int unplug_thresh;
    unsigned long  unplug_delay;
    struct work_struct unplug_work;

    struct backing_dev_info backing_dev_info;    //用於去掉設備的IO調度函數

...
    void *queuedata;

    void *activity_data;  //這些是對設備和設備驅動程序相關的隊列進行管理

...
    unsigned long  bounce_pfn;
    int  bounce_gfp;    //是指內核將高端內存緩存沖區的IO請求copy到低端內存緩沖區去

    unsigned long queue_flags;//變量queue_flags存儲一個或者多個隊列標志,參見下表格。
標志名稱 功能
QUEUE_FLAG_CLUSTER 將介幾個段合成一個 
QUEUE_FLAG_QUEUED 使用通用標志隊列
QUEUE_FLAG_STOPPED 隊列被停止
QUEUE_FLAG_READFULL 讀隊列已經滿了
QUEUE_FLAG_WRITEFULL 寫隊列已經滿了
QUEUE_FLAG_DEAD 隊列被撤銷
QUEUE_FLAG_REENTER 避免重入
QUEUE_FLAG_PLUGGED 插入隊列

 

 

 

 

 

 

 

 

 

spinlock_t *queue_lock;

struct kobject kobj;

unsigned long nr_requests;  
unsigned int nr_congestion_on;
unsigned int nr_congestion_off;
unsigned short max_sectors;
unsigned short max_phys_segments;
unsigned short max_hw_segments;
unsigned short hardsect_size;
unsigned int max_segment_size;

unsigned long seg_boundary_mask;
unsigned long dma_alignment;

struct blk_queue_tag *queue_tags;

atomic_t refcnt;

unsigned int in_flight;

unsigned int sg_timeout;
unsigned int sg_reserved_size;  //前面這些變量定義了請求隊列中可管理的資源
};

  Linux內核通過在設備的_init函數中調用下列函數來初始化塊設備的請求隊列,這些函數中,,可以看出請求對了內部的細節和相關幫助教程,在現在的Linux2.6內核中,每個塊設備控制自己的鎖,並且將自旋鎖作為第二個參數來傳遞,其中第一個參數是塊設備驅動程序提供的請求函數,下面的代碼在drivers/block/11_rw_blk.c中查看得到。

request_queue_t *blk_init_queue(request_fn_proc *rfn,spinlock_t *lock)
{
    request_queue_t *q;
    static int printed;
    
    q = blk_alloc_queue(GFP_KERNEL);  //從內核的內存中分配空間隊列,內容為0
    if(!q)
    return NULL;

    if(blk_init_free_list(q))  //初始化請求清單
       goto out_init;    //表示一直怕goto,懂但不會if(!printed){
       printed = 1;
       printk("Using %s io scheduler\n",chosen_elevator->elevator_name);
    }

    if(elevator_init(q,chosen_elevator))  //這是個初始化的函數
       goto out_elv;

    q->request_fn = rfn;
    q->back_merge_fn = 11_back_merge_fn;
    q->front_merge_fn = 11_front_merge_fn;
    q->merge_requests_fn = 11_merge_requests_fn;
    q->prep_rq_fn = NULL;
    q->unplug_fn = generic_unplug_device;
    q->queue_flags = (1<<QUEUE_FLAG_CLUSTER);
    q->queue_lock = lock;    //上述賦值是將電梯調度程序相關的函數與該隊列關聯

    blk_queue_segment_boundary(q,0xffffffff);  //檢查是否滿足最小尺寸

    blk_queue_make_request(q,__make_request)  //設置驅動從隊列刪除
    blk_queue_max_segment_size(q,MAX_SEGMENT_SIZE);  //初始化歸並段的上限
    
    blk_queue_max_hw_segments(q,MAX_HW_SEGMENTS);  //初始化物理設備可以處理的最大段數
    blk_queue_max_phys_segments(q,MAX_PHYS_SEGMENTS);  初始化每一請求的最大物理數目段
    
    return q0;  //返回已經初始化的隊列
    out_elv:
    blk_cleanup_queue(q);
    out_init:
    kmem_cache_free(requestq_cachep,q);
    return NULL;  //錯誤事件中清除內存的一個例程
}

  

  代碼實在太難,我知道的也只是皮毛,那些都需要好好體會,如若有補充的希望各路大神能夠多加改正我的缺點,現在來看看設備操作,基本 的通用塊設備有open,close,ioctl以及request函數,請求隊列不能直接被訪問,但是可以通過一組幫助例程來訪問,如下:

struct request *elv_next_request(request_queue_t *q)

這個幫助函數返回並指向下一個請求結構的指針,驅動程序可以通過查看該元素來收集所有信息,以確定它的的大小方向以及該請求相關的任何其他自定義操作,之后通過end_request()想內核報告這一信息:

void end_request(struct request *req,int uptodate)  //在請求隊列中傳遞elev_next_request()獲得的參數
{
    if(!end_that_request_first(req,uptpdate,req->hrad_cur_sectors))  //傳輸適合的扇區數
    {
        add_disk_randomness(req->rq_disk);  //加入系統熵池,表示不懂,求大神指教,
        blkdev_dequeue_request(req);  //刪除請求結構
        end_that_request_last(req);  //收集統計信息並且釋放可用的數據結構
    }
}

  下面來介紹一下其它各種設備,與塊設備不同,字符設備用來傳送數據流,所有串行設備都是字符設備,與字符設備類似,網絡設備的數據在物理層上串行傳輸,而時鍾設備是基於硬件脈搏跳動的設備,其實就是時鍾相關的,還有那終端設備,這里就稍微提及一下。因為這些都和輸入輸出相關,大家只要有個印象就行了。

  

  小結

  結束了分析代碼之旅,小結一下今天主要的內容,今天主要分享的是Linux內核是如何處理輸入輸出操作的,具體討論了Linux是如何表示塊設備和它的接口的,也介紹了Linux調度程序並且重點分析了請求隊列,上述敲的代碼,我也還有好多不懂,只能自己慢慢去體會了,希望各路大神看了之后能夠稍加提醒一下,哎,反正這個寒假沒啥事了,就一直和內核作伴吧,這些寫的不好,以后繼續努力,fighting~

 

  版權所有,轉載請注明轉載地址:http://www.cnblogs.com/lihuidashen/p/4244330.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM