1. linux動態庫和靜態庫的區別
接靜態庫其實從某種意義上來說只不過它操作的對象是目標代碼而不是源碼而已。因為靜態庫被鏈接後庫就直接嵌入可執行文件中了,這樣就帶來了兩個問題。
(1)首先就是系統空間被浪費了。如果多個程序鏈接了同一個庫,則每一個生成的可執行文件就都會有一個庫的副本,必然會浪費系統空間。
(2)再者,一旦發現了庫中有bug,挽救起來就比較麻煩了。必須一一把鏈接該庫的程序找出來,然後重新編譯。
而動態庫的出現正彌補了靜態庫的以上弊端。因為動態庫是在程序運行時被鏈接的,所以磁碟上只須保留一份副本,因此節約了磁碟空間。如果發現了bug或要升級也很簡單,只要用新的庫把原來的替換掉就行了。
但是靜態庫的優點:
編譯後的執行程序不需要外部的函數庫支持,因為所有使用的函數都已經被編譯進去了。
靜態庫的名字一般是libxxx.a(Linux)
動態庫的名字一般是libxxx.so(Linux),有時候也是 libxxx.so.major.minor,xxxx是該lib的名稱,major是主版本號, minor是副版本號。
2. 如何查看Linux磁碟空間大小
查看linux磁碟大小的方式如下:
1、首先需要登錄Linux系統,這個時候登錄Linux一定要注意使用的用戶具有一些特殊的許可權(某些查看分區的命令需要許可權,而一般用戶不具有),一般我建議這個時候切換到root用戶或者使用root直接登錄。
3. major minor linux 重啟會變嗎
linuxmint這個發行版的問題,它的X運行會自動重啟,但好在會自動保存東西,你可以換Ubuntu試試,就是太臃腫了
4. Linux磁碟I/O子系統
上文學到 不管什麼文件系統類型,都通過VFS(虛擬文件系統層)讀和寫等操作文件,寫文件的元數據和文件的實際數據到磁碟 。但數據是怎麼落地磁碟中的呢?落到磁碟中的都經過什麼組件?
以一個寫數據到磁碟為例,給出Linux I/O子系統的體系結構。
當磁碟執行寫入操作時發生的 基本操作 (假設磁碟上扇區中的文件數據已經被讀取到分頁緩存)。
1) 一個進程通過write()系統調用 VFS虛擬文件系統 請求寫一個文件。
2) 內核更新已映射文件的分頁緩存。
3) 內核線程 pdflush/Per-BDI flush將分頁緩存刷新到磁碟。
4) 同時 VFS虛擬文件系統層 在一個bio(block input output)結構中放置每個塊緩沖,並向塊設備層提交寫請求。
5) 塊設備層 從上層得到請求,並執行一個 I/O電梯操作,將請求放置到I/O 請求隊列。
6) 設備驅動器 (比如SCSI 或 其他設備特定的驅動器)將執行寫操作。
7) 磁碟設備 固件執行硬體操作,如在碟片扇區上定位磁頭,旋轉,數據傳輸。
過去的20年中,處理器性能的改進要超過計算機系統中的其他組件,如處理器緩存、物理內存及磁碟等等。 訪問內存和磁碟的速度較慢會限制整個系統的性能 ,怎麼解決這個問題呢?引入 磁碟緩存機制 ,在較快的存儲器中緩存頻繁使用的數據,減少了訪問較慢的存儲器的次數。
磁碟緩存機制有以下3個地方解決:
引入存儲層次結構 ,在CPU和磁碟之間放置L1緩存、L2緩存、物理內存和一些其他緩存減少這種不匹配,從而讓進程減少訪問較慢的內存和磁碟的次數,避免CPU花費更多的時間等待來自較慢磁碟驅動器的數據。
另外一種解決思路: 在更快的存儲器上實現更高的緩存命中率,就可能更快地訪問數據 。怎麼提高緩存命中率呢?引入 參考局部性(locality of reference) 的技術。這項技術基於以下2個原則:
1) 大多數最近使用過的數據,在不久的將來有較高的幾率被再次使用(時間局部性)。
2) 駐留在數據附近的數據有較高的幾率被再次使用(空間局部性)。
Linux在許多組件中使用這些原則,比如分頁緩存、文件對象緩存(索引節點緩存、目錄條目緩存等等)、預讀緩沖等。
以進程從磁碟讀取數據並將數據復制到內存的過程為例。進程可以從緩存在內存中的數據副本中檢索相同的數據,用於讀和寫。
1) 進程寫入新數據
當一個進程試圖改變數據時,進程首先在內存中改變數據。此時磁碟上的數據和內存中的數據是不相同的,並且內存中的數據被稱為 臟頁(dirty page) 。臟頁中的數據應該盡快被同步到磁碟上,因為如果系統突然發生崩潰(電源故障)則內存中的數據會丟失。
2) 將內存中的數據刷新到磁碟
同步臟數據緩沖的過程被稱為 刷新 。在Linux 2.6.32內核之前(Red Hat Enterprise Linux 5),通過內核線程pdflush將臟頁數據刷新到磁碟。在Linux 2.6.32內核中(Red Hat Enterprise Linux 6.x)pdflush被Per-BDI flush線程(BDI=Backing Device Interface)取代,Per-BDI flush線程以flush-MAJOR:MINOR的形式出現在進程列表中。當內存中臟頁比例超過閥值時,就會發生刷新(flush)。
塊層處理所有與塊設備操作相關的活動。塊層中的關鍵數據結構是bio(block input output)結構,bio結構是在虛擬文件系統層和塊層之間的一個介面。
當執行寫的時候,虛擬文件系統層試圖寫入由塊緩沖區構成的頁緩存,將連續的塊放置在一起構成bio結構,然後將其發送到塊層。
塊層處理bio請求,並鏈接這些請求進入一個被稱為I/O請求的隊列。這個鏈接的操作被稱為 I/O電梯調度(I/O elevator)。問個問題:為啥叫電梯調度呢?
Linux 2.4內核使用的是一種單一的通用I/O電梯調度方法,2.6內核提供4種電梯調度演算法供用戶自己選擇。因為Linux操作系統適用的場合很廣泛,所以I/O設備和工作負載特性都會有明顯的變化。
1)CFQ(Complete Fair Queuing,完全公平隊列)
CFQ電梯調度為每個進程維護一個I/O隊列,從而 對進程實現一個QoS(服務質量)策略 。CFQ電梯調度能夠很好地適應存在很多競爭進程的大型多用戶系統。它積極地避免進程餓死並具有低延遲特徵。從2.6.18內核發行版開始,CFQ電梯調度成為默認I/O調度器。
CFQ為每個進程/線程單獨創建一個隊列來管理產生的請求,各隊列之間用時間片來調度,以保證每個進程都能分配到合適的I/O帶寬。I/O調度器每次執行一個進程的4個請求。
2)Deadline
Deadline是一種循環的電梯調度(round robin)方法,Deadline 演算法實現了一個近似於實時的I/O子系統。在保持良好的磁碟吞吐量的同時,Deadline電梯調度既提供了出色的塊設備扇區的順序訪問,又確保一個進程不會在隊列中等待太久導致餓死。
Deadline調度器為了兼顧這兩個方面,引入了4個隊列,這4個隊列可分為兩類,每一類都由讀和寫兩種隊列組成。一類隊列用來對 請求 按 起始扇區序號 進行排序(通過紅黑樹來組織),稱為sort_list;另一類對 請求 按 生成時間進行排序 (由鏈表來組織),稱為fifo_list。每當確定了一個傳輸方向(讀或寫),系統都將會從相應的sort_list中將一批連續請求調度到請求隊列里,具體的數目由fifo_batch來確定。 只有遇到三種情況才會導致一次批量傳輸的結束 :1.對應的sort_list中已經沒有請求了;2.下一個請求的扇區不滿足遞增的要求;3.上一個請求已經是批量傳輸的最後一個請求了。
所有的請求在生成時都會被賦上一個期限值,並且按期限值將它們排序在fifo_list中, 讀請求的期限時長默認為500ms,寫請求的期限時長默認為5s。 在Deadline調度器定義了一個writes_starved默認值為2,寫請求的飢餓線。 內核總是優先處理讀請求,當餓死進程的次數超過了writes_starved後,才會去考慮寫請求 。 為什麼內核會偏袒讀請求呢? 這是從整體性能上進行考慮的。讀請求和應用程序的關系是同步的,因為應用程序要等待讀取完畢,方能進行下一步工作所以讀請求會阻塞進程,而寫請求則不一樣。應用程序發出寫請求後,內存的內容何時被寫入塊設備對程序的影響並不大,所以調度器會優先處理讀請求。
3) NOOP
一個簡單的FIFO 隊列,不執行任何數據排序。NOOP 演算法簡單地合並相鄰的數據請求,所以增加了少量的到磁碟I/O的處理器開銷。NOOP電梯調度假設一個塊設備擁有它自己的電梯演算法。當後台存儲設備能重新排序和合並請求,並能更好地了解真實的磁碟布局時,通常選擇NOOP調度,
4)Anticipatory
Anticipatory本質上與Deadline一樣,但Anticipatory電梯調度在處理最後一個請求之後會等待一段很短的時間,約6ms(可調整antic_expire改變該值),如果在此期間產生了新的I/O請求,它會在每個6ms中插入新的I/O操作,這樣可以將一些小的I/O請求合並成一個大的I/O請求,從而用I/O延時換取最大的I/O吞吐量。
Linux內核使用設備驅動程序得到設備的控制權。 設備驅動程序 通常是一個獨立的內核模塊,通常針對每個設備(或是設備組)而提供,以便這些設備在Linux操作系統上可用。一旦載入了設備驅動程序,將被當作Linux內核的一部分運行,並能控制設備的運行。
SCSI (Small Computer System Interface,小型計算機系統介面)是最常使用的I/O設備技術,尤其在企業級伺服器環境中。SCSI在 Linux 內核中實現,可通過設備驅動模塊來控制SCSI設備。 SCSI包括以下模塊類型 :
1) Upper IeveI drivers(上層驅動程序)。 sd_mod、sr_mod(SCSI-CDROM)、st(SCSI Tape)和sq(SCSI通用設備)等。
2) MiddIe IeveI driver(中層驅動程序) 。如scsi_mod實現了 SCSI 協議和通用SCSI功能。
3) Low IeveI drivers(底層驅動程序) 。提供對每個設備的較低級別訪問。底層驅動程序基本上是特定於某一個硬體設備的,可提供給某個設備。
4) Pseudo drive(偽驅動程序) 。如ide-scsi,用於 IDE-SCSI模擬。
通常一個較大的性能影響是文件系統元數據怎樣在磁碟上存放 。引入 磁碟條帶陣列 (RAID 0、RAID 5和RAID 6)解決這個問題。在一個條帶陣列上,磁頭在移動到陣列中下一個磁碟之前,單個磁碟上寫入的數據稱為 CHUNKSIZE ,所有磁碟使用一次它後返回到第一個磁碟。 如果文件系統的布局沒有匹配RAID的設計,則有可能會發生一個文件系統元數據塊被分散到2個磁碟上,導致對2個磁碟發起請求 。或者 將所有的元數據在一個單獨的磁碟上存儲,如果該磁碟發生故障則可能導致該磁碟變成熱點 。
設計RAID陣列需要考慮以下內容:
1) 文件系統使用的塊大小。
2) RAID 陣列使用的CHUNK大小。
3) RAID 陣列中同等磁碟的數量。
塊大小 指可以讀取/寫入到驅動器的最小數據量,對伺服器的性能有直接的影響。塊的大小由文件系統決定,在聯機狀態下不能更改,只有重新格式化才能修改。可以使用的塊大小有1024B、2048B、4096B,默認為 4096 B。
stride條帶 是在一個chunk中文件系統塊的數量。如果文件系統塊大小為4KB,則chunk大小為64KB,那麼stride是64KB/4KB=16塊。
stripe-width 是RAID陣列上一個條帶中文件系統塊的數量。比如 一個3塊磁碟的RAID5陣列 。按照定義,在RAID5陣列每個條帶中有1個磁碟包含奇偶校驗內容。想要得到stripe-width,首先需要知道每個條帶中有多少磁碟實際攜帶了數據塊,即3磁碟-1校驗磁碟=2數據磁碟。2個磁碟中的stride是chunk中文件系統塊的數量。因此能計算 2(磁碟)*16(stride)=32(stripe)。
創建文件系統時可以使用mkfs給定數量:mk2fs -t ext4 -b 4096 -E stripe=16,stripe_width=64 /dev/vda
5. 怎麼修改盤符名稱 Linux系統怎麼修改磁碟參數
1. mknod
用法:mknod 設備名稱 【bcp】【major】【minor】
設備種類,
b : 設置設備名稱為外部的外部的存儲設備文件 eg:硬碟
c : 設置設備名稱為外部輸入設備文件 eg:鍵盤/滑鼠
p : 設置設備名稱為fifo文件
major:主要設備名稱代碼
minor:次要設備代碼
eg:【~~~】#mknod /dev/hda10 3 10
註:3 和 10 只有特殊意義的,不可隨意的設置
在linux系統中,所有的設備都是以文件的形式來表示的,也就是通過文件的主 與 次 來表示的,硬碟的主與次如下所示:
硬碟代號 主【major】 次【minor】
/dev/sda 3 0~63
/dev/sdb 3 64~127
/dev/sdc 22 0~63
/dev/sdd 22 64~127
2.e2label
【~~~~~~】#e2label 設備名稱 新的label名稱
【~~~~~~】#e2label /dev/sda1 sda1
【~~~~~~】#mpe2fs -h /dev/sda1
該命令的主要的主要作用是用來修改「磁碟的表頭數據」,即磁碟的卷標。 但是請注意,如果你剛好有兩個硬碟,如果你修改了卷標,剛好和另外的有個分區有相同的卷標,系統就無法判斷哪個分區是正確的。
這些命令都是不算是特別常用的,常用的命令你可以學習下這里。第2章
新手必須掌握的linux命令。www.linuxprobe.com/chapter-02.html,最後我覺得還算是寫干貨,對於新手很適合。
3.tune2fs
【~~~~~~~】#tune2fs 【-jll】 設備號
-j:將ext2的文件系統轉換為ext3的文件系統
-l:將超級快內的數據度出來,該功能類似於mpe2fs -h的功能
-l:修改文件系統的卷標,類似於e2label的功能
eg:【~~~~~~】#tune2fs -l /dev/sda1
更多的功能請參考man tune2fs
4.hdparm
如硬碟有dma模式的功能,系統卻沒有啟動它,那麼,硬碟的讀取性能可能會降低一半以上,就可以使用該命令來啟動dma模式的功能。該命令有很多的高級的參數設置值,所以不建議隨便的修改,否則容易造成硬碟崩潰,使用這個命令,最多的就是啟動dma功能,並測試硬碟的訪問性能就可以了。
【~~~~~~~】#hdparm 【-icdmxtt】 設備名稱
-i:將系統啟動過程中使用的本身的核心的驅動程序來測試硬碟的測試值取出來,但是這些值不一定是正確的
-d:設置是否啟用dma模式,-d1為啟動,-d0為取消。
6. Linux裡面JVM內存怎麼設置
一、堆內存相關配置
設置堆初始值
指令1:-Xms2g
指令2:-XX:InitialHeapSize=2048m
設置堆區最大值
指令1:`-Xmx2g`
指令2: -XX:MaxHeapSize=2048m
縮小堆內存的時機
-XX:MaxHeapFreeRatio=70//堆內存使用率大於70時擴張堆內存,xms=xmx時該參數無效,默認值70
擴張堆內存的時機
-XX:MinHeapFreeRatio=40//堆內存使用率小於40時縮減堆內存,xms=xmx時該參數無效,默認值40
新生代內存配置
指令1:-Xmn512m
指令2:-XX:MaxNewSize=512m
2個survivor區和Eden區大小比率
指令:-XX:SurvivorRatio=6 //S區和Eden區佔新生代比率為1:6,兩個S區2:6
新生代和老年代的佔比
-XX:NewRatio=4 //表示新生代:老年代 = 1:4 即老年代占整個堆的4/5;默認值=2
二、方法區內存配置常用參數
初始化的Metaspace大小,
-XX:MetaspaceSize :
Metaspace最大值
-XX:MaxMetaspaceSize
三、線程棧內存配置常用參數
每個線程棧最大值
指令1:-Xss256k
指令2:-XX:ThreadStackSize=256k
注意:
棧設置太大,會導致線程創建減少。
棧設置小,會導致深入不夠,深度的遞歸會導致棧溢出。
建議棧深度設置在3000-5000
四、配置垃圾收集器
Serial垃圾收集器(新生代)
開啟:-XX:+UseSerialGC
關閉:-XX:-UseSerialGC
//新生代使用Serial 老年代則使用SerialOld
ParNew垃圾收集器(新生代)
開啟 -XX:+UseParNewGC
關閉 -XX:-UseParNewGC
//新生代使用功能ParNew 老年代則使用功能CMS
Parallel Scavenge收集器(新生代)
開啟 -XX:+UseParallelOldGC
關閉 -XX:-UseParallelOldGC
//新生代使用功能Parallel Scavenge 老年代將會使用Parallel Old收集器
ParallelOl垃圾收集器(老年代)
開啟 -XX:+UseParallelGC
關閉 -XX:-UseParallelGC
//新生代使用功能Parallel Scavenge 老年代將會使用Parallel Old收集器
CMS垃圾收集器(老年代)
開啟 -XX:+UseConcMarkSweepGC
關閉 -XX:-UseConcMarkSweepGC
G1垃圾收集器
開啟 -XX:+UseG1GC
關閉 -XX:-UseG1GC
五、GC策略配置
GC並行執行線程數
-XX:ParallelGCThreads=16
新生代可容納的最大對象
-XX:PretenureSizeThreshold=1000000 //大於此值的對象直接會分配到老年代,設置為0則沒有限制。 //避免在Eden區和Survivor區發生大量的內存復制,該參數只對Serial和ParNew收集器有效,Parallel Scavenge並不認識該參數
進入老年代的GC年齡
進入老年代最小的GC年齡
-XX:InitialTenuringThreshol=7 //年輕代對象轉換為老年代對象最小年齡值,默認值7,對象在堅持過一次Minor GC之後,年齡就加1,每個對象在堅持過一次Minor GC之後,年齡就增加1
進入老年代最大的GC年齡
-XX:MaxTenuringThreshold=15 //年輕代對象轉換為老年代對象最大年齡值,默認值15
六、GC日誌信息配置
配置GC文件路徑
-Xloggc:/data/gclog/gc.log//固定路徑名稱生成 -Xloggc:/home/GCEASY/gc-%t.log //根據時間生成
滾動生成日誌
日誌文件達到一定大小後,生成另一個文件。須配置Xloggc
開啟 -XX:+UseGCLogFileRotation
關閉 -XX:-UseGCLogFileRotation
-XX:NumberOfGCLogFiles=4 //滾動GC日誌文件數,默認0,不滾動 -XX:GCLogFileSize=100k //GC文件滾動大小,需配置UseGCLogFileRotation,設置為0表示僅通過jcmd命令觸發
7. Linux對內存的管理, 以及page fault的概念
http://blog.scoutapp.com/articles/2015/04/10/understanding-page-faults-and-memory-swap-in-outs-when-should-you-worry
Linux allocates memory to processes by dividing the physical memory into pages, and then mapping those physical pages to the virtual memory needed by a process. It does this in conjunction with the Memory Management Unit (MMU) in the CPU. Typically a page will represent 4KB of physical memory. Statistics and flags are kept about each page to tell Linux the status of that chunk of memory.
These pages can be in different states. Some will be free (unused), some will be used to hold executable code, and some will be allocated as data for a program. There are lots of clever algorithms that manage this list of pages and control how they are cached, freed and loaded.
由MMU把物理內存分割成眾多個page,每個page是4KB. 然後把page映射到進程的虛擬內存空間. CPU在執行進程中的指令時, 以虛擬內存地址為基礎, 通過map映射, 進而找到物理內存中實際存放指令的地址.
Imagine a large running program on a Linux system. The program executable size could be measured in megabytes, but not all that code will run at once. Some of the code will only be run ring initialization or when a special condition occurs. Over time Linux can discard the pages of memory which hold executable code, if it thinks that they are no longer needed or will be used rarely. As a result not all of the machine code will be held in memory even when the program is running.
A program is executed by the CPU as it steps its way through the machine code. Each instruction is stored in physical memory at a certain address. The MMU handles the mapping from the physical address space to the virtual address space. At some point in the program's execution the CPU may need to address code which isn't in memory. The MMU knows that the page for that code isn't available (because Linux told it) and so the CPU will raise a page fault.
The name sounds more serious than it really is. It isn't an error, but rather a known event where the CPU is telling the operating system that it needs physical access to some more of the code.
Linux will respond by allocating more pages to the process, filling those pages with the code from the binary file, configuring the MMU, and telling the CPU to continue.
page fault, (嚴格說, 這里指的是major page fault)名字聽起來挺嚴重, 實際上, 並不是什麼"錯誤".
大致是這樣, 一個程序可能占幾Mb, 但並不是所有的指令都要同時運行, 有些是在初始化時運行, 有些是在特定條件下才會去運行. 因此linux並不會把所有的指令都從磁碟載入到page內存. 那麼當cpu在執行指令時, 如果發現下一條要執行的指令不在實際的物理內存page中時, CPU 就會 raise a page fault, 通知MMU把下面要執行的指令從磁碟載入到物理內存page中. 嚴格說, 這里指的是major fault. 還有另一種, 就是minor fault.
There is also a special case scenario called a minor page fault which occurs when the code (or data) needed is actually already in memory, but it isn't allocated to that process. For example, if a user is running a web browser then the memory pages with the browser executable code can be shared across multiple users (since the binary is read-only and can't change). If a second user starts the same web browser then Linux won't load all the binary again from disk, it will map the shareable pages from the first user and give the second process access to them. In other words, a minor page fault occurs only when the page list is updated (and the MMU configured) without actually needing to access the disk.
minor page fault, 指的就是CPU要執行的指令實際上已經在物理內存page中了, 只是這個page沒有被分配給當前進程, 這時CPU就會raise一個minor page fault, 讓MMU把這個page分配給當前進程使用, 因此minor page fault並不需要去訪問磁碟.
當物理內存不夠時,把一些物理內存page中的內容寫入到磁碟, 以騰出一些空閑的page出來供進程使用, 這就是swap out.(The process of writing pages out to disk to free memory is called swapping-out)
反過來說, 當CPU要執行的指令被發現已經swap out到了磁碟中, 這時就需要從磁碟把這些指令再swap in到物理內存中,讓CPU去執行.
swap in和swap out的操作都是比較耗時的, 頻繁的swap in和swap out操作很影響系統性能.
-------DONE.-----------
8. 在linux中,MINOR()函數是干什麼的呢返回值是什麼源代碼在那個文件中謝謝
#include <sys/sysmacros.h>
應該是個系統宏定義,返回設備的次驅動號
9. linux驅動程序結構框架及工作原理分別是什麼
一、Linux device driver 的概念
系統調用是操作系統內核和應用程序之間的介面,設備驅動程序是操作系統內核和機器硬體之間的介面。設備驅動程序為應用程序屏蔽了硬體的細節,這樣在應用程序看來,硬體設備只是一個設備文件,應用程序可以象操作普通文件一樣對硬體設備進行操作。設備驅動程序是內核的一部分,它完成以下的功能:
1、對設備初始化和釋放;
2、把數據從內核傳送到硬體和從硬體讀取數據;
3、讀取應用程序傳送給設備文件的數據和回送應用程序請求的數據;
4、檢測和處理設備出現的錯誤。
在Linux操作系統下有三類主要的設備文件類型,一是字元設備,二是塊設備,三是網路設備。字元設備和塊設備的主要區別是:在對字元設備發出讀/寫請求時,實際的硬體I/O一般就緊接著發生了,塊設備則不然,它利用一塊系統內存作緩沖區,當用戶進程對設備請求能滿足用戶的要求,就返回請求的數據,如果不能,就調用請求函數來進行實際的I/O操作。塊設備是主要針對磁碟等慢速設備設計的,以免耗費過多的CPU時間來等待。
已經提到,用戶進程是通過設備文件來與實際的硬體打交道。每個設備文件都都有其文件屬性(c/b),表示是字元設備還是塊設備?另外每個文件都有兩個設備號,第一個是主設備號,標識驅動程序,第二個是從設備號,標識使用同一個設備驅動程序的不同的硬體設備,比如有兩個軟盤,就可以用從設備號來區分他們。設備文件的的主設備號必須與設備驅動程序在登記時申請的主設備號一致,否則用戶進程將無法訪問到驅動程序。
最後必須提到的是,在用戶進程調用驅動程序時,系統進入核心態,這時不再是搶先式調度。也就是說,系統必須在你的驅動程序的子函數返回後才能進行其他的工作。如果你的驅動程序陷入死循環,不幸的是你只有重新啟動機器了,然後就是漫長的fsck。
二、實例剖析
我們來寫一個最簡單的字元設備驅動程序。雖然它什麼也不做,但是通過它可以了解Linux的設備驅動程序的工作原理。把下面的C代碼輸入機器,你就會獲得一個真正的設備驅動程序。
由於用戶進程是通過設備文件同硬體打交道,對設備文件的操作方式不外乎就是一些系統調用,如 open,read,write,close…, 注意,不是fopen, fread,但是如何把系統調用和驅動程序關聯起來呢?這需要了解一個非常關鍵的數據結構:
STruct file_operatiONs {
int (*seek) (struct inode * ,struct file *, off_t ,int);
int (*read) (struct inode * ,struct file *, char ,int);
int (*write) (struct inode * ,struct file *, off_t ,int);
int (*readdir) (struct inode * ,struct file *, struct dirent * ,int);
int (*select) (struct inode * ,struct file *, int ,select_table *);
int (*ioctl) (struct inode * ,struct file *, unsined int ,unsigned long);
int (*mmap) (struct inode * ,struct file *, struct vm_area_struct *);
int (*open) (struct inode * ,struct file *);
int (*release) (struct inode * ,struct file *);
int (*fsync) (struct inode * ,struct file *);
int (*fasync) (struct inode * ,struct file *,int);
int (*check_media_change) (struct inode * ,struct file *);
int (*revalidate) (dev_t dev);
}
這個結構的每一個成員的名字都對應著一個系統調用。用戶進程利用系統調用在對設備文件進行諸如read/write操作時,系統調用通過設備文件的主設備號找到相應的設備驅動程序,然後讀取這個數據結構相應的函數指針,接著把控制權交給該函數。這是linux的設備驅動程序工作的基本原理。既然是這樣,則編寫設備驅動程序的主要工作就是編寫子函數,並填充file_operations的各個域。
下面就開始寫子程序。
#include <linux/types.h> 基本的類型定義
#include <linux/fs.h> 文件系統使用相關的頭文件
#include <linux/mm.h>
#include <linux/errno.h>
#include <asm/segment.h>
unsigned int test_major = 0;
static int read_test(struct inode *inode,struct file *file,char *buf,int count)
{
int left; 用戶空間和內核空間
if (verify_area(VERIFY_WRITE,buf,count) == -EFAULT )
return -EFAULT;
for(left = count ; left > 0 ; left--)
{
__put_user(1,buf,1);
buf++;
}
return count;
}
這個函數是為read調用准備的。當調用read時,read_test()被調用,它把用戶的緩沖區全部寫1。buf 是read調用的一個參數。它是用戶進程空間的一個地址。但是在read_test被調用時,系統進入核心態。所以不能使用buf這個地址,必須用__put_user(),這是kernel提供的一個函數,用於向用戶傳送數據。另外還有很多類似功能的函數。請參考,在向用戶空間拷貝數據之前,必須驗證buf是否可用。這就用到函數verify_area。為了驗證BUF是否可以用。
static int write_test(struct inode *inode,struct file *file,const char *buf,int count)
{
return count;
}
static int open_test(struct inode *inode,struct file *file )
{
MOD_INC_USE_COUNT; 模塊計數加以,表示當前內核有個設備載入內核當中去
return 0;
}
static void release_test(struct inode *inode,struct file *file )
{
MOD_DEC_USE_COUNT;
}
這幾個函數都是空操作。實際調用發生時什麼也不做,他們僅僅為下面的結構提供函數指針。
struct file_operations test_fops = {?
read_test,
write_test,
open_test,
release_test,
};
設備驅動程序的主體可以說是寫好了。現在要把驅動程序嵌入內核。驅動程序可以按照兩種方式編譯。一種是編譯進kernel,另一種是編譯成模塊(moles),如果編譯進內核的話,會增加內核的大小,還要改動內核的源文件,而且不能動態的卸載,不利於調試,所以推薦使用模塊方式。
int init_mole(void)
{
int result;
result = register_chrdev(0, "test", &test_fops); 對設備操作的整個介面
if (result < 0) {
printk(KERN_INFO "test: can't get major number\n");
return result;
}
if (test_major == 0) test_major = result; /* dynamic */
return 0;
}
在用insmod命令將編譯好的模塊調入內存時,init_mole 函數被調用。在這里,init_mole只做了一件事,就是向系統的字元設備表登記了一個字元設備。register_chrdev需要三個參數,參數一是希望獲得的設備號,如果是零的話,系統將選擇一個沒有被佔用的設備號返回。參數二是設備文件名,參數三用來登記驅動程序實際執行操作的函數的指針。
如果登記成功,返回設備的主設備號,不成功,返回一個負值。
void cleanup_mole(void)
{
unregister_chrdev(test_major,"test");
}
在用rmmod卸載模塊時,cleanup_mole函數被調用,它釋放字元設備test在系統字元設備表中佔有的表項。
一個極其簡單的字元設備可以說寫好了,文件名就叫test.c吧。
下面編譯 :
$ gcc -O2 -DMODULE -D__KERNEL__ -c test.c –c表示輸出制定名,自動生成.o文件
得到文件test.o就是一個設備驅動程序。
如果設備驅動程序有多個文件,把每個文件按上面的命令行編譯,然後
ld ?-r ?file1.o ?file2.o ?-o ?molename。
驅動程序已經編譯好了,現在把它安裝到系統中去。
$ insmod ?–f ?test.o
如果安裝成功,在/proc/devices文件中就可以看到設備test,並可以看到它的主設備號。要卸載的話,運行 :
$ rmmod test
下一步要創建設備文件。
mknod /dev/test c major minor
c 是指字元設備,major是主設備號,就是在/proc/devices里看到的。
用shell命令
$ cat /proc/devices
就可以獲得主設備號,可以把上面的命令行加入你的shell script中去。
minor是從設備號,設置成0就可以了。
我們現在可以通過設備文件來訪問我們的驅動程序。寫一個小小的測試程序。
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
main()
{
int testdev;
int i;
char buf[10];
testdev = open("/dev/test",O_RDWR);
if ( testdev == -1 )
{
printf("Cann't open file \n");
exit(0);
}
read(testdev,buf,10);
for (i = 0; i < 10;i++)
printf("%d\n",buf[i]);
close(testdev);
}
編譯運行,看看是不是列印出全1
以上只是一個簡單的演示。真正實用的驅動程序要復雜的多,要處理如中斷,DMA,I/O port等問題。這些才是真正的難點。上述給出了一個簡單的字元設備驅動編寫的框架和原理,更為復雜的編寫需要去認真研究LINUX內核的運行機制和具體的設備運行的機制等等。希望大家好好掌握LINUX設備驅動程序編寫的方法。
10. 在linux中,MINOR()函數是干什麼的呢返回值是什麼
dev_t dev;/*設備號,dev_t實質是一個32位整,高12位為主設備號,低20位為次設備號*/
提取主設備號的方法:MAJOR(dev_t dev)
提取次設備號的方法:MINOR(dev_t dev)
生成dev_t的方法:MKDEV(int major,int minor)