有足够的理由来说服自己来学习linux设备驱动!学习linux设备驱动,第一个就是helloworld驱动了,第二个应该是memdev这个驱动了。
今天通过学习国嵌的memdev这个设备驱动程序,简单的理解了下设备驱动程序运作过程,这个和前面的globalmem设备驱动是类似的。
我对源码有轻微的改动,如果学习的话,建议手动敲一遍代码,这样理解会深刻一些。
memdev.c
#include <linux/module.h> #include <linux/types.h> #include <linux/fs.h> #include <linux/errno.h> #include <linux/mm.h> #include <linux/sched.h> #include <linux/init.h> #include <linux/cdev.h> #include <asm/io.h> #include <linux/slab.h> //#include <asm/system.h> #include <asm/uaccess.h> #define MEMDEV_MAJOR 250 static mem_major=MEMDEV_MAJOR; struct cdev cdev; struct mem_dev { char *data; unsigned long size; }; struct mem_dev *mem_devp; // open function int mem_open(struct inode *inode,struct file *filp) { struct mem_dev *dev; int num=MINOR(inode->i_rdev); if(num>=2) return -ENODEV; dev=&mem_devp[num]; filp->private_data=dev; return 0; } //release function int mem_release(struct inode *inode,struct file *filp) { return 0; } //read function 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; struct mem_dev *dev=filp->private_data; if(p>=4096) return 0; if(count>4096-p) count=4096-p; if(copy_to_user(buf,(void *)(dev->data+p),count)) { ret=-EFAULT; } else { *ppos+=count; ret=count; printk(KERN_INFO "read %d bytes from %d\n",count,p); } return ret; } // write function 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>=4096) { return 0; } if(count > 4096-p) count=4096-p; if(copy_from_user(dev->data+p,buf,count)) ret=-EFAULT; else { *ppos+=count; ret=count; printk(KERN_INFO "written %d bytes from %d\n",count,p); } return ret; } //seek function static loff_t mem_llseek(struct file *filp,loff_t offset,int whence) { loff_t newpos; switch(whence) { case 0: newpos=offset; break; case 1: newpos=filp->f_pos+offset;break; case 2: newpos=4095+offset;break; default : return -EINVAL; } if((newpos<0)||(newpos>4096)) return -EINVAL; filp->f_pos=newpos; return newpos; } // 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, }; //init function static int memdev_init(void) { int result; dev_t devno=MKDEV(mem_major,0); //get major if(mem_major) { result=register_chrdev_region(devno,2,"memdev"); } else { result=alloc_chrdev_region(&devno,0,2,"memdev"); mem_major=MAJOR(devno); } if(result<0) return result; //init cdev cdev_init(&cdev,&mem_fops); cdev.owner=THIS_MODULE; //register memdev cdev_add(&cdev,MKDEV(mem_major,0),2); mem_devp=kmalloc(2*sizeof(struct mem_dev),GFP_KERNEL); if(!mem_devp) { result= -ENOMEM; goto fail_malloc; } memset(mem_devp,0,2*sizeof(struct mem_dev)); mem_devp[0].size=4096; mem_devp[0].data=kmalloc(4096,GFP_KERNEL); memset(mem_devp[0].data,0,4096); mem_devp[1].size=4096; mem_devp[1].data=kmalloc(4096,GFP_KERNEL); memset(mem_devp[1].data,0,4096); return 0; fail_malloc:unregister_chrdev_region(devno,1); return result; } //exit function static int memdev_exit(void) { cdev_del(&cdev); kfree(mem_devp); unregister_chrdev_region(MKDEV(mem_major,0),2); } module_init(memdev_init); module_exit(memdev_exit); MODULE_AUTHOR("by xzy 214"); MODULE_LICENSE("GPL");
Makefile如下
ifneq ($(KERNELRELEASE),) obj-m :=memdev.o else KERNELDIR:=/lib/modules/$(shell uname -r)/build PWD:=$(shell pwd) default: $(MAKE) -C $(KERNELDIR) M=$(PWD) modules clean: rm -rf *.o *.mod.c *.mod.o *.ko endif
测试该驱动的应用程序
app_mem.c,源码是用c库函数的文件操作来做的。我这里用系统调用来完成。
#include <unistd.h> #include <fcntl.h> #include <stdio.h> #include <string.h> int main() { int fd; char buf[4096]; strcpy(buf,"mem is char dev!!hello xzy"); printf("buf:%s\n",buf); fd=open("/dev/memdev0",O_RDWR); printf("fd: %d\n",fd); if(fd<0) { printf("open memdev0 error!\n"); return -1; } write(fd,buf,sizeof(buf)); lseek(fd,0,SEEK_SET); strcpy(buf,"buf is null"); printf("buf: %s\n",buf); read(fd,buf,sizeof(buf)); printf("buf: %s\n",buf); close(fd); return 0; }
驱动运行的过程:自己理解。应用程序来打开设备节点文件,这个设备节点文件是通过主设备号和驱动程序联系在一起的。打开这个设备文件的同时,在内核空间会相应的有一个关联的struct file结构和关于该设备的inode结构体,所以你必须要了解一下这两个结构,在file结构中保存着文件读写的信息,还有一个file_operation,这个可以认为是一个转化表,对应着驱动程序的一些文件操作的函数,这样就对应起来了,inode包括了设备号,这个在文件打开的时候,可以判断是次设备号。
系统调用open的执行过程:用户空间open,会执行的sys_open,这个就为打开的文件分配一个file结构体和从inode的节点中找到对应的file_operations
具体可以见:http://blog.csdn.net/xzyiverson/article/details/12676911
系统调用read的执行过程:先调用vfs_read,file->fop->read,我们可以看内核源码,我截下了一张图
上述内核源码见于read_write.c
了解了大致的过程,看看上面的memdev驱动程序,从init看起:
1.申请设备号2.初始化并添加cdev结构
然后就是该设备的一些操作函数的编写了,分析起来应该是不难的。
注明:出错误的地方,解决办法如下:
http://blog.csdn.net/xzyiverson/article/details/17315931
运行结果:
insmod memdev.ko
mknod memdev0 c 250 0
mknod memdev1 c 250 1