• register_chrdev_region/alloc_chrdev_region和cdev注册字符设备驱动


    内核提供了三个函数来注册一组字符设备编号,这三个函数分别是 register_chrdev_region()、alloc_chrdev_region() 和 register_chrdev()。


    (1)register_chrdev  比较老的内核注册的形式   早期的驱动
    (2)register_chrdev_region/alloc_chrdev_region + cdev  新的驱动形式

    区别:register_chrdev()函数是老版本里面的设备号注册函数,可以实现静态和动态注册两种方法,主要是通过给定的主设备号是否为0来进行区别,为0的时候为动态注册。register_chrdev_region以及alloc_chrdev_region就是将上述函数的静态和动态注册设备号进行了拆分的强化。

    register_chrdev_region(dev_t first,unsigned int count,char *name)
    First :要分配的设备编号范围的初始值, 这组连续设备号的起始设备号, 相当于register_chrdev()中主设备号
    Count:连续编号范围.   是这组设备号的大小(也是次设备号的个数)
    Name:编号相关联的设备名称. (/proc/devices); 本组设备的驱动名称

    alloc_chrdev_region函数,来让内核自动给我们分配设备号

    (1)register_chrdev_region是在事先知道要使用的主、次设备号时使用的;要先查看cat /proc/devices去查看没有使用的。
    (2)更简便、更智能的方法是让内核给我们自动分配一个主设备号,使用alloc_chrdev_region就可以自动分配了。
    (3)自动分配的设备号,我们必须去知道他的主次设备号,否则后面没法去mknod创建他对应的设备文件。

    int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)

    1:这个函数的第一个参数,是输出型参数,获得一个分配到的设备号。可以用MAJOR宏和MINOR宏,将主设备号和次设备号,提取打印出来,看是自动分配的是多少,方便我们在mknod创建设备文件时用到主设备号和次设备号。 mknod /dev/xxx c 主设备号 次设备号

    2:第二个参数:次设备号的基准,从第几个次设备号开始分配。

    3:第三个参数:次设备号的个数。

    4:第四个参数:驱动的名字。

    5:返回值:小于0,则错误,自动分配设备号错误。否则分配得到的设备号就被第一个参数带出来。

    cdev介绍

    cdev是一个结构体,里面的成员来共同帮助我们注册驱动到内核中,表达字符设备的,将这个struct cdev结构体进行填充,主要填充的内容就是

    struct cdev {
    
    struct kobject kobj;
    
    struct module *owner;//填充时,值要为 THIS_MODULE,表示模块
    
    const struct file_operations *ops;//这个file_operations结构体,注册驱动的关键,要填充成这个结构体变量
    
    struct list_head list;
    
    dev_t dev;//设备号,主设备号+次设备号
    
    unsigned int count;//次设备号个数
    
    };
    

    file_operations这个结构体变量,让cdev中的ops成员的值为file_operations结构体变量的值。这个结构体会被cdev_add函数想内核注册

    cdev结构体,可以用很多函数来操作他。

    如:

    cdev_alloc:让内核为这个结构体分配内存的

    cdev_init:将struct cdev类型的结构体变量和file_operations结构体进行绑定的

    cdev_add:向内核里面添加一个驱动,注册驱动

    cdev_del:从内核中注销掉一个驱动。注销驱动

    设备号

    (1)dev_t类型(包括了主设备号和次设备号  不同的内核中定义不一样有的是16位次设备号和16位主设备号构成  有的是20为次设备号12位主设备号 )

    (2)MKDEV、MAJOR、MINOR三个宏

    MKDEV:  是用来将主设备号和次设备号,转换成一个主次设备号的。(设备号)

    MAJOR:   从设备号里面提取出来主设备号的。

    MINOR宏:从设备号中提取出来次设备号的。

    register_chrdev_region的使用对比register_chrdev:
     1 // 模块安装函数
     2 static int __init chrdev_init(void)
     3 {    
     4     int retval;
     5     
     6     printk(KERN_INFO "chrdev_init helloworld init
    ");
     7 
     8 /*
     9     // 在module_init宏调用的函数中去注册字符设备驱动
    10     // major传0进去表示要让内核帮我们自动分配一个合适的空白的没被使用的主设备号
    11     // 内核如果成功分配就会返回分配的主设备好;如果分配失败会返回负数
    12     mymajor = register_chrdev(0, MYNAME, &test_fops);
    13     if (mymajor < 0)
    14     {
    15         printk(KERN_ERR "register_chrdev fail
    ");
    16         return -EINVAL;
    17     }
    18     printk(KERN_INFO "register_chrdev success... mymajor = %d.
    ", mymajor);
    19 */    
    20 
    21     // 使用新的cdev接口来注册字符设备驱动
    22     // 新的接口注册字符设备驱动需要2步
    23     
    24     // 第1步:注册/分配主次设备号
    25     mydev = MKDEV(MYMAJOR, 0);
    26     retval = register_chrdev_region(mydev, MYCNT, MYNAME);//
    //动态时如下直接改 同时将2526行去掉 其他都一样
    //int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)
    27 if (retval) { 28 printk(KERN_ERR "Unable to register minors for %s ", MYNAME); 29 return -EINVAL; 30 } 31 printk(KERN_INFO "register_chrdev_region success "); 32 // 第2步:注册字符设备驱动 33 cdev_init(&test_cdev, &test_fops); 34 retval = cdev_add(&test_cdev, mydev, MYCNT); 35 if (retval) { 36 printk(KERN_ERR "Unable to cdev_add "); 37 return -EINVAL; 38 } 39 printk(KERN_INFO "cdev_add success "); 40 41 42 // 使用动态映射的方式来操作寄存器 43 if (!request_mem_region(GPJ0CON_PA, 4, "GPJ0CON")) 44 return -EINVAL; 45 if (!request_mem_region(GPJ0DAT_PA, 4, "GPJ0CON")) 46 return -EINVAL; 47 48 pGPJ0CON = ioremap(GPJ0CON_PA, 4); 49 pGPJ0DAT = ioremap(GPJ0DAT_PA, 4); 50 51 *pGPJ0CON = 0x11111111; 52 *pGPJ0DAT = ((0<<3) | (0<<4) | (0<<5)); // 53 54 55 return 0; 56 } 57 58 // 模块下载函数 59 static void __exit chrdev_exit(void) 60 { 61 printk(KERN_INFO "chrdev_exit helloworld exit "); 62 63 *pGPJ0DAT = ((1<<3) | (1<<4) | (1<<5)); 64 65 // 解除映射 66 iounmap(pGPJ0CON); 67 iounmap(pGPJ0DAT); 68 release_mem_region(GPJ0CON_PA, 4); 69 release_mem_region(GPJ0DAT_PA, 4); 70 71 /* 72 // 在module_exit宏调用的函数中去注销字符设备驱动 73 unregister_chrdev(mymajor, MYNAME); 74 */ 75 76 // 使用新的接口来注销字符设备驱动 77 // 注销分2步: 78 // 第一步真正注销字符设备驱动用cdev_del 79 cdev_del(&test_cdev); 80 // 第二步去注销申请的主次设备号 81 unregister_chrdev_region(mydev, MYCNT); 82 }

    完整代码:

      1 #include <linux/module.h>        // module_init  module_exit
      2 #include <linux/init.h>            // __init   __exit
      3 #include <linux/fs.h>
      4 #include <asm/uaccess.h>
      5 #include <mach/regs-gpio.h>
      6 #include <mach/gpio-bank.h>        // arch/arm/mach-s5pv210/include/mach/gpio-bank.h
      7 #include <linux/string.h>
      8 #include <linux/io.h>
      9 #include <linux/ioport.h>
     10 #include <linux/cdev.h>
     11 
     12 
     13 
     14 #define MYMAJOR        200
     15 #define MYCNT        1
     16 #define MYNAME        "testchar"
     17 
     18 #define GPJ0CON        S5PV210_GPJ0CON
     19 #define GPJ0DAT        S5PV210_GPJ0DAT
     20 
     21 #define rGPJ0CON    *((volatile unsigned int *)GPJ0CON)
     22 #define rGPJ0DAT    *((volatile unsigned int *)GPJ0DAT)
     23 
     24 #define GPJ0CON_PA    0xe0200240
     25 #define GPJ0DAT_PA     0xe0200244
     26 
     27 unsigned int *pGPJ0CON;
     28 unsigned int *pGPJ0DAT;
     29 
     30 
     31 int mymajor;
     32 static dev_t mydev;
     33 static struct cdev test_cdev;
     34 
     35 char kbuf[100];            // 内核空间的buf
     36 
     37 
     38 static int test_chrdev_open(struct inode *inode, struct file *file)
     39 {
     40     // 这个函数中真正应该放置的是打开这个设备的硬件操作代码部分
     41     // 但是现在暂时我们写不了这么多,所以用一个printk打印个信息来做代表。
     42     printk(KERN_INFO "test_chrdev_open
    ");
     43     
     44     rGPJ0CON = 0x11111111;
     45     rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));        //
     46     
     47     return 0;
     48 }
     49 
     50 static int test_chrdev_release(struct inode *inode, struct file *file)
     51 {
     52     printk(KERN_INFO "test_chrdev_release
    ");
     53     
     54     rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));
     55     
     56     return 0;
     57 }
     58 
     59 ssize_t test_chrdev_read(struct file *file, char __user *ubuf, size_t count, loff_t *ppos)
     60 {
     61     int ret = -1;
     62     
     63     printk(KERN_INFO "test_chrdev_read
    ");
     64     
     65     ret = copy_to_user(ubuf, kbuf, count);
     66     if (ret)
     67     {
     68         printk(KERN_ERR "copy_to_user fail
    ");
     69         return -EINVAL;
     70     }
     71     printk(KERN_INFO "copy_to_user success..
    ");
     72     
     73     
     74     return 0;
     75 }
     76 
     77 // 写函数的本质就是将应用层传递过来的数据先复制到内核中,然后将之以正确的方式写入硬件完成操作。
     78 static ssize_t test_chrdev_write(struct file *file, const char __user *ubuf,
     79     size_t count, loff_t *ppos)
     80 {
     81     int ret = -1;
     82     
     83     printk(KERN_INFO "test_chrdev_write
    ");
     84 
     85     // 使用该函数将应用层传过来的ubuf中的内容拷贝到驱动空间中的一个buf中
     86     //memcpy(kbuf, ubuf);        // 不行,因为2个不在一个地址空间中
     87     memset(kbuf, 0, sizeof(kbuf));
     88     ret = copy_from_user(kbuf, ubuf, count);
     89     if (ret)
     90     {
     91         printk(KERN_ERR "copy_from_user fail
    ");
     92         return -EINVAL;
     93     }
     94     printk(KERN_INFO "copy_from_user success..
    ");
     95     
     96     if (kbuf[0] == '1')
     97     {
     98         rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));
     99     }
    100     else if (kbuf[0] == '0')
    101     {
    102         rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));
    103     }
    104     
    105     
    106 /*
    107     // 真正的驱动中,数据从应用层复制到驱动中后,我们就要根据这个数据
    108     // 去写硬件完成硬件的操作。所以这下面就应该是操作硬件的代码
    109     if (!strcmp(kbuf, "on"))
    110     {
    111         rGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));
    112     }
    113     else if (!strcmp(kbuf, "off"))
    114     {
    115         rGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));
    116     }
    117 */
    118 
    119     
    120     
    121     return 0;
    122 }
    123 
    124 
    125 // 自定义一个file_operations结构体变量,并且去填充
    126 static const struct file_operations test_fops = {
    127     .owner        = THIS_MODULE,                // 惯例,直接写即可
    128     
    129     .open        = test_chrdev_open,            // 将来应用open打开这个设备时实际调用的
    130     .release    = test_chrdev_release,        // 就是这个.open对应的函数
    131     .write         = test_chrdev_write,
    132     .read        = test_chrdev_read,
    133 };
    134 
    135 
    136 // 模块安装函数
    137 static int __init chrdev_init(void)
    138 {    
    139     int retval;
    140     
    141     printk(KERN_INFO "chrdev_init helloworld init
    ");
    142 
    143 /*
    144     // 在module_init宏调用的函数中去注册字符设备驱动
    145     // major传0进去表示要让内核帮我们自动分配一个合适的空白的没被使用的主设备号
    146     // 内核如果成功分配就会返回分配的主设备好;如果分配失败会返回负数
    147     mymajor = register_chrdev(0, MYNAME, &test_fops);
    148     if (mymajor < 0)
    149     {
    150         printk(KERN_ERR "register_chrdev fail
    ");
    151         return -EINVAL;
    152     }
    153     printk(KERN_INFO "register_chrdev success... mymajor = %d.
    ", mymajor);
    154 */    
    155 
    156     // 使用新的cdev接口来注册字符设备驱动
    157     // 新的接口注册字符设备驱动需要2步
    158     
    159     // 第1步:注册/分配主次设备号
    160     mydev = MKDEV(MYMAJOR, 0);
    161     retval = register_chrdev_region(mydev, MYCNT, MYNAME);
    162     if (retval) {
    163         printk(KERN_ERR "Unable to register minors for %s
    ", MYNAME);
    164         return -EINVAL;
    165     }
    166     printk(KERN_INFO "register_chrdev_region success
    ");
    167     // 第2步:注册字符设备驱动
    168     cdev_init(&test_cdev, &test_fops);
    169     retval = cdev_add(&test_cdev, mydev, MYCNT);
    170     if (retval) {
    171         printk(KERN_ERR "Unable to cdev_add
    ");
    172         return -EINVAL;
    173     }
    174     printk(KERN_INFO "cdev_add success
    ");
    175 
    176     
    177     // 使用动态映射的方式来操作寄存器
    178     if (!request_mem_region(GPJ0CON_PA, 4, "GPJ0CON"))
    179         return -EINVAL;
    180     if (!request_mem_region(GPJ0DAT_PA, 4, "GPJ0CON"))
    181         return -EINVAL;
    182     
    183     pGPJ0CON = ioremap(GPJ0CON_PA, 4);
    184     pGPJ0DAT = ioremap(GPJ0DAT_PA, 4);
    185     
    186     *pGPJ0CON = 0x11111111;
    187     *pGPJ0DAT = ((0<<3) | (0<<4) | (0<<5));        //
    188     
    189 
    190     return 0;
    191 }
    192 
    193 // 模块下载函数
    194 static void __exit chrdev_exit(void)
    195 {
    196     printk(KERN_INFO "chrdev_exit helloworld exit
    ");
    197 
    198     *pGPJ0DAT = ((1<<3) | (1<<4) | (1<<5));    
    199     
    200     // 解除映射
    201     iounmap(pGPJ0CON);
    202     iounmap(pGPJ0DAT);
    203     release_mem_region(GPJ0CON_PA, 4);
    204     release_mem_region(GPJ0DAT_PA, 4);
    205 
    206 /*    
    207     // 在module_exit宏调用的函数中去注销字符设备驱动
    208     unregister_chrdev(mymajor, MYNAME);
    209 */    
    210 
    211     // 使用新的接口来注销字符设备驱动
    212     // 注销分2步:
    213     // 第一步真正注销字符设备驱动用cdev_del
    214     cdev_del(&test_cdev);
    215     // 第二步去注销申请的主次设备号
    216     unregister_chrdev_region(mydev, MYCNT);
    217 }
    218 
    219 
    220 module_init(chrdev_init);
    221 module_exit(chrdev_exit);
    222 
    223 // MODULE_xxx这种宏作用是用来添加模块描述信息
    224 MODULE_LICENSE("GPL");                // 描述模块的许可证
    225 MODULE_AUTHOR("aston");                // 描述模块的作者
    226 MODULE_DESCRIPTION("module test");    // 描述模块的介绍信息
    227 MODULE_ALIAS("alias xxx");            // 描述模块的别名信息
    View Code
     1 #include <stdio.h>
     2 #include <sys/types.h>
     3 #include <sys/stat.h>
     4 #include <fcntl.h>
     5 #include <string.h>
     6 
     7 
     8 #define FILE    "/dev/test"            // 刚才mknod创建的设备文件名
     9 
    10 char buf[100];
    11 
    12 int main(void)
    13 {
    14     int fd = -1;
    15     int i = 0;
    16     
    17     fd = open(FILE, O_RDWR);
    18     if (fd < 0)
    19     {
    20         printf("open %s error.
    ", FILE);
    21         return -1;
    22     }
    23     printf("open %s success..
    ", FILE);
    24 
    25 /*    
    26     // 读写文件
    27     write(fd, "on", 2);
    28     sleep(2);
    29     write(fd, "off", 3);
    30     sleep(2);
    31     write(fd, "on", 2);
    32     sleep(2);
    33 */
    34 /*
    35     write(fd, "1", 1);
    36     sleep(2);
    37     write(fd, "0", 1);
    38     sleep(2);
    39     write(fd, "1", 1);
    40     sleep(2);
    41 */
    42     while (1)
    43     {
    44         memset(buf, 0 , sizeof(buf));
    45         printf("请输入 on | off 
    ");
    46         scanf("%s", buf);
    47         if (!strcmp(buf, "on"))
    48         {
    49             write(fd, "1", 1);
    50         }
    51         else if (!strcmp(buf, "off"))
    52         {
    53             write(fd, "0", 1);
    54         }
    55         else if (!strcmp(buf, "flash"))
    56         {
    57             for (i=0; i<3; i++)
    58             {
    59                 write(fd, "1", 1);
    60                 sleep(1);
    61                 write(fd, "0", 1);
    62                 sleep(1);
    63             }
    64         }    
    65         else if (!strcmp(buf, "quit"))
    66         {
    67             break;
    68         }
    69     }
    70 
    71     
    72     // 关闭文件
    73     close(fd);
    74     
    75     return 0;
    76 }
    View Code

     cdev_alloc给定义出来的struct cdev类型的指针分配堆空间

    cdev_alloc内部其实就是调用了内核中的kmalloc来进行给struct_cdev来分配堆内存的,内存大小就是这个struct cdev的大小。

    例:如果我们定义了一个全局的 static struct cdev *pcdev; 我们就可以用 pcdev = cdev_alloc();来给这个pcdev分配堆内存空间。cdev_del(pcdev)时会释放掉这个申请到的堆内存,cdev_del函数内部是能知道你的struct cdev定义的对象是用的堆内存还是栈内存还是数据段内存的。这个函数cdev_del调用时,会先去看你有没有使用堆内存,如果有用到的话,会先去释放掉你用的堆内存,然后在注销掉你这个设备驱动。防止内存泄漏。注意如果struct cdev要用堆内存一定要用内核提供的这个cdev_alloc去分配堆内存,因为内部会做记录,这样在cdev_del注销掉这个驱动的时候,才会去释放掉那段堆内存。

    使用cdev_alloc可以直接pcdev->ops = fops; 来进行绑定,就不需要cdev_init函数了,

    代码:

    
    

    static dev_t mydev;
    //static struct cdev test_cdev;
    static struct cdev *pcdev;

    .........

    //
    第2步:注册字符设备驱动 pcdev = cdev_alloc(); // 给pcdev分配内存,指针实例化 //cdev_init( &test_cdev, &test_fops);//下面的代码可以讲该句替换了 pcdev->owner = THIS_MODULE; pcdev->ops = &test_fops;

    cdev_init源码:

    void cdev_init(struct cdev *cdev, const struct file_operations *fops)
    {
    	memset(cdev, 0, sizeof *cdev);
    	INIT_LIST_HEAD(&cdev->list);
    	cdev->kobj.ktype = &ktype_cdev_default;
    	kobject_init(&cdev->kobj);
    	cdev->ops = fops;
    }
    

     cdev_init中的其他的操作在cdev_alloc中都有做了。

     

    整理自

    http://blog.csdn.net/tommy_wxie/article/details/7195471

    http://blog.sina.com.cn/s/blog_14f1867f70102wlrj.html

    http://edu.51cto.com/pack/view/id-529.html

     http://blog.csdn.net/zqixiao_09/article/details/50839042(好贴)

  • 相关阅读:
    第三章 IP地址分类及其子网划分
    第二章 NFS简单实战教程
    第一章 Rsync实战教程
    第三章 struts2 (二)
    第二章 struts2入门
    第一章 struts2 入门
    《Python语言及其应用》学习笔记
    ASPX页面请求响应过程
    Python 对象(type/object/class) 作用域 一等函数 (慕课--Python高级,IO并发 第二章)
    javascript 运行机制 事件循环 浏览器缓存 (慕课网 前段跳槽面试必备 4-1,4-2,4-3)
  • 原文地址:https://www.cnblogs.com/zhaobinyouth/p/6227644.html
Copyright © 2020-2023  润新知