Linux第66步_linux字符设备驱动_挂载和卸载

1、了解linux中的驱动类型:

1)、字符设备驱动

字符设备是limnux驱动中最基本的一类设备驱动,字符设备就是一个一个字节,按照字节流进行读写操作的设备,读写数据是分先后顺序的。如:GPIO输入输出、UART、I2C、SPI、USB、LCD、音频等都属于字符设备驱动。

2)、块设备驱动

块设备驱动就是存储器设备的驱动,比如EMMC、NAND、SD卡和U盘等存储设备,因为这些存储设备的特点是以存储块为基础,因此叫做“块设备

3)、网络设备驱动网络设备就是网络驱动,不管是有线的,还是无线的,都属于网络设备驱动的范畴。

注意:

一个设备可以属于多种设备驱动类型,比如:USB WIFI,其使用USB接口,所以属于字符设备,但是其又能上网,所以它又属于网络设备驱动。

2、了解Limux应用程序调用驱动程序的流程

在Linux中一切皆为文件。驱动加载成功以后会在“/dev”目录下生成一个相应的驱动文件,如“xxx”的驱动文件名。应用程序通过对“/dev/xxx”的文件进行相应的操作,就可实现对硬件的操作。比如:现在有个叫做“/dev/led ”的驱动文件,它是1ed灯的驱动文件。应用程序使用“open函数”打开驱动文件“/dev/led ”,然后使用“close函数”关闭驱动文件“/dev/led”。open和close 就是打开和关闭led驱动的函数,如果要点亮或关闭1ed,那么就使用write函数来操作,也就是向此驱动写入数据,这个数据就是要关闭还是要打开led的控制参数。如果要获取led灯的状态,就用read函数从驱动中读取相应的状态。应用程序运行在用户空间,而limux驱动属于内核的一部分,因此驱动运行于内核空间。当我们在用户空间想要实现对内核进行操作,比如使用open函数打开/dev/led这个驱动,因为用户空间不能直接对内核进行操作,因此必须使用一个叫做“系统调用”的方法来实现从用户空间“陷入”到内核空间,这样才能实现对底层驱动的操作。open、close、write和read等这些函数是由C库提供的,在Limux系统中,系统调用作为C库的一部分。

我们重点关注的时应用程序调用的函数驱动调用的函数,至于C库和内核调用的函数,我们不用去关心。

3、了解“file_operations”结构体

打开虚拟机上VSCode,点击“文件”,点击“打开文件夹”,点击“zgq”,点击“linux”,点击“atk-mp1”,点击“linux”,点击“my_linux”,点击“linux-5.4.31”,见下图:

点击“确定

点击“查看”,点击“搜索”,输入“struct file_operations

随便点击其中一个struct file_operations”,得到下面的界面:

在“file_operations”点击“鼠标右键”,点击“转到定义”,见下图:

得到下图:

字符设备驱动重点是“file_operations”结构体

“fs.h”定义“file_operations”结构体如下:

struct file_operations {

struct module *owner;

loff_t (*llseek) (struct file *, loff_t, int);

ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);

ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);

ssize_t (*read_iter) (struct kiocb *, struct iov_iter *);

ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);

int (*iopoll)(struct kiocb *kiocb, bool spin);

int (*iterate) (struct file *, struct dir_context *);

int (*iterate_shared) (struct file *, struct dir_context *);

__poll_t (*poll) (struct file *, struct poll_table_struct *);

long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);

long (*compat_ioctl) (struct file *, unsigned int, unsigned long);

int (*mmap) (struct file *, struct vm_area_struct *);

unsigned long mmap_supported_flags;

int (*open) (struct inode *, struct file *);

int (*flush) (struct file *, fl_owner_t id);

int (*release) (struct inode *, struct file *);

int (*fsync) (struct file *, loff_t, loff_t, int datasync);

int (*fasync) (int, struct file *, int);

int (*lock) (struct file *, int, struct file_lock *);

ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);

unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);

int (*check_flags)(int);

int (*flock) (struct file *, int, struct file_lock *);

ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);

ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);

int (*setlease)(struct file *, long, struct file_lock **, void **);

long (*fallocate)(struct file *file, int mode, loff_t offset,

  loff_t len);

void (*show_fdinfo)(struct seq_file *m, struct file *f);

#ifndef CONFIG_MMU

