DEV Community

shuai991102
shuai991102

Posted on

字符驱动内部实现原理解析及分步注册流程和代码

Image description

当设备驱动,注册进内核,内核会分配给当前的设备驱动一个编号,这个编号叫做设备号,设备号是一个32位的数据,由主设备号和次设备号组成,主设备号用来标识一类设备,次设备号用来标识一类设备中的一个设备,同类设备的主设备号一样,次设备号不同,主设备号是高12位,次设备号是低20位。

当注册驱动后得到驱动相应的设备号,基于这个设备号在文件中创建一个设备文件,这样就完成了设备文件和设备驱动的关联,当在用户空间操作设备文件时,就可以访问到驱动设备了。

Image description
open函数回调驱动中open操作方法的路线:

open()----->sys_open()---->struct inode结构体---->struct cdev结构体---->struct file_operations结构体---->mycdev_open()
Enter fullscreen mode Exit fullscreen mode

字符驱动分步注册流程及API

1.字符设备驱动结构体
    struct cdev {
        struct module *owner;             //THIS_MODULE
        const struct file_operations *ops; //操作方法结构体
        struct list_head list;            //构成链表
        dev_t dev;                        //设备号
        unsigned int count;               //设备的个数
    };
Enter fullscreen mode Exit fullscreen mode
2.分配字符设备驱动的对象
    struct cdev cdev;
    struct cdev *cdev = cdev_alloc();

    struct cdev *cdev_alloc(void)
    功能:为cdev结构体指针分配内存
    参数:
        @无
    返回值:成功返回cdev的结构体指针,失败返回NULL
Enter fullscreen mode Exit fullscreen mode
3.字符设备驱动对象初始化
    void cdev_init(struct cdev *cdev, const struct file_operations *fops)
    功能:完成cdev结构体成员的初始化(部分)
    参数:
        @cdev:cdev的结构体指针
        @fops:操作方法结构体指针
    返回值:无
Enter fullscreen mode Exit fullscreen mode

4、设备号的申请:

int register_chrdev_region(dev_t from, unsigned count, const char *name)
    功能:静态指定设备号
    参数:
         @from:指定的设备号 (主设备号|次设备号 eg: 241<<20|0)
         @count:设备的个数(3)
         @name:设备驱动的名字  cat /proc/devices查看
    返回值:成功返回0,失败返回错误码

    int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,
            const char *name)  
    功能:动态申请设备号
    参数:
         @dev      :申请到的设备号;在dev这个地址。
         @baseminor:次设备号开始值
         @count:设备的个数(3)
         @name:设备驱动的名字  cat /proc/devices查看
    返回值:成功返回0,失败返回错误码
Enter fullscreen mode Exit fullscreen mode
5.注册字符设备驱动
    int cdev_add(struct cdev *p, dev_t dev, unsigned count)
    功能:注册字符设备驱动
    参数:
         @p:cdev结构体指针
         @dev:设备号
         @count:设备的个数
    返回值:成功返回0,失败返回错误码
Enter fullscreen mode Exit fullscreen mode

出口函数:

1.销毁字符设备驱动
    void cdev_del(struct cdev *p)  
    功能:销毁字符设备驱动
    参数:
        @p:cdev结构体指针
    返回值:无
2.释放设备号
    void unregister_chrdev_region(dev_t from, unsigned count) 
    功能:释放设备号
    参数:
        @from:设备号
        @count:设备的个数
    返回值:无
3.释放动态申请的内存
     void kfree(void *p)
     功能:释放动态申请的内存
     参数:
         @p:cdev结构体的首地址
     返回值:无     
Enter fullscreen mode Exit fullscreen mode

分步流程:

//定义cdev的结构体指针变量
struct cdev *cdev;
static int __init mycdev_init(void)
{
    //1.分配对象
    //2.对象的初始化
    //3.申请设备号
    //4.字符设备驱动的注册
    //5.自动创建设备节点
    return 0;
}
static void __exit mycdev_exit(void)
{
    //1.销毁设备节点
    //2.销毁字符设备驱动
    //3.销毁设备号
    //4.释放动态申请的cdev内存

}
module_init(mycdev_init);
module_exit(mycdev_exit);
MODULE_LICENSE("GPL");
Enter fullscreen mode Exit fullscreen mode

完整代码:

#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/slab.h>

#define CNAME "mycdev"

struct cdev *cdev;//定义cdev的结构体指针变量
#if 0
unsigned int major = 0; //动态申请
#else
unsigned int major = 500; //静态指定
#endif
int minor=0;
const int count=3;
struct class *cls;
struct device *dev;

