linux字符设备开发
本篇基于ldd3中第三章,原書自帶的源碼隨著內核版本更新已經不能運行,代碼需要進行升級,文章參考代碼能在內核版本4.17.2運行。
1.?? 分配設備編號
建立一個字符驅動時,需要做的第一件事是獲取一個或多個設備編號來使用.此目的必要的函數是 register_chrdev_region.
注冊字符設備函數執行后會出現在/proc/devices和sysfs中:
int register_chrdev_region(dev_t from, unsigned count, const char *name)
first是要分配的起始設備編號. first 的次編號部分常常是 0, 但是沒有要求是那個效果. count 是請求的連續設備編號的總數. 注意, 如果 count 太大,要求的范圍可能溢出到下一個次編號; 但是只要要求的編號范圍可用, 一切都仍然會正確工作. name 是應當連接到這個編號范圍 的設備的名子; 它會出現在 /proc/devices 和 sysfs 中.
一些主設備編號是靜態分派給最普通的設備的. 一個這些設備的列表在內核源碼樹的 Documentation/devices.txt 中.
對于新驅動,建議使用動態分配來獲取你的主設備編號, 而不是隨機選取一個當前空閑的編號.使用 alloc_chrdev_region, 不是 register_chrdev_region.
這個alloc_chrdev_region是動態分配主設備號的,因為你可能不知道系統中哪些主設備號可以給你的驅動程序使用,動態分配的一個缺點就是不能提前分配設備節點(注:通過MKNOD來創建節點的):
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)
??????????? 兩個函數從名字上也可以區分,一個是注冊,表示我有了主設備號只是想內核注冊,而alloc_則是分配,意味著讓內核來幫著allocate一下。
??????????? 注冊對應的是注銷,這個是驅動程序卸載時候必須做的,從哪里來還那里去,對應的函數是:
void unregister_chrdev_region(dev_t from, unsigned count)
另外,獲取設備主設備號和次設備號,使用如下宏:
MAJOR(dev_t dev);
MINOR(dev_t dev);
將主、次設備號轉換成一個設備號,如下:
MKDEV(int major, int minor);
2.?? 基礎性的驅動操作
基礎性的驅動操作包括 3 個重要的內核數 據結構, 稱為 file_operations, file, 和 inode.
2.1???? file_operation
傳統上, 一個 file_operation 結構或者其一個指針稱為 fops( 或者它的一些變體). 結構中的每個成員 必須指向驅動中的函數, 這些函數實現一個特別的操作
??????????? 其中定義的操作函數并不是需要全部實現,根據具體驅動實現針對的函數功能即可。字符設備主要有一下函數需要實現:
owner,llseek,read,write,ioctl,open,release.
2.2???? file
file結構表示一個打開的文件。在內核中指向file的指針經常叫做filp,就是file pointer,以免是file搞混。
2.3???? inode
inode結構由內核在內部用來表示文件,代表打開文件描述符的文件結構是不同的。多個打開的描述符可能指向一個單個inode結構。
相對于字符設備驅動程序,我們先使用i_rdev和i_cdev。
dev_t i_rdev;//實際設備的節點
Struct cdev *i_cdev; //指向字符設備驅動程序指針
??????????? 現在可以通過宏如下,來獲取節點的主、次設備號:
unsigned int iminor(struct inode *inode);
unsigned int imajor(struct inode *inode);
3.?? 字符設備注冊
內核中使用cdev結構體來表示字符設備。可以通過cdev_alloc來分配。
然后使用cdev_init來初始化。
?????? 我們可以將cdev結構體嵌入到我們自己的設備結構體中,這也正是例子所使用的方法。
??????????? 最后告訴內核添加進去,如果不告訴內核就是空有一身資源而無施展之處,通過函數cdev_add。一旦添加,那么內核就可能來騷擾設備,所以要確保所有都準備好的時候調用cdev_add函數。
??????????? 去除設備調用函數cdev_del.
4.?? 設備布局
設備由內存來模擬,其設備中的布局如下圖。
數據結構如下,scull_qset結構體非常簡單,其實現一個鏈表的同時,每個元素同時指向塊內存:
struct scull_qset {
??????? void **data;
??????? struct scull_qset *next;
};
設備的結構體如下:
struct scull_dev {
??????? struct scull_qset *data;? /* Pointer to first quantum set */
??????? int quantum;? ????????????/* the current quantum size */
??????? int qset;???????????????? /* the current array size */
??????? unsigned long size;?????? /* amount of data stored here */
??????? unsigned int access_key;? /* used by sculluid and scullpriv */
??????? struct semaphore sem;???? /* 互斥所*/
??????? struct cdev cdev;???????? /* Char device structure????????????? */
};
設備的大小為quantum*qset。
?
5.?? 代碼解析
5.1???? 初始化
初始化函數為scull_init_module
如果指定了主設備號,調用register_chrdev_region,否則調用alloc_chrdev_region,并獲取主設備號。
然后分配設備scull_dev結構體數組scull_devices,數量為SCULL_NR_DEVS子設備號數量,并初始化為0。接著根據需要分配的內存空間大小正式初始化scull_devices,其中會調用scull_setup_cdev函數(該函數中會使用cdev_init,cdev_add函數,初始化設備結構中嵌入的cdev,同時綁定scull_fops),scull_fops結構如下。
struct file_operations scull_fops = {
??????? .owner =??? THIS_MODULE,
??????? .llseek =?? scull_llseek,
?? ?????.read =???? scull_read,
??????? .write =??? scull_write,
??????? .unlocked_ioctl = scull_ioctl,
??????? .open =???? scull_open,
??????? .release =? scull_release,
};
??????????? 然后調用
初始化時候分配了指定數量的設備數據結構,并初始化后增加到了內核,可以在/proc/devices中看到,此時其實并沒有分配設備的內存空間,因為還不需要。
?
5.2???? 退出
退出函數是scull_cleanup_module,該函數先獲取設備號。然后根據設備數量循環調用scull_trim,cdev_del函數來刪除cdev設備,最后調用kfree釋放在初始化中分配的結構體數據。
??????????? 其中scull_trim函數負責釋放分配的內存空間。
5.3???? 操作函數集合
驅動的open函數
通過inode獲取設備結構體的指針,這個通過內核中的container_of函數來實現,并將其保存到文件對象的private_data中以備后用。
如果是寫模式打開,則將之前該設備分配的空間使用scull_trim函數清空。
?
llseek
返回當前讀寫位置。
?
release
直接返回0,并不做操作。
read
先從filp->private_data中獲取設備地址。獲取設備總空間大小。
如果要讀的位置大于空間大小則退出。否則計算要讀取的正確位置,因為每個scull_qset結構體指向的item空間大小是固定的,其相互之間是鏈表方式連接的。
然后會調用scull_follow函數,該函數中會通過kmalloc函數動態分配scull_qset結構體(如果沒有被分配過),直到包含的item累計理論空間能包含要讀取的地址。然后返回最后一個scull_qset結構體,如果返回null,說明系統內存空間不夠了。
??????????? 最后調用copy_to_user函數,將內容復制到用戶的buf中。然后更新文件讀取位置,并返回所讀取字節大小。
write
獲取設備結構體的指針,以及對應的設備空間相關大小。如item,quantum。
計算文件讀取位置,調用scull_follow,返回位置所在的那個item的scull_qset結構體,如果該結構體對應的數據指針為NULL,說明之前沒有給其分配內存空間,則調用kmalloc分配qset指向quantum的指針數數組。
然后根據讀取位置,通過函數kmalloc函數分配quantum的內存空間。
最后調用用copy_from_user函數將數據復制到內存中的quantum片段。
?
unlocked_ioctl
對ioctl的實現。
6.?? 使用測試
加載驅動后,執行如下,其中247是主設備號,在/proc/devices中可以查看到:
mknod /dev/scull0 c 247 0
mknod /dev/scull1 c 247 1
mknod /dev/scull2 c 247 2
mknod /dev/scull3 c 247 3
然后可以使用dd命令或者cp命令復制內容到設備中。
# echo "hello" > scull0
# cat scull0
hello
7.?? 代碼
https://github.com/kernel-z/ldd3/tree/master/scull
?
?
?
?
?
總結
以上是生活随笔為你收集整理的linux字符设备开发的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Heartbeat VIP/IP 与 别
- 下一篇: linux 其他常用命令