unsigned (*mmap_capabilities)(struct file *);

#endif

ssize_t (*copy_file_range)(struct file *, loff_t, struct file *,

loff_t, size_t, unsigned int);

loff_t (*remap_file_range)(struct file *file_in, loff_t pos_in,

   struct file *file_out, loff_t pos_out,

   loff_t len, unsigned int remap_flags);

int (*fadvise)(struct file *, loff_t, loff_t, int);

} __randomize_layout;

/* 设备操作函数结构体 */

static struct file_operations chrdevbase_fops = {

    .owner = THIS_MODULE,  

    .open = chrdevbase_open,

    .read = chrdevbase_read,

    .write = chrdevbase_write,

    .release = chrdevbase_release,

};

4、了解“linux-5.4.31”中的驱动

下面我们通过“crc-vpmsum_test.c”来学习linux驱动的编写:

1)、点击“转到”,点击“转到文件”,在输入框中输入“crc-vpmsum_test.c”,按“回车键”打开“crc-vpmsum_test.c

见下图:

crc-vpmsum_test.c”内容如下:

// SPDX-License-Identifier: GPL-2.0-only

/*

 * CRC vpmsum tester

 * Copyright 2017 Daniel Axtens, IBM Corporation.

 */

#include <linux/crc-t10dif.h>

#include <linux/crc32.h>

#include <crypto/internal/hash.h>

#include <linux/init.h>       //必须要包含的头文件

#include <linux/module.h>     //必须要包含的头文件

#include <linux/string.h>     //下面要用到字符串,显然也要包含

#include <linux/kernel.h>     //必须要包含的头文件

#include <linux/cpufeature.h>

#include <asm/switch_to.h>

static unsigned long iterations = 10000;

#define MAX_CRC_LENGTH 65535

/*入口函数初始化*/

static int __init crc_test_init(void)

{

u16 crc16 = 0, verify16 = 0;

u32 crc32 = 0, verify32 = 0;

__le32 verify32le = 0;

unsigned char *data;

unsigned long i;

int ret;

struct crypto_shash *crct10dif_tfm;

struct crypto_shash *crc32c_tfm;

if (!cpu_has_feature(CPU_FTR_ARCH_207S))

return -ENODEV;

data = kmalloc(MAX_CRC_LENGTH, GFP_KERNEL);

if (!data)

return -ENOMEM;

crct10dif_tfm = crypto_alloc_shash("crct10dif", 0, 0);

if (IS_ERR(crct10dif_tfm)) {

pr_err("Error allocating crc-t10dif\n");

goto free_buf;

}

crc32c_tfm = crypto_alloc_shash("crc32c", 0, 0);

if (IS_ERR(crc32c_tfm)) {

pr_err("Error allocating crc32c\n");

goto free_16;

}

do {

SHASH_DESC_ON_STACK(crct10dif_shash, crct10dif_tfm);

SHASH_DESC_ON_STACK(crc32c_shash, crc32c_tfm);

crct10dif_shash->tfm = crct10dif_tfm;

ret = crypto_shash_init(crct10dif_shash);

if (ret) {

pr_err("Error initing crc-t10dif\n");

goto free_32;

}

crc32c_shash->tfm = crc32c_tfm;

ret = crypto_shash_init(crc32c_shash);

if (ret) {

pr_err("Error initing crc32c\n");

goto free_32;

}

pr_info("crc-vpmsum_test begins, %lu iterations\n", iterations);

for (i=0; i<iterations; i++) {

size_t offset = prandom_u32_max(16);

size_t len = prandom_u32_max(MAX_CRC_LENGTH);

if (len <= offset)

continue;

prandom_bytes(data, len);

len -= offset;

crypto_shash_update(crct10dif_shash, data+offset, len);

crypto_shash_final(crct10dif_shash, (u8 *)(&crc16));

verify16 = crc_t10dif_generic(verify16, data+offset, len);

if (crc16 != verify16) {

pr_err("FAILURE in CRC16: got 0x%04x expected 0x%04x (len %lu)\n",

       crc16, verify16, len);

break;

}

crypto_shash_update(crc32c_shash, data+offset, len);

crypto_shash_final(crc32c_shash, (u8 *)(&crc32));

verify32 = le32_to_cpu(verify32le);

        verify32le = ~cpu_to_le32(__crc32c_le(~verify32, data+offset, len));

if (crc32 != (u32)verify32le) {

pr_err("FAILURE in CRC32: got 0x%08x expected 0x%08x (len %lu)\n",

       crc32, verify32, len);

break;

}

}

pr_info("crc-vpmsum_test done, completed %lu iterations\n", i);

} while (0);

free_32:

crypto_free_shash(crc32c_tfm);

free_16:

crypto_free_shash(crct10dif_tfm);

free_buf:

kfree(data);

return 0;

}

