上篇文章介紹了字符設(shè)備的開(kāi)發(fā)模板,但那是一種舊版本的驅(qū)動(dòng)開(kāi)發(fā)模式,設(shè)備驅(qū)動(dòng)需要手動(dòng)分配設(shè)備號(hào)再使用 register_chrdev進(jìn)行注冊(cè),加載成功以后還需要手動(dòng)使用mknod命令創(chuàng)建設(shè)備節(jié)點(diǎn),比較麻煩。
目前Linux內(nèi)核推薦的新字符設(shè)備驅(qū)動(dòng)API函數(shù),使得驅(qū)動(dòng)的使用更加自動(dòng)化,本篇就來(lái)一起研究下。
1 舊字符設(shè)備驅(qū)動(dòng)的弊端
使用register_chrdev函數(shù)注冊(cè)字符設(shè)備,需要指定一個(gè)設(shè)備號(hào),這就造成:
需要事先確定好哪些主設(shè)備號(hào)沒(méi)有使用
會(huì)將一個(gè)主設(shè)備號(hào)下的所有次設(shè)備號(hào)都使用掉,比如主設(shè)備號(hào)為200,那么 0~1048575(2^20-1)這個(gè)區(qū)間的次設(shè)備號(hào)就全部都被占用了
回顧上一篇的操作,先是加載驅(qū)動(dòng):
加載完,還有手動(dòng)使用mknod指令來(lái)手動(dòng)創(chuàng)建該設(shè)備節(jié)點(diǎn),并且指定驅(qū)動(dòng)程序中寫(xiě)死的設(shè)備號(hào):
本篇,就要使用一種新的字符驅(qū)動(dòng)編寫(xiě)方式,實(shí)現(xiàn)設(shè)備號(hào)的自動(dòng)分配,省去mknod指令操作。
2 新字符設(shè)備驅(qū)動(dòng)原理
2.1 分配和釋放設(shè)備號(hào)
使用設(shè)備號(hào)的時(shí)候向Linux內(nèi)核申請(qǐng),需要幾個(gè)就申請(qǐng)幾個(gè),由Linux內(nèi)核分配設(shè)備可以使用的設(shè)備號(hào)。
使用如下函數(shù)來(lái)申請(qǐng)?jiān)O(shè)備號(hào)(該函數(shù)在上篇提到過(guò)):
/*
* dev:保存申請(qǐng)到的設(shè)備號(hào)
* baseminor:次設(shè)備號(hào)起始地址,一般baseminor為0 (次設(shè)備號(hào)以baseminor為起始地址地址開(kāi)始遞)
* count:要申請(qǐng)的設(shè)備號(hào)數(shù)量
* name:設(shè)備名字
*/
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)
如果給定了設(shè)備的主設(shè)備號(hào)和次設(shè)備號(hào)就使用如下所示函數(shù)來(lái)注冊(cè)設(shè)備號(hào)即可:
/*
* from:要申請(qǐng)的起始設(shè)備號(hào)
* count:要申請(qǐng)的設(shè)備號(hào)數(shù)量
* name:設(shè)備名字
*/
int register_chrdev_region(dev_t from, unsigned count, const char *name)
注銷(xiāo)字符設(shè)備之后要釋放設(shè)備號(hào),不管是通過(guò)alloc_chrdev_region函數(shù)的動(dòng)態(tài)分配還是register_chrdev_region函數(shù)手動(dòng)指定的設(shè)備號(hào),統(tǒng)一使用(和上篇使用的一樣)的釋放函數(shù):
/*
* from:要釋放的設(shè)備號(hào)
* count:表示從from開(kāi)始,要釋放的設(shè)備號(hào)數(shù)量
*/
void unregister_chrdev_region(dev_t from, unsigned count)
新字符設(shè)備驅(qū)動(dòng)下,設(shè)備號(hào)分配示例代碼如下:
int major; /*主設(shè)備號(hào)*/
int minor; /*次設(shè)備號(hào)*/
dev_t devid; /*設(shè)備號(hào)*/
/*定義了主設(shè)備號(hào)*/
if (major)
{
devid = MKDEV(major, 0); /*大部分驅(qū)動(dòng)次設(shè)備號(hào)都選擇0*/
register_chrdev_region(devid, 1, "test");
}
/*沒(méi)有定義設(shè)備號(hào)*/
else
{
alloc_chrdev_region(&devid, 0, 1, "test"); /*申請(qǐng)?jiān)O(shè)備號(hào)*/
major = MAJOR(devid); /*獲取分配號(hào)的主設(shè)備號(hào)*/
minor = MINOR(devid); /*獲取分配號(hào)的次設(shè)備號(hào)*/
}
2.2 字符設(shè)備注冊(cè)
2.2.1 cdev字符設(shè)備結(jié)構(gòu)
在Linux中使用cdev結(jié)構(gòu)體表示一個(gè)字符設(shè)備,其定義在include/linux/cdev.h文件中:
struct cdev {
struct kobject kobj;
struct module *owner;
const struct file_operations *ops; /*文件操作函數(shù)集合*/
struct list_head list;
dev_t dev; /*設(shè)備號(hào)*/
unsigned int count;
};
2.2.2 cdev_init 函數(shù)
定義好cdev變量以后就要使用cdev_init函數(shù)對(duì)其進(jìn)行初始化:
/*
* cdev:要初始化的cdev結(jié)構(gòu)體變量
* fops:字符設(shè)備文件操作函數(shù)集合
*/
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
該函數(shù)的使用示例如下:
/*要初始化的cdev結(jié)構(gòu)體*/
struct cdev testcdev;
/* 設(shè)備操作函數(shù) */
static struct file_operations test_fops = {
.owner = THIS_MODULE,
/* 其他具體的初始項(xiàng) */
};
testcdev.owner = THIS_MODULE;
/* 初始化cdev*/
cdev_init(&testcdev, &test_fops);
2.2.3 cdev_add函數(shù)
該函數(shù)用于向Linux系統(tǒng)添加字符設(shè)備,即cdev結(jié)構(gòu)體變量:
/*
* cdev:要初始化的cdev結(jié)構(gòu)體變量
* dev:字符設(shè)備所使用的設(shè)備號(hào)
* count:要添加的設(shè)備數(shù)量
*/
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
2.2.4 cdev_del函數(shù)
卸載驅(qū)動(dòng)的時(shí)候要使用cdev_del函數(shù)從Linux內(nèi)核中刪除字符設(shè)備:
/*
* p:要?jiǎng)h除的字符設(shè)備
*/
void cdev_del(struct cdev *p)
2.3 自動(dòng)創(chuàng)建設(shè)備節(jié)點(diǎn)
上篇的Linux驅(qū)動(dòng)實(shí)驗(yàn)中,在使用modprobe加載驅(qū)動(dòng)程序以后還需要使用“mknod”命令手動(dòng)創(chuàng)建設(shè)備節(jié)點(diǎn),比較麻煩,這里就來(lái)研究一下如何實(shí)現(xiàn)自動(dòng)創(chuàng)建設(shè)備節(jié)點(diǎn)。
2.3.1 mdev機(jī)制
在Linux下通過(guò)udev來(lái)實(shí)現(xiàn)設(shè)備文件的自動(dòng)創(chuàng)建與刪除。使用busybox構(gòu)建根文件系統(tǒng)的時(shí)候,busybox會(huì)創(chuàng)建一個(gè)udev的簡(jiǎn)化版本mdev。
所以,在嵌入式開(kāi)發(fā)中使用mdev來(lái)實(shí)現(xiàn)設(shè)備節(jié)點(diǎn)文件的自動(dòng)創(chuàng)建與刪除。Linux系統(tǒng)中的熱插拔事件也由mdev 管理,在/etc/init.d/rcS 文件中如下語(yǔ)句:
echo /sbin/mdev > /proc/sys/kernel/hotplug
2.3.2 創(chuàng)建和刪除類(lèi)
自動(dòng)創(chuàng)建設(shè)備節(jié)點(diǎn)的工作是在驅(qū)動(dòng)程序的入口函數(shù)中完成的,一般在cdev_add函數(shù)后面添 加自動(dòng)創(chuàng)建設(shè)備節(jié)點(diǎn)相關(guān)代碼。
首先要創(chuàng)建一個(gè)class類(lèi),其實(shí)是個(gè)結(jié)構(gòu)體,定義在include/linux/device.h里面。class_create是類(lèi)創(chuàng)建函數(shù)(宏定義):
#define class_create(owner, name) \
({ \
static struct lock_class_key __key; \
__class_create(owner, name, &__key); \
})
struct class *__class_create(struct module *owner,
const char *name,
struct lock_class_key *key)
卸載驅(qū)動(dòng)程序的時(shí)候需要使用函數(shù)為class_destroy刪除掉類(lèi):
/*
* cls:要?jiǎng)h除的類(lèi)
*/
void class_destroy(struct class *cls);
2.3.3 創(chuàng)建設(shè)備
創(chuàng)建好類(lèi)以后還不能實(shí)現(xiàn)自動(dòng)創(chuàng)建設(shè)備節(jié)點(diǎn),還需要在這個(gè)類(lèi)下創(chuàng)建一個(gè)設(shè)備。使用device_create函數(shù)創(chuàng)建設(shè)備:
/*
* class:設(shè)備要?jiǎng)?chuàng)建哪個(gè)類(lèi)下面
* parent:父設(shè)備, 一般為 NULL
* devt:設(shè)備號(hào)
* drvdata:設(shè)備可能會(huì)使用的一些數(shù)據(jù),一般為 NULL
* fmt:設(shè)備名字
*/
struct device *device_create(struct clas *class,
struct device *parent,
dev_t devt,
void *drvdata,
const char *fmt, ...)
參數(shù)最后的...
表示這在是一個(gè)可變參數(shù)的函數(shù)。
2.4 設(shè)置文件私有數(shù)據(jù)
每個(gè)硬件設(shè)備都有一些屬性, 比如主設(shè)備號(hào)(dev_t),類(lèi)(class)、設(shè)備(device)、開(kāi)關(guān)狀態(tài)(state)等等,在編寫(xiě)驅(qū)動(dòng)的時(shí)候你可以將這些屬性全部寫(xiě)成變量的形式:
dev_t devid; /*設(shè)備號(hào)*/
struct cdev cdev; /*cdev*/
struct class *class; /*類(lèi)*/
struct device *device; /*設(shè)備*/
int major; /*主設(shè)備號(hào)*/
int minor; /*次設(shè)備號(hào)*/
可以將所有屬性信封裝到結(jié)構(gòu)體中, 并在編寫(xiě)驅(qū)動(dòng)open函數(shù)的時(shí)候?qū)⑵渥鳛樗接袛?shù)據(jù)添加到設(shè)備文件中:
/*設(shè)備結(jié)構(gòu)體*/
struct test_dev{
dev_t devid; /*設(shè)備號(hào)*/
struct cdev cdev; /*cdev*/
struct class *class; /*類(lèi)*/
struct device *device; /*設(shè)備*/
int major; /*主設(shè)備號(hào)*/
int minor; /*次設(shè)備號(hào)*/
};
struct test_dev testdev;
/*open函數(shù)*/
static int test_open(struct inode *inode, struct file *filp)
{
filp->private_data = &testdev; /*設(shè)置私有數(shù)據(jù)*/
return 0;
}
3 驅(qū)動(dòng)程序編寫(xiě)
在上篇的基礎(chǔ)上進(jìn)行修改,因?yàn)橹皇歉鼡Q的驅(qū)動(dòng)程序的編寫(xiě)方式,與應(yīng)用程序無(wú)關(guān),因此只修改驅(qū)動(dòng)程序即可。
3.1 添加一些定義
因?yàn)樯掀恼碌拇a中使用的是chrdevbase這個(gè)名稱(chēng),為了減少修改量,這里僅把結(jié)構(gòu)體類(lèi)型定義為帶有new標(biāo)志的newchr_dev,變量名仍使用chrdevbase這個(gè)名稱(chēng)。
#define CHRDEVBASE_CNT 1 /* 設(shè)備號(hào)個(gè)數(shù) */
#define CHRDEVBASE_NAME "chrdevbase" /* 名字 */
/*newchr設(shè)備結(jié)構(gòu)體 */
struct newchr_dev{
dev_t devid; /* 設(shè)備號(hào) */
struct cdev cdev; /* cdev */
struct class *class; /* 類(lèi) */
struct device *device; /* 設(shè)備 */
int major; /* 主設(shè)備號(hào) */
int minor; /* 次設(shè)備號(hào) */
};
struct newchr_dev chrdevbase; /* 自定義字符設(shè)備 */
3.2 修改open函數(shù)
在上篇程序的基礎(chǔ)上增加了一條“設(shè)置私有數(shù)據(jù)”
static int chrdevbase_open(struct inode *inode, struct file *filp)
{
printk("chrdevbase open!\r\n");
filp->private_data = &chrdevbase; /* 設(shè)置私有數(shù)據(jù) */
return 0;
}
3.3 修改init函數(shù)
這個(gè)修改比較大,因?yàn)橐趇nit函數(shù)中使用設(shè)備號(hào)的自動(dòng)分配。
static int __init chrdevbase_init(void)
{
/* 注冊(cè)字符設(shè)備驅(qū)動(dòng) */
/* 1、創(chuàng)建設(shè)備號(hào) */
if (chrdevbase.major) /* 定義了設(shè)備號(hào) */
{
chrdevbase.devid = MKDEV(chrdevbase.major, 0);
register_chrdev_region(chrdevbase.devid, CHRDEVBASE_CNT, CHRDEVBASE_NAME);
}
else /* 沒(méi)有定義設(shè)備號(hào) */
{
alloc_chrdev_region(&chrdevbase.devid, 0, CHRDEVBASE_CNT, CHRDEVBASE_NAME); /* 申請(qǐng)?jiān)O(shè)備號(hào) */
chrdevbase.major = MAJOR(chrdevbase.devid); /* 獲取分配號(hào)的主設(shè)備號(hào) */
chrdevbase.minor = MINOR(chrdevbase.devid); /* 獲取分配號(hào)的次設(shè)備號(hào) */
}
printk("chrdevbase major=%d,minor=%d\r\n",chrdevbase.major, chrdevbase.minor);
/* 2、初始化cdev */
chrdevbase.cdev.owner = THIS_MODULE;
cdev_init(&chrdevbase.cdev, &chrdevbase_fops);
/* 3、添加一個(gè)cdev */
cdev_add(&chrdevbase.cdev, chrdevbase.devid, CHRDEVBASE_CNT);
/* 4、創(chuàng)建類(lèi) */
chrdevbase.class = class_create(THIS_MODULE, CHRDEVBASE_NAME);
if (IS_ERR(chrdevbase.class))
{
return PTR_ERR(chrdevbase.class);
}
/* 5、創(chuàng)建設(shè)備 */
chrdevbase.device = device_create(chrdevbase.class, NULL, chrdevbase.devid, NULL, CHRDEVBASE_NAME);
if (IS_ERR(chrdevbase.device))
{
return PTR_ERR(chrdevbase.device);
}
printk("chrdevbase init done!\r\n");
return 0;
}
3.4 修改exit函數(shù)
因?yàn)閕nit修改較大,對(duì)應(yīng)的exit也要進(jìn)行大的修改:
static void __exit chrdevbase_exit(void)
{
/* 注銷(xiāo)字符設(shè)備驅(qū)動(dòng) */
cdev_del(&chrdevbase.cdev);/* 刪除cdev */
unregister_chrdev_region(chrdevbase.devid, CHRDEVBASE_CNT); /* 注銷(xiāo)設(shè)備號(hào) */
device_destroy(chrdevbase.class, chrdevbase.devid);
class_destroy(chrdevbase.class);
printk("chrdevbase exit done!\r\n");
}
至此,修改完畢,其它的與之前的一樣。
3.5 新舊驅(qū)動(dòng)方式對(duì)比
通過(guò)一張圖來(lái)對(duì)比新舊兩種驅(qū)動(dòng)編寫(xiě)方式的區(qū)別:
舊方式編寫(xiě)驅(qū)動(dòng)的流程
新方式編寫(xiě)驅(qū)動(dòng)的流程
可以看出主要區(qū)別在驅(qū)動(dòng)的加載和卸載。
4 編譯驅(qū)動(dòng)
和上次編譯驅(qū)動(dòng)的方式一樣,使用makefile,因?yàn)轵?qū)動(dòng)的c文件名由chrdevbase.c改為了newchrdevbase.c,因此makefile文件中也要把名字改掉。
編譯完之后,將編譯出的ko文件先復(fù)制到ubuntu虛擬機(jī)的tftpboot目錄中,為后面的測(cè)序做準(zhǔn)備。
復(fù)制后,看一下tftpboot目錄:
5 程序測(cè)試
5.1 文件發(fā)送到板子
和上篇一樣,使用tftp傳輸,將ubuntu虛擬機(jī)編譯出的ko文件發(fā)送到linux板子中。
再來(lái)看下tftp傳輸?shù)挠布h(huán)境示意圖:
然后是傳輸指令以及傳輸結(jié)果,可以看到newchrdevbase.ko已經(jīng)從ubuntu虛擬機(jī)的tftpboot目錄傳輸?shù)搅薼inux板子的/lib/modules/4.1.15目錄中了。
5.2 測(cè)試
輸入如下兩條指令加載 newchrdevbase.ko 驅(qū)動(dòng)模塊:
depmod //第一次加載驅(qū)動(dòng)的時(shí)候需要運(yùn)行此命令 modprobe newchrdevbase.ko //加載驅(qū)動(dòng)
驅(qū)動(dòng)加載成功后,可以看到自動(dòng)申請(qǐng)到的主設(shè)備號(hào)和次設(shè)備號(hào),如下圖,主設(shè)備號(hào)為249。
再輸入ls /dev/chrdevbase -l
指令驗(yàn)證/dev/chrdevbase 這個(gè)設(shè)備節(jié)點(diǎn)文件是否存在,如下圖,可以看到設(shè)備存在,注意和上篇舊驅(qū)動(dòng)方式操作上的不同之處,舊的驅(qū)動(dòng)方式需要額外使用mknod指令來(lái)手動(dòng)創(chuàng)建該設(shè)備節(jié)點(diǎn)。
驅(qū)動(dòng)已經(jīng)加載成功,再來(lái)測(cè)試APP程序,理論上和上篇的效果一樣,實(shí)測(cè)也是:
OK,測(cè)試完畢,測(cè)試完使用rmmod指令卸載驅(qū)動(dòng)。
6 總結(jié)
此篇文章針對(duì)上篇文章使用舊字符驅(qū)動(dòng)編寫(xiě)方式存在的不足,介紹了一種新的字符驅(qū)動(dòng)編寫(xiě)方式,對(duì)比兩種方式編寫(xiě)的主要區(qū)別,在上篇驅(qū)動(dòng)代碼的基礎(chǔ)上進(jìn)行修改,并測(cè)試通過(guò),和上篇實(shí)現(xiàn)一樣的效果,但驅(qū)動(dòng)的加載更加方便,不再需要人為指定設(shè)備號(hào)。
審核編輯:湯梓紅
-
設(shè)備
+關(guān)注
關(guān)注
2文章
4347瀏覽量
70272 -
模板
+關(guān)注
關(guān)注
0文章
107瀏覽量
20533 -
函數(shù)
+關(guān)注
關(guān)注
3文章
4237瀏覽量
61969
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論