本节以chrdevbase虚拟设备为例,它有两个缓冲区写缓冲和读缓冲,大小都为100字节,应用程序可以向写缓冲区写入数据,从读缓冲区读取数据。
基本思路为:应用程序调用open函数打开chrdevbase设备,用wirte函数向写缓冲区写数据,调用read函数向读缓冲区读取数据,操作完后,应用程序调用close函数关闭chrdevbase设备。
目录
一、实验程序编写
1.1、创建工程
这里用vscode建立工程,编写linux驱动需要用到linux源码中的函数,因此需要在vscode中添加linux源码的头文件路径。
打开vscode,按下“ctrl+shift+p”。输入“C/C++:Edit configuration(JSON)”,在includePath栏添加自己虚拟机上linux源码头文件,如:
{
"configurations": [
{
"name": "Linux",
"includePath": [
"${workspaceFolder}/**",
"/home/zk/linux/IMX6ULL/linux/linux-imx-rel_imx_4.1.15_2.1.0_ga/include",
"/home/zk/linux/IMX6ULL/linux/linux-imx-rel_imx_4.1.15_2.1.0_ga/arch/arm/include",
"/home/zk/linux/IMX6ULL/linux/linux-imx-rel_imx_4.1.15_2.1.0_ga/arch/arm/include/generated/"
],
"defines": [],
"compilerPath": "/usr/bin/clang",
"cStandard": "c11",
"cppStandard": "c++17",
"intelliSenseMode": "clang-x64"
}
],
"version": 4
}
1.2、实验程序编写
1.2.1、编写驱动程序
这里要用到两个函数copy_to_user 、copy_from_user实现内核空间数据与用户空间数据的相互访问。
/*
@Decription:用户空间-->内核空间
@to :目标地址(内核空间)
@from :源地址(用户空间)
@n :将要拷贝数据的字节数
@return 成功返回0,失败返回没有拷贝成功的数据字节数
*/
unsigned long copy_from_user(void *to, const void *from, unsigned long n);
/*
@Decription :内核空间-->用户空间
@to :目标地址(用户空间)
@from :源地址(内核空间)
@n :将要拷贝数据的字节数
@return :成功返回0,失败返回没有拷贝成功的数据字节数
*/
unsigned long copy_to_user(void *to, const void *from, unsigned long n)
具体的驱动程序如下:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
static char kernel_data[] = "hello linux";
static char write_buf[100]; //写缓冲区
static char read_buf[100]; //读缓冲区
static int chrdevbase_open(struct inode *inode, struct file *filp)
{
printk("chrdevbase open ok!\n");
return 0;
}
static ssize_t chrdevbase_read(struct file *filp, __user char *buf, size_t count, loff_t *ppos)
{
int ret;
memcpy(read_buf, kernel_data, strlen(kernel_data));
ret = copy_to_user(buf, read_buf, count);
if (ret == 0)
{
printk("read data ok\n");
}
else
{
printk("read data fail\n");
}
}
static ssize_t chrdevbase_write(struct file *filp, const char __user *buf, size_t count, loff_t *oppos)
{
int ret;
ret = copy_from_user(write_buf, buf, count);
if (ret == 0)
{
printk("write_buf is %s\n", write_buf);
}
else
{
printk("write data fail\n");
}
return 0;
}
static int chrdevbase_release(struct inode *inode, struct file *filp)
{
printk("chrdevbase release\n");
return 0;
}
static const struct file_operations chrdevbase_fops = {
.owner = THIS_MODULE,
.open = chrdevbase_open,
.read = chrdevbase_read,
.write = chrdevbase_write,
.release = chrdevbase_release,
};
static int __init chrdevbase_init(void)
{
int ret;
ret = register_chrdev(200, "chrdevbase", &chrdevbase_fops);
if (ret < 0)
{
printk("register chrdevbase fail\n");
}
else
{
printk("register chrdevbase ok\n");
}
return ret;
}
static void __exit chrdevbase_exit(void)
{
unregister_chrdev(200, "chrdevbase");
printk("unregister chrdevbase ok\n");
}
module_init(chrdevbase_init);
module_exit(chrdevbase_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("ZK");
1.2.2、编写应用测试程序
编写应用需要了解文件IO相关的函数,链接如下:
二、编译驱动程序和测试APP
2.1、编译驱动程序
创建Makefile文件
KERNELDIR := /home/zk/linux/IMX6ULL/linux/linux-imx-rel_imx_4.1.15_2.1.0_ga
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
输入make即可编译生成.ko驱动模块
2.2、编译测试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 userdata[] = "user data!";
int main(int argc, char *argv[])
{
int fd, ret;
char *filename;
char readbuf[100], writebuf[100];
if (argc != 3)
{
printf("Usage:\n");
printf("\n");
return -1;
}
filename = argv[1];
fd = open(filename, O_RDWR);
if (fd < 0)
{
printf("open file %s fail\n", filename);
}
if (!strcmp(argv[2], "read"))
{
read(fd, readbuf, sizeof(readbuf));
printf("read %s from kernel\n", readbuf);
}
else if (!strcmp(argv[2], "write"))
{
memcpy(writebuf, userdata, sizeof(writebuf));
write(fd, writebuf, sizeof(writebuf));
printf("write %s to kernel\n", writebuf);
}
else
{
printf("UnKnow Command, please inter for help\n");
}
}
利用交叉编译器编译,本虚拟机上安装的是arm-linux-gnueabihf-gcc,输入如下指令:
arm-linux-gnueabihf-gcc chrdevbaseApp.c -o chrdevbaseApp
三、运行测试
在此之前,设置好开发板的linux系统通过TFTP从网络启动,并使用NFS挂载网络根文件系统。
3.1、加载驱动模块
这里使用modprobe命令加载驱动模块,首先要看板子根文件系统中有没有/lib/modules/<kernel version>,该目录是存放驱动模块的,kernel version根据所使用的内核版本设置,比如我现在用的是4.1.15的内核,因此创建一个/lib/modules/4.1.15,然后把chrdevbase.ko和chrdevbaseAPP复制到此目录下。
之前已经通过NFS将Ubuntu中的rootfs目录挂载为根文件系统,所以复制到rootfs/lib/modules/4.1.15目录中。
sudo cp chrdevbase.ko chrdevbaseApp /home/zk/linux/nfs/rootfs/lib/modules/4.1.15/ -f
在板子上查看文件:
用modprobe chrdevbase.ko加载驱动:
提示没找到modules.dep文件,这里直接输入depmod即可自动生成,然后重新加载,显示加载成功:
输入lsmod查看当前系统中存在的模块:
再查看下当前系统中有没有chrdevbase这个设备:
3.2、创建设备节点
因为应用程序时通过操作设备节点来完成对具体设备的操作,因此驱动加载成功后,需要在/dev目录下创建一个对应的设备节点文件。
mknod /dev/chrdevbase c 200 0
mknod:创建节点命令
/dev/chrdevbase:节点文件
c:字符设备
200:主设备号
0:次设备号
查看下:
3.3、测试
3.4、卸载驱动模块
输入如下指令卸载设备: