驱动入门:一个简单的字符设备驱动

原文:驱动入门:一个简单的字符设备驱动

我按照这几天在视频里从韦老师那儿学到的方法,讲一下写简单字符设备的流程,以在书上看到的 globalmem 这样的一个虚拟设备为例。这个设备的功能是在内核空间里分配 4K 字节的内存,在驱动中提供如何访问和操作这块内存的函数,以供用户空间的进程通过系统调用来访问这块内存。我们可以把它看成是最大容量为 4K 的普通文件。我们要能够像打开普通文件一样打开它,读取其中的内容,或向其中写入数据,定位到文件的某个位置处,清空其中的内容,然后关闭打开的文件。我们的应用程序只需要通过系统调用 open() read() write() lseek() ioctl() release() 。而不用去管它是一个普通文件还是一个字符设备。
第一步、包含进文件中所需的头文件,和宏定义,声明全局变量,并定义一个设备结构体。
#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 <asm/system.h>
#include <asm/uaccess.h>

#define GLOBALMEM_SIZE 0x1000
/*全局内存最大4K字节*/
#define MEM_CLEAR 0x1
/*清0全局内存*/
#define GLOBALMEM_MAJOR 254
/*预设的globalmem的主设备号*/

static int globalmem_major = GLOBALMEM_MAJOR;
/*globalmem设备结构体*/
struct globalmem_dev
{
	struct cdev cdev; /*cdev结构体*/
	unsigned char mem[GLOBALMEM_SIZE]; /*全局内存*/
};
 
第二步、写出驱动框架。 

static const struct file_operations globalmem_fops =
{
	.owner = THIS_MODULE,
	.llseek = globalmem_llseek,
	.read = globalmem_read,
	.write = globalmem_write,
	.ioctl = globalmem_ioctl,
	.open = globalmem_open,
	.release = globalmem_release,
};
 
file_operations 
结构体里面有很多的函数,但并非要实现其中所有的成员函数。要根据实际的需要向 
file_operations 
里添加成员函数,这里实现 
6 
个函数。 
 

第三步、分别实现file_operations里的每个函数。
/*文件打开函数*/
int globalmem_open(struct inode *inode, struct file *filp){
	return 0;
/*这里只是为了讲解大概的流程,不做其他的工作,直接返回*/
}
/*文件释放函数*/
int globalmem_release(struct inode *inode, struct file *filp)
{
	return 0;
/*这里只是为了讲解大概的流程,不做其他的工作,直接返回*/
}

/*读函数,读取其中的内容并返回读取的大小*/
static ssize_t globalmem_read(struct file *filp, char __user *buf, size_t size,loff_t *ppos)
{
	unsigned long p =*ppos;
	/*当前指针所在的位置*/
	unsigned int count = size;
	/*要读取的大小*/
	int ret = 0;
	/*分析和获取有效的写长度*/
	if (p >= GLOBALMEM_SIZE) 
	/*如果当前指针已经到设备最尾端*/
		return count ?- ENXIO: 0;
	/*要读取的大小不为0,则返回出错信息*/
	if (count > GLOBALMEM_SIZE - p) /*如果还有可读取的数据不够conut大小*/
		count = GLOBALMEM_SIZE - p; /*返回conut个数据*/
	/*内核空间->用户空间*/
	if (copy_to_user(buf, (void*)(dev->mem + p), count)) /*用户的地址空间和内核空间的不能直接传输数据,必须通过copy_to_user 
	copy_from_user来在两个地址空间中传递数据 */
	{
		ret =- EFAULT;
	}
	else
	{
		*ppos += count;
		ret = count;
		printk(KERN_INFO "read %d bytes(s) from %d\n", count, p);
	}
	return ret;
}
/*写函数*/
static ssize_t globalmem_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;
	/*分析和获取有效的写长度*/
	if (p >= GLOBALMEM_SIZE) 
	/*没有可写的空间了*/
		return count ?- ENXIO: 0;
	/*如果要写非零个数据,则返回出错信息*/
	if (count > GLOBALMEM_SIZE - p)
		count = GLOBALMEM_SIZE - p;
	/*用户空间->内核空间*/
	if (copy_from_user(dev->mem + p, buf, count))
		ret =- EFAULT;
	else
	{
		*ppos += count;
		ret = count;
		printk(KERN_INFO "written %d bytes(s) from %d\n", count, p);
	}
	return ret;
}
/* seek文件定位函数 */
static loff_t globalmem_llseek(struct file *filp, loff_t offset, int orig)
{
	loff_t ret = 0;
	switch (orig)
	{
	case 0:
		/*相对文件开始位置偏移*/
		if (offset < 0)
		{
			ret =- EINVAL;
			break;
		}
		if ((unsigned int)offset > GLOBALMEM_SIZE)
		{
			ret =- EINVAL;
			break;
		}
		filp->f_pos = (unsigned int)offset;
		ret = filp->f_pos;
		break;
	case 1:
		/*相对文件当前位置偏移*/
		if ((filp->f_pos + offset) > GLOBALMEM_SIZE)
		{
			ret =- EINVAL;
			break;
		}
		if ((filp->f_pos + offset) < 0)
		{
			ret =- EINVAL;
			break;
		}
		filp->f_pos += offset;
		ret = filp->f_pos;
		break;
	default:
		ret =- EINVAL;
		break;
	}
	return ret;
}

