linux驅動
makefile寫法:
ifneq ($*KERNELRELEASE),) obj-m :=hello.o else KERNELDIR?=/lib/modules/$(shell uname -r) /build PWD :=$(shell pwd) default: $(MAKE) -C $(KERNELDIR) M=$(PWD) modules endif
初始化和關閉:
static int __init initialization_funciton(void) { * } module_init(initialization_function); static void __exit cleanup_function(void) { * } module_exit(cleanup_function);
__init意爲在初始化時調用,調用完後會被清除出內存
__exit意爲只在清除時調用,調用後即清除
參數傳遞:
static char *whom =”world”;
static int howmany=1;
module_param(howmany,int,S_IRUGO);
module_param(whom,charp,S_IRUGO);
內核支持的模塊參數類型如下:
bool、invbool、charp、int、long、uint、ulong、ushort
聲明數組參數用以下形式:
module_param_array(name,type,num,perm);
perm爲訪問許可值,S_IRUGO爲任何人可讀,S_IWUSR爲root可寫。
主設備號和次設備號
通常而言,主設備號指示設備對應的驅動程序,次設備號用於正確確定設備文件所指的設備。內核中用dev_t類型保存設備號
獲得主次設備號:
MAJOR(dev_t,dev);
MINOR(dev_t,dev);
生成dev_t類型:
MKDEV(int major,int minor);
分配和釋放設備編號:
靜態分配:
<linux/fs.h>
int register_chrdev_region(dev_t first, unsigned int count, char *name);
first是要分配的設備編號的起始值,count是要請求連續設備編號的個數,name是與範圍相關聯的名稱。成功返回0,錯誤返回負的錯誤碼。
動態分配:
int alloc_chrdev_region(dev_t *dev, unsigned int firstminor, unsigned int count, char *name);
釋放:
void unregister_chrdev_region(dev_t first, unsigned int count);
重要的數據結構:
設備號與驅動程序操作的關聯:
file_operations,指向該結構的指針一般名叫fops,包含指向對文件各個操作的實現函數的指針。
有個owner字段需要被初始化爲THIS_MODULE
file結構:
file結構代表一個打開的文件,系統中每一個打開的文件在內核空間都有一個對應file結構
需要用到的file字段:
mode_t |
f_mode |
文本模式,標識文件是可讀可寫,但不需要在驅動中檢測 |
loff_t |
f_ops |
當前讀寫位置 |
unsigned int |
f_flags |
文件標誌,定義在<linux/fcntl.h> |
Struct file_operations |
* f_op |
與文件相關的操作 |
void |
* private_data |
私有數據,必須在file結構被銷燬前在relrease方法中釋放內存 |
struct dentry |
* f_dentry |
文件對應的目錄項結構 |
inode結構:
dev_t i_rdev; 對錶示設備文件的inode結構,該字段包含了真正的設備編號
struct cdev *i_cdev 表示字符設備的內核的內部結構。當inode指向一個字符設備文件時,該字段包含了指向struct cdev結構的指針。
有兩個宏可用來從一個inode中獲得主設備號和次設備號:
unsigned int iminor(struct inode *inode);
unsigned int imajor(struct inode *inode);
字符設備的註冊:
內核使用struct cdev結構表示字符設備,定義在<linux/cdev.h>
分配和初始化cdev的兩種方式:
獲得獨立的結構:
struct cdev *my_code=cdev_alloc();
my_cdev->ops=&my_fops;
或者嵌入到自己的設備:(不知道爲什麼,,,)
void cdev_init(struct cdev*cdev, struct file_operations *fops);
還有一個struct cdev的owner字段需要被初始化爲THIS_MODULE
告知內核該結構的信息:
int cdev_add(struct cdev *dev, dev_t num, unsigned int count);
num是設備對應的第一個設備號,count應該是和該設備關聯的設備編號的數量。
在驅動程序還沒有完全準備好處理設備上的操作時,不能調用cdev_add。
從系統中移除一個字符設備:
void cdev_del(struct cdev *dev);
open和release
open應該完成以下操作:
檢查設備特定的錯誤
如果設備是首次打開,則對其進行初始化
如有必要,更新f_op指針
分配並填寫置於filp-private_data裏的數據結構。
open方法原型:
int (*open) (struct inode *inode, struct file *filep);
inode參數中i_cdev字段包含註冊設備時的cdev的指針
struct cdev結構體在裝在模塊時被裝如內存,全局只有一個。
個人理解:cdev結構體代表着字符設備,自定義的字符設備需要將cdev結構嵌入到自定義的結構體中,當需要訪問這個結構體時,需要通過inode結構獲得cdev指針,然後通過container_of宏獲得自定義結構體的指針。然後將這個指針通過寫入file結構的private_data傳遞給其他文件操作函數。
release應該完成以下操作:
釋放由open分配的保存在private_data中的所有內容
在最後一次關閉操作時關閉設備。
並不是每個close方法都調用release方法,只在file中維護的計數器爲0時,才調用release。
實例scull的內存結構:
用到定義在<linux/slab.h>的兩個函數
void *kmalloc(size_t size, int flags);
void kfree(void *ptr); //將kmalloc返回的指針傳給kfree,但傳NULL是合法的
定義在<asm/access.h>的兩個函數
unsigned long copy_to_user(void __user *to,const void *from,unsigned long count);
unsigned long copy_from_user(void *to,const void __user *from,unsigned long count);
這兩個函數還會檢測用戶空間指針的有效性,遇到無效地址不拷貝或僅拷貝部分內容,返回值爲還需拷貝的數量。如果確定不需要檢測,可使用__copy_to_user和__copy_from_user。
read和write:
ssize_t read(struct file *filp, char __user *buf, size_t count, loff_t *offp);
ssize_t write(struct file *filp, const char __user *buff, size_t count, loff_t *offp);
ssize_t類型爲“signed size type(有符號的尺寸類型)” loff_t爲“long offset type(長偏移量類型)”,指明用戶在文件中進行存取操作的位置。
出錯時,read和write方法都返回一個負值,大於等於0的返回值告訴的調用程序成功傳遞輸了多少個字節。如果在正確傳輸部分數據之後發生了錯誤,則返回值必須是成功傳輸的字節數,但這個錯誤只能在下一次函數調用時纔會得到報告。這種實現慣例要求驅動程序必須記住錯誤的發生,這樣才能在將來把錯誤狀態返回給應用程序。
內核函數通過返回負值來表示錯誤,而且該返回值表明了錯誤的類型,但運行在用戶空間的程序看到的始終是作爲返回值的-1。爲了找出出錯的原因,用戶空間的程序必須訪問errno變量。用戶空間這種行爲源於POSIX標準,但該標準未對內核內部的操作做任何要求。
read方法:
返回值是等於read系統調用傳遞的count參數,則說明所請求的字節數成功完成了。
返回值小於count,則說明只傳輸了部分數據。需要程序繼續讀取,如果用fread函數,則會一直讀
返回值爲0,表示已經到文件尾
負值表示出現錯誤。
還一種情況是,現在沒有數據,以後可能會有
write方法:
返回值等於count則完成傳輸
是正的但小於count,則完成部分
爲0表示什麼也沒寫入。但這不是錯誤,可能是沒有理由返回一個錯誤碼,例如阻塞
負值表示錯誤。錯誤碼定義在<linux/errno.h>
readv和writev
向量操作,這些向量型函數具有一個結構體數組,每個結構包含一個指向緩衝區的指針和一個長度值。如果驅動程序沒有提供用於處理向量操作的方法,readv和writev會通過read和write方法的多次調用實現。
原型:ssize_t (*readv) (struct file *filp, const struct iovec *iov, unsigned long count, loff_t *ppos);
ssize_t (*writev)(struct file *filp, const struct iovec *iov, unsigned long count, loff_t *ppos);
struct iovec
{
void __user *iov_base;
__kernel_size_t iov_len;
}
iovec應由用戶程序創建。