【Linux】字符设备驱动

字符设备驱动

本文将对字符设备驱动知识进行梳理,搭配字符设备驱动程序globalmem进行理解与应用

1、Linux字符设备驱动结构

1、1 cdev结构体

在Linux内核中,使用cdev结构体描述一个字符设备,而通过字符设备可以完成在用户空间和内核空间的字符输入输出等操作

(1)成员dev_t dev

主要用于字符设备驱动主次设备号的设置

其中,dev_t dev成员定义设备号,为32位,12为主设备号,20位为次设备号

一般,不同类型的设备定义成不同的组设备,而同一类型的设备在同一主设备号下定义为不同的次设备号

使用下列宏可以从dev_t中获得主设备号和此设备号

使用下列宏可以通过主设备号和此设备号生成dev_t

在globalmem字符驱动代码中,

定义一个globalmem_dev结构体,成员包括一个cdev结构体一个字符数组(缓存区,用于字符的读写)

/*globalmem设备结构体*/
struct globalmem_dev {
	struct cdev cdev;		        /*cdev结构体*/
	unsigned char mem[GLOBALMEM_SIZE];	/*全局内存*/
};

在字符设备驱动模块加载函数中,生成主次设备号

/*字符设备驱动模块加载函数*/
int globalmem_init(void)
{
    .....
    /* 使用宏通过主设备号和次设备号生成dev_t               */
    /* "globalmem_major"为主设备号(宏定义),"0"为此设备号*/
	dev_t devno = MKDEV(globalmem_major, 0);
    .....
}

 (2)成员struct file_operations *ops

成员struct file_operations *ops定义了字符设备驱动提供给虚拟文件系统的接口函数

void cdev_init(struct cdev*,struct file_operation *)

struct cdev *cdev_alloc(void)

int cdev_add和void cdev_del

在globalmem字符驱动代码中

globalmem_init函数,字符设备驱动模块加载函数

/*字符设备驱动模块加载函数*/
int globalmem_init(void)
{
    .....
    globalmem_setup_cdev(globalmem_devp, 0);			/*初始化并注册cdev字符设备*/
    .....

}

globalmem_setup_cdev函数,用于初始化并注册cdev字符设备

/*初始化并注册cdev*/
static void globalmem_setup_cdev(struct globalmem_dev *dev, int index)
{
	int err, devno = MKDEV(globalmem_major, index);     /*为cdev结构体生成主次设备号*/
	cdev_init(&dev->cdev, &globalmem_fops);             /*初始化cdev的成员,本程序中为传入的参数dev*/
	dev->cdev.owner = THIS_MODULE;                      /*所属模块*/
	err = cdev_add(&dev->cdev, devno, 1);               /*向系统添加一个cdev*/
	if (err)                                            /*通过err的值判断是否添加成功*/
		printk(KERN_NOTICE "Error %d adding LED%d", err, index);
}

globalmem_exit函数 ,字符设备驱动模块卸载函数

/*模块卸载函数*/
void globalmem_exit(void)
{
    .....
    cdev_del(&globalmem_devp->cdev);                            /*注销cdev*/
    .....
}

 1、2 file_operation结构体

 (1)file_operations结构体的定义

在globalmem字符驱动代码中,使用的file_operations结构体

/*文件操作结构体*/
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,        //释放设备文件
};

(2)file_operation结构体中涉及的函数

file_operation结构体中涉及的函数,由于篇幅较大,将在之后的源码中给出。

1、3 分配和释放设备号

在globalmem字符驱动代码中

globalmem_init函数,字符设备驱动模块加载函数,完成对字符设备号的申请

/*字符设备驱动模块加载函数*/
int globalmem_init(void)
{
	int result;
	//使用宏通过主设备号和次设备号生成dev_t
	dev_t devno = MKDEV(globalmem_major, 0);                    /* "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;

        .....
	
	return 0;

}

globalmem_exit函数,字符设备驱动模块卸载函数,完成对字符设备号的释放

/*模块卸载函数*/
void globalmem_exit(void)
{
        .....
	unregister_chrdev_region(MKDEV(globalmem_major, 0), 1);		/*释放设备号*/
}

1、4 缓存区的申请与释放 

在globalmem字符驱动代码中

globalmem_init函数,字符设备驱动模块加载函数,完成对缓存区的申请

/*字符设备驱动模块加载函数*/
int globalmem_init(void)
{
	......
	/* 动态申请设备结构体的内存*/
	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));	/*设备结构体内存空间清0*/

	.....
	
	return 0;

