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

目录

一、字符设备驱动简介

二、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 这个设备的整个驱动就验证完成了,驱动工作正常。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【开源】前后端分离的在线考试系统,支持多种部署方式

在线考试系统 https://download.csdn.net/download/mo3408/88593116 在线考试系统是一种利用网络技术&#xff0c;实现在线出题、答题、阅卷、成绩查询等一系列考试活动的系统。它不受地理位置限制&#xff0c;可以实现远程考试&#xff0c;大大提高了考试的效率和便利性。此…

HBASE命令行查看中文字符

问题记录 中文显示的是编码字符不方便查看value\xE5\xB8\xB8\xE5\xAE\x89\xE5\xAE\x891修改前中文显示&#xff1a; 解决方法 1、列族 : 列名 : toString ’2、列族 : 列名 : c(org.apache.hadoop.hbase.util.Bytes).toString ’ scan karry:student,{COLUMNS > [info:…

C-语言每日刷题

目录 [蓝桥杯 2015 省 A] 饮料换购 题目描述 输入格式 输出格式 输入输出样例 # [蓝桥杯 2023 省 A] 平方差 题目描述 输入格式 输出格式 输入输出样例 说明/提示 【样例说明】 [NOIP2001 普及组] 数的计算 题目描述 输入格式 输出格式 输入输出样例 说明/提示 样例 1 解释 数据…

SQL自学通之简介

目录 一、SQL 简史 二、数据库简史 1、Dr. Codds 对关系型数据库系统的十二条规则 2、设计数据库的结构 3、数据库的前景 4、对于什么是客户机/服务器型电脑系统 BernardH.Boar的定义如下&#xff1a; 5、交互式语言 6、易于实现 7、SQL 总览 三、流行的 SQL 开发工具…

python初始化矩阵相关

做算法题经常需要初始化一个二维的dp数组 下面两种方法是最常用的 matrix [[0]*n]*n matrix [[0]*n for _ in range(n)]以前经常混用也没发现什么问题&#xff0c;直到昨天debug的时候发现第一种初始化之后对矩阵进行赋值时混乱的&#xff0c;比如matrix[0][1]2会导致所有行…

[Linux ] sed文本处理和免交互

一、sed 1.1 sed是什么 sed 是一种流编辑器&#xff08;stream editor&#xff09;&#xff0c;用于对文本数据进行文本转换和处理。它通常被用于在命令行中执行文本编辑任务&#xff0c;可以对输入的文本进行搜索、替换、删除等操作&#xff0c;并将结果输出。sed 是一个非交…

Linux脚本awk命令

目录 一. awk命令简介 1. awk版本 2. awk与vim的区别 3. awk与sed的区别 4. awk工作原理 5. awk格式 6. awk常用选项 二. awk基础用法 1. awk基础用法 2. BEGIN和END语句块 3. 指定分隔符 4. 首尾关键字 三. awk内置变量 1. FS变量 2. OFS变量 3. RS变量 4. NF…

线程安全的问题以及解决方案

线程安全 线程安全的定义 线程安全:某个代码无论是在单线程上运行还是在多线程上运行,都不会产生bug. 线程不安全:单线程上运行正常,多线程上运行会产生bug. 观察线程不安全 看看下面的代码: public class ThreadTest1 {public static int count 0;public static void main…

Windows驱动中数字签名认证(使用 ci.dll)

1.背景 对于常规应用程序来说&#xff0c;在应用层可以使用 WinVerifyTrust, 在驱动层使用常规的 API无法使用&#xff0c;自己分析数据又太麻烦。 但在内核中 ci.dll 包装了数据签名验证相关的功能&#xff0c;我们可以使用该 dll 来实现我们的数字签名验证。 详细的分析见《内…

《异常检测——从经典算法到深度学习》24 用于单变量时间序列异常检测的端到端基准套件

《异常检测——从经典算法到深度学习》 0 概论1 基于隔离森林的异常检测算法 2 基于LOF的异常检测算法3 基于One-Class SVM的异常检测算法4 基于高斯概率密度异常检测算法5 Opprentice——异常检测经典算法最终篇6 基于重构概率的 VAE 异常检测7 基于条件VAE异常检测8 Donut: …

面试题:千万量级数据中查询 10W 量级的数据有什么方案?

文章目录 前言初版设计方案整体方案设计为&#xff1a;技术方案如下&#xff1a;CK 分页查询使用 ES Scroll Scan 优化深翻页耗时数据 ESHbase 组合查询方案ES 查询的两个阶段组合使用 Hbase RediSearchRedisJSON 优化方案RediSearch 性能数据RedisJSON 性能数据 总结 前言 在…

C++跨目录include问题

不同文件夹下使用预处理器指示符#include 使用举例 假设我们有如下一个工程&#xff0c;其中包含了几个源代码和头文件&#xff0c;其中main.cpp是主源代码文件&#xff0c;里面含有main函数&#xff1a; 在foldder main中包含&#xff1a;func4.hpp&#xff0c;func4.cpp&am…

MIT线性代数笔记-第21讲-特征值,特征向量

目录 21.特征值&#xff0c;特征向量打赏 21.特征值&#xff0c;特征向量 对于一个方阵 A A A&#xff0c;若 A x ⃗ λ x ⃗ A \vec{x} \lambda \vec{x} Ax λx &#xff0c;即 A x ⃗ A \vec{x} Ax 平行于 x ⃗ \vec{x} x &#xff0c;那么 λ \lambda λ是 A A A的特征值…

100W用户、8000W流量在线贺卡应用架构如何优化?

文章目录 &#x1f50a;博主介绍&#x1f964;本文内容&#x1f4e2;文章总结&#x1f4e5;博主目标 &#x1f50a;博主介绍 &#x1f31f;我是廖志伟&#xff0c;一名Java开发工程师、Java领域优质创作者、CSDN博客专家、51CTO专家博主、阿里云专家博主、清华大学出版社签约作…

FL Studio2024水果编曲软件21.2.0中文版本下载更新

FL Studio2024是功能强大的音乐制作解决方案&#xff0c;使用旨在为用户提供一个友好完整的音乐创建环境&#xff0c;让您能够轻松创建、管理、编辑、混合具有专业品质的音乐&#xff0c;一切的一切都集中在一个软件中&#xff0c;只要您想&#xff0c;只要您需要&#xff0c;它…

Leetcode—409.最长回文串【简单】

2023每日刷题&#xff08;四十八&#xff09; Leetcode—409.最长回文串 强烈吐槽&#xff01;&#xff01;&#xff01; 非常不理解&#xff0c;同样的代码&#xff0c;为什么C跑不了C就跑得了&#xff0c;力扣编译器是对C语言有歧视吗&#xff1f;&#xff1f;&#xff1f;…

【C++练级之路】【Lv.1】C++,启动!(命名空间,缺省参数,函数重载,引用,内联函数,auto,范围for,nullptr)

目录 引言入门须知一、命名空间1.1 作用域限定符1.2 命名空间的意义1.3 命名空间的定义1.4 命名空间的使用 二、C输入&输出2.1 cout输出2.2 cin输入2.3 std命名空间的使用惯例 三、缺省参数3.1 缺省参数概念3.2 缺省参数分类 四、函数重载4.1 函数重载概念4.2 函数重载分类…

6-63.圆类的定义与使用(拷贝构造函数)

本题要求完成一个圆类的定义&#xff0c;设计适当的函数&#xff1a;包括构造函数、拷贝构造函数以及析构函数&#xff0c;从而可以通过测试程序输出样例 在这里给出一组输入。例如&#xff1a; 5 输出样例&#xff1a; 在这里给出相应的输出。例如&#xff1a; Constructo…

【Unity动画】状态机添加参数控制动画切换(Animator Controller)

Unity - 手册&#xff1a;动画参数 在Unity中&#xff0c;动画状态的切换是通过Animator Controller中的过渡&#xff08;Transition&#xff09;来实现的。过渡是状态之间的连接&#xff0c;控制过渡一般都是靠调用代码参数 我们来实现一个案例&#xff1a; 创建动画状态机&a…

Pycharm修改文件默认打开方式 + CSV Editor插件使用

1、File —> Settings —> Editor —> File Types 然后将*csv添加到最上面 在plugins中下载插件&#xff0c;CSV Editor 备注&#xff1a;不在上一步的“File Types”中将*.csv设置为CSV格式&#xff0c;插件是不起作用的 就可以使用了