最基本的字符设备驱动程序框架

/*
 * 说明:用于演示一个最基本的字符设备驱动程序框架。
 * 实现一个字符设备驱动的步骤:
 * 1、实现模块代码框架
 * 2、申请设备号,这个设备号由主、次设备号组成,是应用程序
 *    通过文件访问设备的关键所在。在类unix系统中,一切设备
 *    皆文件(网卡设备除外),而一个文件和一个inode对应,应用
 *    层创建设备节点使用的mknod命令其实就是为了能够创建一个能
 *    够代表设备的一个inode。应用程序通过路径名访问设备,但是
 *    这个路径名会最终装换到对应的inode上,在创建设备节点时,
 *    明确给出了主、次设备号,也即是通过路径名得到inode,通过
 *    inode得到了主、次设备号,最后通过主设备号查找到对应的设
 *    备驱动程序,或者说是查找到了对应的cdev。
 * 3、初始化一个代表字符设备的cdev对象,主要实现了cdev内部成员
 *    的初始化和cdev同file_operations的关联,这样找到cdev就能
 *    找到对应的操作方法集,从而实现设备的打开、关闭、读、写等
 *    操作。
 * 4、添加(或注册)cdev到内核,主要实现了将代表设备的cdev对象
 *    的指针保存到内核中的相应数据结构中。另外,还实现了cdev和
 *    主、次设备号的关联,这是通过设备号能找到对应cdev的关键所
 *    在。
 * 5、实现操作方法集中需要实现的操作,并非所有调用接口都要实现,
 *    根据设备的具体情况而定。
 */

#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>

#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>

#include "ioctl.h"

#define	FSDEV_MAJOR	250
#define	FSDEV_MINOR	0
#define FSDEV_NR	1
#define FSDEV_NAME	"fsdev"

/* 可以将该结构体看作是继承于cdev父类的一个子类 */
struct fsdev {
	struct cdev cdev;	/* 代表字符设备的一个抽象父类 */
	unsigned char buf[256]; /* 设备相关的一些资源,封装的概念 */
};

static struct fsdev fsdev;	/* 实例化一个对象 */

/* 该函数通常用来实现对设备的前期初始化,如初始化一些关键寄存器,
 * 使设备处于待工作状态。和具体的设备密切相关
 */
static int fschr_open(struct inode *inode, struct file *filp)
{
	return 0;
}

/* 该函数通常用来实现和open操作相反的操作 */
static int fschr_close(struct inode *inode, struct file *filp)
{
	return 0;
}

/* 从设备中获取数据,和具体的设备密切相关 */
static ssize_t fschr_read(struct file *filp, char __user *buf, size_t count, loff_t *fpos)
{
	int ret;
	int len;

	/* 注意要对参数进行严格的检查,驱动程序的崩溃都可能会导致整个系统的崩溃 */
	len = count > 256 ? 256 : count;
	/* 不同层之间的拷贝,用特殊的拷贝函数,注意该函数返回的是没有拷贝完成的字节数 */
	ret = copy_to_user(buf, fsdev.buf, len);

	return len - ret;
}

/* 写数据到设备,和具体的设备密切相关 */
static ssize_t fschr_write(struct file *filp, const char __user *buf, size_t count, loff_t *fpos)
{
	int ret;
	int len;

	len = count > 256 ? 256 : count;
	ret = copy_from_user(fsdev.buf, buf, len);

	return len - ret;
}

static int fschr_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg)
{
	int ret = 0;

	/* 严格的参数检测,有利于系统的稳定 */
	if (_IOC_TYPE(cmd) != FS_IOC_MAGIC)
		return -ENOTTY;
	if (_IOC_NR(cmd) > FS_IOC_MAXNR)
		return -ENOTTY;
	if (_IOC_DIR(cmd) & _IOC_READ)
		/* 注意,这里的VERIFY_WRITE是针对应用层arg传递过来的指针而言的,应用层
		 * 的读操作恰恰是内核层对该指针所指向内存的写操作
		 */
		ret = !access_ok(VERIFY_WRITE, (void __user *)arg, _IOC_SIZE(cmd));
	else if (_IOC_DIR(cmd) & _IOC_WRITE)
		ret =  !access_ok(VERIFY_READ, (void __user *)arg, _IOC_SIZE(cmd));
	if (ret)
		return -EFAULT;

	switch (cmd) {
	/* 命令字的定义推荐严格按照Linux的要求进行 */
	case FS_IOC_SET_BUF:
		memset(fsdev.buf, *(char *)arg, sizeof(fsdev.buf));
		break;
	}

	return 0;
}

