嵌入式Linux驱动开发(一)chrdevbase虚拟字符设备

Linux下三大驱动:字符设备,块设备,网络设备。一个硬件可以从属于不同的设备分类。

0. Linux应用程序对驱动程序的调用流程

在这里插入图片描述
在这里插入图片描述

  驱动加载成功后会在/dev目录下生成一个文件,对该文件的操作就是对设备的操作。当我们在用户态调用一个函数,会经过“系统调用(C库的一部分)”方法陷入内核空间,内核空间中有对应的驱动函数,由此实现控制。应用访问内核资源的三个方式:系统调用,异常(中断),陷入(软中断)
  Linux内核文件include/linux/fs.h中file_operations结构体保存了内核驱动操作函数集合。
  **Linux驱动有两种运行方式:①将驱动编译进内核,内核启动后自动运行;②驱动编译成.ko模块,内核启动后需要以“insmod”命令加载。**调试驱动一般编译为模块,避免每次都编译一遍linux。

1. 字符驱动设备

  字符设备是指逐字节流进行顺序读写操作的设备,比如LED,KEY,IIC,SPI,LCD等。

1.1 驱动模块加载卸载

字符驱动设备加载卸载函数模板:

static struct file_operations test_fops;  # 设备具体操作函数在这里面

/* 驱动入口函数 */
static int __init xxx_init(void)          # 定义xxx_init驱动入口函数,使用__init修饰
{
	/* 入口函数具体内容 */
	int retvalue = 0;
	/* 注册字符设备驱动 */
	retvalue = register_chrdev(200, "chrtest", &test_fops);
	if(retvalue < 0){
		/* 字符设备注册失败,自行处理 */
	}
	return 0;
}
 
/* 驱动出口函数 */
static void __exit xxx_exit(void)
{
	/* 出口函数具体内容 */
	/* 注销字符设备驱动 */
	unregister_chrdev(200, "chrtest");
}

/* 将上面两个函数指定为驱动的入口和出口函数 */
module_init(xxx_init);      # 调用函数module_init注册xxx_init为驱动入口函数,加载驱动时调用
module_exit(xxx_exit);

模块加载命令:
①insmod:如果一个模块依赖另一个模块,必须先insmod依赖模块。
modprobe:会自动分析依赖并全部加载。
模块卸载命令:
rmmod:对应insmod。
②modprobe -r:对应modpeobe。全部卸载依赖可能导致其它模块受到影响。

1.2 字符设备加载卸载

  模块加载之后需要进行字符设备加载,字符设备注册一般在模块入口函数xxx_init中进行,注销在模块卸载函数xxx_exit中进行。函数原型为:

# major-主设备号	name-设备名	   fops-指向设备操作函数集合变量
static inline int register_chrdev(unsigned int major, const char *name, const struct file_operations *fops)
static inline void unregister_chrdev(unsigned int major, const char *name)

  注册设备时必须确保主设备号没被占用,使用**“cat /proc/devices”查看当前已用设备号**。

1.3 设备具体操作函数

假设对设备有打开(open)关闭(release),读(read)写(write)操作的要求。
App中的函数就这样绑定到了驱动函数。

 static struct file_operations test_fops = {
	.owner = THIS_MODULE, 
	.open = chrtest_open,
	.read = chrtest_read,
	.write = chrtest_write,
	.release = chrtest_release,
};

1.4 添加LICENSE和作者信息

MODULE_LICENSE("GPL");           # 必须添加,采用GPL协议
MODULE_AUTHOR("finches");        # 可加可不加

2. Linux设备号

  Linux中每个设备都有设备号,由主设备号次设备号组成。
  主设备号表示某一个具体的驱动,次设备号表示使用这个驱动的各个设备。设备号由dev_t数据类型表示(其实就是unsigned int 32类型),其中高12位主设备号,低20位次设备号。所以主设备号范围为0-4095。

2.1 主设备号分配

1)静态分配:开发者指定一个设备号,使用“cat /proc/devices”查看,没被占用就可以用。
2)动态分配:注册字符设备之前申请一个设备号,卸载时释放设备号。推荐使用。
 设备号申请函数:

int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)
# dev:保存申请到的设备号
# baseminor:次设备号起始地址,一个alloc_chrdev_region可以申请多个主设备号相同,次设备号不同的硬件
# count:申请设备数
# name:设备名

 设备号释放函数:

void unregister_chrdev_region(dev_t from, unsigned count)
# from:要释放的设备号
# count:从from开始,要释放的设备号数量

3. chrdevbase字符设备驱动开发实验

chrdevbase设备是一个虚拟设备,具有各100字节的读和写缓冲区。
1)编写驱动文件chrdevbase.c:
2)编写测试APP:

# open:pathname-要打开的设备或文件名。   flags-打开模式。
# 打开模式(必选的3个):O_RDONLY只读;O_WRONLY只写;O_RDWR读写。
# 返回值:打开成功返回文件的描述符
int open(const char *pathname, int flags)

# read:fd-要读取的文件描述符。       buf:数据读取到该缓冲区。     count:要读取的字节数。
# 返回值:读取成功返回读取到的字节数,返回0代表读到了文件末尾。
ssize_t read(int fd, void *buf, size_t count)

# write:fd-要进行写的文件描述符。
# 返回值:返回写入的字节数。        返回0表示没有写入任何数据。
ssize_t write(int fd, const void *buf, size_t count);

# close:
int close(int fd);

3)编译驱动程序为.ko模块:
在这里插入图片描述
4)测试应用:

arm-linux-gnueabihf-gcc chrdevbaseApp.c -o chrdevbaseApp    # 编译
file chrdevbaseApp                                          # 查看应用信息

在ubuntu中将.ko和App拷贝到/lib/modules/4.1.15下,在开发板上运行:
1)加载驱动模块并检查

# 加载模块
modprobe chrdevbase.ko
# 若报错没有‘modules.dep’,输入‘depmod’命令即可自动生成

# 查看当前存在的模块
lsmod

# 查看系统所有设备
cat /proc/devices

2)创建设备节点文件

# 创建了chrdevbase节点文件,c表示字符设备,200为主设备号,0是次设备号
mknod /dev/chrdevbase c 200 0

3)设备操作测试
在这里插入图片描述
返回第一行:驱动程序中的chrdevbase_read函数输出信息
返回第二行:App输出的接收到的数据
4)卸载驱动模块

rmmod chrdevbase.ko
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值