?一、如何對設備操作
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 /*它定義了模塊的 API、類型和宏(MODULE_LICENSE、MODULE_AUTHOR等等),所有的內核模塊都必須包含這個頭文件。*/
#include
#include //設備號相關函數
#include //內存分配相關函數
#include
#include //設備號相關函數
#include //字符設備頭文件
#include
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中進行驗證。
評論
查看更多