精华内容
下载资源
问答
  • 杂项设备(misc device) ...其实是因为这些字符设备不符合预先确定的字符设备范畴,所有这些设备采用主编号10,一起归于misc device,其实misc_register就是用主标号10调用register_chrdev()的...

    一、字符设备基础知识

    在 2.4 的内核我们使用 register_chrdev(0, "hello", &hello_fops) 来进行字符设备设备节点的分配,这种方式每一个主设备号只能存放一种设备,它们使用相同的 file_operation 结构体,也就是说内核最多支持 256 个字符设备驱动程序。

    在 2.6 的内核之后,新增了一个 register_chrdev_region 函数,它支持将同一个主设备号下的次设备号进行分段,每一段供给一个字符设备驱动程序使用,使得资源利用率大大提升,同时,2.6 的内核保留了原有的 register_chrdev 方法。在 2.6 的内核中这两种方法都会调用到 __register_chrdev_region 函数,本文将从它入手来分析内核是如何管理字符设备驱动程序的。

    1、设备驱动分类

          linux系统将设备分为3类:字符设备、块设备、网络设备。使用驱动程序:


    字符设备:是指只能一个字节一个字节读写的设备,不能随机读取设备内存中的某一数据,读取数据需要按照先后数据。字符设备是面向流的设备,常见的字符设备有鼠标、键盘、串口、控制台和LED设备等。

    块设备:是指可以从设备的任意位置读取一定长度数据的设备。块设备包括硬盘、磁盘、U盘和SD卡等。
    每一个字符设备或块设备都在/dev目录下对应一个设备文件。linux用户程序通过设备文件(或称设备节点)来使用驱动程序操作字符设备和块设备。

    2、字符设备、字符设备驱动与用户空间访问该设备的程序三者之间的关系
         如图,在Linux内核中:

    a -- 使用cdev结构体来描述字符设备;

    b -- 通过其成员dev_t来定义设备号(分为主、次设备号)以确定字符设备的唯一性;

    c -- 通过其成员file_operations来定义字符设备驱动提供给VFS的接口函数,如常见的open()、read()、write()等;

         在Linux字符设备驱动中:

    a -- 模块加载函数通过 register_chrdev_region( ) 或 alloc_chrdev_region( )来静态或者动态获取设备号;

    b -- 通过 cdev_init( ) 建立cdev与 file_operations之间的连接,通过 cdev_add( ) 向系统添加一个cdev以完成注册;

    c -- 模块卸载函数通过cdev_del( )来注销cdev,通过 unregister_chrdev_region( )来释放设备号;
         用户空间访问该设备的程序:

    a -- 通过Linux系统调用,如open( )、read( )、write( ),来“调用”file_operations来定义字符设备驱动提供给VFS的接口函数;
    3、字符设备驱动模型

    二、cdev 结构体解析

          在Linux内核中,使用cdev结构体来描述一个字符设备,cdev结构体的定义如下:


    <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;                   //隶属于同一主设备号的次设备号的个数.
    };

    内核给出的操作struct cdev结构的接口主要有以下几个:
    a -- void cdev_init(struct cdev *, const struct file_operations *);

    其源代码如代码清单如下:

    void cdev_init(struct cdev *cdev, const struct file_operations *fops)
    {
        memset(cdev, 0, sizeof *cdev);
        INIT_LIST_HEAD(&cdev->list);
        kobject_init(&cdev->kobj, &ktype_cdev_default);
        cdev->ops = fops;
    }
          该函数主要对struct cdev结构体做初始化, 最重要的就是建立cdev 和 file_operations之间的连接:
    (1) 将整个结构体清零;

    (2) 初始化list成员使其指向自身;

    (3) 初始化kobj成员;

    (4) 初始化ops成员;


     b --struct cdev *cdev_alloc(void);

         该函数主要分配一个struct cdev结构,动态申请一个cdev内存,并做了cdev_init中所做的前面3步初始化工作(第四步初始化工作需要在调用cdev_alloc后,显式的做初始化即: .ops=xxx_ops).

    其源代码清单如下:


    struct cdev *cdev_alloc(void)
    {
        struct cdev *p = kzalloc(sizeof(struct cdev), GFP_KERNEL);
        if (p) {
            INIT_LIST_HEAD(&p->list);
            kobject_init(&p->kobj, &ktype_cdev_dynamic);
        }
        return p;
    }
         在上面的两个初始化的函数中,我们没有看到关于owner成员、dev成员、count成员的初始化;其实,owner成员的存在体现了驱动程序与内核模块间的亲密关系,struct module是内核对于一个模块的抽象,该成员在字符设备中可以体现该设备隶属于哪个模块,在驱动程序的编写中一般由用户显式的初始化 .owner = THIS_MODULE, 该成员可以防止设备的方法正在被使用时,设备所在模块被卸载。而dev成员和count成员则在cdev_add中才会赋上有效的值。

     
    c -- int cdev_add(struct cdev *p, dev_t dev, unsigned count);

           该函数向内核注册一个struct cdev结构,即正式通知内核由struct cdev *p代表的字符设备已经可以使用了。

    当然这里还需提供两个参数:

    (1)第一个设备号 dev,

    (2)和该设备关联的设备编号的数量。

    这两个参数直接赋值给struct cdev 的dev成员和count成员。

    d -- void cdev_del(struct cdev *p);

         该函数向内核注销一个struct cdev结构,即正式通知内核由struct cdev *p代表的字符设备已经不可以使用了。

         从上述的接口讨论中,我们发现对于struct cdev的初始化和注册的过程中,我们需要提供几个东西

    (1) struct file_operations结构指针;

    (2) dev设备号;

    (3) count次设备号个数。

    但是我们依旧不明白这几个值到底代表着什么,而我们又该如何去构造这些值!


    三、设备号相应操作

    1 -- 主设备号和次设备号(二者一起为设备号):

          一个字符设备或块设备都有一个主设备号和一个次设备号。主设备号用来标识与设备文件相连的驱动程序,用来反映设备类型。次设备号被驱动程序用来辨别操作的是哪个设备,用来区分同类型的设备。

      linux内核中,设备号用dev_t来描述,2.6.28中定义如下:

      typedef u_long dev_t;

      在32位机中是4个字节,高12位表示主设备号,低20位表示次设备号。

    内核也为我们提供了几个方便操作的宏实现dev_t:

    1) -- 从设备号中提取major和minor

    MAJOR(dev_t dev);                              

    MINOR(dev_t dev);

    2) -- 通过major和minor构建设备号

    MKDEV(int major,int minor);
    注:这只是构建设备号。并未注册,需要调用 register_chrdev_region 静态申请;

    //宏定义:
    #define MINORBITS    20
    #define MINORMASK    ((1U << MINORBITS) - 1)
    #define MAJOR(dev)    ((unsigned int) ((dev) >> MINORBITS))
    #define MINOR(dev)    ((unsigned int) ((dev) & MINORMASK))
    #define MKDEV(ma,mi)    (((ma) << MINORBITS) | (mi))</span>


    2、分配设备号(两种方法):

    a -- 静态申请:

    int register_chrdev_region(dev_t from, unsigned count, const char *name);

    其源代码清单如下:

    int register_chrdev_region(dev_t from, unsigned count, const char *name)
    {
        struct char_device_struct *cd;
        dev_t to = from + count;
        dev_t n, next;
     
        for (n = from; n < to; n = next) {
            next = MKDEV(MAJOR(n)+1, 0);
            if (next > to)
                next = to;
            cd = __register_chrdev_region(MAJOR(n), MINOR(n),
                       next - n, name);
            if (IS_ERR(cd))
                goto fail;
        }
        return 0;
    fail:
        to = n;
        for (n = from; n < to; n = next) {
            next = MKDEV(MAJOR(n)+1, 0);
            kfree(__unregister_chrdev_region(MAJOR(n), MINOR(n), next - n));
        }
        return PTR_ERR(cd);
    }

    b -- 动态分配:
    int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name);

    其源代码清单如下:

    int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,
                const char *name)
    {
        struct char_device_struct *cd;
        cd = __register_chrdev_region(0, baseminor, count, name);
        if (IS_ERR(cd))
            return PTR_ERR(cd);
        *dev = MKDEV(cd->major, cd->baseminor);
        return 0;
    }
    可以看到二者都是调用了__register_chrdev_region 函数,其源代码如下:

    static struct char_device_struct *
    __register_chrdev_region(unsigned int major, unsigned int baseminor,
                   int minorct, const char *name)
    {
        struct char_device_struct *cd, **cp;
        int ret = 0;
        int i;
     
        cd = kzalloc(sizeof(struct char_device_struct), GFP_KERNEL);
        if (cd == NULL)
            return ERR_PTR(-ENOMEM);
     
        mutex_lock(&chrdevs_lock);
     
        /* temporary */
        if (major == 0) {
            for (i = ARRAY_SIZE(chrdevs)-1; i > 0; i--) {
                if (chrdevs[i] == NULL)
                    break;
            }
     
            if (i == 0) {
                ret = -EBUSY;
                goto out;
            }
            major = i;
            ret = major;
        }
     
        cd->major = major;
        cd->baseminor = baseminor;
        cd->minorct = minorct;
        strlcpy(cd->name, name, sizeof(cd->name));
     
        i = major_to_index(major);
     
        for (cp = &chrdevs[i]; *cp; cp = &(*cp)->next)
            if ((*cp)->major > major ||
                ((*cp)->major == major &&
                 (((*cp)->baseminor >= baseminor) ||
                  ((*cp)->baseminor + (*cp)->minorct > baseminor))))
                break;
     
        /* Check for overlapping minor ranges.  */
        if (*cp && (*cp)->major == major) {
            int old_min = (*cp)->baseminor;
            int old_max = (*cp)->baseminor + (*cp)->minorct - 1;
            int new_min = baseminor;
            int new_max = baseminor + minorct - 1;
     
            /* New driver overlaps from the left.  */
            if (new_max >= old_min && new_max <= old_max) {
                ret = -EBUSY;
                goto out;
            }
     
            /* New driver overlaps from the right.  */
            if (new_min <= old_max && new_min >= old_min) {
                ret = -EBUSY;
                goto out;
            }
        }
     
        cd->next = *cp;
        *cp = cd;
        mutex_unlock(&chrdevs_lock);
        return cd;
    out:
        mutex_unlock(&chrdevs_lock);
        kfree(cd);
        return ERR_PTR(ret);
    }
     通过这个函数可以看出  register_chrdev_region和  alloc_chrdev_region 的区别,register_chrdev_region直接将Major 注册进入,而 alloc_chrdev_region从Major = 0 开始,逐个查找设备号,直到找到一个闲置的设备号,并将其注册进去;
    二者应用可以简单总结如下:

                                         register_chrdev_region                                                alloc_chrdev_region 

        devno = MKDEV(major,minor);
        ret = register_chrdev_region(devno, 1, "hello"); 
        cdev_init(&cdev,&hello_ops);
        ret = cdev_add(&cdev,devno,1);
         alloc_chrdev_region(&devno, minor, 1, "hello");
        major = MAJOR(devno);
        cdev_init(&cdev,&hello_ops);
        ret = cdev_add(&cdev,devno,1)
    register_chrdev(major,"hello",&hello
         可以看到,除了前面两个函数,还加了一个register_chrdev 函数,可以发现这个函数的应用非常简单,只要一句就可以搞定前面函数所做之事;

    下面分析一下register_chrdev 函数,其源代码定义如下:


    static inline int register_chrdev(unsigned int major, const char *name,
                      const struct file_operations *fops)
    {
        return __register_chrdev(major, 0, 256, name, fops);
    }
    调用了 __register_chrdev(major, 0, 256, name, fops) 函数:
    int __register_chrdev(unsigned int major, unsigned int baseminor,
                  unsigned int count, const char *name,
                  const struct file_operations *fops)
    {
        struct char_device_struct *cd;
        struct cdev *cdev;
        int err = -ENOMEM;
     
        cd = __register_chrdev_region(major, baseminor, count, name);
        if (IS_ERR(cd))
            return PTR_ERR(cd);
     
        cdev = cdev_alloc();
        if (!cdev)
            goto out2;
     
        cdev->owner = fops->owner;
        cdev->ops = fops;
        kobject_set_name(&cdev->kobj, "%s", name);
     
        err = cdev_add(cdev, MKDEV(cd->major, baseminor), count);
        if (err)
            goto out;
     
        cd->cdev = cdev;
     
        return major ? 0 : cd->major;
    out:
        kobject_put(&cdev->kobj);
    out2:
        kfree(__unregister_chrdev_region(cd->major, baseminor, count));
        return err;
    }
    可以看到这个函数不只帮我们注册了设备号,还帮我们做了cdev 的初始化以及cdev 的注册; 

    3、注销设备号:

    void unregister_chrdev_region(dev_t from, unsigned count);

    4、创建设备文件:

         利用cat /proc/devices查看申请到的设备名,设备号。

    1)使用mknod手工创建:mknod filename type major minor

    2)自动创建设备节点:

        利用udev(mdev)来实现设备文件的自动创建,首先应保证支持udev(mdev),由busybox配置。在驱动初始化代码里调用class_create为该设备创建一个class,再为每个设备调用device_create创建对应的设备。

        详细解析见:Linux 字符设备驱动开发 (二)—— 自动创建设备节点

     

    杂项设备(misc device)

    杂项设备也是在嵌入式系统中用得比较多的一种设备驱动。使用misc_register(&ff_wdt_miscdev); 在 Linux 内核的include/linux目录下有Miscdevice.h文件,要把自己定义的misc device从设备定义在这里。其实是因为这些字符设备不符合预先确定的字符设备范畴,所有这些设备采用主编号10 ,一起归于misc device,其实misc_register就是用主标号10调用register_chrdev()的。

    也就是说,misc设备其实也就是特殊的字符设备,可自动生成设备节点。

    字符设备(char device)

    使用register_chrdev(LED_MAJOR,DEVICE_NAME,&dev_fops)注册字符设备驱动程序时,如果有多个设 备使用该函数注册驱动程序,LED_MAJOR不能相同,否则几个设备都无法注册(我已验证)。如果模块使用该方式注册并且 LED_MAJOR为0(自动分配主设备号 ),使用insmod命令加载模块时会在终端显示分配的主设备号和次设备号,在/dev目录下建立该节点,比如 设备leds,如果加载该模块时分配的主设备号和次设备号为253和0,则建立节点:mknod leds c 253 0。使用register_chrdev (LED_MAJOR,DEVICE_NAME,&dev_fops)注册字符设备驱动程序时都要手动建立节点 ,否则在应用程序无法打开该设备。
     

    杂项设备

    linux里面的misc杂项设备是主设备号为10的驱动设备

    定义头文件<linux/miscdevice.h>

    杂项设备的结构体:

    struct miscdevice{
      int minor; //杂项设备的此设备号(如果设置为MISC_DYNAMIC_MINOR,表示系统自动分配未使用的minor)
      const char *name;
      const stuct file_operations *fops;//驱动主题函数入口指针
      struct list_head list;
      struct device *parent;
      struct device *this device;
      const char *nodename;(在/dev下面创建的设备驱动节点)
      mode_t mode;
    };
    

    注册和释放

    注册:int misc_register(struct miscdevice *misc)

    释放:int misc_deregister(struct miscdevice *misc)

    misc_device是特殊字符设备。注册驱动程序时采用misc_register函数注册,此函数中会自动创建设备节点,即设备文件。无需mknod指令创建设备文件。因为misc_register()会调用class_device_creat或者device_creat().

    杂项字符设备和一般字符设备的区别:

    1.一般字符设备首先申请设备号。  但是杂项字符设备的主设备号为10次设备号通过结构体struct miscdevice中的minor来设置。

    2.一般字符设备要创建设备文件。 但是杂项字符设备在注册时会自动创建。

    3.一般字符设备要分配一个cdev(字符设备)。  但是杂项字符设备只要创建struct miscdevice结构即可。

    4.一般字符设备需要初始化cdev(即给字符设备设置对应的操作函数集struct file_operation). 但是杂项字符设备在结构体truct miscdevice中定义。

    5.一般字符设备使用注册函数 int cdev_add struct (cdev *p,devt_t dev, unsigned)(第一个参数为之前初始化的字符设备,第二个参数为设备号,第三个参数为要添加设备的个数) 而杂项字符设备使用int misc_register(struct miscdevice *misc)来注册

     

    驱动调用的实质:

    就是通过 设备文件找到与之对应设备号的设备,再通过设备初始化时绑定的操作函数硬件进行控制

     内核态驱动

    #include <linux/miscdevice.h>


    static void ff_wdt_handler_enable(void)
    {
        printk(KERN_WARNING "[keep2] ff_wdt_handler_enable\n");
        ff_wdt_enable =1;
    }

    static void ff_wdt_handler_disable(void)
    {
        printk(KERN_WARNING "[keep2] ff_wdt_handler_disable\n");
        ff_wdt_enable = 0;
    }


    static ssize_t ff_wdt_write(struct file *file, const char __user *data,
                     size_t len, loff_t *ppos)
    {
        size_t i;

        printk(KERN_WARNING "[keep2] ff_wdt_write\n");

        if (len) 
        {
            for (i = 0; i != len; i++) {
                char c;
                if (get_user(c, data + i))
                    return -EFAULT;
            }
        
        }

        return len;
    }

    static long ff_wdt_ioctl(struct file *file, unsigned int cmd,
                                unsigned long arg)
    {
        int timeout;
        int options;
        void __user *argp = (void __user *)arg;
        
        //printk(KERN_WARNING "[keep2] ff_wdt_ioctl\n");

        switch (cmd) 
        {
            case WDIOC_SETOPTIONS:
                if (get_user(options, (int __user *)argp))
                    return -EFAULT;

                if (options & WDIOS_DISABLECARD)
                    ff_wdt_handler_disable();

                if (options & WDIOS_ENABLECARD)
                    ff_wdt_handler_enable();
                break;

            case WDIOC_SETTIMEOUT:
                ff_app_wdt_timeout = jiffies;
                if (get_user(timeout, (int __user *)argp))
                    return -EFAULT;

            case WDIOC_GETTIMEOUT:
                if (put_user(ff_kernel_wdt_timeout, (unsigned long __user *)argp))
                    return -EFAULT;
                break;

            default:
                return -ENOTTY;
        }
        
        return 0;
    }

    static int ff_wdt_open(struct inode *inode, struct file *file)
    {
        printk(KERN_WARNING "[keep2] ff_wdt_open\n");

        return nonseekable_open(inode, file);
    }

    static int ff_wdt_release(struct inode *inode, struct file *file)
    {
        printk(KERN_WARNING "[keep2] ff_wdt_release\n");

        return 0;
    }

    static const struct file_operations ff_wdt_fops = {
        .owner = THIS_MODULE,
        .llseek = no_llseek,
        .write = ff_wdt_write,
        .unlocked_ioctl = ff_wdt_ioctl,
        .open = ff_wdt_open,
        .release = ff_wdt_release,
    };

    static struct miscdevice ff_wdt_miscdev = {
        .minor = FF_WATCHDOG_MINOR,
        .name = "ff_watchdog",
        .fops = &ff_wdt_fops,
    };


    static int __init ff_wdt_init(void)
    {
        printk(KERN_WARNING "[keep2] ff_wdt_init\n");
        
        misc_register(&ff_wdt_miscdev);
        
        return 0;
    }

    static void __exit ff_wdt_exit(void)
    {
        printk(KERN_WARNING "[keep2] ff_wdt_exit\n");

        misc_deregister(&ff_wdt_miscdev);
    }

    module_init(ff_wdt_init);
    module_exit(ff_wdt_exit);

    用户态调用

    int main (int argc, char *argv[])
    {  
        unsigned long kernel_wdt_time = 1;   
        unsigned long last_kernel_wdt_time = 0; 
        unsigned long feed_count = 0;   
        int wdt_enable = 0;
        
        printf("[keep]start feed watchdog!\n");   
    
        int fd_watchdog = open(FF_WDT_DEV, O_WRONLY);   
        if(fd_watchdog <= 0)   
        {   
            int err = errno;   
            printf("[keep]failed to open /dev/ff_watchdog, errno: %d\n", err);   
            return -1;   
        }   
    
        wdt_enable = FF_WDT_ENABLECARD;
        ioctl(fd_watchdog, FF_WDT_SETOPTIONS, &wdt_enable);
    
        while (1)
        {
            //printf("[keep] kernel_wdt_time = %ld\n", kernel_wdt_time); 
            ioctl(fd_watchdog, FF_WDT_SETTIMEOUT, &kernel_wdt_time);
            ioctl(fd_watchdog, FF_WDT_GETTIMEOUT, &kernel_wdt_time);
            if(kernel_wdt_time == last_kernel_wdt_time)
            {
                printf("[keep] kernel_wdt_time = %ld,last_kernel_wdt_time = %ld\n", kernel_wdt_time, last_kernel_wdt_time); 
                feed_count++;              
                if(feed_count >= 60)
                {
                    break;
                }
            }
            else
            {
                last_kernel_wdt_time = kernel_wdt_time;
                feed_count = 0;
            }
            sleep(1);
        }
    
        close(fd_watchdog);
        
        return 0;
    }   
    
    

     

     

    展开全文
  • register_chrdev函数解析

    2021-05-16 07:50:53
    register_chrdev大致作用:向内核注册cdev结构体,当在用户空间打开设备文件时内核可以根据设备号快速定位此设备文件的cdev->file_operations结构体,从而调用驱动底层的open,close,read,write,ioctl等函数,当我们...

    在Linux2.6内核以前注册字符设备的函数接口是register_chrdev,在2.6中其可继续使用。

    register_chrdev大致作用:向内核注册cdev结构体,当在用户空间打开设备文件时内核可以根据设备号快速定位此设备文件的cdev->file_operations结构体,从而调用驱动底层的open,close,read,write,ioctl等函数,当我们在用户空间open字符设备文件时,首先调用def_chr_fops->chrdev_open()函数(所有字符设备都调用此函数),chrdev_open会调用kobj_lookup函数找到设备的cdev->kobject,从而得到设备的cdev,进而获得file_operations.要想通过kobj_lookup找到相应的cdev,必需调用register_chrdev()函数。向内核注册。

    现分析如下:

    /**

    * register_chrdev() - Register a major number for character devices.

    * @major: major device number or 0 for dynamic allocation

    * @name: name of this range of devices

    * @fops: file operations associated with this devices

    *

    * If @major == 0 this functions will dynamically allocate a major and return

    * its number.

    *

    * If @major > 0 this function will attempt to reserve a device with the given

    * major number and will return zero on success.

    *

    * Returns a -ve errno on failure.

    *

    * The name of this device has nothing to do with the name of the device in

    * /dev. It only helps to keep track of the different owners of devices. If

    * your module name has only one type of devices it's ok to use e.g. the name

    * of the module here.

    *

    * This function registers a range of 256 minor numbers. The first minor number

    * is 0.

    */

    int register_chrdev(unsigned int major, const char *name,

    const struct file_operations *fops)

    {

    struct char_device_struct *cd;

    struct cdev *cdev;

    char *s;

    int err = -ENOMEM;

    /* 内核中用char_device_struct结构体指针数组chrdevs(哈希表)来维护所有的设备号

    __register_chrdev_region函数向内核注册major主设备号,其有256个次设备号

    在以前内核中用8位来表示次设备号,2.6内核用20位来表示次设备号*/

    cd = __register_chrdev_region(major, 0, 256, name);

    if (IS_ERR(cd))

    return PTR_ERR(cd);

    cdev = cdev_alloc();//2.6内核新接口

    if (!cdev)

    goto out2;

    cdev->owner = fops->owner;

    cdev->ops = fops;//底层驱动接口

    kobject_set_name(&cdev->kobj, "%s", name);//linux内核驱动模型kobject.

    for (s = strchr(kobject_name(&cdev->kobj),'/'); s; s = strchr(s, '/'))

    *s = '!';

    err = cdev_add(cdev, MKDEV(cd->major, 0), 256);//向内核提交cdev.

    if (err)

    goto out;

    cd->cdev = cdev;

    return major ? 0 : cd->major;

    out:

    kobject_put(&cdev->kobj);

    out2:

    kfree(__unregister_chrdev_region(cd->major, 0, 256));

    return err;

    }

    下面分析:__register_chrdev_region

    点击(此处)折叠或打开

    /*

    * Register a single major with a specified minor range.

    *

    * If major == 0 this functions will dynamically allocate a major and return

    * its number.

    *

    * If major > 0 this function will attempt to reserve the passed range of

    * minors and will return zero on success.

    *

    * Returns a -ve errno on failure.

    */

    static struct char_device_struct *

    __register_chrdev_region(unsigned int major, unsigned int baseminor,

    int minorct, const char *name)

    {

    struct char_device_struct *cd, **cp;

    int ret = 0;

    int i;

    cd = kzalloc(sizeof(struct char_device_struct), GFP_KERNEL);

    if (cd == NULL)

    return ERR_PTR(-ENOMEM);

    mutex_lock(&chrdevs_lock);

    /* temporary */

    if (major == 0) {

    for (i = ARRAY_SIZE(chrdevs)-1; i > 0; i--) {

    if (chrdevs[i] == NULL)

    break;

    }

    if (i == 0) {

    ret = -EBUSY;

    goto out;

    }

    major = i;

    ret = major;

    }

    cd->major = major; //主设备号

    cd->baseminor = baseminor;//起始次设备号

    cd->minorct = minorct; //主设备号下有多少个次设备号

    strlcpy(cd->name, name, sizeof(cd->name));

    i = major_to_index(major);//根据major获得哈希chrdevs数组索引

    //chrdevs数组的一项可能对应多个主设备号

    for (cp = &chrdevs[i]; *cp; cp = &(*cp)->next)

    if ((*cp)->major > major ||

    ((*cp)->major == major &&

    (((*cp)->baseminor >= baseminor) ||

    ((*cp)->baseminor + (*cp)->minorct > baseminor))))

    break;

    /* Check for overlapping minor ranges. */

    if (*cp && (*cp)->major == major) { //防止覆盖

    int old_min = (*cp)->baseminor;

    int old_max = (*cp)->baseminor + (*cp)->minorct - 1;

    int new_min = baseminor;

    int new_max = baseminor + minorct - 1;

    /* New driver overlaps from the left. */

    if (new_max >= old_min && new_max <= old_max) {

    ret = -EBUSY;

    goto out;

    }

    /* New driver overlaps from the right. */

    if (new_min <= old_max && new_min >= old_min) {

    ret = -EBUSY;

    goto out;

    }

    }

    cd->next = *cp;

    *cp = cd;

    mutex_unlock(&chrdevs_lock);

    return cd;

    out:

    mutex_unlock(&chrdevs_lock);

    kfree(cd);

    return ERR_PTR(ret);

    }

    cdev_add分析:

    点击(此处)折叠或打开

    int cdev_add(struct cdev *p, dev_t dev, unsigned count)

    {

    p->dev = dev; //设备号

    p->count = count; //次设备计数

    return kobj_map(cdev_map, dev, count, NULL, exact_match, exact_lock, p);

    }

    int kobj_map(struct kobj_map *domain, dev_t dev, unsigned long range,

    struct module *module, kobj_probe_t *probe,

    int (*lock)(dev_t, void *), void *data)

    {

    /*一类设备可以能占有多个主设备号,则此类设备占有连续的主设备号*/

    unsigned n = MAJOR(dev + range - 1) - MAJOR(dev) + 1; //一般情况n=1

    unsigned index = MAJOR(dev);

    unsigned i;

    struct probe *p;

    if (n > 255)

    n = 255;

    p = kmalloc(sizeof(struct probe) * n, GFP_KERNEL);

    if (p == NULL)

    return -ENOMEM;

    for (i = 0; i < n; i++, p++) { //一般n=1

    p->owner = module;

    p->get = probe;

    p->lock = lock;

    p->dev = dev;

    p->range = range;

    p->data = data;//cdev

    }

    mutex_lock(domain->lock);

    for (i = 0, p -= n; i < n; i++, p++, index++) {

    struct probe **s = &domain->probes[index % 255];

    while (*s && (*s)->range < range)

    s = &(*s)->next;

    p->next = *s; //如果major在probes哈希表中的索引一样,则依据rang从小到大排列

    *s = p;

    }

    mutex_unlock(domain->lock);

    return 0;

    }

    在完成register_chrdev的注册以后就可以对设备文件操作,在chrdev_open函数中通过调用kobj_lookup函数找到cdev内嵌的kobject结构体以后就可以得到cdev结构体本身。

    点击(此处)折叠或打开

    struct kobject *kobj_lookup(struct kobj_map *domain, dev_t dev, int *index)

    {

    struct kobject *kobj;

    struct probe *p;

    unsigned long best = ~0UL;

    retry:

    mutex_lock(domain->lock);

    for (p = domain->probes[MAJOR(dev) % 255]; p; p = p->next) {

    struct kobject *(*probe)(dev_t, int *, void *);

    struct module *owner;

    void *data;

    if (p->dev > dev || p->dev + p->range - 1 < dev)

    continue;

    if (p->range - 1 >= best)

    break;

    if (!try_module_get(p->owner))

    continue;

    owner = p->owner;

    data = p->data;

    probe = p->get;

    best = p->range - 1;

    *index = dev - p->dev;

    if (p->lock && p->lock(dev, data) < 0) {

    module_put(owner);

    continue;

    }

    mutex_unlock(domain->lock);

    kobj = probe(dev, index, data);

    /* Currently ->owner protects _only_ ->probe() itself. */

    module_put(owner);

    if (kobj)

    return kobj;

    goto retry;

    }

    mutex_unlock(domain->lock);

    return NULL;

    }

    本文中使用2.6.30.4内核源代码。

    展开全文
  • //使用register_chrdev_region函数 //arg1:第一个设备号(最终设备号), //arg2:从第一个设备号的子设备号开始,连续注册几个 //arg3:设备名 /* 原型: /** * register_chrdev_region() - register a range...

    一次静态注册多个设备:

    	region_dev->major_region_dev = 222;
    	//静态注册需指定主设备号
    	region_dev->minor_region_dev = 0;
    	//静态注册指定第一个次设备号
    	region_dev->dev_no = MKDEV(region_dev->major_region_dev , region_dev->minor_region_dev);
    	//使用MKDEV宏将主次设备号合成一个最终的设备号
    	chk = register_chrdev_region(region_dev->dev_no, 1, "region_dev");
    	//使用register_chrdev_region函数
    	//arg1:第一个设备号(最终设备号),
    	//arg2:从第一个设备号的子设备号开始,连续注册几个
    	//arg3:设备名
    
    /*
    原型:
    /**
     * register_chrdev_region() - register a range of device numbers
     * @from: the first in the desired range of device numbers; must include
     *        the major number.
     * @count: the number of consecutive device numbers required
     * @name: the name of the device or driver.
     *
     * Return value is zero on success, a negative error code on failure.
     */
    int register_chrdev_region(dev_t from, unsigned count, const char *name){
    	...
    }
    
    */
    

    动态注册:无需指定主次设备号:

    //arg1:要把申请到的第一个设备号(最终设备号放哪里)
    //arg2:指定申请时次设备号从哪里开始
    //arg3:要注册几个
    //arg4:名
    	chk = alloc_chrdev_region(&region_dev->dev_no , 0, 1, "region_dev");
    	region_dev->major_region_dev = MAJOR(region_dev->dev_no);
    	//用此宏从最终设备号中拆分出主设备号
    	region_dev->minor_region_dev = MINOR(region_dev->dev_no);
    	//用此宏从最终设备号中拆分出次设备号
    

    使用了上面的,就需自行将cdev和file_operations结构体绑定,并将cdev加入到内核:

    cdev是一个结构体,每个字符设备都对应一个。

    struct cdev {
    	struct kobject kobj;
    	struct module *owner;
    	const struct file_operations *ops;
    	struct list_head list;
    	dev_t dev;
    	unsigned int count;
    };
    

    绑定file_opt 和cdev(cdev初始化)

    cdev_init(&region_dev->cdev_region_dev, &file_opt );
    

    将cdev加入到内核:

    //arg1:cdev结构体地址
    //arg2:cdev要对应哪个设备号(第一个设备)
    //arg3:要加入多少个子,依据次设备号
    cdev_add(&region_dev->cdev_region_dev, region_dev->dev_no, 1);
    //内核解释:
    /**
     * cdev_add() - add a char device to the system
     * @p: the cdev structure for the device
     * @dev: the first device number for which this device is responsible
     * @count: the number of consecutive minor numbers corresponding to this
     *         device
     *
     * cdev_add() adds the device represented by @p to the system, making it
     * live immediately.  A negative error code is returned on failure.
     */
    

    注册设备号->初始化cedv->创建设备节点

    全部过程:

    
    struct region_dev_abstract{
    	dev_t dev_no;
    	unsigned int major_region_dev;
    	unsigned int minor_region_dev;
    	
    	struct class * class_region_dev;
    	struct device * device_region_dev;
    	struct cdev  cdev_region_dev;
    };
    
    struct region_dev_abstract * region_dev;
    
    static int region_dev_open (struct inode *node, struct file * file_p){
    		
    
    	return 0;
    }
    
    static int region_dev_release (struct inode *node, struct file *file_p){
    
    	return 0;
    }
    
    static ssize_t region_dev_write (struct file *file_p, const char __user *val_channel, size_t, loff_t *offset){
    
    	return 0;
    }
    
    static ssize_t region_dev_read (struct file *file_p, char __user *val_channel, size_t, loff_t *offset){
    
    	return 0;
    }
    
    static struct file_operations file_opt={
    	.open = region_dev_open,
    	.release = region_dev_release,
    	.write = region_dev_write,
    	.read = region_dev_read,
    	
    };
    
    static int __init region_dev_init(void){
    int chk = 0; //check flag
    //get heap memory
    	region_dev = (struct region_dev_abstract *)kzalloc(sizeof(struct region_dev_abstract),GFP_KERNEL);
    	if(NULL == region_dev){
    		printk("err_kzalloc\n");
    		goto err_kzalloc;
    	}
    
    //register dev
    #if 1  //static
    	region_dev->major_region_dev = 222;
    	region_dev->minor_region_dev = 0;
    	region_dev->dev_no = MKDEV(region_dev->major_region_dev , region_dev->minor_region_dev);
    	chk = register_chrdev_region(region_dev->dev_no, 1, "region_dev");
    	if(0>chk){
    		printk("err_register_chrdev_region static\n");
    		goto err_register_chrdev_region;
    	}
    #endif
    
    #if 1 //dynamic
    	chk = alloc_chrdev_region(&region_dev->dev_no , 0, 1, "region_dev");
    	region_dev->major_region_dev = MAJOR(region_dev->dev_no);
    	region_dev->minor_region_dev = MINOR(region_dev->dev_no);
    	if(0>chk){
    		printk("err_register_chrdev_region dynamic\n");
    		goto err_register_chrdev_region;
    	}
    #endif
    
    //init and add cdev
    	cdev_init(&region_dev->cdev_region_dev, &file_opt );
    	cdev_add(&region_dev->cdev_region_dev, region_dev->dev_no, 1);
    	
    //create class
    	region_dev->class_region_dev = class_create(THIS_MODULE, "class_region_dev");
    	if(IS_ERR(region_dev->class_region_dev)){
    		printk("err_class_create\n");
    		goto err_class_create;
    	}
    //create device node
    	region_dev->device_region_dev = device_create(region_dev->class_region_dev, \
    												  NULL,region_dev->dev_no, \
    							  					  NULL, "region_dev");
    	if(IS_ERR(region_dev->device_region_dev)){
    		printk("err_device_create\n");
    		goto err_device_create;
    	}
    	
    //correct return
    	return 0;
    
    //error handle
    err_device_create:
    	class_destroy(region_dev->class_region_dev);
    
    err_class_create:
    	unregister_chrdev_region(region_dev->major_region_dev, "region_dev");
    
    err_register_chrdev_region:
    	kfree(region_dev);
    	
    err_kzalloc:
    	return -1;
    }
    
    static void __exit region_dev_exit(void){
    	device_destroy(region_dev->class_region_dev, region_dev->dev_no);
    	class_destroy(region_dev->class_region_dev);
    	unregister_chrdev_region(region_dev->major_region_dev, "region_dev");
    	kfree(region_dev);
    	return;
    }
    
    module_init(region_dev_init);
    module_exit(region_dev_exit);
    MODULE_LICENSE("GPL");
    
    
    
    
    
    展开全文
  • */ int register_chrdev_region(dev_t from, unsigned count, const char *name) cdev_add /* * @description : 添加一个字符设备到操作系统 * @param - p : cdev结构体地址 * @param - dev : 设备号 * @...

    Linux驱动–字符设备学习记录:
    所谓设备驱动程序就是控制与管理硬件设备数据收发的软件,它是应用程序与硬件设备沟通的桥梁。

    从本质上讲驱动程序主要负责硬件设备的数据读写,参数配置与中断处理。

    设备驱动程序是操作系统的一部分,通常运行在内核层。
    硬件设备多种多样,使得设备驱动程序繁多,设备模型将硬件设备分类,抽象出一套标准的数据结构。

    /sys/class目录下是按照设备功能分类的设备模型,
    如系统所有输入设备都会出现在 /sys/class/input 之下,而不论它们是以何种总线连接到系统。
    它也是构成 Linux 统一设备模型的一部分

    root@ubuntu16:/sys/class# ls
    ata_device   devfreq-event   hidraw       mem       power_supply  scsi_device    thermal
    ata_link     dma             hmm_device   misc      ppdev         scsi_disk      tpm
    ata_port     dmi             hwmon        mmc_host  ppp           scsi_generic   tpmrm
    backlight    drm             i2c-adapter  nd        printer       scsi_host      tty
    bdi          drm_dp_aux_dev  i2c-dev      net       pwm           sound          vc
    block        extcon          input        pci_bus   rapidio_port  spi_host       virtio-ports
    bsg          firmware        iommu        pci_epc   regulator     spi_master     vtconsole
    devcoredump  gpio            leds         phy       rfkill        spi_slave      watchdog
    devfreq      graphics        mdio_bus     powercap  rtc           spi_transport
    root@ubuntu16:/sys/class# 
    

    Linux系统为了管理方便,将设备分成三种基本类型:字符设备、块设备、网络设备

    字符设备,字符设备文件(类型为c)

    • 字符(char)设备是个能够像字节流(类似文件)一样被访问的设备,由字符设备驱动程序来实现这种特性。
      字符设备驱动程序通常至少要实现open、close、read和write的系统调用。
      在这里插入图片描述
      设备文件是没有文件大小的,取而代之的是两个号码:主设备号 +次设备号。

    块设备,块设备文件(类型为b)

    • 和字符设备类似,块设备也是通过/dev目录下的文件系统节点来访问。块设备(例如磁盘)上能够容纳filesystem。在大多数的Unix系统中,进行I/O操作时块设备每次只能传输一个或多个完整的块,而每块包含512字节(或2的更高次幂字节的数据)。
      Linux可以让app像字符设备一样地读写块设备,允许一次传递任意多字节的数据。因此,块设备和字符设备的区别仅仅在于内核内部管理数据的方式,也就是内核及驱动程序之间的软件接口,而这些不同对用户来讲是透明的。在内核中,和字符驱动程序相比,块驱动程序具有完全不同的接口。
      在这里插入图片描述

    网络设备

    任何网络事物都需要经过一个网络接口形成,网络接口是一个能够和其他主机交换数据的设备。接口通常是一个硬件设备,但也可能是个纯软件设备,比如回环(loopback)接口。
    网络接口由内核中的网络子系统驱动,负责发送和接收数据包。许多网络连接(尤其是使用TCP协议的连接)是面向流的,但网络设备却围绕数据包的传送和接收而设计。网络驱动程序不需要知道各个连接的相关信息,它只要处理数据包即可。
    由于不是面向流的设备,因此将网络接口映射到filesystem中的节点(比如/dev/tty1)比较困难。
    Unix访问网络接口的方法仍然是给它们分配一个唯一的名字(比如ens33),但这个名字在filesystem中不存在对应的节点。内核和网络设备驱动程序间的通信,完全不同于内核和字符以及块驱动程序之间的通信,内核调用一套和数据包相关的函数socket,也叫套接字。
    查看网络设备使用命令ifconfig:
    在这里插入图片描述

    查看系统已使用主设备号: cat /proc/devices

    cxx@ubuntu16:~$ cat /proc/devices 
    Character devices: //字符设备
      1 mem
      4 /dev/vc/0
      4 tty
    、、、
    247 hmm_device
    248 watchdog
    249 rtc
    250 dax
    251 dimmctl
    252 ndctl
    253 tpm
    254 gpiochip
    
    Block devices: //块设备
      7 loop
      8 sd
      9 md
     11 sr
     65 sd
     66 sd
     67 sd
    、、、
    135 sd
    253 device-mapper
    254 mdp
    259 blkext
    cxx@ubuntu16:~$ 
    

    二、字符设备架构是如何实现的?

    在Linux的世界里面一切皆文件,所有的硬件设备操作到应用层都会被抽象成文件的操作。我们知道如果应用层要访问硬件设备,它必定要调用到硬件对应的驱动程序。Linux内核中有那么多驱动程序,应用层怎么才能精确的调用到底层的驱动程序呢?
    在这里我们字符设备为例,来看一下应用程序是如何和底层驱动程序关联起来的。
    1.在Linux文件系统中,每个文件都用一个struct inode结构体来描述,这个结构体里面记录了这个文件的所有信息,例如:文件类型,访问权限等。
    2.在Linux操作系统中,每个驱动程序在应用层的/dev目录下都会有一个设备文件和它对应,并且该文件会有对应的主设备号和次设备号。
    3.在Linux操作系统中,每个驱动程序都要分配一个主设备号,字符设备的设备号保存在struct cdev结构体中。

    如果想访问底层设备,就必须打开对应的设备文件。也就是在这个打开的过程中,Linux内核将应用层和对应的驱动程序关联起来。

    1.当open函数打开设备文件时,可以根据设备文件对应的struct inode结构体描述的信息,可以知道接下来要操作的设备类型(字符设备还是块设备)。还会分配一个struct file结构体。

    2.根据struct inode结构体里面记录的设备号,可以找到对应的驱动程序。这里以字符设备为例。在Linux操作系统中每个字符设备有一个struct cdev结构体。此结构体描述了字符设备所有的信息,其中最重要一项的就是字符设备的操作函数接口。

    3.找到struct cdev结构体后,Linux内核就会将struct cdev结构体所在的内存空间首地记录在struct inode结构体的i_cdev成员中。将struct cdev结构体的中记录的函数操作接口地址记录在struct file结构体的f_op成员中。

    4.任务完成,VFS层会给应用层返回一个文件描述符(fd)。这个fd是和struct file结构体对应的。接下来上层的应用程序就可以通过fd来找到strut file,然后在由struct file找到操作字符设备的函数接口了。

    cdev数据结构:

     struct cdev {
            struct kobject kobj;
            struct module *owner;
            const struct file_operations *ops;//接口函数集合
            struct list_head list;//内核链表
            dev_t dev;    //设备号
            unsigned int count;//次设备号个数
        };
    

    cdev 初始化函数

    /*
     * @description		: 初始化cdev结构体
     * @param - cdev 	: cdev结构体地址
     * @param - fops 	: 操作字符设备的函数接口地址
     * @return 			: 无
     */
    void cdev_init(struct cdev *cdev, const struct file_operations *fops)
    

    register_chrdev_region

    /*
     * @description		: 注册一个范围()的设备号
     * @param - from 	: 设备号
     * @param - count	: 注册的设备个数
     * @param -	name 	:设备的名字
     * @return 			: 成功返回0,失败返回错误码(负数)
     */										
    int register_chrdev_region(dev_t from, unsigned count, const char *name)
    

    cdev_add

    /*
     * @description		: 添加一个字符设备到操作系统
     * @param - p		: cdev结构体地址
     * @param - dev		: 设备号
     * @param -	count	:次设备号个数
     * @return 			: 成功返回0,失败返回错误码(负数)
     */	
    int cdev_add(struct cdev *p, dev_t dev, unsigned count)
    

    cdev_del

    /*
     * @description		: 从系统中删除一个字符设备
     * @param - p		: cdev结构体地址
     * @param - dev		: 设备号
     * @return 			:  无
     */	
    void cdev_del(struct cdev *p)
    

    register_chrdev

    /*
     * @description		: 注册或者分配设备号,并注册fops到cdev结构体
     * @param - major	: 主设备号,如果major>0,功能为注册该主设备号,major=0,功能为动态分配主设备号。
     * @param - name	: 设备名称,执行 cat /proc/devices显示的名称
     * @param -	fops 	:文件系统的接口指针
     * @return 			: 如果major>0   成功返回0,失败返回负的错误码,major=0  成功返回主设备号,失败返回负的错误码
     */	
    static inline int register_chrdev(unsigned int major, const char *name,const struct file_operations *fops)
    

    unregister_chrdev_region

    /*
     * @description		: 注销一个范围()的设备号
     * @param - from 	: 设备号
     * @param - count	: 注册的设备个数
     * @return 			: 无
     */	
    void unregister_chrdev_region(dev_t from, unsigned count)
    

    vim hellocdev.c

    #include <linux/init.h>
    #include <linux/module.h>
    #include <linux/kdev_t.h>
    #include <linux/fs.h>
    #include <linux/cdev.h>
    
    static int major = 222; //主设备号
    static int minor = 0; //次设备号
    static dev_t hello_devno; //合成设备号
    static struct cdev hello_cdev;
    
    static int hellocdev_open (struct inode *inode, struct file *filep)
    {
            printk("hellocdev_open()\n");
            return 0;
    }
    static struct file_operations hellocdev_ops = 
    {
            .open = hellocdev_open,
    };
    static int hellocdev_init(void)
    {
            int result;
            int error;
            printk("hellocdev_init \n");
            hello_devno = MKDEV(major,minor);       //把主设备号和次设备号合成dev_t
            result = register_chrdev_region(hello_devno, 1, "hellocdev");
            if(result<0)
            {
                    printk("register_chrdev_region fail \n");
                    return result;
            }
            cdev_init(&hello_cdev,&hellocdev_ops);
            error = cdev_add(&hello_cdev,hello_devno,1);
            if(error < 0)
            {
                    printk("cdev_add fail \n");
                    unregister_chrdev_region(hello_devno,1);
                    dev_del(&hello_cdev); //删除cdev
                    return error;
            }
            return 0;
    }
    static void hellocdev_exit(void)
    {
            printk("hellocdev_exit \n");    
            unregister_chrdev_region(hello_devno,1); //注销设备
            cdev_del(&hello_cdev); //删除cdev
            return;
    }
    
    MODULE_LICENSE("GPL");
    module_init(hellocdev_init);
    module_exit(hellocdev_exit);
    
    //proc/devices
    
    make && insmod
    root@ubuntu16:/home/cxx/driver/cdev# make clean
    rm -f *.ko *.o *.mod.o *.symvers *.cmd  *.mod.c *.order
    root@ubuntu16:/home/cxx/driver/cdev# make
    make -C /lib/modules/4.15.0-142-generic/build M=/home/cxx/driver/cdev modules
    make[1]: Entering directory '/usr/src/linux-headers-4.15.0-142-generic'
      CC [M]  /home/cxx/driver/cdev/hellocdev.o
      Building modules, stage 2.
      MODPOST 1 modules
      CC      /home/cxx/driver/cdev/hellocdev.mod.o
      LD [M]  /home/cxx/driver/cdev/hellocdev.ko
    make[1]: Leaving directory '/usr/src/linux-headers-4.15.0-142-generic'
    root@ubuntu16:/home/cxx/driver/cdev# lsmod | grep hellocdev
    root@ubuntu16:/home/cxx/driver/cdev# insmod hellocdev.ko
    root@ubuntu16:/home/cxx/driver/cdev# dmesg 
    [32902.939675] hellocdev_init 
    root@ubuntu16:/home/cxx/driver/cdev# cat /proc/devices | grep hellocdev
    222 hellocdev
    root@ubuntu16:/home/cxx/driver/cdev#  
    

    mknod /dev/hellocdev c 222 0

    手动创建设备文件:mknod 设备文件名[/dev/xyz] b/c 主设备号 次设备号

    root@ubuntu16:/home/cxx/driver/cdev# mknod /dev/hellocdev c 222 0 //c:字符设备文件 b:块设备
    root@ubuntu16:/home/cxx/driver/cdev# ls /dev/hellcodev -l
    crw-r--r-- 1 root root 222, 0 Apr 24 17:46 /dev/hellocdev
    

    vim demo.c

    #include <stdio.h>
    #include <sys/types.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <unistd.h>
    int main()
    {
            int fd;
            fd = open("/dev/hellocdev",O_RDWR);
            if(fd<0)
            {
                    perror("open fail \n");
                    return -1;
            }
            printf("hellocdev open ok \n ");
            close(fd);
            return 0;
    }
    

    ./demo

    root@ubuntu16:/home/cxx/driver/cdev# gcc demo.c -o demo
    root@ubuntu16:/home/cxx/driver/cdev# ./demo
    hellocdev open ok 
    root@ubuntu16:/home/cxx/driver/cdev# 
    

    rmmod && dmesg

    root@ubuntu16:/home/cxx/driver/cdev# rmmod hellocdev.ko
    root@ubuntu16:/home/cxx/driver/cdev# dmesg 
    [23373.355620] hellocdev_init 
    [24474.220663] hellocdev_open()
    [29690.708345] hellocdev_exit 
    root@ubuntu16:/home/cxx/driver/cdev# 
    
    展开全文
  • linux register_chrdev函数解析

    千次阅读 2019-06-24 16:37:56
    register_chrdev大致作用:向内核注册cdev结构体,当在用户空间打开设备文件时内核可以根据设备号快速定位此设备文件的cdev->file_operations结构体,从而调用驱动底层的open,close,read,write,ioctl等函数,当我们...
  • 调用过程2.1 register_chrdev_region2.2 __register_chrdev_region 1. 函数原型   register_chrdev_region()原型如下: 原 型: int register_chrdev_region(dev_t from, unsigned count, const char *name) 功...
  • linux驱动之__register_chrdev_region() 代码在fs/char_dev.c //这个哈希表保存主次设备号 static struct char_device_struct { struct char_device_struct *next; unsigned int major; unsigned int baseminor; ...
  • Linux驱动注册有两种接口,一种是旧接口register_chrdev,另一种是新接口register_chrdev_region + cdev_init/cdev_add。 register_chrdev_region函数内容如下:
  • Linux字符设备注册函数register_chrdev详解当我们需要注册字符设备的时候,需要module_init()中调用register_chrdev()注册。...源代码如下:int __register_chrdev(unsigned int major, unsigned int baseminor,unsi...
  • --- 01:include/linux/fs.h static inline int register_chrdev(unsigned int major, const char *name, const struct file_operations *fo...
  • (二)register_chrdev函数原型int register_chrdev(unsigned int major, const char *name,const struct file_operations *fops)* register_chrdev() -为字符设备注册一个主号码。* @major:用于动态分配的主要设备号...
  • 内核提供了三个函数来注册一组字符设备编号,这三个函数分别是 register_chrdev_region()、alloc_chrdev_region() 和 register_chrdev()。 (1)register_chrdev 比较老的内核注册的形式 早期的驱动 (2)register_...
  • 浅析字符设备驱动程序__register_chrdev_region

    万次阅读 多人点赞 2016-09-29 01:15:30
    在 2.6 的内核之后,新增了一个 register_chrdev_region 函数,它支持将同一个主设备号下的次设备号进行分段,每一段供给一个字符设备驱动程序使用,使得资源利用率大大提升,同时,2.6 的内核保留了原有
  • register_chrdev(unsigned int major, const char *name,const struct file_operations *fops); 但其实这个函数是linux版本2.4之前的注册方式,它的原理是: (1)确定一个主设备号,如果major=0,则会自动分配...
  • 内核共提供了三个函数来注册一组字符设备编号,这三个函数分别是 register_chrdev_region()、alloc_chrdev_region()和 register_chrdev()。 区别:register_chrdev比较老的内核注册的形式 早期的驱动,register_...
  • register_chrdev(unsigned int major, const char *name,const struct file_operations *fops); 但其实这个函数是linux版本2.4之前的注册方式,它的原理是: (1)确定一个主设备号 (2)构造一个file_operations...
  • 一、函数包含的头文件: ...extern int register_chrdev_region(dev_t, unsigned, const char *); //静态的申请和注册设备号  extern int alloc_chrdev_region(dev_t, unsigned, const char *); //动态...
  • 轉載自 https://blog.csdn.net/seanyxie/article/details/5729936分配设备编号,注册设备与注销设备的函数均在fs.h中申明,如下:extern int register_chrdev_region(dev_t, unsigned, const char *); //静态的申请...
  • 下面给大家分享一下内核提供的注册字符设备的三个函数register_chrdev_region()、alloc_chrdev_region() 和 register_chrdev()的区别,相信大家在学习驱动的过程中也会遇到相似的疑问。 内核提供了三个函数来注册一...
  • 1.2.2&nbsp;...register_chrdev&nbsp;(unsigned&nbsp;int&nbsp;major,&nbsp;const&nbsp;&nbsp;char&nbsp;*name,&nbsp;struct&nbsp;file_operations*fo...
  • register_chrdev(unsigned int major, const char *name,const struct file_operations *fops); 但其实这个函数是linux版本2.4之前的注册方式,它的原理是: (1)确定一个主设备号 (2)构造一个file_operations结构体...
  • 之前写字符设备驱动,都是使用register_chrdev向内核注册驱动程序中构建的file_operations结构体,之后创建的设备文件,只要是主设备号相同(次设备号不同),则绑定的都是同一个file_operations结构体,应用程序...
  • 内核提供了三个函数来注册一组字符设备编号,这三个函数分别是 register_chrdev_region()、alloc_chrdev_region() 和 register_chrdev()。这三个函数都会调用一个共用的 __register_chrdev_region() 函数来注册一组...
  • /*register_chrdev = __register_chrdev_region (一次性256个子设备,这种固定模式不太好,不能比256更多子设备,所以后来开始废除这种模式)+ cdev_add*//* unregister_chrdev = __unregister_chrdev_regi...
  •  * register_chrdev_region() - register a range of device numbers  * @from: the first in the desired range of device numbers; must include  * the major number.  * @count: the number of...
  •  其实misc_register就是用主标号10调用register_chrdev() 的 也就是说,misc设备其实也就是特殊的字符设备,可自动生成设备节点 2. 字符设备(char device) 使用register_chrdev(LED_MAJOR,DEVICE_NAME,&dev_fops...
  • 本文中 1. 未使用file指针,在驱动对应的open函数中,对私有数据进行设置, ...新的字符设备注册方式,弃用register_chrdev()(1-3); 并使用自动创建设备节点的mdev机制,实现热插拔和自动识别设备节点(4-5) * 换为 ...
  • 内核提供了三个函数来注册一组字符设备编号,这三个函数分别是register_chrdev_region()、alloc_chrdev_region() 和register_chrdev()。这三个函数都会调用一个共用的 __register_chrdev_region()函数来注册一组设备...
  • 首先我们在注册函数里面调用了register_chrdev(MEM_MAJOR,"mem",&amp;memory_fops),向内核注册了一个字符设备。 第一个参数是主设备号,0代表动态分配,这里的MEM_MAJOR是1。第二个参数是设备的名字...
  • 内核提供了三个函数来注册一组字符设备编号,这三个函数分别是 register_chrdev_region()、alloc_chrdev_region() 和 register_chrdev()。其中,register_chrdev_region()是为提前知道 设备的主次设备号的设备...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 17,402
精华内容 6,960
关键字:

register_chrdev