驱动学习1

发布者:码农创想家最新更新时间:2015-05-04 来源: 51hei关键字:驱动学习 手机看文章 扫描二维码
随时随地手机看文章
看了好长一段时间的书,开始做驱动,从简单的开始学习,实现最简单的字符设备驱动,实现的功能是对两块内存空间(物理内存)进行控制,实现读写操作。
基本的实现与很多学习驱动程序设计的方式一样,跟着别人的程序跑,自己弄明白,搞清楚其中的道理。具体的实现过程,结合注释可以看清楚。主要总结第一次写驱动过程中存在的问题,以及相应的解决方法。
字符设备驱动的基本流程:
首先,驱动函数的初始化,以及清除函数(这部分主要是按着一定的模式编程)
    1、 申请主设备号,其中可以采用静态或者动态的方式申请。
    2、 创建字符设备,其中包括初始化字符设备(分配设备空间),绑定相关的操作(相应的操作),最后是添加字符设备(实现所申请的设备号与设备的绑定)。
    3、 设备的具体实现以及错误的处理问题。
    4、 清除函数主要包含分配物理空间的释放以及设备号的释放,字符设备的注销等。
第二,主要是设备相关操作的具体实现过程。(这部分主要是涉及内核的一些知识)包含具体的文件打开,关闭,以及读写,定位操作。具体的参看源码。
第三,Makefile的基本实现(也是一定的模块,但是能够了解其中的道理)或者直接编译到内核中。
 
出现的问题主要是Makefile文档的设计以及加载问题。
问题一,是kmalloc函数等的运用需要相应的头文件,kmalloc的头文件是linux/slab.h,关于错误(errno.h)是与CPU体系密切相关的,因此通常是asm/errno.h,这个也是常见的问题。
问题二,是加载出错的问题。出现下面的错误,
insmod:error inserting '/home/gong/program/cprogram/module/memdev/memdev.ko': -1 Device or resource busy
这个错误产生的原因主要是因为主设备号真正被别的驱动程序使用导致的。可以通过cat /proc/devices查看具体的情况
...
251 hidraw
252 usbmon
253 bsg
254 rtc
...
需要重新定义主设备号。然后重新编译,即可加载成功。
 
问题三,Makefile文件的设计
编译驱动有两种方法,可以采用直接内核编译的方式,这种方式主要通过修改源码中的Makefile和Kconfig实现。这种方法比较直观,但是会破坏源码的完整性。
通常采用自己编写Makefile的方式实现驱动的编译。这种方式需要了解Makefile的一些语法,同时需要了解自己的源码结构,但是这种模式具有很大的相似性,都是基于模块的设计方式。
常用的Makefile模块如下所示:
 
ifneq ($(KERNELRELEASE),)
obj-m   := memdev.o
else
KDIR    :=/opt/LinuxKernel/linux-2.6.38.1
PWD      :=$(shell pwd)
default:
        make -C $(KDIR) M=$(PWD) modules
clean:
        rm -f *.ko *.o *.mod.o *.mod.c *.symvers
endif
 
具体的分析我说说自己的理解吧。首先第一句ifneq ($(KERNELRELEASE),)是实现两个变量的比较。比较两个参量是否不相等。如果不相等则执行下面的语句:obj-m   := memdev.o,如果相等就执行else后面的语句。
 
具体的过程如下:
如果驱动代码是在内核源码中实现,那么KERNELRELEASE就是一个已知的值,具体的值是:
# Read KERNELRELEASE from include/config/kernel.release (if it exists)
KERNELRELEASE = $(shell cat include/config/kernel.release 2> /dev/null)
KERNELVERSION = $(VERSION).$(PATCHLEVEL).$(SUBLEVEL)$(EXTRAVERSION)
但是另一个值为空,这样两个值就是不相等的,这样就执行obj-m   := memdev.o。
但是如果不是在内核源码中实现,那么KERNELRELEASE就是一个空值,这样两个参数就是相等的。执行else以后的语句。
KDIR    :=/opt/LinuxKernel/linux-2.6.38.1
PWD      :=$(shell pwd)
上面的两句是定义两个变量,其中的KDIR是内核源码的根目录,PWD则是当前驱动代码所在的目录。
然后执行
default:
        make -C $(KDIR) M=$(PWD) modules
default是一个默认的目标,没有相关的依赖,规则是make -C $(KDIR) M=$(PWD) modules
其中参数-C和M=都有各自的意义,具体的可以参看Makefile的相关资料。
我的理解就是:
-C 选项的作用是指将当前工作目录转移到你所指定的位置(页就是内核源码的根目录)
“M=dir”,内核程序会自动到你所指定的dir目录中查找模块源码,将其编译,生成.KO文件。实现驱动程序的编译。
具体的注意事项:KERNELRELEASE这个变量不要写错,这个比较容易出错,还容易检查出来。我第一次写代码就出现了相应的错误。
 
需要注意的是该makefile被执行了两次,其中第一次是由于没有定义KERNELRELEASE对象,当进入到内核源码以后,这时KERNELRELEASE对象已经被定义好了,然后返回到当前的文件夹下,这时候直接执行第一句obj-m   := memdev.o。这样就实现了内核的编译。
 
具体的应用程序代码我都是按着别人的代码修改调试的。
下面是我的源码:
 
    第一个代码是头文件:
    #ifndef _MEMDEV_H_H_
    #define _MEMDEV_H_H_
    #ifndef MEMDEV_MAJOR
    #define MEMDEV_MAJOR 555
    #endif
    #ifndef MEMDEV_NR_DEVS
    #define MEMDEV_NR_DEVS 2
    #endif
    #ifndef MEMDEV_SIZE
    #define MEMDEV_SIZE 2048
    #endif
 
    struct mem_dev
    {
            char *data;
            unsigned long size;
    };
    #endif
    第二个是C文件:
    #include
    #include
    #include
    //#include
    #include
    #include
    #include
    /*该头文件主要实现内存的控制,包括kmalloc等函数*/
    #include
    #include
    #include
    #include
    /*错误的不同,需要具体的类型*/
    #include
    /*自己定义的头文件*/
    #include"memdev.h"
    static mem_major = MEMDEV_MAJOR;
    module_param(mem_major,int,S_IRUGO);
    module_param(mem_major,int,S_IRUGO);
    struct mem_dev *mem_devp;
    struct cdev cdev;
    /*函数的声明*/
    static int memdev_init(void);
    static void memdev_exit(void);
    int mem_open(struct inode *inode,struct file *filp);
    int mem_release(struct inode *inode, struct file *flip);
    static ssize_t mem_read(struct file *flip,char __user *buf, size_t size,loff_t *ppos);
    static ssize_t mem_write(struct file *filp,const char __user *buf,size_t size,loff_t *ppos);
    static loff_t mem_llseek(struct file *filp,loff_t offset, int whence);
    /*添加该模块的基本文件操作支持*/
    static const struct file_operations mem_fops =
    {
            .owner = THIS_MODULE,
            .llseek = mem_llseek,
            .read = mem_read,
           .write = mem_write,
            .open = mem_open,
            .release = mem_release,
    };
    /*初始化函数*/
    static int memdev_init(void)
    {
            int result;
            int i;
            /*创建一个设备号*/
            dev_t devno = MKDEV(mem_major,0);
            /*注册一个设备号*/
            /*如果定义了主设备号采用静态申请的方式*/
            if(mem_major)
                    result = register_chrdev_region(devno,2,"mem_dev");
            else/*动态申请设备号*/
            {
                    result = alloc_chrdev_region(&devno,0,2,"mem_dev");
                    mem_major = MAJOR(result);
            }
            /*错误处理*/
            if(result < 0)
                    return result;
            /*创建一个设备*/
            /*初始化cdev,并将相关的文件操作添加进来*/
            cdev_init(&cdev,&mem_fops);
            cdev.owner = THIS_MODULE;
            cdev.ops = &mem_fops;
            /*注册字符设备*/
            cdev_add(&cdev,MKDEV(mem_major,0),MEMDEV_NR_DEVS);
            /*分配两个内存空间,此处是在物理内存上实现分配,实质是创建两个设备*/
            mem_devp = kmalloc(MEMDEV_NR_DEVS * sizeof(struct mem_dev),GFP_KERNEL);
            if(!mem_devp)/*出错的相应操作*/
            {
                    result = -ENOMEM;
                    /*错误处理,采用典型的goto语句*/
                    goto fail_malloc;
     }[page]
            /*清除空间*/
            memset(mem_devp,0,sizeof(struct mem_dev));
            for(i = 0; i < MEMDEV_NR_DEVS; ++i)
            {
                    mem_devp[i].size = MEMDEV_SIZE;
                    mem_devp[i].data = kmalloc(MEMDEV_SIZE,GFP_KERNEL);
                    /*问题,没有进行错误的控制*/
                    memset(mem_devp[i].data,0,MEMDEV_SIZE);
            }
            return 0;
    fail_malloc:
            unregister_chrdev_region(devno,1);
            return result;
    }
    /*模块清除函数*/
    static void memdev_exit(void)
    {
            cdev_del(&cdev);/*注销字符设备*/
            /*释放两个物理内存*/
            kfree(mem_devp[0].data);
            kfree(mem_devp[1].data);
            kfree(mem_devp);/*释放设备结构体内存*/
            unregister_chrdev_region(MKDEV(mem_major,0),2);
    }
    /*定义相关的操作函数*/
    /*mem_open*/
    int mem_open(struct inode *inode,struct file *filp)
    {
            struct mem_dev *dev;
            /*判断设备文件的次设备号*/
            int num = MINOR(inode->i_rdev);
            if(num >= MEMDEV_NR_DEVS)
                    return -ENODEV;
            dev = &mem_devp[num];
            /*将数据指向两个内存空间*/
            filp->private_data = dev;
            return 0;
    }
    /*release函数的实现*/
    int mem_release(struct inode *inode, struct file *flip)
    {
            return 0;
    }
    /*read函数的实现*/
    static ssize_t mem_read(struct file *filp,char __user *buf, size_t size,loff_t *ppos)
    {
            unsigned long p = *ppos;
            unsigned int count = size;
            int ret = 0;
            /*获得file结构体上的指针*/
            struct mem_dev *dev = filp->private_data;
            /*参数的检查*/
            if(p >= MEMDEV_SIZE)
                    return 0;
            if(count > MEMDEV_SIZE - p)
                    count = MEMDEV_SIZE - p;
            /*从内核读数据到用户空间*/
            if(copy_to_user(buf,(void *)(dev->data + p),count))
            {
                    /*出错误*/
                    ret = -EFAULT;
            }
            else
            {
                    /*移动当前文件光标的位置*/
                    *ppos += count;
                    ret = count;
                    printk(KERN_INFO "read %d bytes(s) from %d ",count,p);
        }
            return ret;
    }
    /*write函数的实现*/
    static ssize_t mem_write(struct file *filp,const char __user *buf,size_t size,loff_t *ppos)
    {
            unsigned long p = *ppos;
            unsigned int count = size;
            int ret = 0;
            /*获得设备结构体的指针*/
            struct mem_dev *dev = filp->private_data;
            /*检查参数的长度*/
            if(p >= MEMDEV_SIZE)
                    return 0;
            if(count > MEMDEV_SIZE - p)
                    count = MEMDEV_SIZE - p;
            if(copy_from_user(dev->data + p,buf,count))
                    ret = -EFAULT;
            else
            {
                    /*改变文件位置*/
                    *ppos += count;
                    ret = count;
                    printk(KERN_INFO "writted %d bytes(s) from %d ",count,p);
            }
            return ret;
    }
    /*lseek的实现*/
    static loff_t mem_llseek(struct file *filp,loff_t offset, int whence)
    {
            loff_t newpos;
            switch(whence)
            {
                    case 0:/*SEEK_SET*/
                            newpos = offset;
                            break;
                    case 1:/*SEEK_CUR*/
                            newpos = filp->f_pos + offset;
                            break;
                    case 2:/*SEEK_END*/
                            newpos = MEMDEV_SIZE - 1 + offset;
                            break;
                    default:
                            return -EINVAL;
            }
            filp->f_pos = newpos;
            return newpos;
    }
    /*作者以及权限的声明*/
    MODULE_AUTHOR("GongPing");
    MODULE_LICENSE("GPL");
    /*通过宏module_init和module_exit实现模块添加*/
    module_init(memdev_init);
    module_exit(memdev_exit);
 
    第三个是Makefile:
    ifneq ($(KERNELRELEASE),)
    obj-m := memdev.o
    else
    #KDIR :=/usr/src/kernels/2.6.35.14-96.fc14.i686
    #KDIR :=/lib/modules/2.6.35.14-96.fc14.i686/build
    #KDIR ?=/opt/LinuxKernel/linux-2.6.38.1
    KDIR :=/opt/LinuxKernel/linux-2.6.38.1
    PWD :=$(shell pwd)
    default:
            make -C $(KDIR) M=$(PWD) modules
    clean:
            rm -f *.ko *.o *.mod.o *.mod.c *.symvers
    endif
    第4个是应用程序:
    #include
    #include
    #include
    int main()
    {
            FILE *fp0 = NULL;
            char Buf[4096];
            /*复制数据到buf中*/
            strcpy(Buf,"Mem is char devices!");
            printf("Buf: %s ",Buf);
            /*打开设备文件*/
            fp0 = fopen("/dev/memdev0","rw");
            /*错误处理*/
            if(fp0 == NULL)
            {
                    printf("Open Memdev0 Error! ");
                    return -1;
            }
            /*写数据到设备中*/
            //fread(Buf,sizeof(Buf),1,fp0);
            fwrite(Buf,sizeof(Buf),1,fp0);
            /*定位*/
            fseek(fp0,0,SEEK_SET);
            /*复制数据到Buf*/
            strcpy(Buf,"Buf is NULL!");
            /*打印*/
            printf("Buf: %s ",Buf);
            /*读数据*/
            fread(Buf,sizeof(Buf),1,fp0);
            printf("BUF: %s ",Buf);
            fclose(fp0);
            return 0;
    }
 
