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