一、如何對裝置操作
linux中對裝置進行操作是通過檔案的方式進行的,包括open、read、write。
對於裝置檔案,一般稱其為裝置節點,
節點有一個屬性是裝置號(主裝置號、次裝置號),其中主裝置號將裝置檔案與驅動模組對應起來
當我們open一個裝置節點時,告訴了kernel要操作的是是主裝置號為XX的節點,然後kernel會通過過XX來尋找合適的記憶體模組,進而調用記憶體模組中定義的open函數
由於動作節點之前kernel就需要有主裝置號的資訊,因此主裝置號的申請、具有該主裝置號的字元裝置的添加都需要在驅動模組的初始化函數中執行
二、主裝置號的申請 建議採用動態申請的主裝置號的方式,linux中有很多裝置,每一個裝置對應著一個主裝置號,動態申請是由核心分配一個沒用的主
裝置號,
動態申請函數為alloc_chrdev_region,相對應的釋放函數為unregister_chrdev_region。
申請完後,可以從/proc/devices中讀到分配的主裝置號,後面建立裝置節點時還需要用到
三、向kernle添加字元裝置 上一步向核心申請了主裝置號,就可以向kernel中添加字元裝置了
kennel中一個字元裝置對應了一個結構體cdev,這個結構體中定義了對字元裝置的操作方式file_operations(包括open、read、write),這些操作方式也需要在驅動模組中事先定義好。
字元裝置結構體cdev的添加步驟:
cdev初始化:cdev_init,該函數將file_operations與cdev對應起來
向kernel添加:cdev_add,該函數將主裝置號與cdev結構體對應起來
當對open裝置節點時,首先通過節點找到主裝置號,然後再kernel中搜尋與主裝置號相對應的字元裝置cdev,然後動過cdev中file_operations結構體定義的open方法(這個open是需要自己實現的)
四、3個重要的結構體 一個是file_operations,這裡面主要包含了驅動的主要實現方法
一個是inode,這個是節點的資訊,包含了主裝置號和cdev結構體
一個是file,當節點首次被開啟時,就會在核心中建立一個file結構體,file結構其充當了file_operations中方法的紐帶,要不然read和wirte方法怎麼知道操作的是那個裝置的資料。
file中的自訂內容(驅動需要的資料)一般是在open中定義,然後read和write就可以操作自訂的資料了。
下面是一個簡單的執行個體,可以看到驅動是怎樣把自訂的open方法和主裝置號對應起來的
#include <linux/module.h> /*它定義了模組的 API、類型和宏(MODULE_LICENSE、MODULE_AUTHOR等等),所有的核心模組都必須包含這個標頭檔。*/
#include <linux/init.h>
#include <linux/fs.h> //裝置號相關函數
#include <linux/slab.h> //記憶體配置相關函數
#include <linux/types.h>
#include <linux/kdev_t.h>//裝置號相關函數
#include <linux/cdev.h>//字元裝置標頭檔
#include <linux/module.h>
struct char_dev
{
int size;
char *data;
struct cdev cdev;//核心中的字元裝置
};
int major = 0;
int minor = 0;
struct char_dev char_devices;
int char_open(struct inode *inode, struct file *filep)
{
int Major = 0;
Major = MAJOR(inode->i_rdev);
printk("open my_char_dev major: %d\n", Major);
return 0;
}
struct file_operations char_fops = {
.owner = THIS_MODULE,
.open = char_open,
};
static void char_exit(void) //如果init函數中調用了該函數,則不應有 __exit
{
dev_t dev;
printk("char device driver exit \n");
//釋放裝置號
dev = MKDEV(major, minor);
unregister_chrdev_region(dev, 1);
printk("release major %d\n", major);
//釋放記憶體
if(char_devices.data){
kfree(char_devices.data);
}
//從核心中刪除字元裝置
cdev_del(&(char_devices.cdev));
}
static int __init char_init(void)//__init一個標記,表明是初始化函數
{
//初始化的代碼
dev_t dev;
int result;
printk("char device driver init \n");
//動態向核心申請裝置號
result = alloc_chrdev_region(&dev, 0, 1, "my_char_dev");
major = MAJOR(dev);
minor = MINOR(dev);
printk("alloc major %d\n", major);
if (result < 0) {
printk(KERN_WARNING "my_char_dev: can't get major %d\n", major);
return result;
}
//為裝置分配一塊記憶體
char_devices.size = 100;
char_devices.data = (char*)kmalloc(char_devices.size, GFP_KERNEL);
if (!char_devices.data) {
result = -ENOMEM;
goto fail; //不能直接退出函數,需要釋放裝置號
}
//向核心中添加字元裝置cdev
cdev_init(&(char_devices.cdev), &char_fops);
char_devices.cdev.owner = THIS_MODULE;
char_devices.cdev.ops = &char_fops;
result = cdev_add(&(char_devices.cdev), dev, 1);
if((result < 0)) {
printk(KERN_WARNING "Error %d adding my_char_dev\n", result);
goto fail;
}
return 0; //成功
fail:
char_exit();
return result;
}
MODULE_LICENSE("Dual BSD/GPL");
//當模組被載入時,執行moudle_init函數,該函數會調用初始化函數
module_init(char_init);
//模組卸載時,調用,釋放資源
module_exit(char_exit);
KDIR=/usr/src/linux-headers-$(shell uname -r)
PWD=$(shell pwd)
obj-m = CharDevice.o
all:
$(MAKE) -C $(KDIR) M=$(PWD)
註:驅動insmod後,通過/proc/devices查看主裝置號,然後通過mknod在/dev下建立裝置節點,注意保持主裝置好的一致,當前的節點只支援open方法,可以在demsg中進行驗證。