/* ioctl设备控制函数 ,这里只是实现一个清空该内存的命令*/
static int globalmem_ioctl(struct inode *inodep, struct file *filp, unsigned
int cmd, unsigned long arg)
{
	switch (cmd)
	{
		case MEM_CLEAR:
		memset(dev->mem, 0, GLOBALMEM_SIZE);
		printk(KERN_INFO "globalmem is set to zero\n");
		break;
		default:
		return - EINVAL;
	}
	return 0;
}
 
第四步、实现注册和卸载函数 

到这里我们看似已经实现设备的功能了,但是应用程序还不能够使用它,因为还没有注册该设备,所以我们要把这个设备加载进内核,相对应的当我们不需要该设备了的时候就应该把它从内核卸载掉。在模块加载函数中完成的功能主要有:申请设备号、注册 cdev 设备结构体。相应的卸载函数中就应该卸载 cdev 设备结构体,释放设备号。
/*初始化并注册cdev*/
static void globalmem_setup_cdev(struct globalmem_dev *dev, int index)
{
	int err, devno = MKDEV(globalmem_major, index);
	cdev_init(&dev->cdev, &globalmem_fops);
	dev->cdev.owner = THIS_MODULE;
	dev->cdev.ops = &globalmem_fops;
	err = cdev_add(&dev->cdev, devno, 1);
	if (err)
	printk(KERN_NOTICE "Error %d adding LED%d", err, index);
}
/*设备驱动模块加载函数*/
int globalmem_init(void)
{
	int result;
	dev_t devno = MKDEV(globalmem_major, 0);
	/* 申请设备号*/
	if (globalmem_major)
		result = register_chrdev_region(devno, 1, "globalmem");
	else
	/* 动态申请设备号 */
	{
		result = alloc_chrdev_region(&devno, 0, 1, "globalmem");
		globalmem_major = MAJOR(devno);
	}
	if (result < 0)
		return result;
	/* 动态申请设备结构体的内存*/
	globalmem_devp = kmalloc(sizeof(struct globalmem_dev), GFP_KERNEL);
	if (!globalmem_devp)
	/*申请失败*/
	{
		result =- ENOMEM;
		goto fail_malloc;
	}
	memset(globalmem_devp, 0, sizeof(struct globalmem_dev));
	globalmem_setup_cdev(globalmem_devp, 0);
	return 0;
	fail_malloc: unregister_chrdev_region(devno, 1);
	return result;
}

/*模块卸载函数*/
void globalmem_exit(void)
{
	cdev_del(&globalmem_devp->cdev);
	/*注销cdev*/
	kfree(globalmem_devp);
	/*释放设备结构体内存*/
	unregister_chrdev_region(MKDEV(globalmem_major, 0), 1); /*释放设备号*/
}
第五步、声明一些必要的信息。
我们看看前面的加载和卸载函数,它们与另外的几个函数并没有什么特别之处,当我们加载驱动的时候,内核怎么知道要调用 globalmem_init() 函数,在卸载驱动时怎么知道调用 globalmem_exit() 呢?所以我们应该向内核指示它们就是入口和出口函数,这就宏 module_init() module_exit() 的作用。
module_init(globalmem_init);
module_exit(globalmem_exit);
除此之外我们还必须声明我们的驱动遵循的 license, 不然会报错。
MODULE_LICENSE("Dual BSD/GPL");
到这里我们的这个简单的设备就写好了。




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值