|
字符设备驱动介绍
字符设备是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,&#34;chrtest&#34;);
}
//操作密令insmode 和 rmmode的来源=========指定驱动的出入口函数
module_init(xxx_init);
module_exit(xxx_exit);
//许可证和作者的信息
MODULE_LICENSE(&#34;GPL&#34;);
MODULE_AUTHOR(&#34;zuozhongkai&#34;); 4.添加许可证和作者info
MODULE_LICENSE() //添加模块LICENSE 信
MODULE_AUTHOR() //添加模块作者信息
文章福利】小编推荐自己的Linux内核技术交流群:【1143996416】整理了一些个人觉得比较好的学习书籍、视频资料共享在群文件里面,有需要的可以自行添加哦!!!(含视频教程、电子书、实战项目及代码)
资料直通车:最新Linux内核源码资料文档+视频资料
学习直通车:Linux内核源码/内存调优/文件系统/进程管理/设备驱动/网络协议栈
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,&#34;chrtest&#34;,&test_fops);
if(retvalue<0)
{
//失败处理
}
return 0;
}
//驱动出口函数
static void __exit xxx_exit(void)
{
//注销字符设备驱动
unregister_chrdev(200,&#34;chrtest&#34;);
}
//操作密令insmode 和 rmmode的来源=========指定驱动的出入口函数
module_init(xxx_init);
module_exit(xxx_exit);
//许可证和作者的信息
MODULE_LICENSE(&#34;GPL&#34;);
MODULE_AUTHOR(&#34;zuozhongkai&#34;); 1.实验程序编写
1.创建vscode工程=》2.添加头文件路径
2.编写测试app
c库文件操作 基本函数 open函数、read函数、write函数、close函数
编写测试app程序 此app是用户空间的,主要对驱动设备进行读写
#include &#34;stdio.h&#34;
#include &#34;unistd.h&#34;
#include &#34;sys/types.h&#34;
#include &#34;sys/stat.h&#34;
#include &#34;fcntl.h&#34;
#include &#34;stdlib.h&#34;
#include &#34;string.h&#34;
static char usrdata[]={&#34;usr data!&#34;};
int main(int argc,char *argv[])
{
int fd,retvalue;
char* filename;
char readbuf[100],writebuf[100];
if(argc!=3)
{
printf(&#34;Error Usage!\r\n&#34;);
return -1;
}
filename=argv[1];
fd=open(filename,O_RDWR);//只读打开
if(fd<0){
printf(&#34;can&#39;t opne file %s\r\n&#34;,filename);
return -1;
}
if(atoi(argv[2])==1){//为1是读数据
retvalue=read(fd,readbuf,50);//会把参数fd所指的文件传送50个字节到buf指针所指的内存中
if(retvalue<0)//fail
printf(&#34;read file %s failed!\r\n&#34;,filename);
else
printf(&#34;read data:%s\r\n&#34;,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(&#34;write file %s failed!\r\n&#34;,filename);
}
retvalue=close(fd);
if(retvalue<0){
printf(&#34;can&#39;t close file %S\r\n&#34;,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
原文链接:https://blog.csdn.net/weixin_47397155/article/details/120659899 |
|