/*出口函数初始化*/

static void __exit crc_test_exit(void) {}

module_init(crc_test_init);/*将crc_test_init()指定为入口函数*/

module_exit(crc_test_exit); /*将crc_test_exit()指定为出口函数*/

module_param(iterations, long, 0400);

MODULE_AUTHOR("Daniel Axtens <dja@axtens.net>");//添加作者名字

MODULE_DESCRIPTION("Vector polynomial multiply-sum CRC tester");

//从字面意思上看,指的是模块介绍

MODULE_LICENSE("GPL");//LICENSE采用“GPL协议”

上面的程序用到“pr_err()和pr_info()”,通过查找,它们都是调用“printk()”,内容如下:

#define pr_err(fmt, ...) \

printk(KERN_ERR pr_fmt(fmt), ##__VA_ARGS__)

#define pr_info(fmt, ...) \

printk(KERN_INFO pr_fmt(fmt), ##__VA_ARGS__)

因此,我们知道linux的串口输出是调用printk()来实现的,相当于C语言的printf()函数。

Linux驱动的运行方式:

1)、将驱动源码编译进Linux内核中,生成uImage/zImage系统镜像里面。

2)、将驱动源码编译成模块生成以“.ko结尾的文件然后在Linux内核加载驱动模块。也可以编译进内核,最终集成到uImage里面。

4、加载模块使用modprobe命令卸载模块使用insmod命令

比如执行“insmod drv.ko”加载“drv.ko”模块,由于drv.ko模块需要依赖first.ko模块,因此,要先执行“insmod first.ko”,然后再执行“insmod drv.ko”,才可以加载。而执行“modprobe drv.ko”加载“drv.ko”模块,它会分析first.kodrv.ko模块之间的依赖关系,自动将“first.kodrv.ko”加载到内核中。因此加载驱动使用modprobe命令

在卸载的时候,执行“rmmod drv.ko”就可以卸载了,而modprobe命令在分析出“first.kodrv.ko”模块之间有依赖关系,若first.ko被其它模块使用le,就不能执行“modprobe -r drv;若first.ko没有被其它模块使用,就可以执行“modprobe -r drv;。因此卸载模块使用insmod命令

注意:

Limnux kemnel的版本号为5.4.31modprobe命令会到“/lib/modules/5.4.31”目录中查找相应的驱动模块,因此我们需要将编写好的驱动会放到“/lib/modules/5.4.31”目录中

5、通过“crc-vpmsum_test.c”学习,我们仿写一个驱动,命名为My_TestDriver.c。

1)、创建“/home/zgq/linux/Linux_Drivers/00_My_TestDriver/”目录

输入“cd /home/zgq/linux/Linux_Drivers/

切换到“/home/zgq/linux/Linux_Drivers/”目录

输入“mkdir 00_My_TestDriver

创建“/home/zgq/linux/Linux_Drivers/00_My_TestDriver/”目录

输入“ls”,查询“/home/zgq/linux/Linux_Drivers/”目录下的文件和文件夹

输入“cd 00_My_TestDriver/

切换到“/home/zgq/linux/Linux_Drivers/00_My_TestDriver/”目录

输入“vi My_TestDriver.c”打开“My_TestDriver.c”

输入内容如下:

#include <linux/init.h>       //必须要包含的头文件

#include <linux/module.h>     //必须要包含的头文件

#include <linux/string.h>     //下面要用到字符串,显然也要包含

#include <linux/kernel.h>     //必须要包含的头文件

/*入口函数初始化*/

static int __init My_TestDriver_init(void)

{

int ret = 0;

    printk("My_TestDriver_init\r\n");

return ret;

}

/*出口函数初始化*/

static void __exit My_TestDriver_exit(void)

{

printk("My_TestDriver_exit\r\n");

}

module_init(My_TestDriver_init);/*将My_TestDriver_init()指定为入口函数*/

