linux下字符设备驱动开发的基本框架(linux驱动开发篇)

字符设备驱动介绍

  • 字符设备是linux驱动中最基本的一类,占90%以上。
    常见的字符设备:点灯、按键、iic、spi、lcd等等

  • 这里有个非常主要的一点就是应用程序和驱动之间的关系:
    1.应用程序app是运行在用户空间,而驱动运行在内核空间。二者互相访问的桥梁即是***“系统调用”***。系统调用属于C库的一部分。主要即是open、release、write、read等等
    2.应用程序使用到的函数在具体驱动程序中都有与之对应的函数,比
    如应用程序中调用了 open 这个函数,那么在驱动程序中也得有一个名为 open 的函数。每一个系统调用,在驱动中都有与之对应的一个驱动函数

  • linux中一切都是文件
    驱动加载成功以后会在“/dev”目录下生成一个相应的文件,应用程序通过对这个名为“/dev/xxx“即可对硬件进行操作。



驱动开发的步骤

1.驱动模块的加载和卸载

Linux驱动有两种运行方式,
1.第一种就是将驱动编译进 Linux内核中,这样当Linux内核启动的时候就会自动运行驱动程序。
2.第二种就是将驱动编译成模块(Linux下模块扩展名为.ko),在Linux内核启动以后使用“insmod”命令加载驱动模块。在调试驱动的时候一般都选择将其编译为模块,这样我们修改驱动以后只需要编译一下驱动代码即可,不需要编译整个 Linux 代码。(我们选择第二种)

module_init(xxx_init); //注册模块加载函数 ==insmod命令会触发括号里的函数xxx_init
module_exit(xxx_exit); //注册模块卸载函数 ==rmmod指令会触发。。。
当然指令后面跟的是编译好的驱动模块xxx.ko
insmod xxx.ko

2.字符设备注册与注销

xxx_init函数内部就是调用字符设备的注册register_chrdev函数
xxx_exit函数。。。。。。。。。。。unregister_chrdev函数

当驱动模块加载成功后,我们需要注册字符设备

static inline int register_chrdev(unsigned int major, const char *name, const struct file_operations *fops)
major:设备号
name:设备名字
fops(重点来了):指向设备的操作函数集合变量

卸载驱动模块的时候也需要注销掉字符设备

static inline void unregister_chrdev(unsigned int major, const char *name)

3.实现设备的具体操作函数

此部分就是解决上面遗留下的额问题 即是file_operations结构体变量fops
我们主要对它进行初始化:xxx为设备名字这里就是定为chrtest,主要实现设备文件的打开和关闭,还有读写

//打开设备
static int chrtest_open(struct inode* inode,struct file* filep)
{
	/*用户具体实现的功能*/
	return 0;
}
//从设备读数据
static ssize_t chrtest_read(struct file *filep,char __user* buf,size_t cnt,loff_t*  offt)
{
	/*用户具体实现的功能*/
	return 0;
}
//向设备写数据

static ssize_t chrtest_write(struct file* filp,const char __user* buf,
size_t cnt,loff_t* offt)
{
	/*用户具体实现的功能*/
	return 0;
}
//关闭/释放设备
static int chrtest_release(struct inode* inode,struct file* filp)
{
	/*用户具体实现的功能*/
	return 0;
}

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

//驱动入口函数
static int __init xxx_init(void)
{
	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");
}

//操作密令insmode 和 rmmode的来源=========指定驱动的出入口函数
module_init(xxx_init);
module_exit(xxx_exit);

//许可证和作者的信息
MODULE_LICENSE("GPL"); 
MODULE_AUTHOR("zuozhongkai"); 

4.添加许可证和作者info

MODULE_LICENSE() //添加模块LICENSE 信
MODULE_AUTHOR() //添加模块作者信息



linux设备号

1.设备号的组成

高12位是主设备号,低20位是次设备号

,Linux 中每个设备都有一个设备号,设备号由主设备号和次设备号两部分组成,主设备号表示某一个具体的驱动,次设备号表示使用这个驱动的各个设备。

2.设备号的分配

静态分配

静态分配设备号需要我们检查当前系统中所有被使用了的设备号,然后挑选一个没有使用

动态分配

而且静态分配设备号很容易带来冲突问题,Linux社区推荐使用动态分配设备号,在注册字
符设备之前先申请一个设备号,系统会自动给你一个没有被使用的设备号,这样就避免了冲突。卸载驱动的时候释放掉这个设备号即可。

注册设备驱动之前,需要申请设备号
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)

  • dev:保存申请到的设备号。
  • baseminor:次设备号起始地址,alloc_chrdev_region 可以申请一段连续的多个设备号,这
    些设备号的主设备号一样,但是次设备号不同,次设备号以 baseminor 为起始地址地址开始递
    增。一般baseminor为0,也就是说次设备号从 0开始。
  • count:要申请的设备号数量。
  • name:设备名字。

注销字符设备之后要释放掉设备号,设备号释放函数如下:
void unregister_chrdev_region(dev_t from, unsigned count)
此函数有两个参数:

  • from:要释放的设备号。
  • count:表示从from开始,要释放的设备号数量。


以一个虚拟字符设备为例子来介绍流程

***字符设备驱动开发的基本步骤我们已经了解了,本节我们就以 chrdevbase 这个虚拟设备为
例,完整的编写一个字符设备驱动模块。chrdevbase 不是实际存在的一个设备:chrdevbase 设备有两个缓冲区,一个为读缓冲区,一个为写缓冲区,这两个缓冲区的大小都为 100 字节。在应用程序中可以向 chrdevbase设备的写缓冲区中写入数据,从读缓冲区中读取数据
它包含了字符设备的最基本功能。 ***

1.实验程序的编写

主要是实现 初始化设备的操作函数

//打开设备
static int chrtest_open(struct inode* inode,struct file* filep)
{
	/*用户具体实现的功能*/
	return 0;
}
//从设备读数据
static ssize_t chrtest_read(struct file *filep,char __user* buf,size_t cnt,loff_t*  offt)
{
	/*用户具体实现的功能*/
	return 0;
}
//向设备写数据

static ssize_t chrtest_write(struct file* filp,const char __user* buf,
size_t cnt,loff_t* offt)
{
	/*用户具体实现的功能*/
	return 0;
}
//关闭/释放设备
static int chrtest_release(struct inode* inode,struct file* filp)
{
	/*用户具体实现的功能*/
	return 0;
}

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

//驱动入口函数
static int __init xxx_init(void)
{
	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");
}

//操作密令insmode 和 rmmode的来源=========指定驱动的出入口函数
module_init(xxx_init);
module_exit(xxx_exit);

//许可证和作者的信息
MODULE_LICENSE("GPL"); 
MODULE_AUTHOR("zuozhongkai"); 

1.实验程序编写

1.创建vscode工程=》2.添加头文件路径

2.编写测试app

c库文件操作 基本函数 open函数、read函数、write函数、close函数

编写测试app程序

此app是用户空间的,主要对驱动设备进行读写

#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"

static char usrdata[]={"usr data!"};

int main(int argc,char *argv[])
{
	int fd,retvalue;
	char* filename;
	char readbuf[100],writebuf[100];
	if(argc!=3)
	{
		printf("Error Usage!\r\n");
		return -1;
	}	
	
	filename=argv[1];

	fd=open(filename,O_RDWR);//只读打开
	if(fd<0){
		printf("can't opne file %s\r\n",filename);
		return -1;
	}
	
	if(atoi(argv[2])==1){//为1是读数据
		retvalue=read(fd,readbuf,50);//会把参数fd所指的文件传送50个字节到buf指针所指的内存中
		if(retvalue<0)//fail
			printf("read file %s failed!\r\n",filename);
		else
			printf("read data:%s\r\n",readbuf);//successful
	}
	
	if(atoi(argv[2])==2)//为2是写数据
	{
		memcpy(writebuf,usrdata,sizeof(usrdata));
		retvalue=write(fd,writebuf,50);//将writebuf缓冲区的50个字节写入fd指向的文件中
		if(retvalue<0)
			printf("write file %s failed!\r\n",filename);
	}

	retvalue=close(fd);
	if(retvalue<0){
		printf("can't close file %S\r\n",filename);
		return -1;
	}
	return 0;
}

3.编写驱动程序和测试app

编译驱动程序
将chrdevbase.c这个文件,编译成.ko模块
主要就是编写makefile文件

/*内核源码目录*/
KERNELDIR := /home/rgd/linux/IMX6ULL/linux/temp/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek

/*当前所属路径*/
CURRENT_PATH := $(shell pwd)

/*编译成模块*/
obj-m := chrdevbase.o

/**/
build:kernel_modules

/*具体编译命令*/
kernel_modules:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean

编译测试app

由于在开发板上面运行,所以我们需要使用交叉编译器:
arm-linux-gnueabihf-gcc chrdevbaseApp.c -o chrdevbaseApp
命令执行之后生成的程序是chrdevbaseApp 这个可执行文件是32位LSB格式

4.最终的运行测试

1.加载模块

  • 启动linux系统 :
    tftp 80800000 zImage;tftp 83000000 imx6ull-alientek-emmc.dtb;bootz 80800000 - 83000000
  • 确保bootrags环境变量的值为:
    console=ttymxc0,115200 root=/dev/nfs rw nfsroot=192.168.1.250:/home/zuozhongkai/linux/nfs/
    rootfs ip=192.168.1.251:192.168.1.250:192.168.1.1:255.255.255.0::eth0:off
  • 将ubuntu中的rootfs目录挂载为根文件系统 :
    sudo cp chrdevbase.ko chrdevbaseApp/home/zuozhongkai/linux/nfs/rootfs/lib/modules/4.1.15/ -f
  • 当在开发板上有chrdevbase.ko 和 chrdevbaseApp后,我们加载启动文件:
    insmod chrdevbswe.ko

2.创建设备节点文件
驱动加载成功后需要在/dev目录下创建一个与之对应的设备节点文件,应用程序就是通过操作这个设备文件来完成对具体设备的操作。

  • 创建/dev/chrdevbase这个设备节点文件:
    mknod /dev/chrdevbase c 200 0
  • 可以使用“ls /dev/chrdevbase -l”命令查看,有没有生成这个文件
  • 此时chrdevbaseAPP想要读写chrdevbase设备,直接对/dev/chrdevbase进行读写操作即可。
  • 相当于/dev/chrdevbase这个文件是chrdevbase设备在用户空间中的实现

3.chrdevbase设备操作测试
万事俱备,现在开始进行操作

  • 使用命令开始对设备进行读操作
    ./chrdevbaseApp /dev/chrdevbase 1
  • 使用命令对设备进行写操作
    ./chrdevbaseApp /dev/chrdevbase 2

4.卸载驱动模块

  • 卸载设备驱动
    rmmod chrdevbase.ko
  • 使用命令查看
    lsmod
  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

栋哥爱做饭

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值