# Char Drivers (Linux device drivers) drivers的基本操作主要涉及到三種重要的資料結構 * file_operations * file * inode # file_operations (操作說明書) 負責把device numbers跟drivers的功能連結起來, The structure, defined in <linux/fs.h>,是一堆function pointers.他是一組函示指標的集合,看起來會像這樣子,裡面定義的就是對於這個file的所有操作動作的指派 ![](https://i.imgur.com/SVMmHpW.png) 對於每個已開啟的file, kernel會各自附與一組專屬的作業函式(就是給每個file操作說明書),做法就是把file struct裡面的f_op欄位指向一組定義好的file_operation結構 這裡可以注意到file_operations struct所宣告的函式原型裡有一些參數會帶__user字串,這是一種另類的註解,註明該pointer指向一個user-space的位址,不可以直接取值!!(dereference)。平常編譯時__user沒有效果,但可以讓外部檢查軟體找出是否有誤用user-space位址的現象 下面介紹最重要的幾個操作 ![](https://i.imgur.com/dGgQptk.png) ![](https://i.imgur.com/WBwUv7g.png) ![](https://i.imgur.com/tfrJq3w.png) ![](https://i.imgur.com/I3MzhgD.png) # The file Structure 對driver而言,重要性僅次於file_operations的結構,定義於<linux/fs.h>的struct file. 注意!!! file跟user-space程式中常用的FILE指標完全沒有關係,FILE是定義於glibc library的東東,不可能出現在kernel程式碼裡。反之,file結構也絕不會出現在user-space應用程式裡。 file struct代表已開啟的檔案(open file), 這不是drivers的專利,對於系統上每個被開啟的檔案,在kernel-space都有一個對應的struct file. * 生命週期: * 每一個file struct都是kernel在收到open()系統呼叫時自動建立的,注意到使用到這個file的人可能不只一個,所以file的生命週期會一直到系統上最後一個使用的人也close()後才會釋放掉他 * 變數名稱: * 通常用file或 filp(file pointer)來指向struct file的指標,為了避免混淆,這裡通通用filp來代表指標. * file代表結構 * filp代表指向struct file的指標 * file裡的重要欄位: ![](https://i.imgur.com/rtrSxIn.png) ![](https://i.imgur.com/XStfwty.png) ![](https://i.imgur.com/t79KZqH.png) ![](https://i.imgur.com/EfhfTP5.png) # inode結構 kernel使用inode來代表檔案,不同於file結構,inode是用來代表已開啟的檔案的FD,同一個file可以被開啟多次,在這種情況下,kernel會有多個代表FD的file結構,但是他們全指向同一個inode結構。 inode結構含有大量關於檔案的資訊,但是一般而言,drivers只對其中兩個欄位比較有興趣 * ![](https://i.imgur.com/U7U9Xbu.png) * 代表裝置檔的inodes,此欄位含有實際的device number(major+minor) * ![](https://i.imgur.com/VJDu37o.png) * kernel用來指向cdev的結構 在2.5版的開發過程中,i_rdev被改變了,為了保持相容性,你應該改用這兩個macro. ![](https://i.imgur.com/S8LfWdo.png) 你應該盡量使用這兩個macro, 避免直接操作i_rdev # 註冊char devices 兩種方法註冊 1. ![](https://i.imgur.com/VAtFCCM.png) 2. ![](https://i.imgur.com/qvDXy01.png) 加入核心 ![](https://i.imgur.com/8v0BJPG.png) 這邊dev是你設定的cdev struct, num是第一個裝置編號,count是裝置編號的總數。通常count會設定1,除非你的driver要同時應付多個device number 當cdev_add() return 成功,你的device就當場生效了 # scull的裝置註冊程序 ![](https://i.imgur.com/rg7FKDK.png) ![](https://i.imgur.com/HCKJ2bu.png) # 舊式的註冊方法 ![](https://i.imgur.com/tzvJEtw.png) # open open方法要提供driver的op所需的一切初始化 in most drivers, open要提供這些tasks * 檢查device特有的錯誤 * 數據機佔線 * 沒放光碟片 * 任何特殊的完全跟硬體相關的功能 * 如果device是首次被開啟,要進行初始化 * 更新 f_op pointer, 如果有需要的話 * 填 filp->private_data 的資料 然而就順序上來說,open的第一件工作是判斷目前被開啟的是哪個device, 也就是要找出目前要被開啟的device所對應的scull_dev structure. 回想 open method is ![](https://i.imgur.com/s80bUzP.png) 我們所需的資訊就藏在inode引數的 i_cdev欄位裡,因為該欄位就指向一個cdev結構,唯一的問題是我們要的不是cdev結構本身,而是含有該cdev結構的scull_dev結構,C語言讓程式師得完盡花招才能找出欄位所屬的結構,然而,這部份很容易出錯,而且會讓程式碼很難看懂,還好linux的高手們已經包辦了最困難的部分,我們只要學會使用<linux/kernel.h>所定義的container_of macro就好 ![](https://i.imgur.com/Xcol5Gz.png) ![](https://i.imgur.com/r0aIxeq.png) * pointer指標指向container_field欄位 * 該欄位所屬的結構之型別為container_type * macro展開後是一個指向容器結構的指標 利用container_of,scull_open()可以下列方式找出cdev結構所屬scull_dev結構 ![](https://i.imgur.com/2Jps4wo.png) container_of的解釋 https://stackoverflow.com/questions/13723422/why-this-0-in-type0-member-in-c 既然scull_open()好不容易才找到scull_dev結構,將此結構的指標存入file結構的private_data欄位,以便未來方便運用。 # release open的相反,有時候會發現實際的函示名稱是device_close(),而非device_release() * 釋放open儲存於filp->private_data的任何東西 * 在最後一次關閉時,將目標裝置關機 Q: 如果device被close的次數超過被open的次數會怎樣? 畢竟dup() and fork()都會增加開檔次數,但不會呼叫open(),但是發出這些系統呼叫的Process結束時,都會發出close()系統呼叫,舉例來說,大多數程式不會開啟他們的stdin檔,但是他們最後都會被關閉,drivers是如何知道一個以開啟的檔案真的已被關閉了? 值得一讀 ![](https://i.imgur.com/ao2cy7z.png) # read ![](https://i.imgur.com/FGZnpow.png) 不管read/write實際傳輸了多少資料量,最後都應該更正 *f_pos所指的檔案位置,使其符合系統呼叫結束之後的實況 描繪了典型的read作業方法如何使用他所收到的各個引數 ![](https://i.imgur.com/7bbH7yq.png) # error handleing 若你的read/write發生錯誤,則應該回傳一個錯誤代碼(負值)。若回傳值大於0,則會被應用程式解釋為實際傳輸成功的byte數,如果順利傳輸了部分資料才錯誤,則回傳值必須與成功完成的bytes數相符,回報錯誤的動作留到下次被呼叫時才執行。 雖然kernel會回傳錯誤代碼,但user-space程式永遠只會看到-1,他們還必須檢查errno變數才能知道到底遇到何種錯誤。這是POSIX的規定,因握這讓kernel裡的函示不必理會errno(只有系統呼叫本身才需要處理errno) * 若return value == count引數 * 傳輸成功了 * 若return value > 0 && < count * 只順利傳輸了部分資料 * 通常app會再嘗試重新讀取,以fread()這個c lib為例,他會重新發出system call,值到取得所要的資料量 * if return value == 0 * 已經遇到檔案尾端(EOF) * 若A process正在讀取某個deivce, B process以write-only mode開啟device, 這時候device長度會瞬間縮減為0 - 同時也使的讀取點的位置跑過頭,在這種情況下,A會發現他自己闖越了EOF,則下次的read()系統呼叫將回傳0 * if return value == -1 (不可能有其他負數) * 代表發生了某種錯誤 * app不能從-1就知道是哪種錯誤,必須另外檢查errno變數才知道 * 個種錯誤都定義在 <linux/errno.h> 標頭檔 * -EINTR (系統呼叫被打斷) * -EFAULT (錯誤位址) * 目前沒有資料,但是可能隨後就到情況 * read() should block first # write ![](https://i.imgur.com/PXRDZ6f.png) # ioctl