Linux行走(2)——分析字符设备过程

该文章参考 http://blog.csdn.net/luoshengyang/article/details/6568411 老罗的笔记
也感谢以下文章
http://www.embedu.org/Column/Column433.htm 揭开linux内核中container_of的神秘面纱

http://blog.csdn.net/ghostyu/article/details/6876667 linux字符设备驱动 cdev

http://www.cnblogs.com/itech/archive/2012/05/15/2502284.html

Linux的inode的理解


这里只引用  传统的设备文件的方法来访问 这个方法来分析 如何实现的字符设备驱动
一下不明白的内容均用红色标注

hello.h
  • #ifndef _HELLO_ANDROID_H_  
  • #define _HELLO_ANDROID_H_  
  •   
  • #include <linux/cdev.h>  
  • #include <linux/semaphore.h>  
  •   
  • #define HELLO_DEVICE_NODE_NAME  "hello"  
  • #define HELLO_DEVICE_FILE_NAME  "hello"  
  • #define HELLO_DEVICE_PROC_NAME  "hello"  
  • #define HELLO_DEVICE_CLASS_NAME "hello"  
  •   
  • struct hello_android_dev {  
  •     int val;  //寄存器内容
  •     struct semaphore sem; //互斥变量//同步访问信号量 
  •     struct cdev dev;  //dev成员变量是一个内嵌的字符设备,这个Linux驱动程序自定义字符设备结构体的标准方法。//这里不用深入的了解,只要记住这个变量要将来要与方法绑定就OK了
  • };  
  •   
  • #endif  

  • 这里没什么说的

    主要说一下那个 struct cdev dev;

    1. /*  
    2. *内核源码位置  
    3. *linux2.6.38/include/linux/cdev.h  
    4. */  
    5.   
    6. struct cdev {  
    7.     struct kobject kobj;  
    8.     struct module *owner;   //一般初始化为:THIS_MODULE  
    9.     const struct file_operations *ops;   //字符设备用到的例外一个重要的结构体file_operations,cdev初始化时与之绑定  
    10.     struct list_head list;  
    11.     dev_t dev;  //dev_t为32位整形 设备号  
    12.     unsigned int count;  
    13. };  

    Hello.c
    1. #include <linux/init.h>  
    2. #include <linux/module.h>  
    3. #include <linux/types.h>  
    4. #include <linux/fs.h>  
    5. #include <linux/proc_fs.h>  
    6. #include <linux/device.h>  
    7. #include <asm/uaccess.h>  
    8.   
    9. #include "hello.h"  
    10.   
    11. /*主设备和从设备号变量*/  
    12. static int hello_major = 0;  
    13. static int hello_minor = 0;  
    14.   
    15. /*设备类别和设备变量*/  
    16. static struct class* hello_class = NULL;  
    17. static struct hello_android_dev* hello_dev = NULL;  

    1. /*传统的设备文件操作方法*/  
    2. static int hello_open(struct inode* inode, struct file* filp);  
    3. static int hello_release(struct inode* inode, struct file* filp);  
    4. static ssize_t hello_read(struct file* filp, char __user *buf, size_t count, loff_t* f_pos);  
    5. static ssize_t hello_write(struct file* filp, const char __user *buf, size_t count, loff_t* f_pos);  
    6.   

    1. /*设备文件操作方法表*/  
    2. static struct file_operations hello_fops = {  
    3.     .owner = THIS_MODULE,  
    4.     .open = hello_open,  
    5.     .release = hello_release,  
    6.     .read = hello_read,  
    7.     .write = hello_write,   
    8. };  

    将来的可执行文件里面的
    标准read write open release 等方法 就映射到这上面

    下面看方法的具体实现

    /*打开设备*/
    static int hello_open(struct inode* inode, struct  file* filp)
    {
        struct hello_android_dev* dev;
        
        /*将dev的首地址保存在filp中以便以后随便调用,其实我对于这里不是真正的理解*/
        dev = container_of(inode->i_cdev, struct hello_android_dev, dev);  

        filp->private_data = dev;  

        return 0
    }

    /*设备文件释放时调用,空实现*/

    1. static int hello_release(struct inode* inode, struct file* filp) {  
    2.     return 0;  
    3. }  

    /*读取设备的寄存器值val的值*/
    static  ssize_t hello_read( struct  file* filp,  char  __user *buf,  size_t  count, loff_t* f_pos)
    {
        ssize_t err = 0;

        struct hello_android_dev* dev = filp->private_data;
         /*将寄存器val的值拷贝到用户提供的缓冲区*/  
        if(copy_to_user(buf,&(dev->val),sizeof( dev->val )))
        {
            err = -EFAULT;
            goto out;
        }
        err = sizeof(dev->val);

    out:
        return err;
    }
    /*写设备寄存器的值*/
    static  ssize_t hello_write( struct  file* filp,  const   char  __user *buf,  size_t  count, loff_t* f_pos) 
    {
        struct hello_android_dev* dev = filp->private_data;
        ssize_t err = 0;
        if(cout != sizeof(dev->val))
        {
            goto out;
        }
        
        if(copy_from_user(&(dev->val),buf,count))
        {
            err = -EFAULT;
            goto out;
        }

        err = sizeof(dev->val);

    out:
        return err;
         
    }

    上面的那些还是属于将来的驱动方法和加载没有任何关系。

    下面是加载和卸载方法 加载时只要执行设备注册,和初始化操作就OK。

    /*初始化设备*/
    static int __hello_setup_dev(struct hello_android_dev* dev)
    {
        dev_t devno = MKDEV(hello_major,hello_minor);
        //将major 和minor 合成这个dev_t
        memset(dev,0,sizeof(struct hello_android_dev));
        /*通过查询memset 发现这句在实际上是没有效果的,只是清楚了dev的内容*/
        /*初始化cdev*/
    cdev_init(&(dev->dev),&hello_fops);
        

    dev->dev.owner = THIS_MODULE;
        dev->dev.ops = &hello_fops;

    /*注册字符设备*/
    err = cdev_add(&(dev->dev),devno,1);
    if(err)
        {
            return err;
        }

        /*初始化信号量和集群器的val 其实这里完全可以不用这个信号量*/
        init_MUTEX(&(dev->sem));
        dev->val = 0;

        return 0;
    }

    /*模块加载*/
    static ini __init hello_init(void)
    {
        int err = -1;
        dev_t dev = 0;
        struct device* temp = NULL;

         printk(KERN_ALERT"Initializing hello device.\n");
        
        /*动态分配主设备和从设备号*/
        err = alloc_chrdev_region(&dev,0,1,HELLO_DEVICE_NODE_NAME);
        if(err < 0)
        {
            printk(KERN_ALERT"Failed to alloc char dev region.\n");
            goto fail;
        }
        hello_major = MAJOR(dev);
        hello_minor = MINOR(dev);
        /*分配hello设备结构体变量*/
        hello_dev = kmalloc(sizeof(struct hello_android_dev),GFP_KERNEL);
        if(!hello_dev)
        {
            err = -ENOMEM; 
            printk(KERN_ALERT"Failed to alloc hello_dev.\n");  
            goto unregister;  
        }
        
         /*初始化设备*/   
        err = __hello_setup_dev(hello_dev);  
        if(err)
        {  
            printk(KERN_ALERT"Failed to setup dev: %d.\n", err);  
            goto cleanup;
        }
        
       
    1. /*在/sys/class/目录下创建设备类别目录hello 创建字符设备的class*/  
    2.     hello_class = class_create(THIS_MODULE, HELLO_DEVICE_CLASS_NAME);  
    3.     if(IS_ERR(hello_class)) {  
    4.         err = PTR_ERR(hello_class);  
    5.         printk(KERN_ALERT"Failed to create hello class.\n");  
    6.         goto destroy_cdev;  
    7.     }          
    8.   
    9.     /*在/dev/目录和/sys/class/hello目录下分别创建设备文件hello 创建设备*/  
    10.     temp = device_create(hello_class, NULL, dev, "%s", HELLO_DEVICE_FILE_NAME);  
    11.     if(IS_ERR(temp)) {  
    12.         err = PTR_ERR(temp);  
    13.         printk(KERN_ALERT"Failed to create hello device.");  
    14.         goto destroy_class;  
    15.     }      


    1. destroy_cdev:  
    2.     cdev_del(&(hello_dev->dev)); 

    1. cleanup:  
    2.     kfree(hello_dev); 

    1. unregister:  
    2.     unregister_chrdev_region(MKDEV(hello_major, hello_minor), 1); 

    1. fail:  
    2.     return err;
    }

    1. /*模块卸载方法*/  
    2. static void __exit hello_exit(void) {  
    3.     dev_t devno = MKDEV(hello_major, hello_minor);  
    4.   
    5.     printk(KERN_ALERT"Destroy hello device.\n");          
    6.         
    7.   
    8.     /*销毁设备类别和设备*/  
    9.     if(hello_class) {  
    10.         device_destroy(hello_class, MKDEV(hello_major, hello_minor));  
    11.         class_destroy(hello_class);  
    12.     }          
    13.   
    14.     /*删除字符设备和释放设备内存*/  
    15.     if(hello_dev) {  
    16.         cdev_del(&(hello_dev->dev));  
    17.         kfree(hello_dev);  
    18.     }          
    19.   
    20.     /*释放设备号*/  
    21.     unregister_chrdev_region(devno, 1);  
    22. }  


    1. MODULE_LICENSE("GPL");  
    2. MODULE_DESCRIPTION("First Android Driver");  
    3.   
    4. module_init(hello_init);  
    5. module_exit(hello_exit);  


    几个小知识
    1, struct file_operations
    该数据结构的主要作用就是把系统调用和驱动程序关联起来
    file_operations结构的每一个成员的名字,都对应着一个系统调用,
    用户进程利用系统调用,对设备文件进行诸如read/write操作时,系统调用通过设备文件的主设备号照相倒影的设备驱动程序,读取这个数据结构响应的函数指针
    接着将控制权交给该函数。
    这个是linux设备驱动程序工作的几本原理。
    编写设备驱动程序的主要工作就是编写子函数,并填充file_operations的各个域

    2,  alloc_chrdev_region 动态分配设备编号
    int  alloc_chrdev_region(dev_t *dev,unsigned int -firstminor,unsigned int -count,char *name) 函数原型
    该函数需要传递给它指定的第一次设备号firstminor(一般为0)和要分配的设备数(count) ,以及设备名(name),调用该函数后自动分配得到的设备号保存在dev中

    3, hello_dev = kmalloc(sizeof(struct hello_android_dev),GFP_KERNEL);
    void * kmalloc (size_t size, int flags) 函数原型
    设备 驱动程序 或者 内核 模块中动态开辟内存, 释放内存用的是kfree
    4, cdev_init
    函数原型
    void cdev_init(struct cdev *cdev, const struct file_operations *fops)
    {
       memset(cdev, 0, sizeof *cdev);//清空cdev的控件,也是后初始化内容赋空
       INIT_LIST_HEAD(&cdev->list);
       kobject_init(&cdev->kobj, &ktype_cdev_default);
       cdev->ops = fops;//连接fops
    5, 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);
    }
    6, cdev_del
    void cdev_del(struct cdev *p)
    {
       cdev_unmap(p->dev, p->count);
       kobject_put(&p->kobj);
    }


    字符设备的注册过程

    1,初始化cdev 分为静态和动态,上面使用的是静态
        也就是上面调用的
        cdev_init();
    2,初始化以后,需要将cdev添加到系统中去,调用cdev_add()函数,传入cdev结构体指针,起始设备编号,以及设备数(设备号范围)
    3,当一个字符设备驱动不再需要的时候,就可以cdev_del()函数来释放cdev占用的内存

    4,创建字符设备的class

    5,创建设备

    然后就是 加载,卸载的那些事了
    6,模块卸载,销毁设备类别和设备,删除字符设备和释放设备内存,释放设备号
    1. /*销毁设备类别和设备*/  
    2.     if(hello_class) {  
    3.         device_destroy(hello_class, MKDEV(hello_major, hello_minor));  
    4.         class_destroy(hello_class);  
    5.     }          
    6.   
    7.     /*删除字符设备和释放设备内存*/  
    8.     if(hello_dev) {  
    9.         cdev_del(&(hello_dev->dev));  
    10.         kfree(hello_dev);  
    11.     }          
    12.   
    13.     /*释放设备号*/  
    14.     unregister_chrdev_region(devno, 1);  



    http://blog.csdn.net/yangdelong/article/details/5493145
    感谢百度,感谢谷歌!

    你可能感兴趣的:(linux,驱动)