zoukankan      html  css  js  c++  java
  • Linux字符设备驱动结构(一)--cdev结构体、设备号相关知识机械【转】

    本文转载自:http://blog.csdn.net/zqixiao_09/article/details/50839042

    一、字符设备基础知识

    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结构体的定义如下:

    [cpp] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. <include/linux/cdev.h>  
    2.   
    3. struct cdev {   
    4.     struct kobject kobj;                  //内嵌的内核对象.  
    5.     struct module *owner;                 //该字符设备所在的内核模块的对象指针.  
    6.     const struct file_operations *ops;    //该结构描述了字符设备所能实现的方法,是极为关键的一个结构体.  
    7.     struct list_head list;                //用来将已经向内核注册的所有字符设备形成链表.  
    8.     dev_t dev;                            //字符设备的设备号,由主设备号和次设备号构成.  
    9.     unsigned int count;                   //隶属于同一主设备号的次设备号的个数.  
    10. };  


    内核给出的操作struct cdev结构的接口主要有以下几个:

    a -- void cdev_init(struct cdev *, const struct file_operations *);

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

    [cpp] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. void cdev_init(struct cdev *cdev, const struct file_operations *fops)  
    2. {  
    3.     memset(cdev, 0, sizeof *cdev);  
    4.     INIT_LIST_HEAD(&cdev->list);  
    5.     kobject_init(&cdev->kobj, &ktype_cdev_default);  
    6.     cdev->ops = fops;  
    7. }  

          该函数主要对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).

    其源代码清单如下:

    [cpp] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. struct cdev *cdev_alloc(void)  
    2. {  
    3.     struct cdev *p = kzalloc(sizeof(struct cdev), GFP_KERNEL);  
    4.     if (p) {  
    5.         INIT_LIST_HEAD(&p->list);  
    6.         kobject_init(&p->kobj, &ktype_cdev_dynamic);  
    7.     }  
    8.     return p;  
    9. }  

         在上面的两个初始化的函数中,我们没有看到关于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 静态申请;

    [cpp] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. //宏定义:  
    2. #define MINORBITS    20  
    3. #define MINORMASK    ((1U << MINORBITS) - 1)  
    4. #define MAJOR(dev)    ((unsigned int) ((dev) >> MINORBITS))  
    5. #define MINOR(dev)    ((unsigned int) ((dev) & MINORMASK))  
    6. #define MKDEV(ma,mi)    (((ma) << MINORBITS) | (mi))</span>  

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

    a -- 静态申请

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

    其源代码清单如下:

    [cpp] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. int register_chrdev_region(dev_t from, unsigned count, const char *name)  
    2. {  
    3.     struct char_device_struct *cd;  
    4.     dev_t to = from + count;  
    5.     dev_t n, next;  
    6.   
    7.     for (n = from; n < to; n = next) {  
    8.         next = MKDEV(MAJOR(n)+1, 0);  
    9.         if (next > to)  
    10.             next = to;  
    11.         cd = __register_chrdev_region(MAJOR(n), MINOR(n),  
    12.                    next - n, name);  
    13.         if (IS_ERR(cd))  
    14.             goto fail;  
    15.     }  
    16.     return 0;  
    17. fail:  
    18.     to = n;  
    19.     for (n = from; n < to; n = next) {  
    20.         next = MKDEV(MAJOR(n)+1, 0);  
    21.         kfree(__unregister_chrdev_region(MAJOR(n), MINOR(n), next - n));  
    22.     }  
    23.     return PTR_ERR(cd);  
    24. }  


    b -- 动态分配:

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

    其源代码清单如下:

    [cpp] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,  
    2.             const char *name)  
    3. {  
    4.     struct char_device_struct *cd;  
    5.     cd = __register_chrdev_region(0, baseminor, count, name);  
    6.     if (IS_ERR(cd))  
    7.         return PTR_ERR(cd);  
    8.     *dev = MKDEV(cd->major, cd->baseminor);  
    9.     return 0;  
    10. }  

    可以看到二者都是调用了__register_chrdev_region 函数,其源代码如下:

    [cpp] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. static struct char_device_struct *  
    2. __register_chrdev_region(unsigned int major, unsigned int baseminor,  
    3.                int minorct, const char *name)  
    4. {  
    5.     struct char_device_struct *cd, **cp;  
    6.     int ret = 0;  
    7.     int i;  
    8.   
    9.     cd = kzalloc(sizeof(struct char_device_struct), GFP_KERNEL);  
    10.     if (cd == NULL)  
    11.         return ERR_PTR(-ENOMEM);  
    12.   
    13.     mutex_lock(&chrdevs_lock);  
    14.   
    15.     /* temporary */  
    16.     if (major == 0) {  
    17.         for (i = ARRAY_SIZE(chrdevs)-1; i > 0; i--) {  
    18.             if (chrdevs[i] == NULL)  
    19.                 break;  
    20.         }  
    21.   
    22.         if (i == 0) {  
    23.             ret = -EBUSY;  
    24.             goto out;  
    25.         }  
    26.         major = i;  
    27.         ret = major;  
    28.     }  
    29.   
    30.     cd->major = major;  
    31.     cd->baseminor = baseminor;  
    32.     cd->minorct = minorct;  
    33.     strlcpy(cd->name, name, sizeof(cd->name));  
    34.   
    35.     i = major_to_index(major);  
    36.   
    37.     for (cp = &chrdevs[i]; *cp; cp = &(*cp)->next)  
    38.         if ((*cp)->major > major ||  
    39.             ((*cp)->major == major &&  
    40.              (((*cp)->baseminor >= baseminor) ||  
    41.               ((*cp)->baseminor + (*cp)->minorct > baseminor))))  
    42.             break;  
    43.   
    44.     /* Check for overlapping minor ranges.  */  
    45.     if (*cp && (*cp)->major == major) {  
    46.         int old_min = (*cp)->baseminor;  
    47.         int old_max = (*cp)->baseminor + (*cp)->minorct - 1;  
    48.         int new_min = baseminor;  
    49.         int new_max = baseminor + minorct - 1;  
    50.   
    51.         /* New driver overlaps from the left.  */  
    52.         if (new_max >= old_min && new_max <= old_max) {  
    53.             ret = -EBUSY;  
    54.             goto out;  
    55.         }  
    56.   
    57.         /* New driver overlaps from the right.  */  
    58.         if (new_min <= old_max && new_min >= old_min) {  
    59.             ret = -EBUSY;  
    60.             goto out;  
    61.         }  
    62.     }  
    63.   
    64.     cd->next = *cp;  
    65.     *cp = cd;  
    66.     mutex_unlock(&chrdevs_lock);  
    67.     return cd;  
    68. out:  
    69.     mutex_unlock(&chrdevs_lock);  
    70.     kfree(cd);  
    71.     return ERR_PTR(ret);  
    72. }  

     通过这个函数可以看出 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 函数,其源代码定义如下:

    [cpp] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. static inline int register_chrdev(unsigned int major, const char *name,  
    2.                   const struct file_operations *fops)  
    3. {  
    4.     return __register_chrdev(major, 0, 256, name, fops);  
    5. }  

    调用了 __register_chrdev(major, 0, 256, name, fops) 函数:

    [cpp] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. int __register_chrdev(unsigned int major, unsigned int baseminor,  
    2.               unsigned int count, const char *name,  
    3.               const struct file_operations *fops)  
    4. {  
    5.     struct char_device_struct *cd;  
    6.     struct cdev *cdev;  
    7.     int err = -ENOMEM;  
    8.   
    9.     cd = __register_chrdev_region(major, baseminor, count, name);  
    10.     if (IS_ERR(cd))  
    11.         return PTR_ERR(cd);  
    12.   
    13.     cdev = cdev_alloc();  
    14.     if (!cdev)  
    15.         goto out2;  
    16.   
    17.     cdev->owner = fops->owner;  
    18.     cdev->ops = fops;  
    19.     kobject_set_name(&cdev->kobj, "%s", name);  
    20.   
    21.     err = cdev_add(cdev, MKDEV(cd->major, baseminor), count);  
    22.     if (err)  
    23.         goto out;  
    24.   
    25.     cd->cdev = cdev;  
    26.   
    27.     return major ? 0 : cd->major;  
    28. out:  
    29.     kobject_put(&cdev->kobj);  
    30. out2:  
    31.     kfree(__unregister_chrdev_region(cd->major, baseminor, count));  
    32.     return err;  
    33. }  

    可以看到这个函数不只帮我们注册了设备号,还帮我们做了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 字符设备驱动开发 (二)—— 自动创建设备节点

     

    下面看一个实例,练习一下上面的操作:

    hello.c

    [cpp] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. #include <linux/module.h>  
    2. #include <linux/fs.h>  
    3. #include <linux/cdev.h>  
    4. static int major = 250;  
    5. static int minor = 0;  
    6. static dev_t devno;  
    7. static struct cdev cdev;  
    8. static int hello_open (struct inode *inode, struct file *filep)  
    9. {  
    10.     printk("hello_open  ");  
    11.     return 0;  
    12. }  
    13. static struct file_operations hello_ops=  
    14. {  
    15.     .open = hello_open,           
    16. };  
    17.   
    18. static int hello_init(void)  
    19. {  
    20.     int ret;      
    21.     printk("hello_init");  
    22.     devno = MKDEV(major,minor);  
    23.     ret = register_chrdev_region(devno, 1, "hello");  
    24.     if(ret < 0)  
    25.     {  
    26.         printk("register_chrdev_region fail  ");  
    27.         return ret;  
    28.     }  
    29.     cdev_init(&cdev,&hello_ops);  
    30.     ret = cdev_add(&cdev,devno,1);  
    31.     if(ret < 0)  
    32.     {  
    33.         printk("cdev_add fail  ");  
    34.         return ret;  
    35.     }     
    36.     return 0;  
    37. }  
    38. static void hello_exit(void)  
    39. {  
    40.     cdev_del(&cdev);  
    41.     unregister_chrdev_region(devno,1);  
    42.     printk("hello_exit  ");  
    43. }  
    44. MODULE_LICENSE("GPL");  
    45. module_init(hello_init);  
    46. module_exit(hello_exit);  

    测试程序 test.c

    [cpp] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. #include <sys/types.h>  
    2. #include <sys/stat.h>  
    3. #include <fcntl.h>  
    4. #include <stdio.h>  
    5.   
    6. main()  
    7. {  
    8.     int fd;  
    9.   
    10.     fd = open("/dev/hello",O_RDWR);  
    11.     if(fd<0)  
    12.     {  
    13.         perror("open fail  ");  
    14.         return ;  
    15.     }  
    16.   
    17.     close(fd);  
    18. }  

    makefile:

    [cpp] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. ifneq  ($(KERNELRELEASE),)  
    2. obj-m:=hello.o  
    3. $(info "2nd")  
    4. else  
    5. KDIR := /lib/modules/$(shell uname -r)/build  
    6. PWD:=$(shell pwd)  
    7. all:  
    8.     $(info "1st")  
    9.     make -C $(KDIR) M=$(PWD) modules  
    10. clean:  
    11.     rm -f *.ko *.o *.symvers *.mod.c *.mod.o *.order  
    12. endif  

    编译成功后,使用 insmod 命令加载:

    然后用cat /proc/devices 查看,会发现设备号已经申请成功;

  • 相关阅读:
    asp.net中分页与存储过程的一些总结
    Ajax与Json的一些总结
    aspx页面与服务器控件间运行原理
    ASP.NET中Server对象的几个方法
    Cookie与Session的一些总结
    ASP.NET的学习之asp.net整体运行机制
    Find offset of first/last found substring
    由于DNS反向解析导致的登录BI启动版速度变慢问题
    4-自定义BI启动版Logon界面
    3-自定义BI启动版是否隐藏CMS名称
  • 原文地址:https://www.cnblogs.com/zzb-Dream-90Time/p/6145292.html
Copyright © 2011-2022 走看看