設(shè)備驅(qū)動充當了硬件和應用軟件之間的紐帶,它使得應用軟件只需要調(diào)用系統(tǒng)軟件的應用編程接口(API)就可讓硬件去完成要求的工作。本文主要講解了Linux設(shè)備驅(qū)動與硬件的關(guān)系,Linux設(shè)備驅(qū)動的開發(fā)模式以及內(nèi)核中相關(guān)的重要基礎(chǔ)數(shù)據(jù)結(jié)構(gòu)。
設(shè)備驅(qū)動與硬件的關(guān)系
對設(shè)備驅(qū)動最通俗的解釋就是“驅(qū)使硬件設(shè)備行動”。驅(qū)動與底層硬件直接打交道,按照硬件設(shè)備的具體工作方式,讀寫設(shè)備的寄存器,完成設(shè)備的輪詢、中斷處理、 DMA 通信,進行物理內(nèi)存向虛擬內(nèi)存的映射等,最終讓通信設(shè)備能收發(fā)數(shù)據(jù),讓顯示設(shè)備能顯示文字和畫面,讓存儲設(shè)備能記錄文件和數(shù)據(jù)。
設(shè)備分類
Linux對將外設(shè)分為3類:
字符設(shè)備
字符設(shè)備指那些必須以串行順序依次進行訪問的設(shè)備,如觸摸屏、磁帶驅(qū)動器、鼠標等。
塊設(shè)備
塊設(shè)備可以用任意順序進行訪問,以塊為單位進行操作,如硬盤、軟驅(qū)等。
網(wǎng)絡設(shè)備
網(wǎng)絡設(shè)備面向數(shù)據(jù)包的接收和發(fā)送而設(shè)計,它并不對應于文件系統(tǒng)的節(jié)點。
總體框圖
機制與策略
機制強調(diào)“提供什么能力”,而策略旨在“如何使用這些能力”。因此驅(qū)動開發(fā)需要遵守的是驅(qū)動程序的角色是提供機制, 而不是策略。
編寫內(nèi)核代碼來存取硬件, 但是不能強加特別的策略給用戶, 因為不同的用戶有不同的需求。驅(qū)動應當做到使硬件可用, 將所有關(guān)于如何使用硬件的事情留給應用程序。
內(nèi)核模塊
Linux 提供了一種代碼動態(tài)地加載到內(nèi)核中機制,這種機制被稱為模塊(Module)。具有如下特點:
模塊本身不被編譯入內(nèi)核映像, 從而控制了內(nèi)核的大小。
模塊一旦被加載,它就和內(nèi)核中的其他部分完全一樣。
可動態(tài)加載與移除,不需重啟系統(tǒng),節(jié)約開發(fā)時間。
模塊放在用戶空間,這部分代碼可以不開源。
因此驅(qū)動多數(shù)情況以內(nèi)核模塊的形式加載到內(nèi)核。
組成
一個 Linux 內(nèi)核模塊主要由如下幾個部分組成:
模塊加載函數(shù)(一般需要)
當通過 insmod 或 modprobe 命令加載內(nèi)核模塊時,模塊的加載函數(shù)會自動被內(nèi)核執(zhí)行,完成本模塊的相關(guān)初始化工作。static int __init initialization_function(void){ /* 初始化代碼 */}module_init(initialization_function);
模塊卸載函數(shù)(一般需要)
當通過 rmmod 命令卸載某模塊時,模塊的卸載函數(shù)會自動被內(nèi)核執(zhí)行,完成與模塊卸載函數(shù)相反的功能。static void __exit cleanup_function(void){ /* 釋放代碼 */}module_exit(cleanup_function);
模塊許可證聲明(必須)
許可證( LICENSE)聲明描述內(nèi)核模塊的許可權(quán)限,如果不聲明 LICENSE,模塊被加載時,將收到內(nèi)核被污染 ( kernel tainted)的警告。在 Linux 2.6 內(nèi)核中,可接受的 LICENSE 包括“ GPL”、“ GPL v2”、“ GPL and additional rights”、“ Dual BSD/GPL”、“ Dual MPL/GPL” 和“ Proprietary”。
模塊參數(shù)(可選)
模塊參數(shù)是模塊被加載的時候可以被傳遞給它的值,它本身對應模塊內(nèi)部的全局變量。module_param(參數(shù)名,參數(shù)類型,參數(shù)讀/寫權(quán)限);module_param_array(數(shù)組名,數(shù)組類型,數(shù)組長,參數(shù)讀/寫權(quán)限);
模塊導出符號(可選)
內(nèi)核模塊可以導出符號( symbol,對應于函數(shù)或變量),這樣其他模塊可以使用本模塊中的變量或函數(shù)。EXPORT_SYMBOL(符號名);EXPORT_SYMBOL_GPL(符號名);
模塊作者等信息聲明(可選)
MODULE_AUTHOR(author);MODULE_DESCRIPTION(description);MODULE_VERSION(version_string);MODULE_DEVICE_TABLE(table_info);MODULE_ALIAS(alternate_name);
編譯
# Makefile2.6TARGET = demo_moduleifneq ($(KERNELRELEASE),)#kbuild syntax. dependency relationshsip of files and target modules are listed here.obj-m := $(TARGET).o else# build from shell directly not in kernel rootCURDIR = $(shell pwd)KVER := $(shell uname -r)KDIR := /lib/modules/$(KVER)/buildall: $(MAKE) -C $(KDIR) M=$(CURDIR) modulesclean: $(MAKE) -C $(KDIR) M=$(CURDIR) cleaninsert: sudo insmod $(TARGET).koremove: sudo rmmod $(TARGET)endif
重要的數(shù)據(jù)結(jié)構(gòu)
大部分的基礎(chǔ)性的驅(qū)動操作包括3個重要的內(nèi)核數(shù)據(jù)結(jié)構(gòu), 稱為 file_operations, file, 和 inode。
file_operations
file_operations 結(jié)構(gòu)體中的成員函數(shù)是設(shè)備驅(qū)動程序設(shè)計的主體內(nèi)容,這些函數(shù)實際會在應用程序進行 Linux 的 open()、 write()、 read()、 close()等系統(tǒng)調(diào)用時最終被調(diào)用。
struct file_operations { struct module *owner; /* 擁有該結(jié)構(gòu)的模塊的指針,一般為 THIS_MODULES */ loff_t(*llseek)(struct file *, loff_t, int); /* 用來修改文件當前的讀寫位置 */ ssize_t(*read)(struct file *, char __user *, size_t, loff_t*); /* 從設(shè)備中同步讀取數(shù)據(jù) */ ssize_t(*write)(struct file *, const char __user *, size_t, loff_t*); /* 向設(shè)備發(fā)送數(shù)據(jù)*/ ssize_t(*aio_read)(struct kiocb *, char __user *, size_t, loff_t); /* 初始化一個異步的讀取操作*/ ssize_t(*aio_write)(struct kiocb *, const char __user *, size_t, loff_t); /* 初始化一個異步的寫入操作*/ int(*readdir)(struct file *, void *, filldir_t); /* 僅用于讀取目錄,對于設(shè)備文件,該字段為 NULL */ unsigned int(*poll)(struct file *, struct poll_table_struct*); /* 輪詢函數(shù),判斷目前是否可以進行非阻塞的讀取或?qū)懭?/ int(*ioctl)(struct inode *, struct file *, unsigned int, unsigned long); /* 執(zhí)行設(shè)備 I/O 控制命令*/ long(*unlocked_ioctl)(struct file *, unsigned int, unsigned long); /* 不使用 BLK 的文件系統(tǒng),將使用此種函數(shù)指針代替 ioctl */ long(*compat_ioctl)(struct file *, unsigned int, unsigned long); /* 在 64 位系統(tǒng)上, 32 位的 ioctl 調(diào)用,將使用此函數(shù)指針代替*/ int(*mmap)(struct file *, struct vm_area_struct*); /* 用于請求將設(shè)備內(nèi)存映射 int(*open)(struct inode *, struct file*); /* 打開 */ int(*release)(struct inode *, struct file*); /* 關(guān)閉*/ int (*fsync) (struct file *, struct dentry *, int datasync); /* 刷新待處理的數(shù)據(jù)*/ int(*aio_fsync)(struct kiocb *, int datasync); /* 異步 fsync */ int(*fasync)(int, struct file *, int); /* 通知設(shè)備 FASYNC 標志發(fā)生變化*/ ...};
file
file 結(jié)構(gòu)體代表一個打開的文件(設(shè)備對應于設(shè)備文件),系統(tǒng)中每個打開的文件在內(nèi)核空間都有一個關(guān)聯(lián)的 struct file。它由內(nèi)核在打開文件時創(chuàng)建,并傳遞給在文件上進行操作的任何函數(shù)。在文件的所有實例都關(guān)閉后,內(nèi)核釋放這個數(shù)據(jù)結(jié)構(gòu)。
struct file { const struct file_operations *f_op; /* 和文件關(guān)聯(lián)的操作*/ unsigned int f_flags; /*文件標志,如 O_RDONLY、 O_NONBLOCK、 O_SYNC*/ fmode_t f_mode; /*文件讀/寫模式, FMODE_READ 和 FMODE_WRITE*/ loff_t f_pos; /* 當前讀寫位置*/ void *private_data; /*文件私有數(shù)據(jù),可存儲自定義數(shù)據(jù)的指針*/ ...};
inode
VFS inode 包含文件訪問權(quán)限、屬主、組、大小、生成時間、訪問時間、最后修改時間等信息。它是 Linux 管理文件系統(tǒng)的最基本單位,也是文件系統(tǒng)連接任何子目錄、文件的橋梁。
struct inode { umode_t i_mode; /* inode 的權(quán)限 */ uid_t i_uid; /* inode 擁有者的 id */ gid_t i_gid; /* inode 所屬的群組 id */ dev_t i_rdev; /* 若是設(shè)備文件,此字段將記錄設(shè)備的設(shè)備號 */ loff_t i_size; /* inode 所代表的文件大小 */ struct timespec i_atime; /* inode 最近一次的存取時間 */ struct timespec i_mtime; /* inode 最近一次的修改時間 */ struct timespec i_ctime; /* inode 的產(chǎn)生時間 */ unsigned long i_blksize; /* inode 在做 I/O 時的區(qū)塊大小 */ unsigned long i_blocks; /* inode 所使用的 block 數(shù),一個 block 為 512 byte*/ struct block_device *i_bdev; /*若是塊設(shè)備,為其對應的 block_device 結(jié)構(gòu)體指針*/ struct cdev *i_cdev; /*若是字符設(shè)備,為其對應的 cdev 結(jié)構(gòu)體指針*/ ...};
?
評論