char kbuf[128] = {0};
/*******************************open*************************************/
int mycdev_open(struct inode *inode, struct file *file)
{
    printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);
    return 0;
}
/*******************************read*************************************/
ssize_t mycdev_read(struct file *file, char __user *ubuf, size_t size, loff_t *offs)
{
    int ret;
    printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);

    if(size > sizeof(kbuf)) size=sizeof(kbuf);
    ret = copy_to_user(ubuf,kbuf,size);
    if(ret){ 
        printk("copy data to user error\n");
        return -EIO;
    }
    return size; 
}
/*******************************write*************************************/
ssize_t mycdev_write(struct file *file, 
    const char __user *ubuf, size_t size, loff_t *off)
{
    int ret;
    printk("%s:%s:%d\n",__FILE__,__func__,__LINE__);

    if(size > sizeof(kbuf)) size=sizeof(kbuf);
    ret = copy_from_user(kbuf, ubuf, size);
    if(ret){ 
        printk("copy data from user error\n");
        return -EIO; 
    }
    return size; 
}
/*******************************close*************************************/
int mycdev_close(struct inode *inode, struct file *file)
{
    printk("%s:%s:%d\n",__FILE__,__func__,__LINE__); 
    return 0;
}
/*******************************方法结构体************************************/
const struct file_operations fops = {
    .open = mycdev_open,
    .read = mycdev_read,
    .write = mycdev_write,
    .release = mycdev_close,
};
/******************************init入口函数*************************************/
static int __init mycdev_init(void)
{
    int i,ret;
    dev_t devno;//设备号
    //1.分配对象空间
    cdev = cdev_alloc();
    if(cdev == NULL){
        printk("cdev alloc memory error\n");
        ret = -ENOMEM;
        goto ERR1;//返回一个错误值-ENOMEM
    }
    //2.对象的初始化
    cdev_init(cdev,&fops);
    //3.申请设备号
    if(major == 0){
        //动态申请
        ret = alloc_chrdev_region(&devno,minor,count,CNAME);
        if(ret){
            printk("dynamic:alloc device number error\n");
            goto ERR2;//销毁前面申请到的对象空间,并且返回一个错误值-ENOMEM
        }
        major = MAJOR(devno);//根据申请到的设备号分解出主设备号
        minor = MINOR(devno);//根据申请到的设备号分解出次设备号
    }else if(major > 0){
        //静态指定
        ret = register_chrdev_region(MKDEV(major,minor),count,CNAME);
        if(ret){
            printk("static:alloc device number error\n");
            goto ERR2;//销毁前面申请到的对象空间,并且返回一个错误值-ENOMEM
        }
    }
    //4.字符设备驱动的注册
    ret = cdev_add(cdev,MKDEV(major,minor),count);
    if(ret){
        printk("cdev register error\n");
        goto ERR3;//销毁申请到的设备号,前面申请到的对象空间,并且返回一个错误值-ENOMEM
    }
    //5.自动创建设备节点
    cls = class_create(THIS_MODULE,CNAME);//向上提交目录
    if(IS_ERR(cls)){
        printk("class create error\n");
        ret = PTR_ERR(cls);
        goto ERR4;/*销毁字符驱动,销毁申请到的设备号,前面申请到的对象空间,并且返回一个错误值-ENOMEM*/
    }
    for(i=0;i<count;i++){
        dev = device_create(cls,NULL,MKDEV(major,i),NULL,"mycdev%d",i);//向上提交设备节点
        if(IS_ERR(dev)){
            printk("device create error\n");
            ret = PTR_ERR(dev);
            goto ERR5;/*销毁目录,以及申请到的设备节点,销毁字符驱动,销毁申请到的设备号,前面申请到的对象空间,并且返回一个错误值-ENOMEM*/
        }
    }
    return 0; //!!!!!!!这里的return 0千万不要忘记写!!!!!!!!!!!!!!
ERR5:
    for(--i;i>=0;i--){
        device_destroy(cls,MKDEV(major,i));
    }
    class_destroy(cls);
ERR4:
    cdev_del(cdev);
ERR3:
    unregister_chrdev_region(MKDEV(major,minor),count);
ERR2:
    kfree(cdev);
ERR1:
    return ret;
}
/******************************exitchukou函数*************************************/
static void __exit mycdev_exit(void)
{
    int i;
    //1.销毁设备节点
    for(i=0;i<count;i++){
        device_destroy(cls,MKDEV(major,i));//销毁向上提交的设备节点
    }
    class_destroy(cls);//销毁向上提交的设备目录
    //2.销毁字符设备驱动
     cdev_del(cdev);
    //3.销毁设备号
    unregister_chrdev_region(MKDEV(major,minor),count);
    //4.释放动态申请的cdev内存
     kfree(cdev);

}
module_init(mycdev_init);
module_exit(mycdev_exit);
MODULE_LICENSE("GPL");
Enter fullscreen mode Exit fullscreen mode

AWS GenAI LIVE image

How is generative AI increasing efficiency?

Join AWS GenAI LIVE! to find out how gen AI is reshaping productivity, streamlining processes, and driving innovation.

Learn more

Top comments (0)

AWS GenAI LIVE image

How is generative AI increasing efficiency?

Join AWS GenAI LIVE! to find out how gen AI is reshaping productivity, streamlining processes, and driving innovation.

Learn more

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay