真的是悲喜交加呀,本來這個寒假早上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