源码是别人的,我只是做了一下注释,然后做了适当的修改,驱动程序与应用程序以及硬件代码存在较大的差别,以后要多写,多理解,多总结。
关键字:驱动学习 引用地址:驱动学习1

上一篇:C语言数组分析
下一篇:Linux驱动总结3

推荐阅读最新更新时间:2024-03-16 14:00

07-S3C2440驱动学习(一)嵌入式linux字符设备驱动-按键驱动程序
一、异步通知机制 从按键的实现方式来说,可以分为以下几种方式 查询方式,极度耗费CPU资源 中断方式,平时休眠,按键按下,唤醒休眠 poll机制,不需要一直read,根据poll返回值来决定是否read 以上都是应用程序主动去read。 下面来介绍异步通知实现按键,由驱动程序,提醒应用程序有按键按下了,告诉应用程序现在需要去read了。 1测试应用程序,命令行发信号 (1)进程间发信号如何实现呢: 我们可以通过ps查看某个应用程序的PID是多少。然后执行: Kill -9 PID来杀死这个应用程序进程。当然我们可以自定义一些信号类型,发送给应用程序,执行相应的方法。 (2)我们写一个测试应用程序,然后给
[单片机]
07-S3C2440<font color='red'>驱动</font><font color='red'>学习</font>(一)嵌入式linux字符设备<font color='red'>驱动</font>-按键<font color='red'>驱动</font>程序
OK6410A学习笔记二:嵌入式Linux驱动开发环境的配置和测试
环境配置: Window7 32-bit OS + VMware Workstation 6.5 + Ubuntu9.10 FORLINX OK6410A开发板 嵌入式Linux驱动开发环境的搭建 1. 安装arm-linux-gcc交叉编译器,并设置环境变量 2. 将飞凌提供的FORLINX_linux-3.0.1.tar.gz拷贝到/usr/src,解压并进入linux-3.0.1目录 3. 执行以下命令:#make oldconfig; #make prepare; 这两个命令用来测试交叉编译器和驱动开发需要的源文件包是否能正常使用,可能会出现提示arm-linux-gcc找
[单片机]
OK6410A<font color='red'>学习</font>笔记二:嵌入式Linux<font color='red'>驱动</font>开发环境的配置和测试
小广播
添点儿料...
无论热点新闻、行业分析、技术干货……
设计资源 培训 开发板 精华推荐

最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

换一换 更多 相关热搜器件
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved