Linux驱动开发学习笔记1《字符设备驱动开发》

news2025/1/25 9:02:44

目录

一、字符设备驱动简介

二、chrdevbase 字符设备驱动开发实验 

1.创建驱动程序的目录

2.创建vscode工程 

3.编写实验程序

4.编译驱动程序和测试APP代码

(1)加载驱动模块

(2)创建设备节点文件

(3)chrdevbase 设备操作测试

(4)卸载驱动模块


一、字符设备驱动简介

        字符设备是Linux 驱动中最基本的一类设备驱动,字符设备就是一个一个字节,按照字节流进行读写操作的设备,读写数据是分先后顺序的。比如我们最常见的点灯、按键、IIC、SPI,LCD 等等都是字符设备,这些设备的驱动就叫做字符设备驱动。

二、chrdevbase 字符设备驱动开发实验 

        本节我们就以chrdevbase 这个虚拟设备为例,完整的编写一个字符设备驱动模块。chrdevbase 不是实际存在的一个设备,是为了方便讲解字符设备的开发而引入的一个虚拟设备。chrdevbase 设备有两个缓冲区,一个为读缓冲区,一个为写缓冲区,这两个缓冲区的大小都为100 字节。在应用程序中可以向chrdevbase 设备的写缓冲区中写入数据,从读缓冲区中读取数据。chrdevbase 这个虚拟设备的功能很简单,但是它包含了字符设备的最基本功能。

1.创建驱动程序的目录

2.创建vscode工程 

 将工作区另存为chrdevbase

使用命令code . 进入vscode

因为是编写Linux 驱动,因此会用到Linux 源码中的函数。我们需要在VSCode 中添加Linux
源码中的头文件路径。打开VSCode,按下“Crtl+Shift+P”打开VSCode 的控制台,然后输入
“C/C++: Edit configurations(UI) ”,打开C/C++编辑配置文件,如下图所示:

 

打开以后会自动在.vscode 目录下生成一个名为c_cpp_properties.json 的文件,此文件修改为如下所示如下所示: 

{
    "configurations": [
        {
            "name": "Linux",
            "includePath": [
                "${workspaceFolder}/**",
                "/home/ssz/linux/IMX6ULL/linux/temp/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek/include",
                "/home/ssz/linux/IMX6ULL/linux/temp/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek/arch/arm/include",
                "/home/ssz/linux/IMX6ULL/linux/temp/linux-imx-rel_imx_4.1.15_2.1.0_ga_alientek/arch/arm/include/generated"
            ],
            "defines": [],
            "compilerPath": "/usr/bin/gcc",
            "cStandard": "c11",
            "cppStandard": "c++17",
            "intelliSenseMode": "clang-x64"
        }
    ],
    "version": 4
}

上面includePath 表示头文件路径,需要将Linux 源码里面的头文件路径添加进来,也就是我们前面移植的Linux 内核源码中的头文件路径。

3.编写实验程序

创建chrdevbase.c驱动程序,代码如下:

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>


#define CHRDEVBASE_MAJOR 200 //主设备号
#define CHRDEVBASE_NAME "chrdevbase" //设备名

static char readbuf[100]; //读缓冲区
static char writebuf[100]; //写缓冲区
static char kerneldata[] = {"kernel data!"};

//打开设备
static int chardevbase_open(struct inode *inode, struct file *filp)
{
    printk("chrdevbase open!\r\n");
    return 0;
}
//向设备读取数据
static ssize_t chrdevbase_read(struct file *filp, char __user *buf, size_t cnt, loff_t *off_t)
{
    int retvalue = 0;

    //向用户空间发送数据
    memcpy(readbuf, kerneldata, sizeof(kerneldata));
    //内核到用户
    retvalue = copy_to_user(buf, readbuf, cnt);
    if(retvalue == 0)
    {
        printk("kernel sendata ok!\r\n");
    }
    else
    {
        printk("kernel senddata failed!\r\n");
    }

    //printk("chrdevbase read!\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(writebuf, buf, cnt);
    if(retvalue == 0)
    {
        printk("kernel recevdata:%s\r\n",writebuf);
    }
    else
    {
        printk("kernel recedata failed!\r\n");
    }

    //printk("chrdevbase write!\r\n");
    return 0;
}
//关闭/释放设备
static int chrdevbase_release(struct inode *inode, struct file *filp)
{
    //printk("chrdevbase release!\r\n");
    return 0;
}


static struct file_operations chrdevbase_fops = {
    .owner = THIS_MODULE,
    .open = chardevbase_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()\r\n");
    return 0;
}
static void __exit chrdevbase_exit(void)
{
    //注销字符设备驱动
    unregister_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME);
    printk("chrdevbase_exit()\r\n");
}
/*
驱动模块入口与出口
*/
module_init(chrdevbase_init);//入口
module_exit(chrdevbase_exit);//出口

//LICENSE和作者信息
MODULE_LICENSE("GPL");
MODULE_AUTHOR("ssz");

创建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"

static char usrdate[] = {"usr data!"};

int main(int argc, char *argv[])
{
    int fd,retvalue;
    char *filename;
    char readbuf[100],writebuf[100];

    if(argc != 3)
    {
        printf("Error Usage!\r\n");
        return -1;
    }

    filename = argv[1];

    //打开驱动文件
    fd = open(filename, O_RDWR);
    if(fd < 0)
    {
        printf("Cant't open file %s\r\n",filename);
        return -1;
    }
    if(atoi(argv[2]) == 1)
    {
        //从驱动文件读取数据
        retvalue = read(fd, readbuf, 50);
        if(retvalue < 0)
        {
            printf("read file %s failed!\r\n",filename);
        }
        else
        {
            //读取成功,打印出读取成功的数据
           // printf("1111\n");
            printf("read data: %s \r\n",readbuf);
        }  
    }
    if(atoi(argv[2]) == 2)
    {
        //向设备驱动写数据
        memcpy(writebuf, usrdate, sizeof(usrdate));
        retvalue = write(fd, writebuf, 50);
        if(retvalue < 0)
        {
            printf("write file %s failed!\r\n",filename);
        }
    }
    retvalue = close(fd);
    if(retvalue < 0)
    {
        printf("Can't close file %s\r\n",filename);
        return -1;
    }
    return 0;
}

编译驱动程序,也就是chrdevbase.c 这个文件,我们需要将其编译为.ko 模块,创建
Makefile 文件,然后在其中输入如下内容:

KERNELDIR := /home/ssz/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

4.编译驱动程序和测试APP代码

编译驱动程序,输入make命令,会生成chrdevbase.ko文件:

编译测试APP:

使用file命令,chrdevbaseApp这个可执行文件是32 位LSB 格式,ARM 版本的,因此chrdevbaseApp只能在ARM 芯片下运行。

 运行测试:

(1)加载驱动模块

驱动模块chrdevbase.ko 和测试软件chrdevbaseAPP 都已经准备好了,接下来就是运行测试。为了方便测试,Linux 系统选择通过TFTP 从网络启动,并且使用NFS 挂载网络根文件系统,确保uboot 中bootcmd 和bootargs环境变量的值为(确保电脑与开发板通过网线连接):

setenv bootcmd 'tftp 80800000 zImage;tftp 83000000 imx6ull-alientek-emmc.dtb;bootz 80800000 - 83000000'
setenv bootargs 'console=ttymxc0,115200 root=/dev/nfs rw nfsroot=192.168.1.66:/home/ssz/linux/nfs/
rootfs ip=192.168.1.66:192.168.1.55:192.168.1.1:255.255.255.0::eth0:off'
saveenv

设置好以后启动Linux 系统,检查开发板根文件系统中有没有“/lib/modules/4.1.15”这个目录,如果没有的话自行创建。注意,“/lib/modules/4.1.15”这个目录用来存放驱动模块,使用modprobe 命令加载驱动模块的时候,驱动模块要存放在此目录下。“/lib/modules”是通用的,不管你用的什么板子、什么内核,这部分是一样的。不一样的是后面的“4.1.15”,这里要根据你所使用的Linux 内核版本来设置,比如ALPHA 开发板现在用的是4.1.15 版本的Linux 内核,因此就是“/lib/modules/4.1.15”。如果你使用的其他版本内核,比如5.14.31,那么就应该创建“/lib/modules/5.14.31”目录,否则modprobe 命令无法加载驱动模块。因为是通过NFS 将Ubuntu 中的rootfs(第三十八章制作好的根文件系统)目录挂载为根文件系统,所以可以很方便的将chrdevbase.ko 和chrdevbaseAPP 复制到rootfs/lib/modules/4.1.15 目录中,命令如下:

拷贝完成以后就会在开发板的/lib/modules/4.1.15 目录下存在chrdevbase.ko 和chrdevbaseAPP 这两个文件,如图所示: 

输入如下命令加载chrdevbase.ko 驱动文件: 

insmod chrdevbase.ko
或者
modprobe chrdevbase.ko

从上图 可以看出,modprobe 提示无法打开“modules.dep”这个文件,因此驱动挂载失败了。我们不用手动创建modules.dep 这个文件,直接输入depmod 命令即可自动生成modules.dep,有些根文件系统可能没有depmod 这个命令,如果没有这个命令就只能重新配置busybox,使能此命令,然后重新编译busybox。输入“depmod”命令以后会自动生成modules.alias、modules.symbols 和modules.dep 这三个文件,如下图所示:

驱动加载成功:

输入“lsmod”命令即可查看当前系统中存在的模块,结果如下图所示: 

 

从上图可以看出,当前系统只有“chrdevbase”这一个模块。输入如下命令查看当前系统中有没有chrdevbase 这个设备:

从上图可以看出,当前系统存在chrdevbase 这个设备,主设备号为200,跟我们设置
的主设备号一致。 

(2)创建设备节点文件

驱动加载成功需要在/dev 目录下创建一个与之对应的设备节点文件,应用程序就是通过操作这个设备节点文件来完成对具体设备的操作。输入如下命令创建/dev/chrdevbase 这个设备节点文件: 

其中“mknod”是创建节点命令,“/dev/chrdevbase”是要创建的节点文件,“c”表示这是个字符设备,“200”是设备的主设备号,“0”是设备的次设备号。创建完成以后就会存在/dev/chrdevbase 这个文件,可以使用“ls /dev/chrdevbase -l”命令查看,结果如上图所示:

如果chrdevbaseAPP 想要读写chrdevbase 设备,直接对/dev/chrdevbase 进行读写操作即可。相当于/dev/chrdevbase 这个文件是chrdevbase 设备在用户空间中的实现。前面一直说Linux 下一切皆文件,包括设备也是文件, 

(3)chrdevbase 设备操作测试

读写操作测试:

 

 

(4)卸载驱动模块

如果不再使用某个设备的话可以将其驱动卸载掉,比如输入如下命令卸载掉chrdevbase 这个设备:  

从上图可以看出,此时系统已经没有任何模块了,chrdevbase 这个模块也不存在了,说明模块卸载成功。至此,chrdevbase 这个设备的整个驱动就验证完成了,驱动工作正常。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1279205.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

设计模式-结构型模式之代理设计模式

文章目录 八、代理设计模式 八、代理设计模式 代理设计模式通过代理控制对象的访问&#xff0c;可以详细访问某个对象的方法&#xff0c;在这个方法调用处理&#xff0c;或调用后处理。既(AOP微实现) 。 代理有分静态代理和动态代理&#xff1a; 静态代理&#xff1a;在程序…

阅读笔记|A Survey of Large Language Models

阅读笔记 模型选择&#xff1a;是否一定要选择参数量巨大的模型&#xff1f;如果需要更好的泛化能力&#xff0c;用于处理非单一的任务&#xff0c;例如对话&#xff0c;则可用选更大的模型&#xff1b;而对于单一明确的任务&#xff0c;则不一定越大越好&#xff0c;参数小一…

Basemap地图绘制_Python数据分析与可视化

Basemap地图绘制 安装和使用地图投影地图背景在地图上画数据 Basemap是Matplotlib的一个子包&#xff0c;负责地图绘制。在数据可视化过程中&#xff0c;我们常需要将数据在地图上画出来。 比如说我们在地图上画出城市人口&#xff0c;飞机航线&#xff0c;军事基地&#xff0c…

Windows远程桌面提示出现身份验证错误 要求的函数不支持

现象 解决方案&#xff1a; 在cmd运行框输入&#xff1a;gpedit.msc打开组策略编辑器路径&#xff1a;计算机配置→管理模板→Windows组件→远程桌面服务→远程桌面会话主机→安全开启远程连接要求使用指定的安全层 禁用要求使用网络级别的身份验证对远程连接的用户进行身份验…

光学3D表面轮廓仪超0.1nm纵向分辨能力,让显微形貌分毫毕现

在工业应用中&#xff0c;光学3D表面轮廓仪超0.1nm的纵向分辨能力能够高精度测量物体的表面形貌&#xff0c;可用于质量控制、表面工程和纳米制造等领域。 与其它表面形貌测量方法相比&#xff0c;光学3D表面轮廓仪达到纳米级别的相移干涉法(PSI)和垂直扫描干涉法(VSI)&#x…

深入理解Servlet(下)

作者简介&#xff1a;大家好&#xff0c;我是smart哥&#xff0c;前中兴通讯、美团架构师&#xff0c;现某互联网公司CTO 联系qq&#xff1a;184480602&#xff0c;加我进群&#xff0c;大家一起学习&#xff0c;一起进步&#xff0c;一起对抗互联网寒冬 在这一篇文章里&#x…

Centos图形化界面封装OpenStack Ubuntu镜像

目录 背景 环境 搭建kvm环境 安装ubuntu虚机 虚机设置 系统安装 登录虚机 安装cloud-init 安装cloud-utils-growpart 关闭实例 删除细节信息 删除网卡细节 使虚机脱离libvirt纳管 结束与验证 压缩与转移 验证是否能够正常运行 背景 一般的镜像文件在上传OpenSt…

计算机毕业设计 基于协同推荐的白酒销售管理系统的设计与实现 Java实战项目 附源码+文档+视频讲解

博主介绍&#xff1a;✌从事软件开发10年之余&#xff0c;专注于Java技术领域、Python人工智能及数据挖掘、小程序项目开发和Android项目开发等。CSDN、掘金、华为云、InfoQ、阿里云等平台优质作者✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精…

TCA9548A I2C 多路复用器 Arduino 使用相同地址 I2C 设备

在本教程中&#xff0c;我们将学习如何将 TCA9548A I2C 多路复用器与 Arduino 结合使用。我们将讨论如何通过整合硬件解决方案来使用多个具有相同地址的 Arduino 的 I2C 设备。通过使用 TCA9548A I2C 多路复用器&#xff0c;我们将能够增加 Arduino 的 I2C 地址范围&#xff0c…

前端打包添加前缀

