新建一个VScode文档,先配置开发环境
Ctrl+Shift+P//打开控制台
C/C++:Edit configuration(JSON)
在生成的c_cpp_properties.json中添加Linux开发需要包含的头文件
{
"configurations": [
{
"name": "Linux",
"includePath": [
"${workspaceFolder}/**",
"/home/lux/Linux/linux/linux-lux/include",
"/home/lux/Linux/linux/linux-lux/arch/arm/include",
"/home/lux/Linux/linux/linux-lux/arch/arm/include/generated"
],
"defines": [],
"compilerPath": "/usr/bin/gcc",
"cStandard": "c11",
"cppStandard": "c++17",
"intelliSenseMode": "clang-x64"
}
],
"version": 4
}
编写驱动程序chrdevbase.c
#include
#include
#include
#include
#include
#include
#define CHRDEVBASE_MAJOR 200 //主设备号
#define CHRDEVBASE_NAME "chrdevbase" //设备名
static char readbuff[100];//读缓冲区
static char writebuff[100];//写缓冲区
static char kerneldata[]={"kernel data!"};
static int chrdevbase_open(struct inode *inode,struct file *filp)
{
return 0;
}
static ssize_t chrdevbase_read(struct file *filp,char __user *buf,size_t cnt,loff_t *offt)
{
int retvalue = 0;
memcpy(readbuff,kerneldata,sizeof(kerneldata));
retvalue = copy_to_user(buf,readbuff,cnt);
if (retvalue == 0)
{
printk("kernel senddata ok!\r\n");
}
else
{
printk("Kernek senddata failed!\r\n");
}
return 0;
}
static ssize_t chrdevbase_write(struct file *filp,
const char __user *buf,
size_t cnt,loff_t *offt)
{
int retvalue = 0;
retvalue = copy_from_user(writebuff,buf,cnt);
if (retvalue == 0)
{
printk("kernel recevdata:%s\r\n",writebuff);
}
else
{
printk("kernel recevdata failed!\r\n");
}
return 0;
}
static int chrdevbase_release(struct inode *inode,struct file *filp)
{
return 0;
}
static 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 retvalue = 0;
retvalue = register_chrdev(CHRDEVBASE_MAJOR,CHRDEVBASE_NAME,&chrdevbase_fops);
if (retvalue < 0)
{
printk("chrdevbase driver register failed\r\n");
}
printk("chrdevbase init done!\r\n");
return 0;
}
static void __exit chrdevbase_exit(void)
{
unregister_chrdev(CHRDEVBASE_MAJOR,CHRDEVBASE_NAME);
printk("chrdevbase_exit done!\r\n");
}
module_init(chrdevbase_init);
module_exit(chrdevbase_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Lux");
字符设备注册函数
static int __init chrdevbase_init(void)
{
}
module_init(chrdevbase_init);
字符设备注销函数
static void __exit chrdevbase_exit(void)
{
}
module_exit(chrdevbase_exit);
添加LICENSE和作者信息
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Lux");
动态分配设备号
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)
/*
函数 alloc_chrdev_region 用于申请设备号,此函数有 4 个参数:
dev:保存申请到的设备号。
baseminor: 次设备号起始地址, alloc_chrdev_region 可以申请一段连续的多个设备号,这
些设备号的主设备号一样,但是次设备号不同,次设备号以 baseminor 为起始地址地址开始递
增。一般 baseminor 为 0,也就是说次设备号从 0 开始。
count: 要申请的设备号数量。
name:设备名字
*/
释放设备号
void unregister_chrdev_region(dev_t from, unsigned count)
/*
此函数有两个参数:
from:要释放的设备号。
count: 表示从 from 开始,要释放的设备号数量。
*/
编写应用程序chrdevbaseApp.c
#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
//数组 usrdata 是测试 APP 要向 chrdevbase 设备写入的数据
static char usrdata[] = {"user data!"};
int main(int argc, char const *argv[])
{
int fd,retvalue;
char *filename;
char readbuf[100],writebuf[100];
/*判断运行测试APP的时候输入的参数是不是为3个,main函数的argc参数表示参数数量,argv[]保存着具体的参数,如果参数不为 3 个的话就表示测试APP用法错误
第一个参数表示运行chrdevbaseAPP这个软件,
第二个参数表示测试APP要打开/dev/chrdevbase这个设备。
第三个参数就是要执行的操作,1表示从chrdevbase中读取数据,2表示向chrdevbase 写数据。*/
if(argc != 3)
{
printf("Error Usage!\r\n");
return -1;
}
//获取要打开的设备文件名字, argv[1]保存着设备名字
filename = argv[1];
//调用 C 库中的 open 函数打开设备文件: /dev/chrdevbase。
fd = open(filename,O_RDWR);
if (fd < 0)
{
printf("Can not open file %s\r\n",filename);
return -1;
}
/*判断 argv[2]参数的值是 1 还是 2,因为输入命令的时候其参数都是字符串格式的,因此需要借助 atoi 函数将字符串格式的数字转换为真实的数字
当 argv[2]为 1 的时候表示要从 chrdevbase 设备中读取数据,一共读取 50 字节的数据,读取到的数据保存在 readbuf 中,读取成功以后就在终端上打印 出读取到的数据。*/
if (atoi(argv[2])==1)
{
retvalue = read(fd,readbuf,50);
if (retvalue < 0)
{
printf("read file %s failed\r\n",filename);
}
else
{
printf("read data %s\r\n",readbuf);
}
}
/*当 argv[2]为 2 的时候表示要向 chrdevbase 设备写数据。*/
if (atoi(argv[2])==2)
{
memcpy(writebuf,usrdata,sizeof(usrdata));
retvalue = write(fd,writebuf,50);
if (retvalue < 0)
{
printf("wtite file %s\r\n",filename);
}
}
//对 chrdevbase 设备操作完成以后就关闭设备。
retvalue = close(fd);
if (retvalue < 0)
{
printf("can not open file %s \r\n",filename);
return -1;
}
return 0;
}
编译驱动程序
将.c文件编译为.ko的模块文件
创建Makefile文件
#KERNELDIR 表示开发板所使用的 Linux 内核源码目录,使用绝对路径,
KERNELDIR := /home/lux/Linux/linux/linux-lux
#CURRENT_PATH 表示当前路径,直接通过运行“pwd”命令来获取
CURRENT_PATH := $(shell pwd)
#obj-m 表示将 chrdevbase.c 这个文件编译为 chrdevbase.ko 模块
obj-m := chrdevbase.o
build: kernel_modules
#具体的编译命令,后面的 modules 表示编译模块, -C 表示将当前的工作目录切换到指定目录中,也就是 KERNERLDIR 目录。
#M 表示模块源码目录,“make modules”命令中加入 M=dir 以后程序会自动到指定的 dir 目录中读取模块的源码并将其编译为.ko 文件
kernel_modules:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
编译应用程序
arm-linux-gnueabihf-gcc chrdevbaseApp.c -o chrdevbaseApp
查看文件类型
file chrdevbaseApp
测试程序
为了方便测试, Linux 系统选择通过 TFTP 从网络启动,并且使用 NFS 挂载网络根文件系统,确保 uboot 中 bootcmd 环境变量的值为:
tftp 80800000 zImage;tftp 83000000 imx6ull-lux-emmc.dtb;bootz 80800000 - 83000000
bootargs 环境变量的值为:
console=ttymxc0,115200 root=/dev/nfs rw nfsroot=192.168.31.224:/home/lux/Linux/nfs/lux_rootfs ip=192.168.31.55:192.168.31.224:192.168.31.1:255.255.255.0::eth0:off
设置好以后启动 Linux 系统,检查开发板根文件系统中有没有“/lib/modules/4.1.15”这个目录,如果没有的话自行创建。 注意,“/lib/modules/4.1.15”这个目录用来存放驱动模块,使用modprobe 命令加载驱动模块的时候,驱动模块要存放在此目录下。“/lib/modules”是通用的,不管你用的什么板子、什么内核,这部分是一样的。不一样的是后面的“4.1.15”,这里要根据你所使用的 Linux 内核版本来设置否则 modprobe 命令无法加载驱动模块
将 chrdevbase.ko 和 chrdevbaseAPP 复制到 rootfs/lib/modules/4.1.15 目录中,命令如下:
sudo cp chrdevbase.ko chrdevbaseApp /home/lux/Linux/nfs/lux_rootfs/lib/modules/4.1.15/ -f
在开发板终端安装模块
使用insmod安装
使用rmmod卸载
使用modprobe安装,如果报错"can`t open ‘modules.dep’:no such file or directory",执行
depmod//即可
安装完成
查看当前系统安装的模块
lsmod
查看当前系统中有没有chrdevbase这个设备
cat /proc/devices
创建设备结点文件(应用程序就是通过操作这个设备节点文件来完成对具体设备的操作 )
mknod /dev/chrdevbase c 200 0
其中“mknod”是创建节点命令,“/dev/chrdevbase”是要创建的节点文件,“c”表示这是个字符设备,“ 200”是设备的主设备号,“ 0”是设备的次设备号。创建完成以后就会存在/dev/chrdevbase 这个文件,可以使用“ls /dev/chrdevbase -l”命令查看
应用程序测试
读操作
./chrdevbaseApp /dev/chrdevbase 1
写操作
./chrdevbaseApp /dev/chrdevbase 2
卸载模块
rmmod chrdevbase.ko