1、申请字符类设备号
1.1 字符设备和杂项设备的区别
杂项设备的主设备号号是固定的,固定为10,而字符类设备需要自己或者系统分配主设备号。杂项设备可以自动生成设备节点,字符设备需要套我们自己生成设备节点。
1.2 注册字符类设备号的两个方法
1.2.1 静态分配一个设备号
第一种:静态分配一个设备号,使用的函数是:在include/linux/fs.h
extern int register_chrdev_region(dev_t, unsigned, const char *);//在fs.h
注意:使用该函数需要明确知道系统里面有哪些设备号没有用
参数:
第一个:设备号的起始值。类型是dev_t。
dev_t在include/linux/types.h
dev_t是用来保存设备号的,是一个32为数。高12位用来保存主设备号,低12位用来保存次设备号
第二个:次设备号的个数。
第三个:设备的名称。
返回值:成功返回0,失败返回小于0。
Linux提供了几个宏定义来操作设备号:在include/linux/kdev_t.h
#define MINORBITS 20 //次设备号的位数,一共20位
define MINORMASK ((1U << MINORBITS) - 1) //此设备号的掩码
#define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS))//在dev_t里面获取主设备号
#define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))//在dev_t里面获取次设备号
//将主设备号和次设备号组成一个dev_t类型,第一个参数是主设备号,第二个参数的次设备号。
#define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))
1.2.2 动态分配一个设备号
第二种方法:动态分配,使用的函数是:
extern int alloc_chrdev_region(dev_t *, unsigned, unsigned, const char *);
使用动态分配会优先使用255到234。
参数:
第一个:保存生成的设备号。
第二个:请求的第一个次设备号,通常是0
第三个:连续申请的设备号个数。
第四个:设备名称。
返回值:成功返回0,失败返回负数。
1.3 如何注销设备号
extern void unregister_chrdev_region(dev_t, unsigned);//在include/linux/fs.h
参数:
第一个:分配设备号的起始地址
第二个:申请的连续设备号的个数
1.4 实验代码
#include <linux/init.h>
#include <linux/module.h>//最基本的文件,支持动态添加和卸载模块
#include <linux/fs.h> //包含了文件操作相关的struct的定义
#include <linux/kdev_t.h>
static int major_num;//主设备号
static int minor_num;//次设备号
module_param(major_num,int,S_IRUSR);//S_IRUSR在include/linux/stat.h
module_param(minor_num,int,S_IRUSR);//S_IRUSR在include/linux/stat.h
#define DEVICE_NUMBER 1 //定义次设备号的个数
#define DEVICE_SNAME "schrdev" //静态注册设备号的名称
#define DEVICE_ANAME "achrdev" //动态注册设备号的名称
#define DEVICE_MINOR_NUMBER 0 //定义次设备号的起始地址
static int hello_init(void)
{
int ret;
dev_t dev_num;
if(major_num && minor_num)
{
//静态注册设备号
printk("major_num = %d\n",major_num);
printk("minor_num = %d\n",minor_num);
dev_num = MKDEV(major_num,minor_num);
ret = register_chrdev_region(dev_num,DEVICE_NUMBER,DEVICE_SNAME);
if(ret < 0)
{
printk("registr_chrdev_region error\n");
return ret;
}
printk("registr_chrdev_region ok\n");
}
else
{
//动态注册设备号
ret = alloc_chrdev_region(&dev_num,DEVICE_MINOR_NUMBER,1,DEVICE_ANAME);
if(ret < 0)
{
printk("alloc_chrdev_region error\n");
return ret;
}
major_num = MAJOR(dev_num);
minor_num = MINOR(dev_num);
printk("major_num = %d\n",major_num);
printk("minor_num = %d\n",minor_num);
}
return 0;
}
static void hello_exit(void)
{
unregister_chrdev_region(MKDEV(major_num,minor_num),DEVICE_NUMBER);
printk("hello_exit \n");
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
1.4.1 静态分配的方法
首先需要使用cat /proc/devices需要明确知道系统里面有哪些设备号没有用,发现9可以使用,加载ko的时候使用静态分配的方法向驱动传参:
cat /proc/devices看看设备号是否生成,可以看到已经生成:
1.4.2 动态分配的方法
加载驱动:
cat /proc/devices看看设备号是否生成,可以看到已经生成:
建议使用动态申请设备号。
2、注册字符类设备步骤及实现
2.1 杂项设备回顾
//在include/linux/miscdevice.h
extern int misc_register(struct miscdevice *misc);
extern void misc_deregister(struct miscdevice *misc);
2.2 cdev结构体——描述字符设备的一个结构体
struct cdev {
struct kobject kobj;
struct module *owner;
const struct file_operations *ops;
struct list_head list;
dev_t dev;
unsigned int count;
};
2.3 注册一个字符类设备的步骤
2.3.1 步骤一:定义一个cdev结构体
2.3.2 步骤二:使用cdev_init函数初始化cdev结构体成员变量
void cdev_init(struct cdev *, const struct file_operations *);
参数一:要初始化的cdev
参数二:文件操作集
cdev->ops = fops;//实际就是把文件操作集写给ops
2.2.3 步骤三:使用cdev_add函数注册到内核
int cdev_add(struct cdev *, dev_t, unsigned);
参数一:cdev的结构体指针
参数二:设备号
参数三:次设备号的数量
2.4 注销字符类设备
void cdev_del(struct cdev *);
2.5 实验代码
#include <linux/init.h>
#include <linux/module.h>//最基本的文件,支持动态添加和卸载模块
#include <linux/fs.h> //包含了文件操作相关的struct的定义
#include <linux/kdev_t.h>
#include <linux/cdev.h>
static int major_num;//主设备号
static int minor_num;//次设备号
module_param(major_num,int,S_IRUSR);//S_IRUSR在include/linux/stat.h
module_param(minor_num,int,S_IRUSR);//S_IRUSR在include/linux/stat.h
#define DEVICE_NUMBER 1 //定义次设备号的个数
#define DEVICE_SNAME "schrdev" //静态注册设备号的名称
#define DEVICE_ANAME "achrdev" //动态注册设备号的名称
#define DEVICE_MINOR_NUMBER 0 //定义次设备号的起始地址
struct cdev cdev;
int chrdev_open(struct inode *inode, struct file *file)
{
printk("chrdev_open \n");
return 0;
}
struct file_operations chrdev_fops = {
.owner = THIS_MODULE,
.open = chrdev_open
};
static int hello_init(void)
{
int ret;
dev_t dev_num;
if(major_num && minor_num)
{
//静态注册设备号
printk("major_num = %d\n",major_num);
printk("minor_num = %d\n",minor_num);
dev_num = MKDEV(major_num,minor_num);
ret = register_chrdev_region(dev_num,DEVICE_NUMBER,DEVICE_SNAME);
if(ret < 0)
{
printk("registr_chrdev_region error\n");
return ret;
}
printk("registr_chrdev_region ok\n");
}
else
{
//动态注册设备号
ret = alloc_chrdev_region(&dev_num,DEVICE_MINOR_NUMBER,1,DEVICE_ANAME);
if(ret < 0)
{
printk("alloc_chrdev_region error\n");
return ret;
}
major_num = MAJOR(dev_num);
minor_num = MINOR(dev_num);
printk("major_num = %d\n",major_num);
printk("minor_num = %d\n",minor_num);
}
cdev.owner = THIS_MODULE;
//cdev_init函数初始化cdev结构体成员变量
cdev_init(&cdev,&chrdev_fops);
//完成字符设备注册到内核
cdev_add(&cdev,dev_num,DEVICE_NUMBER);
return 0;
}
static void hello_exit(void)
{
//注销设备号
unregister_chrdev_region(MKDEV(major_num,minor_num),DEVICE_NUMBER);
//注销字符设备
cdev_del(&cdev);
printk("hello_exit \n");
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
查看主设备号:cat /proc/devices/
注意字符设备注册完并不会自动生成设备节点,设备节点需要手动创建,所以需要用别的方法。要想字符设备生成设备节点,可以使用mknod命令创建一个设备节点,
格式为:
mknod 名称 类型 主设备号 次设备号
举例:
mknod /dev/test c 247 0
可以看到已经生成设备节点了,应用层可以使用文件IO接口对其进行读写等操作。
那怎么使得字符设备驱动自动生成设备节点呢,请看下小节
3、如何自动创建设备节点
在上面的Linux实验中使用insmod命令加载模块后,还需要通过mknod命令来收到创建设备节点,这样使用起来太麻烦了,并且不可能每个设备都去这样操作,Linux系统的存在就是为了方便使用,所以我们来看一下如何实现自动创建设备节点,当加载模块时,在/dev目录下自动创建相应的设备文件。
3.1 怎么自动创建一个设备节点?
在嵌入式linux中使用mdev来实现设备节点文件的自动创建和删除。
3.2 什么是mdev?
mdev是udev的简化版本,是busybox中所带的程序,最适合用在嵌入式系统。
3.3 什么是udev?
udev是一种工具,它能够根据系统中的硬件设备的状态动态更新设备文件,包括设备文件的创建,删除等。设备文件通常放在/dev目录下。使用udev后,在/dev目录下就只包含系统中真正存在的设备。udev一般用在PC上的linux中,相对mdev来说要复杂些。
3.4 怎么自动创建设备节点?
自动创建设备节点分为两个步骤:
3.4.1 步骤一:使用class_create函数创建一个class的类
3.4.2 步骤二:使用device_create函数在我们创建的类下面创建一个设备。
3.5 创建和删除类函数
在Linux驱动程序中一般通过两个函数来完成设备节点的创建和删除。首先要创建一个class类结构体,class结构体定义在include/linux/device.h里面。class_create是类创建函数。class_create是个宏定义,内容如下:
#define class_create(owner, name) \
({ \
static struct lock_class_key __key; \
__class_create(owner, name, &__key); \
})
参数 owner:一般为THIS_MODULE。
参数name:类名字。
返回值:指向结构体class的指针,也就是创建的类。
extern struct class * __must_check __class_create(struct module *owner,
const char *name,
struct lock_class_key *key);
卸载驱动程序的时候需要删除类,类删除函数为class_destory,函数原型如下:
extern void class_destroy(struct class *cls);
3.6 创建设备函数
当使用上节的函数创建完成一个类后,使用device_create函数在这个类下创建一个设备device_create函数原型如下:
struct device *device_create(struct class *cls, struct device *parent,
dev_t devt, void *drvdata,
const char *fmt, ...);
可见该函数是一个可变参数的函数。
参数class:就是设备要创建那个类下面;
参数parent:是父设备,一般为NULL,也就是没有父设备;
参数devt:设备号。
参数drvdata:是设备肯呢个会使用的一些数据,一般为NULL.
参数fmt:是设备名字,如果设置fmt=xxx的话,就会生成/dev/xxx这个设备文件。
返回值:创建好的设备。
3.7 删除创建的设备
卸载驱动的时候需要删除掉创建的设备,设备删除函数为device_destory,函数原型如下:
extern void device_destroy(struct class *cls, dev_t devt);
参数cls:要删除的设备所处的类
参数devt:要删除的设备号
3.8 实验代码
#include <linux/init.h>
#include <linux/module.h>//最基本的文件,支持动态添加和卸载模块
#include <linux/fs.h> //包含了文件操作相关的struct的定义
#include <linux/kdev_t.h>
#include <linux/cdev.h>
#include <linux/device.h> //包含了 device、class 等结构的定义
static int major_num;//主设备号
static int minor_num;//次设备号
module_param(major_num,int,S_IRUSR);//S_IRUSR在include/linux/stat.h
module_param(minor_num,int,S_IRUSR);//S_IRUSR在include/linux/stat.h
#define DEVICE_NUMBER 1 //定义次设备号的个数
#define DEVICE_SNAME "schrdev" //静态注册设备号的名称
#define DEVICE_ANAME "achrdev" //动态注册设备号的名称
#define DEVICE_MINOR_NUMBER 0 //定义次设备号的起始地址
#define DEVICE_CLASS_NAME "chrdev_class"
#define DEVICE_NODE_NAME "chrdev_test" //设备节点名称
struct cdev cdev;
struct class *class;
dev_t dev_num;
struct device *device;
int chrdev_open(struct inode *inode, struct file *file)
{
printk("chrdev_open \n");
return 0;
}
struct file_operations chrdev_fops = {
.owner = THIS_MODULE,
.open = chrdev_open
};
static int hello_init(void)
{
int ret;
if(major_num && minor_num)
{
//静态注册设备号
printk("major_num = %d\n",major_num);
printk("minor_num = %d\n",minor_num);
dev_num = MKDEV(major_num,minor_num);
ret = register_chrdev_region(dev_num,DEVICE_NUMBER,DEVICE_SNAME);
if(ret < 0)
{
printk("registr_chrdev_region error\n");
return ret;
}
printk("registr_chrdev_region ok\n");
}
else
{
//动态注册设备号
ret = alloc_chrdev_region(&dev_num,DEVICE_MINOR_NUMBER,1,DEVICE_ANAME);
if(ret < 0)
{
printk("alloc_chrdev_region error\n");
return ret;
}
major_num = MAJOR(dev_num);
minor_num = MINOR(dev_num);
printk("major_num = %d\n",major_num);
printk("minor_num = %d\n",minor_num);
}
//初始化cdev
cdev.owner = THIS_MODULE;
//cdev_init函数初始化cdev结构体成员变量
cdev_init(&cdev,&chrdev_fops);
//完成字符设备注册到内核
cdev_add(&cdev,dev_num,DEVICE_NUMBER);
//创建类
class = class_create(THIS_MODULE,DEVICE_CLASS_NAME);//会在/sys/class/目录下生成类名
//在class类下创建设备
device = device_create(class,NULL,dev_num,NULL,DEVICE_NODE_NAME);
return 0;
}
static void hello_exit(void)
{
//注销设备号
unregister_chrdev_region(MKDEV(major_num,minor_num),DEVICE_NUMBER);
//删除设备
cdev_del(&cdev);
//注销字符设备
device_destroy(class,dev_num);
//删除类
class_destroy(class);
printk("hello_exit \n");
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
可以发现不仅在/dev/下生成设备节点。也在/sys/class下生成了类: