驱动程序固定格式
- 依赖<linux/init.h>和<linux/module.h>
- 入口函数 module_init(fun_init) 是int静态函数
- 出口函数 module_exit(fun_exit) 是voidt静态函数
- 许可证 MODULE_LICCENSE("GPL")
固定格式
c
复制代码
#include <linux/init.h>
#include <linux/module.h>
// 入口文件
static int __init fun_init(void){
return 0;
}
module_init(fun_init);
// 出口文件
static int __exit fun_exit(void){
return 0;
}
module_exit(fun_exit);
MODULE_LICCENSE("GPL");
通用编译文件
- 编译指令 make arch=x86/arm modename=1_hello(文件名不带后缀)
- 清除编译后文件 make clean
- 加载模块 sudo insmod 1_hello.ko
- 加载模块和依赖 sudo modprobe -a 1_hello.ko // -r卸载 -l列出已加载 -v显示详细信息 -a加载所有依赖 -F显示模块配置
- 卸载模块 sudo rmmod 1_hello.ko
- 查看模块信息 sudo lsmod 1_hello.ko
- 查看调试信息(后十条,后创建的在后) dmesg | tail
- 查看驱动程序是否在运行(前十条,后创建的在前) lsmod | head
sh
复制代码
#arch 如果是第一次出现 赋值 arm架构,如果不是 命令行有参数传进来 按照参数指定
arch ?= arm
modename ?= test
ifeq ($(arch),arm)
#源代码目录准备
#板载源代码
KERNELDIR:=/home/linux/quDong/linux-5.4.31
else
#乌班图源代码
KERNELDIR:=/lib/modules/5.4.0-26-generic/build
endif
#执行一个shell 命令
PWD:=$(shell pwd)
#换行 tab键
#模块位置
all:
make -C $(KERNELDIR) M=$(PWD) modules
#依赖 命令
clean:
make -C $(KERNELDIR) M=$(PWD) clean
#最后形成的模块名字是什么?
#指定要构建的模块名字 对应的 1_hello这个源文件
obj-m:=$(modename).o
驱动程序内打印信息
- 格式 1是级别1~7,参数二打印内容,参数参以后都是打印内容的值,参数一和参数二没有逗号
- printk(1"xxxx%d",6);
- 等级可以不写,不写等级默认为4
- 在黑白终端中,要打印级别高于4级才显示
- 基本上和c语言的printf一样,只是多了个等级
c
复制代码
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
// 入口文件
static int __init fun_init(void)
{
printk(KERN_EMERG"1\n");
printk(KERN_ALERT "2\n");
printk(KERN_CRIT"3\n");
printk(KERN_ERR"4\n");
printk(KERN_WARNING"5\n");
printk(KERN_NOTICE"6\n");
printk(KERN_INFO"7\n");
printk(KERN_DEBUG"7\n");
return 0;
}
// 出口文件
static void __exit fun_exit(void)
{
printk("exit");
}
module_init(fun_init);
module_exit(fun_exit);
MODULE_LICENSE("GPL");
驱动程序传参
执行驱动程序时传递参数
- sudo insmod chuancan.ko a=10 b=500 // 指定上通过文件后x=值传递,可以传多个
- 只能传递基本类型(char,bool,int,long,short,byte,ushort,uint),数组array,字符串string(charp)
- 依赖头文件<linux/moduleparam.h>
- 接收基本类型 module_param
- 接收数组类型 module_param_array
- 接收字符串类型 module_param_string
- 接收函数的三个/四个参数 变量名称 变量类型 长度(基本类型没有) 权限(一般设置为0664)
- 写入参数说明MODULE_PARM_DESC(arr, "An array of integers")
- 查看参数说明指令 modinfo chuancan.ko
- 三种类型的参数指令 sudo insmod 4_params.ko a=123 b="hello" arr=10,20,30
c
复制代码
#include <linux/init.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
int a = 100;
module_param(a, int, 0664);
MODULE_PARM_DESC(a, "a说明");
// 修改:字符串参数使用字符数组
char b[100] = "test";
module_param_string(b, b, sizeof(b), 0664);
MODULE_PARM_DESC(b, "b说明");
// 数组参数
int arr[10] = {0}; // 最多接收10个元素
int arr_len = 0;
module_param_array(arr, int, &arr_len, 0664);
MODULE_PARM_DESC(arr, "arr说明");
// 入口函数
static int __init fun_init(void)
{
int i;
printk(KERN_INFO "a = %d, str = %s\n", a, b);
for (i = 0; i < arr_len; i++) {
printk(KERN_INFO "arr[%d] = %d\n", i, arr[i]);
}
return 0;
}
// 退出函数
static void __exit fun_exit(void)
{
printk(KERN_INFO "Module exit.\n");
}
module_init(fun_init);
module_exit(fun_exit);
MODULE_LICENSE("GPL");
导出符号表(通过导出符号表,实现驱动模块和驱动模块之间的函数相互调用)
- 依赖头文件<linux/export.h>
- 导出符号表会有部分安全问题,例如:符号重定义、版本依赖、版权问题、安全漏洞等
- 导出函数方法EXPORT_SYMBOL_GPL(funadd);//funadd是要导出的函数
操作顺序
安装顺序
- 安装导出模块 sudo insmod a.ko
- 查看 dmesg | tail
- 安装使用模块 sudo insmod b.ko
- 查看 dmesg | tail
卸载顺序
- 卸载导出模块 sudo rmmod b.ko
- 查看 dmesg | tail
- 卸载使用模块 sudo rmmod a.ko
- 查看 dmesg | tail
导出模块
c
复制代码
#include <linux/init.h>
#include <linux/module.h>
// 导出符号表头文件
#include <linux/export.h>
// a + b 的和
int funadd(int a, int b)
{
return a + b;
}
// 导出符号表
EXPORT_SYMBOL_GPL(funadd);
// 入口文件
static int __init fun_init(void)
{
printk("这里导出符号表funadd\n");
return 0;
}
// 出口文件
static void __exit fun_exit(void)
{
printk("exit");
}
module_init(fun_init);
module_exit(fun_exit);
MODULE_LICENSE("GPL");
使用模块
c
复制代码
#include <linux/init.h>
#include <linux/module.h>
int funadd(int a,int b);
// 入口文件
static int __init fun_init(void)
{
printk("这里使用符号表funadd\n");
printk("res = %d\n",funadd(100,200));
return 0;
}
// 出口文件
static void __exit fun_exit(void)
{
printk("exit");
}
module_init(fun_init);
module_exit(fun_exit);
MODULE_LICENSE("GPL");
应用程序调用字符设备驱动程序的函数
- 要给驱动程序分配一个主设备号major
- 分配设备号依赖头文件<linux/fs.h>
- 可以调用api生成,不会重复
- 入口程序生成字符设备驱动函数主设备号
- register_chrdev(unsigned int major, const char *name,const struct file_operations *fops)
- 出口函数释放设备号
- 创建文件操作结构体 struct file_operations {}
- 文件操作结构体有一大堆成员函数,常用的四个开关读写open、release、read、write
运行流程
- 编译驱动程序,编译程序里需要打印主设备号
- 安装驱动程序 sudo insmod cdev.ko
- 查看信息,看看主设备号是多少 dmesg | tail
- 根据主设备号手动创建设备节点(也可以在代码里创建) sudo mknod /dev/mycdev c 240 0
- 编译应用程序,在编译程序调用对应方法,看是否会执行驱动程序的代码(可以在驱动程序增加打印)
驱动程序源码
c
复制代码
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/moduleparam.h>
#include <linux/fs.h>
int major; // 主设备号
#define CNAME "chardev" // 字符设备驱动名称
int myopen(struct inode *node, struct file *file)
{
printk("this is my %s test \n", __func__);
return 0;
}
int myclose(struct inode *node, struct file *file)
{
printk("this is my%s\n", __func__);
return 0;
}
ssize_t myread(struct file *file, char __user *ubuf, size_t size, loff_t *offs)
{
printk("this is my%s\n", __func__);
return 0;
}
ssize_t mywrite(struct file *file, const char __user *ubuf, size_t size, loff_t *offs)
{
printk("this is my%s\n", __func__);
return 0;
}
const struct file_operations fop = {
.open = myopen,
.release = myclose,
.write = mywrite,
.read = myread
};
static int __init fun_init(void)
{
printk("this is %s-%d test\n", __func__, __LINE__);
major = register_chrdev(0, CNAME, &fop);
if (major < 0)
{
printk("字符设备注册失败\n");
return -1;
}
else
{
printk("字符设备注册成功,major:%d\n", major); // 打印主设备号
}
return 0;
}
static void __exit fun_exit(void)
{
unregister_chrdev(major, CNAME);
printk("this is %s %d exit\n", __func__, __LINE__);
}
module_init(fun_init);
module_exit(fun_exit);
// 证书信息
MODULE_LICENSE("GPL");
应用程序源代码
c
复制代码
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/fcntl.h>
int main()
{
int fd = open("/dev/mycdev", O_RDWR);
if (fd < 0)
{
perror("open error\n");
}
printf("open /dev/mycdevs success\n");
char buf[128] = "Write succ";
write(fd, buf, strlen(buf));
close(fd);
return 0;
}
驱动程序和应用程序相互传递数据
- 依赖头文件#include <linux/uaccess.h>
- 在驱动文件的write函数中调用copy_from_user函数将应用程序的数据传递到驱动程序
- 在驱动文件的read函数中调用copy_to_user函数将驱动程序的数据传递到应用程序
c
复制代码
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/moduleparam.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
int major; // 主设备号
#define CNAME "chardev" // 字符设备驱动名称
// 内核缓冲区
char kbuf[128] = "";
int myopen(struct inode *node, struct file *file)
{
printk("this is my %s test \n", __func__);
return 0;
}
int myclose(struct inode *node, struct file *file)
{
printk("this is my%s\n", __func__);
return 0;
}
ssize_t myread(struct file *file, char __user *ubuf, size_t size, loff_t *offs)
{
int ret;
printk("this is my%s\n", __func__);
if (size > sizeof(kbuf))
{
size = sizeof(kbuf);
}
ret = copy_to_user(ubuf, kbuf, size);
if (ret < 0)
{
printk("copy to user error\n");
return -EIO;
}
return size;
}
ssize_t mywrite(struct file *file, const char __user *ubuf, size_t size, loff_t *offs)
{
int ret;
printk("this is my%s\n", __func__);
if (size > sizeof(kbuf))
{
size = sizeof(kbuf);
}
ret = copy_from_user(kbuf, ubuf, size);
if (ret < 0)
{
printk("copy from user error\n");
return -EIO;
}
return size;
}
const struct file_operations fop = {
.open = myopen,
.release = myclose,
.write = mywrite,
.read = myread};
static int __init fun_init(void)
{
printk("this is %s-%d test\n", __func__, __LINE__);
major = register_chrdev(0, CNAME, &fop);
if (major < 0)
{
printk("字符设备注册失败\n");
return -1;
}
else
{
printk("字符设备注册成功,major:%d\n", major);
}
return 0;
}
static void __exit fun_exit(void)
{
unregister_chrdev(major, CNAME);
printk("this is %s %d exit\n", __func__, __LINE__);
}
module_init(fun_init);
module_exit(fun_exit);
// 证书信息
MODULE_LICENSE("GPL");
代码中创建设备节点
- 依赖头文件<linux/device.h>和<linux/fs.h>
- 创建目录指针struct class *cls 和设备文件指针 struct device *dev
- 在入口函数中,先创建目录class_create(THIS_MODULE,CNAME)// 参数一当前内核模块(基本上固定是THIS_MODULE),参数二类名
- 生成32位综合设备号MKDEV(major,0);//参数1:设备号,参数2:次设备号
- 在入口函数中,再创建设备文件device_create(cls,NULL,MKDEV(major,0),NULL,CNAME),参数1:父目录指针,参数2:一般NULL,参数3:设备号,参数4:传递的数据,参数5:文件名称
- 设备文件创建失败要销毁类class_destroy(cls)
- 出口函数要先销毁设备再销毁类
- 应用程序open的设备文件名要和驱动程序用代码创建的文件名一致
c
复制代码
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/moduleparam.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/device.h>
#include <linux/errno.h>
int major; // 主设备号
#define CNAME "chardev" // 字符设备驱动名称
// 内核缓冲区
char kbuf[128] = "";
int myopen(struct inode *node, struct file *file)
{
printk("this is my %s test \n", __func__);
return 0;
}
int myclose(struct inode *node, struct file *file)
{
printk("this is my%s\n", __func__);
return 0;
}
ssize_t myread(struct file *file, char __user *ubuf, size_t size, loff_t *offs)
{
int ret;
printk("this is my%s\n", __func__);
if (size > sizeof(kbuf))
{
size = sizeof(kbuf);
}
ret = copy_to_user(ubuf, kbuf, size);
if (ret < 0)
{
printk("copy to user error\n");
return -EIO;
}
return size;
}
ssize_t mywrite(struct file *file, const char __user *ubuf, size_t size, loff_t *offs)
{
int ret;
printk("this is my%s\n", __func__);
if (size > sizeof(kbuf))
{
size = sizeof(kbuf);
}
ret = copy_from_user(kbuf, ubuf, size);
if (ret < 0)
{
printk("copy from user error\n");
return -EIO;
}
return size;
}
const struct file_operations fop = {
.open = myopen,
.release = myclose,
.write = mywrite,
.read = myread
};
// 创建设备目录指针
struct class *cls;
// 创建设备文件指针
struct device *dev;
static int __init fun_init(void)
{
printk("this is %s-%d test\n", __func__, __LINE__);
major = register_chrdev(0, CNAME, &fop);
if (major < 0)
{
printk("字符设备注册失败\n");
return -1;
}
printk("字符设备注册成功,major:%d\n", major);
cls = class_create(THIS_MODULE,CNAME);
if(IS_ERR(cls)){
printk("class create error\n");
return PTR_ERR(cls);
}
// 创建设备文件
dev= device_create(cls,NULL,MKDEV(major,0),NULL,CNAME);
if(IS_ERR(dev)){
// 销毁目录
class_destroy(cls);
// 注销
unregister_chrdev(major,CNAME);
printk("class create error\n");
return PTR_ERR(dev);
}
printk("设备目录和设备文件创建成功\n");
return 0;
}
static void __exit fun_exit(void)
{
unregister_chrdev(major, CNAME);
class_destroy(cls);
printk("this is %s %d exit\n", __func__, __LINE__);
}
module_init(fun_init);
module_exit(fun_exit);
// 证书信息
MODULE_LICENSE("GPL");
驱动程序操作寄存器
- 依赖头文件<linux/io.h>
- 通过void *ioremap(unsigned long phys_addr, unsigned long size)拿到寄存器指针,参数1物理地址,参数2大小
c
复制代码
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/moduleparam.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/device.h>
#include <linux/errno.h>
#include <linux/io.h>
// 内核缓冲区
char kbuf[128] = "";
#define RCC_BASE 0x50000000 // AHB4总线 GPIOE
#define AHB4_CLOCK_ENABLE (RCC_BASE+0xA28) // AHB4总线地址
// PE10 - LED1
#define GPIOE_BASE 0x50006000
#define GPIO_MODER (GPIOE_BASE+0x00) // 模式寄存器
#define GPIO_OPTYE (GPIOE_BASE+0x04) // 输出类型寄存器
#define GPIO_OUTDATA (GPIOE_BASE+0x14) // 输出数据寄存器
unsigned int *ahb4_clock_enable = NULL;
unsigned int *gpioe_mode = NULL;
unsigned int *gpioe_10_odr = NULL;
int major; // 主设备号
#define CNAME "leddev" // 字符设备驱动名称
int myopen(struct inode *node, struct file *file)
{
printk("this is my %s test \n", __func__);
// 在open函数中完成映射
// GPIOE时钟 ok
ahb4_clock_enable = (unsigned int *)ioremap(AHB4_CLOCK_ENABLE,sizeof(unsigned int));
if(ahb4_clock_enable == NULL){
printk("ioremap clock error");
return -1;
}
// 驱动第四个位置的GPIOE
*ahb4_clock_enable |= (1 << 4);
printk("rcc_ahb4_clockenable success %d\n",__LINE__);
// 在write函数中 完成控制
gpioe_mode = (unsigned int *)ioremap(GPIO_MODER,sizeof(unsigned int));
if(gpioe_mode == NULL ){
printk("gpioe_mode error\n");
return -1;
}
printk("gpioe_mode set success %d\n",__LINE__);
// 模式寄存器:01输出模式
*gpioe_mode &=~(0x11<<(2*10)); //清空初始化
// 赋值0x11
*gpioe_mode|=0x11<<(2*10);
// 输出数据寄存器
gpioe_10_odr = (unsigned int *)ioremap(GPIO_OUTDATA,sizeof(unsigned int));
if(gpioe_10_odr==NULL){
printk("gpioe_10_odr error\n");
return -1;
}
printk("gpioe_10_odr set success%d\n",__LINE__);
// 输出数据,默认给低电平
*gpioe_10_odr &= ~(0x1<<10); // 关灯
// *gpioe_10_odr |=(0x1 << 10);
return 0;
}
int myclose(struct inode *node, struct file *file)
{
printk("this is my%s\n", __func__);
return 0;
}
ssize_t myread(struct file *file, char __user *ubuf, size_t size, loff_t *offs)
{
int ret;
printk("this is my%s\n", __func__);
if (size > sizeof(kbuf))
{
size = sizeof(kbuf);
}
ret = copy_to_user(ubuf, kbuf, size);
if (ret < 0)
{
printk("copy to user error\n");
return -EIO;
}
return size;
}
ssize_t mywrite(struct file *file, const char __user *ubuf, size_t size, loff_t *offs)
{
int ret;
printk("this is my%s\n", __func__);
if (size > sizeof(kbuf))
{
size = sizeof(kbuf);
}
ret = copy_from_user(kbuf, ubuf, size);
if (ret < 0)
{
printk("copy from user error\n");
return -EIO;
}
// 判断kbuf当中是否有数据来开关led
if(kbuf[0]==1){
*gpioe_10_odr |=(0x1<<10);
}else{
*gpioe_10_odr &= ~(0x1 << 10);
}
return size;
}
const struct file_operations fop = {
.open = myopen,
.release = myclose,
.write = mywrite,
.read = myread
};
// 创建设备目录指针
struct class *cls;
// 创建设备文件指针
struct device *dev;
static int __init fun_init(void)
{
printk("this is %s-%d test\n", __func__, __LINE__);
major = register_chrdev(0, CNAME, &fop);
if (major < 0)
{
printk("字符设备注册失败\n");
return -1;
}
printk("字符设备注册成功,major:%d\n", major);
cls = class_create(THIS_MODULE,CNAME);
if(IS_ERR(cls)){
printk("class create error\n");
return PTR_ERR(cls);
}
// 创建设备文件
dev= device_create(cls,NULL,MKDEV(major,0),NULL,CNAME);
if(IS_ERR(dev)){
// 销毁目录
class_destroy(cls);
// 注销
unregister_chrdev(major,CNAME);
printk("class create error\n");
return PTR_ERR(dev);
}
printk("设备目录和设备文件创建成功\n");
return 0;
}
static void __exit fun_exit(void)
{
unregister_chrdev(major, CNAME);
class_destroy(cls);
printk("this is %s %d exit\n", __func__, __LINE__);
}
module_init(fun_init);
module_exit(fun_exit);
// 证书信息
MODULE_LICENSE("GPL");