fail_malloc:
	unregister_chrdev_region(devno, 1);
	return result;
}

globalmem_eixt函数,字符设备驱动模块卸载函数,完成对缓存区的释放

void globalmem_exit(void)
{
    .....
	kfree(globalmem_devp);							/*释放设备结构体内存*/
    .....	
}

2、globalmem字符设备驱动代码结构

 3、globalmem字符设备驱动代码

/*
 * A globalmem driver as an example of char device drivers
 *
 * The initial developer of the original code is Barry Song
 * <author@linuxdriver.cn>. All Rights Reserved.
 */

#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>
#include<linux/device.h>
#define GLOBALMEM_SIZE	0x1000  /*全局内存最大4K字节*/
#define MEM_CLEAR 0x1			/*清0全局内存*/
#define GLOBALMEM_MAJOR 250		/*预设的globalmem的主设备号*/

static int globalmem_major = GLOBALMEM_MAJOR;


/*globalmem设备结构体*/
struct globalmem_dev {
	struct cdev cdev;					/*cdev结构体*/
	unsigned char mem[GLOBALMEM_SIZE];	/*全局内存*/
};

struct globalmem_dev *globalmem_devp;	/*设备结构体指针*/

/*文件打开函数*/
int globalmem_open(struct inode *inode, struct file *filp)
{
	/*将设备结构体指针赋值给文件私有数据指针*/
	filp->private_data = globalmem_devp;
	return 0;
}
/*文件释放函数*/
int globalmem_release(struct inode *inode, struct file *filp)
{
	return 0;
}

/* ioctl设备控制函数 */
static int globalmem_ioctl(struct inode *inodep, struct file *filp, unsigned
	int cmd, unsigned long arg)
{
	struct globalmem_dev *dev = filp->private_data;/*获得设备结构体指针*/

	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;
}

/*读函数*/
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;
	struct globalmem_dev *dev = filp->private_data; /*获得设备结构体指针*/

	/*分析和获取有效的写长度*/
	if (p >= GLOBALMEM_SIZE)
		return 0;
	if (count > GLOBALMEM_SIZE - p)
		count = GLOBALMEM_SIZE - p;

	/*内核空间->用户空间*/
	if (copy_to_user(buf,dev->mem+p,count)) {
		ret =  - EFAULT;
	} else {
		*ppos += count;
		ret = count;

		printk(KERN_INFO "read %u bytes(s) from %lu\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;
	struct globalmem_dev *dev = filp->private_data; /*获得设备结构体指针*/

	/*分析和获取有效的写长度*/
	if (p >= GLOBALMEM_SIZE)
		return 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 %u bytes(s) from %lu\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;
}

/*文件操作结构体*/
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,
};

/*初始化并注册cdev*/
static void globalmem_setup_cdev(struct globalmem_dev *dev, int index)
{
	int err, devno = MKDEV(globalmem_major, index);		/*为cdev结构体生成主次设备号*/

	cdev_init(&dev->cdev, &globalmem_fops);				/*初始化cdev的成员,本程序中为传入的参数dev*/
	dev->cdev.owner = THIS_MODULE;						/*所属模块*/
	err = cdev_add(&dev->cdev, devno, 1);				/*向系统添加一个cdev*/
	if (err)											/*通过err的值判断是否添加成功*/
		printk(KERN_NOTICE "Error %d adding LED%d", err, index);
}

/*字符设备驱动模块加载函数*/
int globalmem_init(void)
{
	int result;
	//使用宏通过主设备号和次设备号生成dev_t
	dev_t devno = MKDEV(globalmem_major, 0);        /* "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));	/*设备结构体内存空间清0*/

	globalmem_setup_cdev(globalmem_devp, 0);					/*初始化并注册cdev字符设备*/
	
	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);		/*释放设备号*/
}

MODULE_AUTHOR("Barry Song <21cnbao@gmail.com>");
MODULE_LICENSE("Dual BSD/GPL");

module_param(globalmem_major, int, S_IRUGO);

module_init(globalmem_init);
module_exit(globalmem_exit);

 4、globalmem字符设备驱动结构

5、参考

《Linux设备驱动开发详解基于最新的Linux4.0内核》作者:宋宝华

 

 

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值