使用register_chrdev函数注册字符设备,浪费了很多设备号,而且需要我们手动指定主设备号,还有这个函数还得需要自己去查哪个设备号没有被使用。因此,我们可以使用新的设备驱动函数来实现。
1、原理
是要使用设备号的时候向 Linux 内核申请,需要几个就申请几个,由 Linux 内核分配设备可以使用的设备号。
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name);//没有指定设备号
int register_chrdev_region(dev_t from, unsigned count, const char *name);//给定了设备的主设备号和次设备号
void unregister_chrdev_region(dev_t from, unsigned count);//给定了设备的主设备号和次设备号
2、准备工作
在Ubuntu中新建文件夹,进入Linux_Driver
ls
mkdir 3_newchrled
cp 2_led/ * 3_newchrled / -rf
cd 3_newchrled/
ls -a
cp ../2_led/.vscode/ ./ -rf
ls -a
rm led.code-workspace
ls
mv led.c newchrled.c
ls //驱动程序变了,应用程序不变
用VSCode打开工程,打开newchrled.c文件,只保留寄存器地址、地址映射后的虚拟地址指针、定义的开关灯,其余的全都删了。
3、编写驱动
3.1、编写驱动框架
#define NEWCHRLED_NAME "newchrled"
#define NEWCHRLED_COUNT 1
/* LED设备结构体 */
struct newchrled_dev{
dev_t devid; /* 设备号 */
int major; /* 主设备号 */
int minor; /* 次设备号 */
};
struct newchrled_dev newchrled;/* led设备 */
/* 入口 */
static int __init newchrled_init(void)
{
int ret = 0;
printk("newchrled_init!\r\n");
/* 1、初始化LED */
/* 2、申请字符设备 参照Linux内核来写*/
if(newchrled.major){/* 给定主设备号 */
newchrled.devid = MKDEV(newchrled.major, 0);
ret = register_chrdev_region(newchrled.devid, NEWCHRLED_COUNT , NEWCHRLED_NAME);
}else{/* 没有给定主设备号 */
ret = alloc_chrdev_region(&newchrled.devid,0,NEWCHRLED_COUNT ,NEWCHRLED_NAME);
newchrled.major = MAJOR(newchrled.devid);/* 提取出来设备号 */
newchrled.minor = MINOR(newchrled.devid);
}
if(ret < 0){
printk("newchrled chrdev_region err!\r\n");
return -1;
}
printk("newchrled major=%d, minor=%d\r\n",newchrled.major, newchrled.minor);
/* 3、注册字符设备 */
return 0;
}
/* 出口 */
static void __exit newchrled_exit(void)
{
printk("newchrled_exit!\r\n");
/* 注销设备号 */
unregister_chrdev_region(newchrled.devid, NEWCHRLED_COUNT);
}
//注册和卸载驱动
module_init(newchrled_init);
module_exit(newchrled_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("yang");
修改Makefile,将文件名字修改为:newchrled.o
打开终端。编译。
3.2、字符设备注册方法
在Linux中使用用 cdev 结构体表示一个字符设备,cdev 结构体在 include/linux/cdev.h 文件中。
在 cdev 中有两个重要的成员变量:ops 和 dev,这两个就是字符设备文件操作函数集合file_operations 以及设备号 dev_t。编写字符设备驱动之前需要定义一个 cdev 结构体变量,这个变量就表示一个字符设备,如下所示:
struct cdev test_cdev;
因此在前面定义的结构体newchrled_dev中添加下面这句代码:
struct newchrled_dev{
struct cdev cdev;
...
};
添加结构体cdev所在的一个头文件。
使用cdev_init()函数初始化结构体cdev的成员变量。参数 cdev 就是要初始化的 cdev 结构体变量,参数 fops 就是字符设备文件操作函数集合。
初始化完了之后,使用函数cdev_add()向内核添加设备。
void cdev_init(struct cdev *cdev, const struct file_operations *fops);//第一个参数是指针类型,第二个参数是结构体,需要定义一个结构体。
cdev_add(struct cdev *p, dev_t dev, unsigned count);//第一个参数是指哪个设备,第二个参数是设备号,第三个是设备数
在.c文件中定义结构体:
static const struct file_operations newchrled_fops = {
.owner = THIS_MODULE,
};
在newchrled_init(void)中添加如下代码:
/* 3、注册字符设备 */
newchrled.cdev.owner = THIS_MODULE;
cdev_init(&newchrled.cdev, &newchrled_fops);//初始化
ret = cdev_add(&newchrdev.cdev, newchrled.devid, NEWCHRLED_COUNT);
在newchrled_exit(void)函数中,先删除字符设备,再注销设备号
/* 1、删除字符设备 */
cdev_del(&newchrled.cdev);
/* 2、注销设备号 */
完善字符设备结构体
static int newchrled_open(struct inode *inode, struct file *filp)
{
return 0;
}
static int newchrled_release(struct inode *inode, struct file *filp)
{
return 0;
}
static ssize_t newchrled_write(struct file *filp, const char __user *buf, size_t count, loff_t *ppos)
{
}
static const struct file_operations newchrled_fops={
.owner = THIS_MODULE,
.write = newchrled_write,
.open = newchrled_open,
.release = newchrled_release,
}
3.3、测试
在vscode中打开终端设备
make
sudo cp newchrled.ko /home/yang/linux/nfs/rootfs/lib/modules/4.1.15/ -f
开发板上电,进入串口端:
/lib/modules/4.1.15/ # ls //查看newchrled.ko是否存在
/lib/modules/4.1.15/ # depmod
/lib/modules/4.1.15/ # modprobe newchrled.ko
/lib/modules/4.1.15/ #
3.4、点灯
将上一节的点灯代码拷贝过来。
在newchrled_write()里面添加灯的处理程序。
将上一节的初始化灯的函数拷贝过来到init函数中。
在exit函数中添加关灯函数。
3.5、测试
/lib/modules/4.1.15/ # lsmod
/lib/modules/4.1.15/ # modprobe newchrled.ko
/lib/modules/4.1.15/ # lsmod
/lib/modules/4.1.15/ # cat /proc/devices
/lib/modules/4.1.15/ # mknod /dev/newchrled c 249 0
/lib/modules/4.1.15/ # ls
/lib/modules/4.1.15/ # ./ledAPP /dev/newchrled 1
/lib/modules/4.1.15/ # ./ledAPP /dev/newchrled 0
4、自动创建设备节点
在init函数中自动创建设备节点。借鉴Linux内核人家的用法。拷贝过来,修改。
4.1、创建class
class_create()有一个返回值,返回值是一个class(是一个指针)。因此,把返回的值放入结构体newchrled_dev中,添加:struct class *class; 添加头文件:#include <linux/device.h>
newchrled.class = class_create(THIS_MODULE, NEWCHRLED_NAME);
if(IS_ERR(newchrled.class))
return PTR_ERR(newchrled.class);
在注销的时候,要删除class。在exit函数中:
class_destroy(newchrled.class);
4.2、创建设备
参考别人写的。
返回值是一个结构体类型的指针。
在结构体newchrled_dev中添加:struct device * device;//设备
newchrdev.device = device_create(newchrled.class, NULL, newchrled.devid, NULL, NEWCHRLED_NAME);
if(IS_ERR(newchrled.device))
return PTR_ERR(newchrled.device);
先摧毁设备,再摧毁类,因为这个类用到了设备。
device_destory(newchrled.class, newchrled.devid);
4.3、测试
make
sudo cp newchrled.ko /home/yang/linux/nfs/rootfs/lib/modules/4.1.15/ -f
/lib/modules/4.1.15/ # lsmod
/lib/modules/4.1.15/ # ls /dev/new+TAB
/lib/modules/4.1.15/ # modprobe newchrled.ko
/lib/modules/4.1.15/ # ls /dev/newchrled -l
/lib/modules/4.1.15/ # ./ledAPP /dev/newchrled 1
/lib/modules/4.1.15/ # ./ledAPP /dev/newchrled 0
/lib/modules/4.1.15/ # rmmod newchrled.ko
5、文件私有数据
参考cm4000.c文件的用法:
一般在open的时候设置私有数据。struct file *filp这个结构体中有一个私有数据的变量。因此我们可以在open函数中设置:
static int newchrled_open(struct inode *inode, struct file *filp)
{
filp->private_data = &newchrled;
return 0;
}
在release、write中就可以不用调用newchrled了。
static int newchrled_release(struct inode *inode, struct file *filp)
{
struct newchrled_dev *dev = (struct newchrled_dev*)filp->private_data;
//下面就可以直接访问dev
return 0;
}
在注册字符设备时,判断newchrled.major是否为给定的字符设备号,在这之前我们没有初始化major,为了保险起见,初始化结构体:
newchrled.major = 0;//手动清0
我们在判断是否成功的时候,返回了-1或者其它的,但是人家写的都是使用goto返回的。
goto fail_devid;
goto fail_cdevinit;
fail_devid:
return -1;
fail_cdevinit:
unregister_chedev_region(newchrled.devid, NEWCHRLED_COUNT);