2.1字符设备框架

设备文件

在Linux中有一切皆文件的概念(网络设备除外),这意味着设备也会被抽象成一个文件,应用程序对设备的访问最终转变成了对文件的访问。Linux的设备文件通常在/dev目录下,通过命令ls -l /dev可以查看设备文件:
在这里插入图片描述
另外还可以通过命令“mknod /dev/设备文件名 设备类型(c或者d) 主设备号 次设备号”创建设备文件(关于在驱动中创建设备文件的内容在后续章节介绍)

设备号

Linux系统通过设备号来区分不同的设备和驱动(所以设备文件有对应的设备号与之关联),Linux的设备号用一个32位的变量来表示(数据类型dev_t),它分为主设备号和次设备号,其中主设备号占高12位,用于区分驱动程序,次设备号占低20位,用于区分设备。
设备号相关的函数和宏

/* 通过主设备号和次设备号生成一个设备号
 1. ma 主设备号
 2. mi 次设备号
 3. 返回合成的设备号
 */
#define MKDEV(ma,mi)
//从设备号中提取次设备号
#define MINOR(dev)
//从设备号中提取主设备号
#define MAJOR(dev)
/* 注册字符设备号,可以连续注册多个字符设备号
 4. from 起始设备号
 5. count 要注册的数量,注册多个时设备号依次递增
 6. name 标记主设备号名称
 7. 成功返回0
 */
int register_chrdev_region(dev_t from, unsigned count, const char *name)
/* 动态分配字符设备号,可以连续分配多个字符设备号
 8. dev 用于返回起始设备号
 9. baseminor 次设备号起始值
 10. count 要分配的数量,注册多个时设备号依次递增
 11. name 标记主设备号名称
 12. 成功返回0
 */
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)
/* 注销字符设备号
 13. from 起始设备号
 14. count 要注销的数量,与创建或分配时传入的值一样
 */
void unregister_chrdev_region(dev_t from, unsigned count)

struct cdev 对象

在Linux内核中struct cdev对象管理字符设备驱动(Linux内核采用C语言编写,但是大量使用了面向对象的编程思维),其核心成员如下:

//指向所关联的设备操作函数集合
const struct file_operations *ops;
//设备号
dev_t dev;
//驱动所管理的设备数量,一个驱动可以同时支持多个设备
unsigned int count;

struct file_operations 对象

struct file_operations 对象对设备的操作进行了抽象,驱动开发过程中应根据设备特性和功能实现相应的函数,其核心成员如下:

//定位读写位置,与应用层的lseek函数对应
loff_t (*llseek) (struct file *, loff_t, int);
//读数据,与应用层的read函数对应
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
//写设备,与应用层的write函数对应
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
//控制设备,与应用层的iotl函数对应
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
//功能与unlocked_ioctl一样,只有在64位系统中运行32位程序时才会调用,与应用层的iotl函数对应
long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
//将设备内存映射到用户空间,与应用层的mmap函数对应
int (*mmap) (struct file *, struct vm_area_struct *);
//打开设备,与应用层的open函数对应
int (*open) (struct inode *, struct file *);
//释放设备,当应用层最后一次调用close时执行
int (*release) (struct inode *, struct file *);
//多路复用I/O,与应用层的poll、select、epoll函数对应
unsigned int (*poll) (struct file *, struct poll_table_struct *);

struct file 对象

在执行open的过程中系统会分配一个struct file 对象用于表示打开的文件(包括设备文件),其核心成员如下:

//指向文件对应的inode节点
struct inode *f_inode;
//文件打开方式,对应系统调用open的int flags参数
unsigned int f_flags;
//文件权限,对应系统调用open的mode_t mode参数
fmode_t f_mode;
//文件的当前读写位置,需要在struct file_operations 的read、write进行维护和更新
loff_t f_pos;
//供驱动使用的私有数据,驱动程序可以在其中暂存一些参数
void *private_data;

struct inode 对象

struct inode 对象表示文件的inode节点,内核在第一次打开文件时会为其创建相应的inode节点,其核心成员如下:

//设备号
dev_t			i_rdev;
//如果是块设备则指向对应的block_device对象
//如果是字符设备则指向对应的cdev对象
union {
	struct block_device	*i_bdev;
	struct cdev		*i_cdev;
};

编写一个简单的字符设备

实现一个字符设备的步骤如下:

  1. 构建struct file_operations对象
  2. 注册/分配设备号
  3. 利用struct file_operations对象构造struct cdev 对象
  4. 将struct cdev 对象添加到系统的cdev_map中
    如下是一个简单的字符设备驱动框架:
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/cdev.h>

//使能自动分配设备号
#define USING_AUTO_REGION

//主设备号
#define CDEV_MAJOR	300
//次设备号
#define CDEV_MINOR	0
//此驱动支持的设备数量
#define CDEV_CNT	2
//主设备号名称
#define CDEV_NAME	"test_cdev"

//设备号
static dev_t dev;
//cdev 对象,用于管理字符设备驱动
static struct cdev char_dev;

static int cdev_open(struct inode *inode, struct file *filep)
{
	dev_t rdev;

	//inode节点的i_rdev即是当前设备的设备号
	rdev = inode->i_rdev;

	//file对象的private_data是专为驱动预留的一个成员,这里用于存储设备号
	filep->private_data = (void*)rdev;

	printk("%s\r\n", __FUNCTION__);
	printk("major = %d,", MAJOR(rdev));
	printk("minor = %d\r\n", MINOR(rdev));

	return 0;
}

static int cdev_release(struct inode *inode, struct file *filep)
{
	dev_t rdev = (dev_t)filep->private_data;

	printk("%s\r\n", __FUNCTION__);
	printk("major = %d,", MAJOR(rdev));
	printk("minor = %d\r\n", MINOR(rdev));

	return 0;
}

static ssize_t cdev_read(struct file *filep, char __user *buffer, size_t size, loff_t *pos)
{
	dev_t rdev = (dev_t)filep->private_data;

	printk("%s\r\n", __FUNCTION__);
	printk("major = %d,", MAJOR(rdev));
	printk("minor = %d\r\n", MINOR(rdev));

	return 0;
}

static ssize_t cdev_write(struct file *filep, const char __user *buffer, size_t size, loff_t *pos)
{
	dev_t rdev = (dev_t)filep->private_data;

	printk("%s\r\n", __FUNCTION__);
	printk("major = %d,", MAJOR(rdev));
	printk("minor = %d\r\n", MINOR(rdev));

	return size;
}

/***************************1. 构建struct file_operations对象***************************/
//字符设备操作方法
static struct file_operations ops = 
{
	.owner = THIS_MODULE,
	.open = cdev_open,
	.release = cdev_release,
	.read = cdev_read,
	.write = cdev_write,
};
static int __init char_init(void)
{
	int i;
	int ret;

	printk("cdev init\r\n");
	
/***************************2. 注册/分配设备号***************************/
#ifndef USING_AUTO_REGION
	/* 合成设备号
	 * ma 主设备号
	 * mi 次设备号
	 * 返回合成的设备号
	 */
	dev = MKDEV(CDEV_MAJOR, CDEV_MINOR);
	/* 注册一系列字符设备号
	 * from 起始设备号
	 * count 要注册的数量,注册多个时设备号依次递增
	 * name 标记主设备号名称
	 * 成功返回0
	 */
	ret = register_chrdev_region(dev, CDEV_CNT, CDEV_NAME);
	if(ret != 0)
	{
		printk("region device ID failed\r\n");
		return ret;
	}
	for(i=0; i<CDEV_CNT; i++)
	{
		printk("device%d ", i);
		/* 提取主设备号
		 * dev 设备号
		 * 返回主设备号
		 */
		printk("major = %d,", MAJOR(dev+i));
		/* 提取次设备号
		 * dev 设备号
		 * 返回次设备号
		 */
		printk("minor = %d\r\n", MINOR(dev+i));
	}
#else
	/* 动态分配一系列字符设备号
	 * dev 用于返回第一个设备号
	 * baseminor 次设备号起始值
	 * count 要分配的数量,注册多个时设备号依次递增
	 * name 标记主设备号名称
	 * 成功返回0
	 */
	ret = alloc_chrdev_region(&dev, CDEV_MINOR, CDEV_CNT, CDEV_NAME);
	if(ret != 0)
	{
		printk("alloc device ID failed\r\n");
		return ret;
	}
	for(i=0; i<CDEV_CNT; i++)
	{
		printk("device%d ", i);
		printk("major = %d,", MAJOR(dev+i));
		printk("minor = %d\r\n", MINOR(dev+i));
	}
#endif
/***************************3. 利用struct file_operations对象构造struct cdev 对象**************************/
	/* 初始化 cdev 对象
	 * cdev 要初始化的对象
	 * fops 设备操作函数
	 */
	cdev_init(&char_dev, &ops);
	char_dev.owner = THIS_MODULE;
/***************************4. 将struct cdev 对象添加到系统的cdev_map中**************************/
	/* 添加cdev对象到内核
	 * p 指向要添加的cdev对象
	 * dev 设备号
	 * count cdev对象所管理的设备数量
	 */
	ret = cdev_add(&char_dev, dev, CDEV_CNT);
	if(ret != 0)
	{
		unregister_chrdev_region(dev, CDEV_CNT);
		printk("add cdev failed\r\n");
		return ret;
	}
	return 0;
}

static void __exit char_exit(void)
{
	printk("cdev exit\r\n");
	/* 从内核删除cdev对象
	 * p 要删除的内核对象
	 */
	cdev_del(&char_dev);
	/* 注销一系列字符设备号
	 * from 起始设备号
	 * count 要注销的数量
	 */
	unregister_chrdev_region(dev, CDEV_CNT);
}


module_init(char_init);
module_exit(char_exit);

MODULE_LICENSE("GPL");

实验

  1. 在这里下载代码后,进行编译,并拷贝到模板板root目录中
  2. 执行命令insmod cdev_frame.ko安装内核模块,可以看到有两个字符设备,主设备号均为241,次设备号分别是0和1(这里采用自动分配设备号,具体的设备号依实际情况而定)
    在这里插入图片描述
  3. 执行mknod /dev/cdev_test0 c 241 0为主设备号为241次设备号为0的设备创建一个设备文件,创建成功后的/dev/目录下会有相应的设备文件名(同样也可以用mknod /dev/cdev_test0 c 241 1为主设备号为241次设备号为1的设备创建一个设备文件)
    在这里插入图片描述
  4. 通过命令cat /dev/cdev_test0可以执行到设备驱动的open raed release函数(因为read函数返回0,所以认为读取结束,cat自动结束)
    在这里插入图片描述
  5. 通过命令echo “123456” >> /dev/cdev_test0可以执行到设备驱动的open write release函数(write函数直接返回写入字节数,表示数据全部写入成功)
    在这里插入图片描述
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值