一个简单的字符设备驱动

1.字符设备驱动源码

#include<linux/module.h>
#include<linux/init.h>
#include<linux/types.h>
#include<linux/fs.h>
#include<linux/errno.h>
#include<linux/mm.h>
#include<linux/sched.h>
#include<linux/cdev.h>
#include<asm/io.h>
#include<asm/system.h>
#include<asm/uaccess.h>

#include<linux/slab.h> /*kmalloc头文件*/
#include<linux/semaphore.h>/*信号量头文件*/

#define MEMDEV_MAJOR 251 /*预设的mem的主设备号*/
#define MEMDEV_NUM 2     /*设备数*/
#define MEMDEV_SIZE 1024 /*分配的内存大小*/

struct mem_dev
{
	unsigned int size;
	char *data;
	struct semaphore sem;
};

static int mem_major = MEMDEV_MAJOR; /*预设的mem的主设备号*/
struct cdev mem_cdev;
struct mem_dev *mem_devp; /*设备结构体指针*/

/*文件打开函数*/
static int
mem_open(struct inode *inode,struct file *filp)
{
	struct mem_dev *dev;
	unsigned int num;
	printk("mem_open.\n");

	num= MINOR(inode->i_rdev); /*获得次设备号*/
	if(num>(MEMDEV_NUM-1))
	return -ENODEV;
	
	dev = &mem_devp[num];
	filp->private_data = dev; /*将设备结构保存为私有数据*/
	
	return 0;
}

/*关闭时调用*/
static int
mem_release(struct inode *inode,struct file *filp)
{
	printk("mem_release.\n");
	return 0;
}

/*读函数*/
static ssize_t
mem_read(struct file *filp,char __user *buf,size_t size,loff_t *ppos)
{
	int ret = 0;
	struct mem_dev *dev;
	unsigned long p;
	unsigned long count;
	
	printk("mem_read.\n");
	
	dev = filp->private_data; /*获得设备结构*/
	count = size;
	p = *ppos;
	
	/*检查偏移量和数据大小的有效性*/
	if(p > MEMDEV_SIZE)
		return 0;
	if(count >(MEMDEV_SIZE-p))
 		count = MEMDEV_SIZE - p;
	if(down_interruptible(&dev->sem)) /*锁定互斥信号量*/
		return -ERESTARTSYS;
	/*读取数据到用户空间*/
	if(copy_to_user(buf,dev->data+p,count))
	{
		ret = -EFAULT;
		printk("copyfrom user failed\n");
	}
	else
	{
		*ppos +=count;
		ret = count;
		printk("read %d bytes from dev\n",count);
	}
		up(&dev->sem); /*解锁互斥信号量*/
		return ret;
}

/*写函数*/
static ssize_t
mem_write(struct file *filp,const char __user *buf,size_t size,loff_t *ppos)
{
	int ret = 0;
	struct mem_dev *dev;
	unsigned long p;
	unsigned long count;
	
	printk("mem_write.\n");
	dev = filp->private_data;
	count = size;
	
	p = *ppos;
	if(p>MEMDEV_SIZE)
	{
		return 0;
	}
	if(count >(MEMDEV_SIZE- p))
	count = MEMDEV_SIZE - p;
	if(down_interruptible(&dev->sem))
		return -ERESTARTSYS;
	if(copy_from_user(dev->data+p,buf,count))
	{
		ret = -EFAULT;
		printk("copyfrom user failed\n");
	}
	else
	{
		*ppos+=count;
		ret = count;
		printk("writed %d bytes to dev\n",count);
	}
	up(&dev->sem);

	return ret;
}

/*修改文件当前的读写位置*/
static loff_t
mem_llseek(struct file *filp,loff_t offset,int whence)
{
	int newpos;
	printk("mem_llsek.\n");
	switch(whence)
	{
		case 0 :
			newpos =offset;
		 	break;
		case 1:
			newpos = filp->f_pos +offset;
			break;
		case 2:
			newpos = MEMDEV_SIZE - 1 +offset;
			break;
		default :
			return -EINVAL;
	}
	if((newpos<0)||(newpos>(MEMDEV_SIZE - 1)))
		return -EINVAL;
	filp->f_pos = newpos;
	return newpos;
}

/*文件操作结构体*/
static const struct file_operations mem_fops ={
	.owner = THIS_MODULE,
	.open =mem_open,
	.write =mem_write,
	.read = mem_read,
	.release = mem_release,
	.llseek=mem_llseek,
	};

/*设备驱动模型加载函数*/
static int
__init memdev_init(void)
{
	int result;
	int err;
	int i;
	
	/*申请设备号*/
	dev_t devno = MKDEV(mem_major,0);
	
	if(mem_major)
	/*注意静态申请的dev_t参数和动态dev_t参数的区别*/
	result = register_chrdev_region(devno,MEMDEV_NUM,"memdev");
	else
	{
		result = alloc_chrdev_region(&devno,0,MEMDEV_NUM,"memdev");
		mem_major = MAJOR(devno);
	}
	if(result<0)
	{
		printk("can not get major devno:%d\n",mem_major);
		return result;
	}

	/*注册设备驱动*/
	cdev_init(&mem_cdev,&mem_fops);/*初始化cdev结构*/

	mem_cdev.owner = THIS_MODULE;

	/*注册字符驱动设备*/
	err = cdev_add(&mem_cdev,MKDEV(mem_major,0),MEMDEV_NUM);
	if(err)
		printk("addcdev faild,err is %d\n",err);

	/*分配设备内存*/
	mem_devp = kmalloc(MEMDEV_NUM*(sizeof(struct mem_dev)),GFP_KERNEL);
	if(!mem_devp)
	{
		result = -ENOMEM;
		goto fail_malloc;
	}

	memset(mem_devp,0,MEMDEV_NUM*(sizeof(struct mem_dev)));
	
	for(i=0;i<MEMDEV_NUM;i++)
	{
		mem_devp[i].size = MEMDEV_SIZE;
		mem_devp[i].data = kmalloc(MEMDEV_SIZE,GFP_KERNEL);
		memset(mem_devp[i].data,0,MEMDEV_SIZE);
	//	init_MUTEX(&mem_devp[i].sem); /*初始化互斥锁*/
		sema_init(&mem_devp[i].sem,1);
	}

	return result;

fail_malloc:
	unregister_chrdev_region(MKDEV(mem_major,0),MEMDEV_NUM);
	return result;
}


/*设备驱动模型卸载函数*/
static void
memdev_exit(void)
{
	cdev_del(&mem_cdev);
	/*注意释放的设备号个数一定要和申请的设备号个数一致,否则会导致设备号资源流失*/
	unregister_chrdev_region(MKDEV(mem_major,0),MEMDEV_NUM);
	printk("memdev_exit\n");
}

module_init(memdev_init);
module_exit(memdev_exit);

MODULE_AUTHOR("BQL");
MODULE_LICENSE("GPL");


1)设备编号的内部表示

在内核中,dev_t类型(<linux/types.h>中定义)用来持有设备编号--主次部分都包括.对于2.6.0

,dev_t32位的量,12位用作主编号,20位用作次编号.当然,对于设备编号的内部组织从不做任何假设;相反,应当利用在<linux/kdev_t.h>中的一套宏定义.为获得一个dev_t的主

或者次编号,使用:

MAJOR(dev_t dev);

MINOR(dev_t dev);

相反,如果你有主次编号,需要将其转换为一个dev_t,使用:

MKDEV(int major, int minor);

注意,2.6内核能容纳有大量设备,而以前的内核版本限制在255个主编号和255个次编号.有人认

为这么宽的范围在很长时间内是足够的,但是计算领域被这个特性的错误假设搞乱了.因此你应当

希望dev_t的格式将来可能再次改变;但是,如果你仔细编写你的驱动,这些变化不会是一个问题.

2)分配和释放设备编号

在建立一个字符驱动时你的驱动需要做的第一件事是获取一个或多个设备编号来使用.为此目的必要的函数是register_chrdev_region,<linux/fs.h>中声明:


int register_chrdev_region(dev_t first,unsigned int count, char *name);


first是你要分配的起始设备编号.first的次编号部分常常是0,但是没有要求是那个效果.

count是你请求的连续设备编号的总数.注意,如果count太大,你要求的范围可能溢出到下一个次编号;

但是只要你要求的编号范围可用,一切都仍然会正确工作.

