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

也感谢以下文章
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);  



感谢百度,感谢谷歌!
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值