• 初入android驱动开发之字符设备(一)


    大学毕业,初入公司,招进去的是android驱动开发工程师的岗位,那时候刚进去,首先学到的就是如何搭建kernel、android的编译环境,然后就是了解如何刷设备以及一些最基本的工具。如adb、fastboot、grep、minicom、kermit、svn、git、eclispe、ndk等相关的知识,记得那时候很挫,过去很多东西都不懂。到了那,一周,都是熟悉使用ubuntu,然后了解刷机的流程,了解uboot、kernel、ramdisk、recovery、system的作用以及相关的框架,印象最深的是,就搞定刷机这个问题,都折腾了很久,原因之前的文章也说了,usb id 没有配好,因为android设备在开机状态和fastboot的模式下,usb id是不一样的。在开机状态下,可以通过adb shell 进入android系统,但一切换fastboot模式,就发现无法找到设备。

    当初,学习驱动开发的第一步,就是点亮一个LED灯,当然是基于android系统的,不是裸版上操作。正所谓初生牛犊不怕虎,先把百度,网上多的是例子,很高兴,马上copy一份代码,修改修改,试一试,编译通过,然后按着说明步骤,一步一步操作,发现insmod led.ko的时候,加载不成功,没办法,继续百度,搞了半天,没找到问题所在,然后尝试静态的编进去,别说,成功加载了,在/dev下找到自己的驱动,灯也亮了。当时,觉得完成任务,也没有多考虑什么,就向师父说,搞定了。就这样,一步一步的学下去,平台设备驱动模型,帧缓冲设备,输入子系统,中断,并态竞争,并开始慢慢解bug,调模块,UHF,nfc,rfid,电池,3G,音频,扫描头,wifi等一些,也许由于时间较紧,或者更可能也是因为得过且过,觉得在这家公司也能生存下去,对一些细节、原理性的东西并未深究。比如,内核层的数组越界表现为设备整个重启,JNI层数组越界,可能从andorid重启,app出现问题,表现应用挂掉。现在,事情不太多,想着把以前学的东西,从新梳理一下,并且深入的跟一下,毕竟不能浮于表面,应该多学习学习。但因中途电脑出现故障,所存资料全报废了,只能挑一些当时印象比较深刻的问题,重新学习一下。这次主要讲一下字符设备。

    1. 什么是字符设备?

    字符设备是 3 大类设备(字符设备、块设备和网络设备)中较简单的一类设备,提供连续的数据流,应用程序可以顺序读取,通常不支持随机存取。相反,此类设备支持按字节/字符来读写数据。

    其驱动程序中完成的主要工作是初始化、添加和删除 cdev 结构体,申请和释放设备号,以及填充file_operations 结构体中的操作函数,实现file_operations 结构体中的read()、write()和ioctl()等函数是驱动设计的主体工作。

    2. 字符设备的框架模型:

    (备注:此图片来源于:http://my.oschina.net/u/1169027/blog/191538)

    3. 字符设备的重要的数据结构

    3.1 一个简单的字符设备的例子:

    [cpp] view plain copy
     
    1. #include <linux/module.h>  
    2. #include <linux/kernel.h>  
    3. #include <linux/init.h>  
    4. #include <linux/input.h>  
    5. #include <linux/platform_device.h>  
    6. #include <linux/miscdevice.h>  
    7.   
    8.   
    9.   
    10. static int first_drv_open(struct inode *inode, struct file *file)  
    11. {  
    12.     printk("first_drv_open ");  
    13.     return 0;  
    14. }  
    15.   
    16. static int first_drv_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos)  
    17. {  
    18.     printk("first_drv_write ");  
    19.     return 0;  
    20. }  
    [cpp] view plain copy
     
    1. /*3. 这个结构是字符设备驱动程序的核心  
    2.  * 当应用程序操作设备文件时所调用的open、read、write等函数,  
    3.  * 最终会调用这个结构中指定的对应函数  
    [cpp] view plain copy
     
    1. static struct file_operations first_drv_fops = {  
    2.     .owner  =   THIS_MODULE,     
    3.     .open   =   first_drv_open,       
    4.     .write  =   first_drv_write,         
    5. };  
    6. static struct class *firstdrv_class;  
    7. static struct device *firstdrv_class_dev;  
    8. int major;  
    [cpp] view plain copy
     
    1. // 执行insmod命令时就会调用这个函数   
    [cpp] view plain copy
     
    1. static int first_drv_init(void)  
    2. {  
    3.     major = register_chrdev(0, "first_drv", &first_drv_fops);   
    4.     firstdrv_class = class_create(THIS_MODULE, "firstdrv");  
    5.     firstdrv_class_dev = device_create(firstdrv_class, NULL, MKDEV(major, 0), NULL, "myhello");   
    6.     printk("add ko,/dev/myhello  ");  
    7.     return 0;  
    8. }  
    [cpp] view plain copy
     
    1. /* 
    2.  * 执行rmmod命令时就会调用这个函数 ,注销函数,主要是释放你在注册是申请的资源,与你注册顺序相反,先注册的后释放。 
    3.  */  
    4. static void first_drv_exit(void)  
    5. {  
    6.     unregister_chrdev(major, "first_drv");   
    7.     device_unregister(firstdrv_class_dev);  
    8.     class_destroy(firstdrv_class);  
    9.     printk("del ko,  ");  
    10. }  
    [cpp] view plain copy
     
    1. //1. 我们一般从入口函数看起,先找到该字符设备的入口函数,  
    2. module_init(first_drv_init);  
    3. module_exit(first_drv_exit);  
    4. MODULE_LICENSE("GPL");  

     3.2. 主要看入口函数:

    在入口函数中,有3个主要的函数:几个重要的结构体:firstdrv_class,firstdrv_class_dev,cdev


    先看一下 字符设备注册函数:
    static inline int register_chrdev(unsigned int major, const char *name,const struct file_operations *fops)        
      参数说明:

       major:cdev的主设备号,此为0;  

      name:cdev的名称,此为 first_drv;

                      file_operation: cdev的文件操作接口,非常主要,一般为open、close、read、write、ioctl等,此只有open、write。


    看下此函数如何调下去的:
    __register_chrdev(major, 0, 256, name, fops);    这里可以看出,它把major的主设备号下的256个次设备号都归为此字符设备
       下面三个函数,是注册字符设备的3步:
       cd = __register_chrdev_region(major, baseminor, count, name);    
       cdev = cdev_alloc();    
       err = cdev_add(cdev, MKDEV(cd->major, baseminor), count);

     此中重要的结构体:

    struct cdev *cdev;    
    struct cdev {
    struct kobject kobj;//内嵌的kobject对象    
    struct module *owner;所属模块,通常为THIS_MODULE
    const struct file_operations *ops;//文件操作结构体 
    struct list_head list;
    dev_t dev; //设备号    
    unsigned int count;
    };

    然后看device_create,这是创建一个类,然后在类下创建一个设备,这个其实就是帮你在proc/ 和 dev/下创建设备节点,赋予相应的属性,

    我们跟下代码,看看是如何调用的:

    __class_create(struct module *owner, const char *name,struct lock_class_key *key)        
        __class_register(cls, key);    
        error = kset_register(&cp->subsys);    
        kobject_uevent(&k->kobj, KOBJ_ADD);    
        kobject_uevent_env(kobj, action, NULL);    
        /* environment buffer *//* 分配保存环境变量的内存 */
            env = kzalloc(sizeof(struct kobj_uevent_env), GFP_KERNEL);
            /* complete object path */
            devpath = kobject_get_path(kobj, GFP_KERNEL);
    /* default keys */  /*设置环境变量 */        
                retval = add_uevent_var(env, "ACTION=%s", action_string);
            retval = add_uevent_var(env, "DEVPATH=%s", devpath);
            retval = add_uevent_var(env, "SUBSYSTEM=%s", subsystem);
    /* 调用应用程序: 比如mdev */
            /* 启动脚本 echo /sbin/mdev > /proc/sys/kernel/
            * 设置了uevent_helper为“/sbin/mdev“
            */    
            argv [0] = uevent_helper;
             argv [1] = (char *)subsystem;
            argv [2] = NULL;
            retval = add_uevent_var(env, "HOME=/");
           retva=add_uevent_var(env,"PATH=/sbin:/bin:/usr/sbin:/usr/bin");
            retval = call_usermodehelper(argv[0], argv,env->envp, UMH_WAIT_EXEC);    

    再看一下设备创建:     
    device_create()
        device_destroy(struct class *cls, dev_t devt);    
        dev = class_find_device(class, NULL, &devt, __match_devt);  


    编译的Makefile 文件:

    # 下面这个很重要,指向你的内核路径,在编译完成后,会出现Module.symvers
    #在内核源码树根目录中,其中的Module.symvers文件就包含了内核所有的导出符号以及所有编译后模块的导出符号。
    #在编译内核时,根目录下会生成Module.symvers文件,它包含了内核以及编译后的模块导出的所有符号。对于每一个符号,相应的CRC校验值也被保存,
    #当内核编译选项CONFIG_MODVERSIONS关闭时,所有的CRC值都为0x00000000。
    #Module.symvers文件主要有以下用途:
    #1.列出vmlinux和所有模块的导出函数
    #2.列出所有符号的CRC校验值
    #若不指向,则insmod 模块时,会不成功。

    [cpp] view plain copy
     
    1. KERN_DIR = /home/yl/workplace/svn4.0/urovo-sq39/samsung_android_kernel_3.0/    
    [cpp] view plain copy
     
    1. all:  
    2.     make -C $(KERN_DIR) M=`pwd` modules   
    3. clean:  
    4.     make -C $(KERN_DIR) M=`pwd` modules clean  
    5.     rm -rf modules.order  
    6. obj-m   += first_drv.o  
    编译:

    只需要配置好内核的交叉编译环境即可,

    编译: make

    清除: make clean

    4、 字符设备驱动的加载、卸载、测试

    编译: 

    [cpp] view plain copy
     
    1. yl@yl-Lenovo:~/workplace/svn4.0/urovo-sq39/study$ make -j4  
    2. make -C /home/yl/workplace/svn4.0/urovo-sq39/samsung_android_kernel_3.0/ M=`pwd` modules   
    3. make[1]: Entering directory `/home/yl/workplace/svn4.0/urovo-sq39/samsung_android_kernel_3.0'  
    4. make[1]: warning: jobserver unavailable: using -j1.  Add `+' to parent make rule.  
    5.   CC [M]  /home/yl/workplace/svn4.0/urovo-sq39/study/first_drv.o  
    6.   Building modules, stage 2.  
    7.   MODPOST 1 modules  
    8.   CC      /home/yl/workplace/svn4.0/urovo-sq39/study/first_drv.mod.o  
    9.   LD [M]  /home/yl/workplace/svn4.0/urovo-sq39/study/first_drv.ko  
    10. make[1]: Leaving directory `/home/yl/workplace/svn4.0/urovo-sq39/samsung_android_kernel_3.0'  
    推送到设备的目录下,并加载设备:

    获得root权限

    [cpp] view plain copy
     
    1. yl@yl-Lenovo:~/workplace/svn4.0/urovo-sq39/study$ adb root  
    2. adbd is already running as root  

    加载驱动,并查看:

    [cpp] view plain copy
     
    1. yl@yl-Lenovo:~/workplace/svn4.0/urovo-sq39/study$ adb push first_drv.ko system/  
    2. 1288 KB/s (59300 bytes in 0.044s)  
    3. yl@yl-Lenovo:~/workplace/svn4.0/urovo-sq39/study$ adb shell  
    4. root@android:/ # insmod system/first_drv.ko                                      
    5. root@android:/ # ls dev/myhello -l                                               
    6. crw------- root     root     248,   0 2015-08-04 15:31 myhello   

    查看kernel日志,adb shell cat proc/kmsg ,会发现加载驱动时,打印的log。

    这一部分,主要涉及到一些基本的命令,如adb push ,adb pull,adb root,

    或 lsmod 查看系统加载的动态模块

        insmod 加载模块

        rmmod  删除模块

    至于如何编译android文件系统下的字符设备测试程序,字符设备的高阶写法,如具体去操作某一个设备(led、按键),加入中断、并发、定时器等,留在下一编讲解,不过,字符设备的框架基本这样:

    1. 分配
    2. 设置
    3. 注册
    4. 硬件相关的代码




    版权声明:本文为博主原创文章,未经博主允许不得转载。
  • 相关阅读:
    细心也是一种态度
    EDM数据访问的三种方式
    如何快速提交网站备案 ICP备案
    c# winform 关于给静态全局变量赋值的问题
    c#DIY随机数类winform 2010
    手把手教你如何用IIS搭建手机WAP网站(图文)
    admin密码对应的MD5值,16位和32位,admin解密自己留着方便.
    C#实现MD5加密,winform c#2005
    最全的c#日期函数 winform
    如何解决因网站备案号没下来而网站被迫关闭的办法
  • 原文地址:https://www.cnblogs.com/wanghuaijun/p/7732445.html
Copyright © 2020-2023  润新知