linux驱动程序

驱动程序固定格式

  1. 依赖<linux/init.h>和<linux/module.h>
  2. 入口函数 module_init(fun_init) 是int静态函数
  3. 出口函数 module_exit(fun_exit) 是voidt静态函数
  4. 许可证 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");

通用编译文件

  1. 编译指令 make arch=x86/arm modename=1_hello(文件名不带后缀)
  2. 清除编译后文件 make clean
  3. 加载模块 sudo insmod 1_hello.ko
  4. 加载模块和依赖 sudo modprobe -a 1_hello.ko // -r卸载 -l列出已加载 -v显示详细信息 -a加载所有依赖 -F显示模块配置
  5. 卸载模块 sudo rmmod 1_hello.ko
  6. 查看模块信息 sudo lsmod 1_hello.ko
  7. 查看调试信息(后十条,后创建的在后) dmesg | tail
  8. 查看驱动程序是否在运行(前十条,后创建的在前) 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");

驱动程序传参

执行驱动程序时传递参数

  1. sudo insmod chuancan.ko a=10 b=500 // 指定上通过文件后x=值传递,可以传多个
  2. 只能传递基本类型(char,bool,int,long,short,byte,ushort,uint),数组array,字符串string(charp)
  3. 依赖头文件<linux/moduleparam.h>
  4. 接收基本类型 module_param
  5. 接收数组类型 module_param_array
  6. 接收字符串类型 module_param_string
  7. 接收函数的三个/四个参数 变量名称 变量类型 长度(基本类型没有) 权限(一般设置为0664)
  8. 写入参数说明MODULE_PARM_DESC(arr, "An array of integers")
  9. 查看参数说明指令 modinfo chuancan.ko
  10. 三种类型的参数指令 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");

导出符号表(通过导出符号表,实现驱动模块和驱动模块之间的函数相互调用)

  1. 依赖头文件<linux/export.h>
  2. 导出符号表会有部分安全问题,例如:符号重定义、版本依赖、版权问题、安全漏洞等
  3. 导出函数方法EXPORT_SYMBOL_GPL(funadd);//funadd是要导出的函数

操作顺序

  • 安装要先安装导出模块再安装使用模块,卸载要相反
安装顺序
  1. 安装导出模块 sudo insmod a.ko
  2. 查看 dmesg | tail
  3. 安装使用模块 sudo insmod b.ko
  4. 查看 dmesg | tail
卸载顺序
  1. 卸载导出模块 sudo rmmod b.ko
  2. 查看 dmesg | tail
  3. 卸载使用模块 sudo rmmod a.ko
  4. 查看 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");

应用程序调用字符设备驱动程序的函数

  1. 要给驱动程序分配一个主设备号major
  2. 分配设备号依赖头文件<linux/fs.h>
  3. 可以调用api生成,不会重复
  4. 入口程序生成字符设备驱动函数主设备号
  • register_chrdev(unsigned int major, const char *name,const struct file_operations *fops)
  1. 出口函数释放设备号
  2. 创建文件操作结构体 struct file_operations {}
  3. 文件操作结构体有一大堆成员函数,常用的四个开关读写open、release、read、write

运行流程

  1. 编译驱动程序,编译程序里需要打印主设备号
  2. 安装驱动程序 sudo insmod cdev.ko
  3. 查看信息,看看主设备号是多少 dmesg | tail
  4. 根据主设备号手动创建设备节点(也可以在代码里创建) sudo mknod /dev/mycdev c 240 0
  5. 编译应用程序,在编译程序调用对应方法,看是否会执行驱动程序的代码(可以在驱动程序增加打印)

驱动程序源码

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;
}

驱动程序和应用程序相互传递数据

  1. 依赖头文件#include <linux/uaccess.h>
  2. 在驱动文件的write函数中调用copy_from_user函数将应用程序的数据传递到驱动程序
  3. 在驱动文件的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");

代码中创建设备节点

  1. 依赖头文件<linux/device.h>和<linux/fs.h>
  2. 创建目录指针struct class *cls 和设备文件指针 struct device *dev
  3. 在入口函数中,先创建目录class_create(THIS_MODULE,CNAME)// 参数一当前内核模块(基本上固定是THIS_MODULE),参数二类名
  4. 生成32位综合设备号MKDEV(major,0);//参数1:设备号,参数2:次设备号
  5. 在入口函数中,再创建设备文件device_create(cls,NULL,MKDEV(major,0),NULL,CNAME),参数1:父目录指针,参数2:一般NULL,参数3:设备号,参数4:传递的数据,参数5:文件名称
  6. 设备文件创建失败要销毁类class_destroy(cls)
  7. 出口函数要先销毁设备再销毁类
  8. 应用程序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");

驱动程序操作寄存器

  1. 依赖头文件<linux/io.h>
  2. 通过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");
相关推荐
SoniaChen331 小时前
Rust基础-part3-函数
开发语言·后端·rust
全干engineer1 小时前
Flask 入门教程:用 Python 快速搭建你的第一个 Web 应用
后端·python·flask·web
William一直在路上1 小时前
SpringBoot 拦截器和过滤器的区别
hive·spring boot·后端
小马爱打代码2 小时前
Spring Boot 3.4 :@Fallback 注解 - 让微服务容错更简单
spring boot·后端·微服务
曾曜3 小时前
PostgreSQL逻辑复制的原理和实践
后端
豌豆花下猫3 小时前
Python 潮流周刊#110:JIT 编译器两年回顾,AI 智能体工具大爆发(摘要)
后端·python·ai
轻语呢喃3 小时前
JavaScript :事件循环机制的深度解析
javascript·后端
ezl1fe3 小时前
RAG 每日一技(四):让AI读懂你的话,初探RAG的“灵魂”——Embedding
后端
经典19923 小时前
spring boot 详解以及原理
java·spring boot·后端
Aurora_NeAr3 小时前
Apache Iceberg数据湖高级特性及性能调优
大数据·后端