static struct file_operations fsops = {
	/* 结构体中特定成员赋初值,前面加. */
	.owner = THIS_MODULE,
	.open = fschr_open,
	.release = fschr_close,
	.read = fschr_read,
	.write = fschr_write,
	.ioctl = fschr_ioctl,
};

static int __init fschr_init(void)
{
	int ret;
	dev_t devno;

	/* 推荐使用该宏来生成设备号,因为内核对设备号的定义可能会变 */
	devno = MKDEV(FSDEV_MAJOR, FSDEV_MINOR);
	/* 静态的设备号申请,申请成功后可以通过cat /proc/devices命令查看 */
	ret = register_chrdev_region(devno, FSDEV_NR, FSDEV_NAME);
	if (ret) {
		printk(KERN_ERR "fschr: register chrdev region failed\n");
		goto reg_err;
	}

	memset(&fsdev, 0, sizeof(struct fsdev));
	/* 初始化cdev对象,并和操作方法集关联 */
	cdev_init(&fsdev.cdev, &fsops);
	/* THIS_MODULE相当于this指针,指向驱动所在的模块,用于防止驱动在使用时,
	 * 模块被卸载
	 */
	fsdev.cdev.owner = THIS_MODULE;
	/* 注册cdev到内核,并和设备号关联 */
	ret = cdev_add(&fsdev.cdev, devno, FSDEV_NR);
	if (ret) {
		printk(KERN_ERR "fschr: add cdev failed\n");
		goto add_err;
	}
	return 0;

add_err:
	unregister_chrdev_region(devno, FSDEV_NR);
reg_err:
	return ret;
}

static void __exit fschr_exit(void)
{
	dev_t devno;

	devno = MKDEV(FSDEV_MAJOR, FSDEV_MINOR);

	cdev_del(&fsdev.cdev);
	unregister_chrdev_region(devno, FSDEV_NR);
}

module_init(fschr_init);
module_exit(fschr_exit);

MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("xxxx <xxxx@xxxx.com>");
MODULE_DESCRIPTION("This is an example for linux char driver");

Makefile

# 模块被编译时会前后两次进入该目录进行编译。
# 在该目录下(即模块所在的目录)执行make命令时,第一次根据该Makefile
# 进行编译,此时KERNELRELEASE变量的值为空,所以ifeq条件成立。
# KERNELRELEASE是在内核源码树的顶层Makefile中定义的一个变量,其值为
# 内核的版本号。
ifeq ($(KERNELRELEASE),)

# 定义模块所在目录的变量
PWD := $(shell pwd)
# 定义内核源码所在目录的变量,该目录下的内核源码应该是被移植好的内核
# 源码,并且经过了正确的配置和编译,修改Makefile时需要修改该变量的值。
KERNELDIR ?= /home/kevin/Workspace/FSC100/kernel/linux-2.6.35-farsight
# 定义根文件系统的目录,该目录即为NFS挂载的主机目录。模块将被安装在
# 该目录下的lib/modules/<内核版本号>/extra目录下,修改Makefile时需要
# 修改该变量的值。
INSTALLDIR ?= /home/kevin/Workspace/FSC100/rootfs

# 第一个目标,为默认的目标,即执行make modules命令和执行make命令的效果相同。
modules:
	# $(MAKE)相当于make,-C表明进入到一个指定目录进行编译,此时会进入
	# 到内核源码所在的目录,即KERNELDIR所指定的目录进行编译。
	# 进入到内核源码目录进行编译的最主要的效果是KERNELRELEASE变量将会
	# 被定义,并且被导出到各个子目录,以便在第二次进入模块所在的目录
	# 进行编译时,ifeq条件不成立。M变量指定了内核源码树外的模块目录,用于
	# 指导编译器从内核源码树目录重新回到模块所在目录进行编译。
	# modules用于指定编译模块,正如make zImage用于编译内核映像一样。
	$(MAKE) -C $(KERNELDIR) M=$(PWD) modules

# 用于模块的安装,推荐使用该方式,这样可以使用modprobe命令进行模块的装载。
modules_install:
	# INSTALL_MOD_PATH用于指定根文件系统的路径,路径名一定要正确。
	# modules_install用于说明是进行模块安装操作。
	$(MAKE) -C $(KERNELDIR) M=$(PWD) INSTALL_MOD_PATH=$(INSTALLDIR) modules_install
clean:
	rm -rf *.o *.ko *.mod.c .*.cmd modules.order Module.symvers .tmp_versions

# 第二次进入模块所在的目录进行编译时,由于KERNELRELEASE变量已被定义,
# 所以else条件成立。
else

# obj-m用于指明相应的文件被编译成模块,正如obj-y用于指明相应的文件编译进内核映像一样。
obj-m := fsmod.o

endif


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值