name是应当连接到这个编号范围的设备的名子;它会出现在/proc/devicessysfs.


如同大部分内核函数,如果分配成功进行,register_chrdev_region的返回值是0.出错的情况下,返回

一个负的错误码,你不能存取请求的区域.

如果你确实事先知道你需要哪个设备编号,register_chrdev_region工作得好.然而,你常常不会知道

你的设备使用哪个主编号;

Linux内核开发社团中一直努力使用动态分配设备编号.内核会乐于动态为你分配一个主编号,但是你必须使用一个不同的函数来请求这个分配.


int alloc_chrdev_region(dev_t *dev,unsigned int firstminor, unsigned int count, char *name);


使用这个函数,

dev是一个只输出的参数,它在函数成功完成时持有你的分配范围的第一个数.

firstminor应当是请求的第一个要用的次编号;它常常是0. count name参数如同给

request_chrdev_region的一样.

不管你任何分配你的设备编号,你应当在不再使用它们时释放它.设备编号的释放使用:


void unregister_chrdev_region(dev_tfirst, unsigned int count);


调用unregister_chrdev_region的地方常常是你的模块的cleanup函数.

上面的函数分配设备编号给你的驱动使用,但是它们不告诉内核你实际上会对这些编号做什么.在用户空间程序能够存取这些设备号中一个之前,你的驱动需要连接它们到它的实现设备操作的内部函数上.我们将描述如何简短完成这个连接,但首先顾及一些必要的枝节问题.

(3)主编号的动态分配

对于新驱动,强烈建议使用动态分配来获取你的主设备编号,而不是随机选取一个当

前空闲的编号.

换句话说,你的驱动应当几乎肯定地使用alloc_chrdev_region,不是register_chrdev_region.

动态分配的缺点是你无法提前创建设备节点,因为分配给你的模块的主编号会变化.对于驱动的正常使用,这不是问题,因为一旦编号分配了,你可从/proc/devices中读取它.

为使用动态主编号来加载一个驱动,因此,可使用一个简单的脚本来代替调用insmod,在调用

insmod,读取/proc/devices来创建特殊文件.

我的开发板上的/proc/devices文件如下:

cat /proc/devices

Character devices:

1 mem

2 pty

3 ttyp

4 /dev/vc/0

4 tty

5 /dev/tty

5 /dev/console

5 /dev/ptmx

7 vcs

10 misc

13 input

21 sg

29 fb

89 i2c

90 mtd

108 ppp

128 ptm

136 pts

180 usb

188 ttyUSB

189 usb_device

204 ttySAC

248 ALPU-MP-900

251 memdev

252 ubi1

253 ubi0

254 rtc

Block devices:

259 blkext

7 loop

8 sd

31 mtdblock

65 sd

66 sd

因此加载一个已经安排了一个动态编号的模块的脚本,可以使用一个工具来编写,awk , 来从/

proc/devices获取信息以创建/dev中的文件.

下面的脚本,test_load.以模块发布的驱动的用户可以从系统的rc.local文件

中调用这样一个脚本,或者在需要模块时手工调用它.

#!/bin/sh

module="memchar"

device="memdev"

# mode="664"

# invoke insmod with all arguments wegot

# and use a pathname, as newer modutilsdon't look in . by default

/sbin/insmod ./$module.ko $* || exit 1

# remove stale nodes

major=$(awk -F: '$2=="$module" {print $1}'/proc/devices)(有些问题,还是手动添加的)

rm -f /dev/${device}[0-1]

mknod /dev/${device}0 c $major 0

mknod /dev/${device}1 c $major 1


获取主编号的代码如下:

/*申请设备号*/

dev_t devno = MKDEV(mem_major,0);

if(mem_major)

/*注意静态申请的dev_t参数和动态dev_t参数的区别*/

result =register_chrdev_region(devno,MEMDEV_NUM,"memdev");

else

{

result =alloc_chrdev_region(&devno,0,MEMDEV_NUM,"memdev");

mem_major = MAJOR(devno);

}

if(result<0)

{

printk("can not get majordevno:%d\n",mem_major);

return result;

}


2.重要的数据结构

点击打开链接

你可能感兴趣的:(一个简单的字符设备驱动)