module_exit(My_TestDriver_exit); /*将My_TestDriver_exit()指定为出口函数*/

MODULE_AUTHOR("Zhanggong");//添加作者名字

MODULE_DESCRIPTION("This is test module!");//模块介绍

MODULE_LICENSE("GPL");//LICENSE采用“GPL协议”

MODULE_INFO(intree,"Y");//去处显示“loading out-of-tree module taints kernel.”

按“ESC键”,输入“:wq回车

输入“vim Makefile

添加内容如下:

KERNELDIR := /home/zgq/linux/atk-mp1/linux/my_linux/linux-5.4.31

#使用“:=”将其后面的字符串赋值给KERNELDIR

CURRENT_PATH := $(shell pwd)

#采用“shell pwd”获取当前打开的路径

#使用“$(变量名)”引用“变量的值”

obj-m := My_TestDriver.o

#生成“obj-m”需要依赖“My_TestDriver.o”

build: kernel_modules

#生成“build”需要依赖“kernel_modules”

kernel_modules:

        $(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules

#后面的"modules"表示编译成模块

#“KERNELDIR”上面定义为“/home/zgq/linux/atk-mp1/linux/my_linux/linux-5.4.31”,即“指定的工作目录”

#“CURRENT_PATH”上面定义为“当前的工作目录”

#“-C $(KERNELDIR) M=$(CURRENT_PATH) ”表示将“当前的工作目录”切换到“指定的目录”中

#即切换到“/home/zgq/linux/atk-mp1/linux/my_linux/linux-5.4.31”。

#M表示模块源码目录

#在“make和modules”之间加入“M=$(CURRENT_PATH)”,表示切换到由“CURRENT_PATH”指定的目录中读取源码,同时将其编>译为.ko 文件

clean:

        $(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean

#“KERNELDIR”上面定义为“/home/zgq/linux/atk-mp1/linux/my_linux/linux-5.4.31”,即“指定的工作目录”

#“CURRENT_PATH”上面定义为“当前的工作目录”

按“ESC键”,输入“:wq回车

在linx驱动中的Makefile文件,有点像“八股文”,在格式上基本是固定的。

驱动测试

在连接开发板之前,需要将“My_TestDriver.ko”拷贝到“/home/zgq/linux/nfs/rootfs/lib/modules/5.4.31/

输入“cd /home/zgq/linux/Linux_Drivers/00_My_TestDriver/

切换到“/home/zgq/linux/Linux_Drivers/00_My_TestDriver/”目录

输入“ls”,查询“/home/zgq/linux/Linux_Drivers/00_My_TestDriver/”目录下的文件和文件夹

输入“make clean回车”清除工程

输入“make回车”执行编译

输入“ls”,查询“/home/zgq/linux/Linux_Drivers/00_My_TestDriver/”目录下的文件和文件夹

输入“sudo cp  My_TestDriver.ko  /home/zgq/linux/nfs/rootfs/lib/modules/5.4.31

将“My_TestDriver.ko”拷贝到“/home/zgq/linux/nfs/rootfs/lib/modules/5.4.31

启动开发板,从网络下载程序

输入“root”

输入“cd /lib/modules/5.4.31/

在nfs挂载中,切换到“/home/zgq/linux/nfs/rootfs/lib/modules/5.4.31/”目录

输入“ls

输入“depmod”,驱动在第一次执行时,需要运行“depmod”

输入“modprobe My_TestDriver.ko”,加载“My_TestDriver.ko”模块

输入“lsmod”查看有哪些驱动在工作

输入“rmmod My_TestDriver.ko”,卸载“My_TestDriver.ko”模块

注意:输入“rmmod My_TestDriver也可以卸载“My_TestDriver.ko模块

输入“lsmod”查看有哪些驱动在工作

至此,我们完成了linux字符设备驱动的挂载和卸载。

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

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

获取tensorflow lite模型指定中间层的输出

以mobilenet v2为例子&#xff0c;我已经训练好了模型&#xff0c;有tflite格式和onnx格式两种模型的权重文件&#xff0c;我想获取tflite模型在推理阶段neck部分的输出。 查看onnx模型得到neck最后一层位置 使用netron查看onnx模型结构 从name中能知道Reshape是neck的最后一…

HTTP---------状态码

当服务端返回 HTTP 响应时&#xff0c;会带有一个状态码&#xff0c;用于表示特定的请求结果。比如 HTTP/1.1 200 OK&#xff0c;里面的 HTTP/1.1 表示协议版本&#xff0c;200 则是状态码&#xff0c;OK 则是对状态码的描述。 由协议版本、状态码、描述信息组成的行被称为起始…

Spring ReflectionUtils 反射工具介绍和使用

一、ReflectionUtils 在 Java 中&#xff0c;反射&#xff08;Reflection&#xff09;是一种强大的机制&#xff0c;允许程序在运行时动态地检查类、获取类的信息、调用类的方法、访问或修改类的属性等。Java 的反射机制提供了一组类和接口&#xff0c;位于 java.lang.reflect…

记录一些mac电脑重装mysql和pgsql的坑

为什么要重装,是想在mac电脑 创建data目录…同事误操作,导致电脑重启不了.然后重装系统后,.就连不上数据库了.mysql和pgsql两个都连不上.网上也查了很多资料.实在不行,.就重装了… 重装mysql. 1.官网下载 https://www.mysql.com/downloads/ 滑到最下面 选择 选择对应的芯片版本…

MFC由初值终值步长生成数值序列

matlab的冒号运算符可以生成数值序列; 下面来生成自己的数值序列; vc6新建一个对话框工程; 放几个控件;添加成员变量如下; void CMycolonDlg::OnButton1() {// TODO: Add your control notification handler code hereUpdateData(TRUE);double d1, d2;CString str1, …

前后端分离vue.js+nodejs学生考勤请假系统 _fbo36

此系统设计主要采用的是nodejs语言来进行开发&#xff0c;采用vue框架技术&#xff0c;框架分为三层&#xff0c;分别是控制层Controller&#xff0c;业务处理层Service&#xff0c;持久层dao&#xff0c;能够采用多层次管理开发&#xff0c;对于各个模块设计制作有一定的安全性…

文献阅读:Transformers are Multi-State RNNs

文献阅读&#xff1a;Transformers are Multi-State RNNs 1. 内容简介2. 方法介绍 1. 基础回顾 1. RNN2. Transformer 2. Transformer解构 1. MSRNN2. Transformer 3. TOVA 1. 现有转换策略2. TOVA 3. 实验考察 & 结论 1. 实验设计2. 实验结果 1. LM2. 长文本理解3. 文本生…

时间序列分析实战(三):时序因素分解法

&#x1f349;CSDN小墨&晓末:https://blog.csdn.net/jd1813346972 个人介绍: 研一&#xff5c;统计学&#xff5c;干货分享          擅长Python、Matlab、R等主流编程软件          累计十余项国家级比赛奖项&#xff0c;参与研究经费10w、40w级横向 文…

Spring Boot Profiles简单介绍

Spring Boot application.properties和application.yml文件的配置 阅读本文之前&#xff0c;请先阅读上面的配置文件介绍。 Spring Boot Profiles是一个用于区分不同环境下配置的强大功能。以下是如何在Spring Boot应用程序中使用Profiles的详细步骤和代码示例。 1. 创…

【深度学习笔记】3_14 正向传播、反向传播和计算图

3.14 正向传播、反向传播和计算图 前面几节里我们使用了小批量随机梯度下降的优化算法来训练模型。在实现中&#xff0c;我们只提供了模型的正向传播&#xff08;forward propagation&#xff09;的计算&#xff0c;即对输入计算模型输出&#xff0c;然后通过autograd模块来调…

【国产MCU】-CH32V307-定时器同步模式

定时器同步模式 文章目录 定时器同步模式1、定时器同步模式介绍2、驱动API介绍3、定时器同步模式实例1、定时器同步模式介绍 CH32V307的定时器能够输出时钟脉冲(TRGO),也能接收其他定时器的输入(ITRx)。不同的定时器的ITRx的来源(别的定时器的TRGO)是不一样的。 通用定…

ShardingSphere 5.x 系列【14】广播表、单表

有道无术,术尚可求,有术无道,止于术。 本系列Spring Boot 版本 3.1.0 本系列ShardingSphere 版本 5.4.0 源码地址:https://gitee.com/pearl-organization/study-sharding-sphere-demo 文章目录 1.广播表1.1 概述1.2 案例演示1.2.1 环境准备1.2.2 配置1.2.3 测试2. 单表2.…