vue2添加前缀 router的base加上前缀 export default new Router({mode: history, // 去掉url中的#base: privateDeployUrl, // 这里加上前缀scrollBehavior: () > ({y: 0}),routes: constantRoutes })vue.config.js&#xff0c;publicPath属性加上前缀 publicPath: proces…

组件化编程

hello&#xff0c;我是小索奇&#xff0c;精心制作的Vue系列持续发放&#xff0c;涵盖大量的经验和示例&#xff0c;如果对您有用&#xff0c;可以点赞收藏哈~ 组件化编程 组件是什么&#xff1f; 一句话概括就是&#xff1a;实现特定功能的模块化代码单元 vm就是大哥&#xff…

Leetcode刷题详解——乘积最大子数组

1. 题目链接&#xff1a;152. 乘积最大子数组 2. 题目描述&#xff1a; 给你一个整数数组 nums &#xff0c;请你找出数组中乘积最大的非空连续子数组&#xff08;该子数组中至少包含一个数字&#xff09;&#xff0c;并返回该子数组所对应的乘积。 测试用例的答案是一个 32-位…

2023-12-03 LeetCode每日一题(可获得的最大点数)

2023-12-03每日一题 一、题目编号 1423. 可获得的最大点数二、题目链接 点击跳转到题目位置 三、题目描述 几张卡牌 排成一行&#xff0c;每张卡牌都有一个对应的点数。点数由整数数组 cardPoints 给出。 每次行动&#xff0c;你可以从行的开头或者末尾拿一张卡牌&#x…

[HTB][Sherlocks] Meerkat

作为一家快速发展的初创公司&#xff0c;Forela一直在利用商业管理平台。不幸的是&#xff0c;我们的文档很少&#xff0c;而且我们的管理员也不是最有安全意识的。作为我们的新安全提供商&#xff0c;我们希望您查看我们导出的一些PCAP和日志数据&#xff0c;以确认我们是否已…

Nginx实现多虚拟主机配置

Nginx实现多虚拟主机配置 Nginx为什么要进行多虚拟主机配置呢&#xff1f;what&#xff1f; Nginx实现多虚拟主机配置的主要原因是&#xff0c;一个服务器可能会承载多个网站或应用程序&#xff0c;这些网站或应用程序需要使用不同的域名或IP地址来进行访问。如果只有一个虚拟…

代码随想录第二十二天(一刷C语言)|组合总数电话号码的字母组合

创作目的&#xff1a;为了方便自己后续复习重点&#xff0c;以及养成写博客的习惯。 一、组合总数 思路&#xff1a;参考carl文档和视频 1、需要一维数组path来存放符合条件的结果&#xff0c;二维数组result来存放结果集。 2、targetSum 目标和&#xff0c;也就是题目中的…

Java基础-----Date类及其相关类(一)

文章目录 1. Date类1.1 简介1.2 构造方法1.3 主要方法 2. DateFormat 类2.1 简介2.2 实例化方式一&#xff1a;通过静态方法的调用2.2 实例化方式二&#xff1a;通过创建子类对象 3. Calendar类4. GregorianCalendar 1. Date类 1.1 简介 java.util.Date:表示指定的时间信息&a…

市面上的AR眼镜:优缺点分析

AR眼镜是近年来备受关注的科技产品之一。它通过将虚拟信息叠加到现实世界中&#xff0c;为用户提供全新的视觉体验。目前&#xff0c;市面上的AR眼镜主要分为两类&#xff1a;消费级AR眼镜和企业级AR眼镜。 消费级AR眼镜 消费级AR眼镜的特点是轻便、时尚、易于佩戴&#xff0…

DOM 事件的注册和移除

前端面试大全DOM 事件的注册和移除 &#x1f31f;经典真题 &#x1f31f;DOM 注册事件 HTML 元素中注册事件 DOM0 级方式注册事件 DOM2 级方式注册事件 &#x1f31f;DOM 移除事件 &#x1f31f;真题解答 &#x1f31f;总结 &#x1f31f;经典真题 总结一下 DOM 中如何…

【STM32】TIM定时器

第一部分&#xff1a;定时器基本定时的功能&#xff1b; 第二部分&#xff1a;定时器的输出比较功能&#xff1b; 第三部分&#xff1a;定时器输入捕获的功能&#xff1b; 第四部分&#xff1a;定时器的编码接口。 1 TIM简介 TIM&#xff08;Timer&#xff09;定时器&#…