https://www.cnblogs.com/lifexy/p/7827559.html
1、之前注冊字元裝置用的如下函數注冊字元裝置驅動:
int register_chrdev(unsigned int major, const char *name, const struct file_operations *fops)
但其實這個函數是linux版本2.4之前的注冊方式,它的原理是:
(1)确定一個主裝置号
(2)構造一個file_operations結構體,然後放在chrdevs數組中
(3)注冊:register_chrdev
然後當讀寫字元裝置的時候,就會根據主裝置号從chrdevs數組中取出相應的結構體,并調用相應的處理函數。
它會有個很大的缺點:
每注冊一個字元裝置的時候,還會連續注冊0~255個次裝置号,使它們綁定在同一個file_operations操作方法結構體上,在大多數情況下,都隻用極少的次裝置号,是以會浪費很多資源。
2、是以在2.4版本後,核心裡就加入了以下幾個函數也可以來實作注冊字元裝置:
分為:靜态注冊(指定裝置編号來注冊)、動态配置設定(不指定裝置編号來注冊),以及有連續注冊的次裝置編号範圍區間,避免了register_chrdev()浪費資源的缺點
2.1:指定裝置編号來靜态注冊一個字元裝置
int register_chrdev_region(dev_t from, unsigned count, const char *name)
from:注冊的指定起始裝置編号,比如:MKDEV(100, 0),表示起始主裝置号100,起始次裝置号為0
count:需要連續注冊的次裝置編号個數,比如:起始次裝置号為0,count=100,表示0~99的次裝置号都要綁定在同一個file_operations操作方法結構體上
*name:字元裝置名稱
當傳回值小于0,表示注冊失敗
2.2 動态配置設定一個字元裝置,注冊成功并将配置設定到的主次裝置号放入*dev裡
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)
*dev:存放起始裝置編号的指針,當注冊成功,*dev就會等于配置設定到的起始裝置編号,可以通過MAJOR()和MINOR()函數來提取主次裝置号
baseminor:次裝置号基位址,也就是起始次裝置号
count:需要連續注冊的次裝置編号個數,比如:起始次裝置(baseminot)為0,baseminor=2,表示0~1的此裝置号都要綁定在同一個file_operations操作方法結構體上
*name:字元裝置名稱
當傳回值小于0,表示注冊失敗
2.3 初始化cdev結構體,并将file_operations結構體放入cdev->ops裡
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
其中cdev結構體的成員,如下所示:
struct cdev {
struct kobject kobj; //内嵌的kobject對象
struct module *owner; //所屬子產品
const struct file_operations *ops; //操作方法結構體
struct list_head list; //與cdev對應的字元裝置檔案的inode->i_devices的連結清單頭
dev_t dev; //起始裝置編号,可以通過MAJOR(),MINOR()來提取主次裝置号
unsigned int count; //連續注冊的次裝置号個數
};
2.4 将cdev結構體添加到系統中,并将dev(注冊好的裝置編号)放入cdev->dev裡,count(次裝置編号個數)放入cdev->count裡
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
2.5 将系統中的cdev結構體删除掉
void cdev_del(struct cdev *p)
2.6 登出字元裝置
void unregister_chrdev_region(dev_t from, unsigned count)
from:登出的指定起始裝置編号,比如:MKDEV(100, 0),表示起始主裝置号為100,起始裝置号為0
count:需要連續登出的次裝置編号個數,比如:起始次裝置号為0,baseminor=100,表示登出0~99的次裝置号
3、接下來,我們便來寫一個字元裝置驅動
裡面調用兩次上面的函數,構造兩個不同的file_operations操作結構體,
次裝置号0~1 對應 第一個file_operations,
次裝置号2 對應 第二個file_operations,
然後再/dev/目錄下,通過次裝置号(0~3)建立4個裝置節點,利用測試應用程式打開這5個檔案,看有什麼現象
3.1 驅動代碼如下:hello.c
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/irq.h>
#include <asm/uaccess.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <asm/arch/regs-gpio.h>
#include <asm/hardware.h>
#include <linux/poll.h>
#include <linux/cdev.h>
/* 1. 主裝置号 */
static int major;
static int hello_open (struct inode *inode, struct file *file)
{
printk("hello_open\n");
return 0;
}
static int hello2_open (struct inode *inode, struct file *file)
{
printk("hello2_open\n");
return 0;
}
/* 2. 構造file_operations */
//操作結構體1
static struct file_operations hello_fops = {
.owner = THIS_MODULE,
.open = hello_open,
};
//操作結構體2
static struct file_operations hello2_fops = {
.owner = THIS_MODULE,
.open = hello2_open,
};
#define HELLO_CNT 2 //主裝置
static struct cdev hello_cdev; //儲存hello1_fops操作結構體的字元裝置
static struct cdev hello2_cdev; //儲存hello2_fops操作結構體的字元裝置
static struct class *cls;//class讓系統自動建立裝置節點
static int hello_init(void)
{
dev_t devid;
#if 0
/* 一個驅動程式就占據了256個次裝置号 */
major = register_chrdev(0, "hello", &file_operations);/* (major, 0), (major, 1), ..., (major, 255)都對應hello_fops */
#else
/* 3. 告訴核心 */
if (major) {
devid = MKDEV(major, 0);
register_chrdev_region(devid, HELLO_CNT, "hello"); /* (major, 0~1)對應hello_fops, (major, 2~255)都不對應hello_fops */
} else {
alloc_chrdev_region(&devid, 0, HELLO_CNT, "hello"); /* (major, 0~1)對應hello_fops, (major, 2~255)都不對應hello_fops */
major = MAJOR(devid);
}
//初始化cdev
cdev_init(&hello_cdev, &hello_fops);
//注冊cdev
cdev_add(&hello_cdev, devid, HELLO_CNT);
//測試另外一個file_operations
devid = MKDEV(major, 2);
register_chrdev_region(devid, 1, "hello2"); /* (major, 0~1)對應hello_fops, (major, 2~255)都不對應hello_fops */
//初始化cdev
cdev_init(&hello2_cdev, &hello2_fops);
//注冊cdev
cdev_add(&hello2_cdev, devid, 1);
#endif
//建立一個類
cls = class_create(THIS_MODULE, "hello");
//在這個類下面再建立一個裝置(裝置節點)
//mdev是udev的一個簡化版本
//mdev應用程式,就會被核心調用,會根據類和類下面的裝置這些資訊
class_device_create(cls, NULL, MKDEV(major, 0), NULL, "hello0");/* /dev/hello0 */
class_device_create(cls, NULL, MKDEV(major, 1), NULL, "hello1");/* /dev/hello1 */
//open hello2肯定打不開,因為注冊的區域(次裝置号)0~1,2用不了
class_device_create(cls, NULL, MKDEV(major, 2), NULL, "hello2");/* /dev/hello2 */
class_device_create(cls, NULL, MKDEV(major, 3), NULL, "hello3");/* /dev/hello3 */
return 0;
}
static void hello_exit(void)
{
class_device_destroy(cls, MKDEV(major, 0));
class_device_destroy(cls, MKDEV(major, 1));
class_device_destroy(cls, MKDEV(major, 2));
class_device_destroy(cls, MKDEV(major, 3));
class_destroy(cls);
cdev_del(&hello_cdev);
unregister_chrdev_region(MKDEV(major, 0), HELLO_CNT);
cdev_del(&hello2_cdev);
unregister_chrdev_region(MKDEV(major, 2), 1);
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
3.2 測試代碼如下所示
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
/*
* hello_test /dev/hello0
*/
void print_usaeg(char *file) //列印使用幫助資訊
{
printf("%s <dev>\n", file);
}
int main(int argc, char **argv)
{
int fd;
if(argc != 2)
{
print_usaeg(argv[0]);
return 0;
}
fd = open(argv[1], O_RDWR);
if(fd < 0)
printf("can't open %s\n", argv[1]);
else
printf("can open %s\n", argv[1]);
return 0;
}
4、運作測試:
如下圖,挂在驅動後,通過ls /dev/hello* -l ,看到建立了4個字元裝置節點
接下來開始測試驅動,如下圖所示,
打開/dev/hello0時,調用的是驅動代碼的操作結構體hello_fops裡的.open(),
打開/dev/hello1時,調用的是驅動代碼的操作結構體hello_fops裡的.open(),
打開/dev/hello2時,調用的是驅動代碼的操作結構體hello2_fops裡的.open(),
打開/dev/hello3時,打開無效,因為在驅動代碼裡沒有配置設定次裝置号3的操作結構體,
總結一下:
使用register_chrdev_region()等函數來注冊字元裝置,裡面可以存放多個不同的file_operations操作結構體,實作各種不同的功能