Linux系統驅動開發
1:Linux 設備通常劃分爲三種:字符設備、塊設備和網絡接口設備。
字符設備是指:那些只能一個字節一個字節讀寫數據的設備,不能隨即讀取設備內存中的某一數據。其讀取數據需要按照先後順序,從這點上看,字符設備是面向數據流的設備。常用的字符設備有鼠標、鍵盤、串口、控制檯和LED等設備。
塊設備是指:可以從設備的任意位置讀取一定長度數據的設備。其讀取數據不必按照先後的順序,可以定位到設備的某一具體的位置,讀取數據。常見的塊設備有硬盤,磁盤,U盤和SD卡。
每一個字符設備或者塊設備都在/dev目錄下對應一個設備文件。進入/dev目錄下,執行 ls –l 命令,以C開頭的是字符設備,以b開頭的是塊設備。
2:主設備號和次設備號
一個字符設備或者一個塊設備都有一個主設備號和次設備號。主設備號和次設備號統稱爲設備號。主設備號用來表示 一個特定的驅動程序。此設備號用來表示使用該驅動程序的各設備。例如一個嵌入式系統,有兩個LED指示燈,LED燈需要獨立的打開或者關閉。那麼可以寫一個LED燈的字符設備驅動程序,可以將其主設備號註冊成5號設備,次設備號分別爲1和2.這裏次設備號爲別表示兩個LED燈。
2.1 主設備號和次設備號的表示:
在linux內核中,設備號用dev_t類型來表示。在linux 2.6.29.4中,dev_t定義爲一個無符號長整型變量,如下:
typedef u_long dev_t ;
u_long 在32位機中佔4個字節,在64位機中佔8個字節,以32位機爲例,其中高12位表示主設備號,低20位表示次設備號。
2.2 動態分配設備號和靜態分配設備號
靜態分配設備號,就是驅動程序開發者靜態的指定一個設備號。對於一部分成用的設備,內核開發者已經爲其分配了設備號,這些設備號可以在內核源碼documentation/divice.txt文件中找到,如果只有開發者自己使用這些設備驅動程序,那麼可以選擇一個尚未使用的一個設備號。在不添加新硬件的時候,這種方式不會產生設備號衝突的問題。但是當添加新硬件的時候,則很可能造成設備號衝突,影響設備的使用。
動態分配設備號,避免了設備號衝突的問題,該函數是alloc_chrdev_region().
2.3 查看設備號
當靜態分配設備號的時候,需要查看系統中已經存在的設備號,從而決定使用哪個新設備號,可以讀取/proc/devices文件獲得設備的設備號,該文件中包含字符設備和塊設備的設備號。
3:申請和釋放設備號
3.1:申請設備號
在構建字符設備之前,首先要向系統申請一個或者多個設備號。完成該工作的函數是register_chrdev_region(),該函數在<fs/ char_dev.c>中定義 。
int register_chrdev_region(dev_t from, unsigned count, const char *name);
其中,from是 要分配的設備號的起始值,一般只提供from的主設備號,from的此設備號通常被設置成0. Count是需要申請連續設備號的個數。Name是和該範圍編號關聯的設備名稱,該名稱不能超過64個字節。
register_chrdev_region()函數成功執行返回值爲0, 錯誤時,返回一個負的錯誤碼,並且不能爲字符設備分配設備號。
在linux中有非常多的字符設備,在人爲的爲字符設備分配設備號時,很可能發生衝突,linux 內核開發者一直在努力的將設備號變爲動態的。可以使用alloc_chrdev_region()達到這個目的。
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char*name);
在上面的函數中,dev作爲輸出參數,在函數成功返回後將保存已經分配好的設備號,函數有可能申請一段連續的設備號,這是dev返回第一個設備號。Baseminor表示要申請的第一個次設備號, 其通常設爲0.
3.2 :釋放設備號
以上兩種方式申請的設備號,都應該在不使用的時候釋放設備號,設備號的統一釋放使用下面的函數:
void unregister_chrdev_region(dev_t from, unsigned count);
這個函數中的from是要釋放的設備號,count 表示從form開始要釋放的設備號的個數。通常在模塊卸載的函數中,調用unregister_chrdev_region()函數。
4:字符設備結構體cdev
Struct cdev
{
Struct kobject kobj;/*用於內核管理字符設備,驅動開發人員一般不適用該成員*/
Struct module *owner;/*指向包含該結構的模塊的指針*/
Const struct file_operations *ops;/*指向字符設備操作函數的指針*/
Struct list_head list;/*該結構體將使用該驅動的字符設備連成一個結構體,廣泛應用,需要掌握*/
Dev_t dev;/*該字符設備的起始設備號,一個設備可能有多個設備號*/
Unsigned int count;/*使用該字符設備驅動的設備數量*/
}
5 file_operatins結構體和其成員函數
static const struct file_operations xxx_fops
{
.owner = THIS_MODULE , /*模塊引用,任何時候都賦值THIS_MODULE*/
.read = xxx_read, /*指定設備的讀函數*/
.write = xxx_write, /*指定設備的寫函數*/
.ioctol = xxx_ioctol, /*指定設備的控制函數*/
};
/*讀函數*/
static ssize_t xxx_read(struct file * filp, char __user * buf, ssize_t size, loff_t *opps)
{
…
If( size > 8)
Copy_to_user(buf, …, …); /*當數據較大時,使用copy_to_user(), 效率較高;
Else
put_user(…, buf);/*當數據較小時, 使用put_user(),效率較高。
}
/*寫函數*/
Static ssize_t xxx_write(struct file * filp, char __user * buf, ssize_t size, loff_t *opps)
{
…
If( size > 8)
Copy_from_user(buf, …, …); /*當數據較大時,使用copy_form_user(), 效率較高;
else
get_user(…, buf); /*當數據較小時, 使用get_user(),效率較高。
}
/* seek文件定位函數 */
static loff_t VirtualDisk_llseek(struct file *filp, loff_t offset, int orig)
{
loff_t ret = 0; /*返回的位置偏移*/
switch (orig)
{
case SEEK_SET: /*相對文件開始位置偏移*/
if (offset < 0) /*offset不合法*/
{
ret = - EINVAL; /*無效的指針*/
break;
}
if ((unsigned int)offset > VIRTUALDISK_SIZE)
/*偏移大於設備內存*/
{
ret = - EINVAL; /*無效的指針*/
break;
}
filp->f_pos = (unsigned int)offset; /*更新文件指針位置*/
ret = filp->f_pos; /*返回的位置偏移*/
break;
case SEEK_CUR: /*相對文件當前位置偏移*/
if ((filp->f_pos + offset) > VIRTUALDISK_SIZE)
/*偏移大於設備內存*/
{
ret = - EINVAL; /*無效的指針*/
break;
}
if ((filp->f_pos + offset) < 0) /*指針不合法*/
{
ret = - EINVAL; /*無效的指針*/
break;
}
filp->f_pos += offset; /*更新文件指針位置*/
ret = filp->f_pos; /*返回的位置偏移*/
break;
default:
ret = - EINVAL; /*無效的指針*/
break;
}
return ret;
}
/*ioctol設備控制函數*/
static long ioctol (static file *file, unsigned int cmd, unsigned long arg)
{
…
Switch( cmd )
{
Case xxx_cmd1:
… /*命令1 執行的操作*/
Break;
Case xxx_cmd2:
… /*命令2 執行的操作*/
Break;
Default:
Return -EINVAL;/*內核和驅動程序都不支持該命令時,返回無效的命令*/
}
Return 0;
}
7 驅動程序與應用程序的數據交換
驅動程序和應用程序的數據交換是非常重要的。
File_operations中的read()和write()函數就是用來在驅動程序和應用程序間進行數據交換的。通過數據交換,驅動程序和應用程序可以彼此瞭解對方。但是驅動程序和應用程序屬於不同的地址空間,驅動程序不能直接訪問應用程序的地址空間;同樣,應用程序也不能直接訪問驅動程序的地址空間,否則會破壞彼此空間的數據,從而造成系統崩潰,或者數據紊亂。
安全的方法是:使用內核提供的專用函數,完成數據在應用程序空間和驅動程序空間的交換。這些函數對用戶程序傳過來的指針進行了嚴格的檢查和必要的轉換,從而保證了用戶程序和驅動程序數據交換的安全性。這些函數有:
unsigned long copy_to_user(void __user * to, const void *form, unsigned long n);
unsigned long copy_from_user(void __user * to, const void *form, unsigned long n);
put_user();
get_user();
8--字符設備小結:
字符設備的驅動程序完成的主要工作是初始化,添加和刪除cdev結構體,申請和釋放設備號,以及填充file_operations結構體中的read(),write()ioctl()等重要函數。