【学习笔记】

一、申请字符类设备号

1、字符设备和杂项设备的区别

================================================================================

(1)设备号的不同:杂项设备的主设备号是固定的,固定为10,而字符类设备需要我们自己或者系统来给我们分配。


(2)设备节点的生成方式不同:杂项设备可以自动生成设备节点,而字符设备需要我们自己生成设备节点。

2、两种方法注册字符类设备号

(1)静态分配设备号

需要明确知道系统里面哪些设备号没有被使用,然后手动分配。

函数定义在linux-4.9.268/include/linux/fs.h
extern int register_chrdev_region(dev_t, unsigned, const char *);
参数:
    第一个:设备的起始值,类型是dev_t类型
    第二个:次设备号的个数
    第三个:设备的名称

dev_t类型: dev_t是用来保存设备号的,是一个32位数
其中高12为用来保存设备号,低12为用来保存次设备号

dev_t定义在linux-4.9.268/include/linux/types.h里边
typedef __u32 __kernel_dev_t;
typedef __kernel_dev_t        dev_t;

Linux 提供了几个宏定义来操作设备号

定义在linux-4.9.268/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里面获取主次设备号
#define MKDEV(ma,mi)    (((ma) << MINORBITS) | (mi))//将主设备号和次设备号组成dev_t类型(上面提到,dev_t类型是用来保存设备号的)
其中MKDEV(ma,mi)参数:
    ma:主设备号
    mi:次设备号
返回值:
    成功:返回0
    失败:返回非零

(2)动态分配设备号

这个函数同样也定义在linux-4.9.268/include/linux/fs.h中
extern int alloc_chrdev_region(dev_t *, unsigned, unsigned, const char *);
参数:
    第一个:保存生成的设备号
    第二个:我们请求的第一个设备号,通常是0
    第三个:连续申请的设备号的个数
    第四个:设备名称
    
返回值:
    成功:返回0
    失败:返回负数
使用动态分配会优先使用255~234设备号

3、注销设备号

这个函数同样也定义在linux-4.9.268/include/linux/fs.h中
extern void unregister_chrdev_region(dev_t, unsigned);
参数:
    第一个:分配设备号的起始地址
    第二个:申请的连续设备号个数

实例操作:

chrdev.c

#include <linux/init.h>
#include <linux/module.h>
//注册设备号函数所在头文件
#include <linux/fs.h>
//处理设备号宏定义所在头文件
#include <linux/kdev_t.h>

//定义次设备号个数
#define DEVICE_NUMBER 1
//次设备号起始地址,通常为0
#define DEVICE_MINOR_NUMBER 0

//定义设备名称
#define DEVICE_SNAME "schrdev"    //静态注册
#define DEVICE_ANAME "achrdev"    //动态注册


static int major_num,minor_num;

module_param(major_num,int,S_IRUSR);
module_param(minor_num,int,S_IRUSR);


static int hello_init(void){
    
    dev_t dev_num;

    int ret;//定义保存函数返回值变量
    
    //静态申请设备号
    if(major_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){//返回值<0,注册失败
            printk("register_chrdev_region error\n");
        }
        printk("register_chrdev_region successful\n");//否则说明注册成功
    }
    else{//动态申请设备号

        ret = alloc_chrdev_region(dev_num, DEVICE_MINOR_NUMBER, DEVICE_NUMBER, DEVICE_ANAME);

        if(ret < 0){//返回值<0,注册失败
            printk("register_chrdev_region error\n");
        }
        printk("register_chrdev_region successful\n");//否则说明注册成功

        //使用宏定义获取设备号
        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("bye bye\n");
    
}

//入口和出口
module_init(hello_init);
module_exit(hello_exit);

//声明许可证
MODULE_LICENSE("GPL");


在实际开发中,建议使用动态申请设备号的方式,多人开发时,使用静态申请很容易造成设备号冲突。

二、注册字符设备

1、重要结构说明

==================================================================

cdev结构体:描述字符设备的结构体

//它定义在linux-4.9.268/include/linux/cdev.h中
struct cdev {
    struct kobject kobj;
    struct module *owner;//说明模块所属
    const struct file_operations *ops;//文件操作集
    struct list_head list;//链表节点
    dev_t dev;//设备号
    unsigned int count;//次设备号的数量
};

2、操作步骤

(1)定义一个cdev结构体

(2)使用cdev\_init函数初始化cdev结构体成员变量

void cdev_init(struct cdev *, const struct file_operations *){
    memset(cdev, 0, sizeof *cdev);
    INIT_LIST_HEAD(&cdev->list);
    kobject_init(&cdev->kobj, &ktype_cdev_default);
    
    cdev->ops = fops;//把文件操作集写给cdev的成员变量ops
}
参数:
    第一个:要初始化的cdev结构体指针
    第二个:文件操作集
    

(3)使用cdev\_add函数注册字符设备到内核

int cdev_add(struct cdev *, dev_t, unsigned);
参数:
    第一个:cdev的结构体指针
    第二个:设备号
    第三个:次设备号的数量

(4)注销字符设备

void cdev_del(struct cdev *);

3、实际操作展示

