有足够的理由来说服自己来学习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