直接在上面申请设备号的代码修改,前面已有的代码注释,下面不在写,便于区别修改位置

chrdev.c

#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/kdev_t.h>
//注册字符设备所在
#include <linux/cedv.h>


#define DEVICE_NUMBER 1
#define DEVICE_MINOR_NUMBER 0

#define DEVICE_SNAME "schrdev"
#define DEVICE_ANAME "achrdev"

static int major_num,minor_num;

//定义cdev结构体
struct cdev cdev;

module_param(major_num,int,S_IRUSR);
module_param(minor_num,int,S_IRUSR);

//应用层调用设备节点,触发的open函数
int chrdev_open(struct inode *inode, struct file *file){//(*open)函数实现
    printk("hello chrdev_open\n");
    return 0;
}

//定义文件操作集
struct file_operations chrdev_ops = {
    .owner = THIS_MODULE,
    .open = chrdev_open
};

static int hello_init(void){
    
    dev_t dev_num;
    int ret;
    
    if(major_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("register_chrdev_region error\n");
        }
        printk("register_chrdev_region successful\n");
    }
    else{
        ret = alloc_chrdev_region(dev_num, DEVICE_MINOR_NUMBER, DEVICE_NUMBER, DEVICE_ANAME);

        if(ret < 0){
            printk("register_chrdev_region error\n");
        }
        printk("register_chrdev_region successful\n");

        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结构体成员变量,第二个参数,要提前定义文件操作集
    cdev_init(&cdev, chrdev_ops);
    //将字符设备注册到内核
    cdev_add(&cdev, dev_num, DEVICE_NUMBER);

    return 0;
}

static void hello_exit(void){

    //注销设备号
    unregister_chrdev_region(MKDEV(major_num, minor_num), DEVICE_NUMBER);
    
    //注销字符设备(注意把它写到注册设备号的下面,一个简单的逻辑问题)
    void cdev_del(&cdev);
    printk("bye bye\n");
    
}

module_init(hello_init);
module_exit(hello_exit);

MODULE_LICENSE("GPL");



app.c(只保留打开节点功能)

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int main(int argc, char *argv[]){//如果打开设备节点成功,这会调用驱动里边的misc_open()函数

    int fd;
    
    char buf[64] = {0};

    fd = open("/dev/test",O_RDWR);//open the device node

    if(fd < 0){        //determine whether the opening is successful
    
        perror("open error\n");
        
        return fd;
    }

    //close(fd);//关闭节点
    
    return 0;
}


【注意】字符设备注册完后并不会自动生成设备节点,需要是哦那个mknod命令创建设备节点

命令格式:

mknod [名称] [类型] [主设备号] [次设备号]

例如:

mknod /dev/test c 247 0        //"dev/test"为app.c中定义的设备节点名称

三、自动创建设备节点

===============================

当加载模块时,在/dev目录下自动创建相应的设备文件

1、怎么自动创建一个设备节点

在嵌入式Linux中使用mdev来实现设备节点文件的自动创建和删除

2、什么是mdev

mdev是udev的简化版本,是busybox中所带的程序,最适合用在嵌入式系统

3、什么是udev

udev是一种工具,它跟狗根据系统中的硬件设备的状态动态更新设备文件,包括设备文件的创建,删除等。设备文件通常放在/dev目录下。使用udev后,在/dev目录下就只包含系统中真正存在的设备,udev一般用在PC上的linux中,相对于mdev来说复制些。

4、怎么创建设备节点

自动创建设备节点分为两个步骤:

(1)使用class\_create函数创建一个class的类

(2)使用device\_create函数在我们创建的类下面创建一个设备

5、创建和删除类函数

在Linux驱动程序中一般通过两个函数来完成设备节点的创建和删除。首先要创建一个class类结构体。

calss结构体定义在include/linux/device.h中。class\_create是类创建函数,class\_create是一个宏定义,内容如下

#define class_create(owner, name)        \
({                        \
    static struct lock_class_key __key;    \
    __class_create(owner, name, &__key);    \
})

class\_create一共有两个参数,参数owner一般为THIS\_MODULE,参数name是类的名字。返回值是个指向结构体class的指针,也就是创建的类。

卸载驱动程序的时候需要删除掉类,类删除函数为class\_destory,函数原型如下:

void class_destroy(struct class *cls);
//参数cls就是要删除的类。

6、创建设备函数

当使用上节点的函数创建完成一个类后,使用device\_create函数在这个类下创建一个设备device\_create

函数原型如下:

//同样定义在include/linux/device.h中
struct device *device_create_vargs(struct class *cls, struct device *parent,
                   dev_t devt, void *drvdata,
                   const char *fmt, va_list vargs);
参数说明:
device_create 是个可变参数函数
class:设备要创建在哪个类下面
parent:父设备,一般为NULL,也就是没有父设备
devt:设备号
drvdata:是设备可能会使用的一些数据,一般为NULL
fmt:是设备名字,如果设置fmt=xxx的话,就会生成/dev/xxx这个设备文件
返回值就是创建号的设备

整理自嵌入式学习之Linux驱动篇

标签: Linux, dev, 字符, int, 驱动, 设备, include, cdev, num

相关文章推荐

添加新评论,含*的栏目为必填