Linux驱动开发

2023-05-16

  本文为一个简单的字符设备驱动,涉及驱动编写、测试程序编写、Makefile编写、驱动加载/卸载,运行于Linux虚拟机,不涉及底层配置。撰写本文的主要目的为记录一下驱动的开发流程,参考了正点原子的驱动开发指南。

驱动代码

  创建文件夹 1_chrdevbase/ ,下属 APP/ 与 Driver/ 两个文件夹,前者放测试程序,后者放驱动代码。

  在 Driver/ 下创建 chrdevbase.c,驱动代码如下

/* 
 * file name	: chrdevbase.c
 * description	: 一个简单的字符设备demo
 * author		: 今朝无言
 */

#include<linux/types.h>
#include<linux/kernel.h>
#include<linux/delay.h>
#include<linux/ide.h>
#include<linux/init.h>		//引入module_init()以及module_exit()
#include<linux/module.h>	//与module相关的宏

MODULE_LICENSE("GPL");
MODULE_AUTHOR("今朝无言");

#define CHRDEVBASE_MAJOR	200				//主设备号,可通过 cat /proc/devices 查看所有设备及其主设备号
#define CHRDEVBASE_NAME		"chrdevbase"	//设备名

static char readbuf[100];							//读缓冲区
static char writebuf[100];							//写缓冲区
static char kerneldata[]	= {"kernel data!"};		//内核数据,用于传递给测试APP,进行读取测试

/* 
 * description		: 打开设备
 * @param - inode	: 传递给设备的inode
 * @param - filp	: 设备文件
 * @return			: 0 success;other failed
 */
static int chrdevbase_open(struct inode *inode, struct file *filp){
	printk("chrdevbase open!\n");
	return 0;
}

/* 
 * description		: 从设备读取数据
 * @param - filp	: 设备文件
 * @param - buf		: 返回给用户空间的数据缓冲区
 * @param - cnt		: 要读取的数据长度
 * @param - offt	: 相对文件首地址的偏移
 */
static ssize_t chrdevbase_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt){
	int retvalue = 0;
	
	memcpy(readbuf, kerneldata, sizeof(kerneldata));
	retvalue = copy_to_user(buf, readbuf, cnt);
	
	if(retvalue == 0){
		printk("kernel send data ok!\n");
	}
	else {
		printk("kernel send data failed!\n");
	}
	
	return 0;
}

/* 
 * description		: 向设备写数据
 * @param - filp	: 设备文件
 * @param - buf		: 要写入设备的数据
 * @param - cnt		: 要写入的数据长度
 * @param - offt	: 相对文件首地址的偏移
 */
static ssize_t chrdevbase_write(struct file *filp, const char *buf, size_t cnt, loff_t *offt){
	int retvalue = copy_from_user(writebuf, buf, cnt);
	
	retvalue = copy_from_user(writebuf, buf, cnt);
	if(retvalue == 0){
		printk("kernel receive data: %s \n",writebuf);
	}
	else {
		printk("kernel receive data failed!\n");
	}
	
	return 0;
}

/* 
 * description		: 关闭设备
 * @param - filp	: 设备文件描述符
 * @return			: 0 success;other failed
 */
static int chrdevbase_release(struct inode *inode, struct file *filp){
	printk("chrdevbase release! \n");
	return 0;
}

/*
 * chrdevbase的file_operations结构体
 * file_operations的定义见Kernel/include/linux/fs.h
 * 注意函数定义一定要相同,否则报`initialization from incompatible pointer type [-Werror=incompatible-pointer-types]`错
 */
static struct file_operations chrdevbase_fops = {
	.owner		= THIS_MODULE,
	.open		= chrdevbase_open,
	.read		= chrdevbase_read,
	.write		= chrdevbase_write,
	.release	= chrdevbase_release
};

/* 
 * description		: 驱动入口函数
 */
static int __init chrdevbase_init(void){
	//若函数没有参数,要加void,否则报`function declaration isn’t a prototype [-Werror=strict-prototypes]`错
	int retvalue = 0;
	
	retvalue = register_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME, &chrdevbase_fops);
	if(retvalue < 0){
		printk("chrdevbase driver register failed!\n");
	}
	else {
		printk("chrdevbase driver register success!\n");
	}
	
	return 0;
}

/* 
 * description		: 驱动出口函数
 */
static void __exit chrdevbase_exit(void){
	unregister_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME);
	printk("chrdevbase exit!\n");
	
	return;
}

//指定驱动入口和出口函数
module_init(chrdevbase_init);
module_exit(chrdevbase_exit);

驱动代码Makefile

  在 Driver/ 下创建 Makefile,内容如下

KERNELDIR := /lib/modules/4.15.0-189-generic/build
#本机编译就/lib/modules/`uname -r`/build
#交叉编译就使用对应的Kernel源码目录

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

测试程序代码

  在 APP/ 下创建 chrdevbaseAPP.c,代码如下

/* 
 * file name	: chrdevbaseAPP.c
 * description	: chedevbase驱动的测试程序
 * author		: 今朝无言
 */

#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 usrdata[] = {"usr data!"};	//用户数据,用于传递给驱动,进行写入测试

// 用法:./chrdevbaseAPP /dev/chrdevbase arg
int main(int argc, char *argv[]){
	int fd, retvalue;
	char *filename;
	char readbuf[100], writebuf[100];
	
	//检查参数
	if(argc != 3){
		printf("Error Usage!\n");
		return -1;
	}
	
	filename = argv[1];
	
	//打开驱动文件
	fd = open(filename, O_RDWR);
	if(fd < 0){
		printf("Can't open file %s!\n", filename);
		return -2;
	}
	
	//arg=1,从驱动文件读取数据
	if(atoi(argv[2]) == 1){
		retvalue = read(fd, readbuf, 50);
		if(retvalue < 0){
			printf("read file %s failed!\n", filename);
		}
		else {
			printf("read data: %s\n", readbuf);
		}
	}
	
	//arg=2,向驱动写数据
	if(atoi(argv[2]) == 2){
		memcpy(writebuf, usrdata, sizeof(usrdata));
		retvalue = write(fd, writebuf, 50);
		if(retvalue < 0){
			printf("write file %s failed!\n", filename);
		}
		else {
			printf("write file success!\n");
		}
	}
	
	//关闭设备
	retvalue = close(fd);
	if(retvalue < 0){
		printf("Can't close file %s!\n", filename);
		return -3;
	}
	
	return 0;
}

测试程序Makefile

  在 APP/ 下创建 Makefile,内容如下

build:
	gcc chrdevbaseAPP.c -o chrdevbaseAPP

clean:
	rm chrdevbaseAPP

编译测试程序

在这里插入图片描述

编译驱动

在这里插入图片描述

驱动加载

  使用 insmod 命令加载刚刚生成的驱动模块

sudo insmod chrdevbase.ko

  执行

cat /proc/devices

查看驱动,如下图,可以看到驱动已经加载

在这里插入图片描述

创建设备节点文件

  使用 mkmod 命令创建驱动节点

sudo mknod /dev/chrdevbase c 200 0

则创建字符设备文件/dev/chrdevbase,对该文件进行读写操作即可使用驱动,其中 ‘c’ 表示字符设备,200为主设备号,0为次设备号。

测试

  进入 APP/ 文件夹,执行

sudo ./chrdevbaseAPP /dev/chrdevbase 1

进行设备读取测试,结果如下

在这里插入图片描述

可以看到用户接收到了从内核传递来的数据 ‘kernel data’ 。

  执行

sudo ./chrdevbaseAPP /dev/chrdevbase 2

进行设备写入测试,结果如下

在这里插入图片描述

  查看最后6条日志消息:

dmesg | tail -6

在这里插入图片描述

其中前三条是前面进行读取测试的日志输出,后三条是进行写入测试的日志输出,可以看到内核接收到了用户发送来的数据 ‘usr data’ 。

驱动卸载

  使用 rmmod 命令卸载驱动:

sudo rmmod chrdevbase.ko

再使用 cat /proc/devices 查看,将发现 chrdevbase 设备已被卸载。

  加载/卸载模块时的日志如下:

在这里插入图片描述

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Linux驱动开发 的相关文章

  • 如果目录不存在,有没有办法让 mv 创建要移动到的目录?

    因此 如果我在主目录中并且想将 foo c 移动到 bar baz foo c 但这些目录不存在 是否有某种方法可以自动创建这些目录 以便你只需要输入 mv foo c bar baz 一切都会顺利吗 似乎您可以将 mv 别名为一个简单的
  • Linux mremap 不释放旧映射?

    我需要一种方法将页面从一个虚拟地址范围复制到另一个虚拟地址范围 而无需实际复制数据 范围很大 延迟很重要 mremap 可以做到这一点 但问题是它也会删除旧的映射 由于我需要在多线程环境中执行此操作 因此我需要旧映射能够同时使用 因此稍后当
  • X 服务器使用什么像素格式?

    X 服务器使用什么像素格式 RGBA ARBG BGRA 如果有任何特定格式的话 Update 我专门寻找有关颜色分量顺序和位模式的信息 你的意思是帧缓冲区格式 或者所有支持的像素图格式 无论是哪一种 都取决于您的图形硬件 驱动程序和配置
  • 如何防止 CMake 在构建时(而不是安装时)为共享库创建符号链接?

    我正在使用 CMake 在 Linux 上使用 Bullet3 构建一个项目 在构建整个解决方案时 它会构建附加了 SOVERSION 的 Bullet 输出库 并创建一个不带版本的符号链接 对于我的特定场景 我不喜欢这种行为 并且我不想编
  • 为什么 RHEL 上的 bash 中 `read -t` 没有超时?

    Why read t在 RHEL5 或 RHEL6 上从管道读取时不会超时 这是我的示例 在从管道读取数据时 我的 RHEL 盒子不会超时 tail f logfile log grep something read t 3 variabl
  • 如何更改 Kubernetes 中的文件系统观察程序限制 (fs.inotify.max_user_watches)

    我在用着pm2 https github com Unitech pm2查看保存我的应用程序服务器的 NodeJS 程序源代码的目录 该程序在 Kubernetes 集群中运行 但是 我收到此错误 ENOSPC System limit f
  • shell中基于正则表达式的颜色突出显示输出

    我想知道是否可以用颜色突出显示与某些字符串匹配的 shell 命令的输出 例如 如果我运行 myCommand 输出如下 gt myCommand DEBUG foo bar INFO bla bla ERROR yak yak 我希望所有
  • 尝试编译 git 但在 linux 中找不到 libcurl

    我想编译支持 http https 的 git 我有 ls usr include curl curlbuild h curl h curlrules h curlver h easy h mprintf h multi h stdchea
  • Tk 初始化失败:无显示名称且无 $DISPLAY 环境变量

    我试图从 Ubuntu 终端调用 Centos 服务器上的工具 我收到以下错误 Tk 初始化失败 没有显示名称 也没有 DISPLAY 环境变量 请帮我解决这个问题 提前致谢 连接到你的 CentOS 机器ssh Y其中 每man ssh
  • Python 线程与 Linux 中的多处理

    基于此question https stackoverflow com questions 807506 threads vs processes in linux我假设创建新流程应该几乎和创造新线程在Linux中 然而 很少的测试显示出截
  • 警告构建使用导出符号的内核模块

    我有两个内核模块 例如 modA 和 modB modA 导出一个符号EXPORT SYMBOL symA modB 使用它 我有标题modA h对于modA extern void symA int param and in modB c
  • 如何获取文件夹的大小,包括稀疏文件的表观大小? (du太慢了)

    我有一个包含很多KVM qcow2文件的文件夹 它们都是稀疏文件 现在我需要获取文件夹的总大小 qcow2 文件大小应计为表观大小 而不是实际大小 例如 图片 c9f38caf104b4d338cc1bbdd640dca89 qcow2 文
  • gnutls_handshake() 失败:握手失败 GIT

    一切都工作正常 但突然我收到错误 致命 无法访问 https 电子邮件受保护 cdn cgi l email protection name repo name git gnutls handshake 失败 握手失败 我在我的计算机和 E
  • Bash 脚本错误 [重复]

    这个问题在这里已经有答案了 我想知道下面的脚本有什么错误 我收到错误为 command not foundh line 1 command not foundh line 2 其连续的 我试过添加 但现在工作请告诉我该怎么做 bin bas
  • AMD OpenCL 在 Linux 上工作所需的最小必要文件子集是什么?

    我已经使用 buildroot 构建了 Linux 内核 我已将开源 amdgpu 驱动程序和所需的固件合并到其中 驱动程序很好 检测 GPU 模式设置运行良好 调整 小文本 的分辨率 启动后会显示命令行 现在我需要运行 OpenCL 程序
  • Pthread互斥锁由不同线程解锁

    一个天真的问题 我之前读到过 MUTEX 只能由锁定它的线程解锁 但我写了一个程序THREAD1锁定 mutexVar 并进入睡眠状态 然后THREAD2可以直接解锁mutexVar做一些操作并返回 gt 我知道每个人都说我为什么要这样做
  • 串口读取未完成

    下面的函数用于在Linux下从串口读取数据 我在调试时可以读取完整的数据 但是当我启动程序时 读缓冲区似乎并不完整 我正确接收了一小部分数据 但缓冲区的其余部分完全正确zero 可能是什么问题呢 int8 t serial port ope
  • 使用inotify监控文件

    我正在使用 inotify 来监视本地文件 例如使用 root temp inotify add watch fd root temp mask 删除该文件后 程序将被阻止read fd buf bufSize 功能 即使我创建一个新的 r
  • tcmalloc/jemalloc 和内存池之间有什么区别(以及选择的理由)?

    tcmalloc jemalloc是改进的内存分配器 还引入了内存池以更好地分配内存 那么它们之间有什么区别以及在我的应用中如何选择它们呢 这取决于您的程序的要求 如果您的程序有更多的动态内存分配 那么您 需要从可用的分配器中选择一个内存分
  • python 的 fcntl.flock 函数是否提供文件访问的线程级锁定?

    Python 的 fcnt 模块提供了一种名为 flock 1 的方法来证明文件锁定 其描述如下 对文件执行锁定操作op 描述符 fd 文件对象提供 fileno 方法被接受为 出色地 请参阅 Unix 手册集群 2 了解详情 在某些系统上

随机推荐

  • 在树莓派/4.x内核下安装IgH EtherCAT master主站

    树莓派安装ethercat主站 环境 xff1a 4 14 91 rt49 v7 下载源码 xff0c 解压 tar xvf ethercat 1 5 2 tar bz2 cd ethercat 1 5 2 configure enable
  • C语言中static修饰函数和变量用法

    static修饰函数 xff0c 局部变量和全局变量的用法 在c语言中static关键字可以修饰函数和变量 修饰变量又可以分为修饰全局变量和局部变量 static作用是限定变量的生命周期 xff0c 限定变量或函数的作用域 写在前面 xff
  • SOEM控制io超简洁程序

    SOEM控制io超简洁程序 我想用SOEM简单控制io模块 xff0c 因为我的io模块每个出入输出旁边都会有一个小灯 xff0c 所以这也算是点灯程序 xff0c 但是我看了例子并不知道怎么修改 xff0c 都说igh麻烦 xff0c 我
  • Deep Learning 最优化方法之Adam

    本文是Deep Learning 之 最优化方法系列文章的Adam方法 主要参考Deep Learning 一书 整个优化系列文章列表 xff1a Deep Learning 之 最优化方法 Deep Learning 最优化方法之SGD
  • SOEM控制伺服电机

    我只完成了pv模式 xff0c 对于csp模式我不知道是哪里出现了问题 xff0c 有知道的可以在下方评论 这个代码我的pv模式可以正常运行和控制电机 xff0c csp模式可以使能电机 xff0c 但是电机不转 span class to
  • c语言常用的打印/输出函数

    c语言中除了最开始接触的printf 函数 xff0c 还经常遇到其他函数 xff0c sprintf printk fprintf 等 1 xff0c printf 这个函数应该是用的最多的 xff0c 或者是最先接触的 xff0c 至少
  • stm32f103介绍

    完整学习一遍stm32开发板开发 xff0c 并打算坚持一直写笔记 这是第一课 xff0c stm32的介绍 1 什么是STM32 从字面意义来看 xff1a ST xff1a 意法半导体 xff0c 是一个公司的名字 M xff1a Mi
  • 数据结构之单链表操作

    数据结构 xff0c 单链表操作 xff0c 本来应该三年前就应该会的 xff0c 奈何上学的时候呼呼睡大觉 xff0c 最近看代码又接触到了 xff0c 花了几天时间自己重新写了一下 链表操作应该是基础的 xff0c 并且需要会的 xff
  • igh etherlab主站介绍

    一 xff0c 简单介绍 目前用的最多的开源ethercat主站是igh和soem xff0c igh主站功能更多 xff0c 结构较为复杂 xff1b soem功能相对没有那么完善 xff0c 实现更为简单一些 使用场景 xff1a 主站
  • U盘变小恢复方法

    在使用中经常由于使用不当 xff0c 导致u盘空间变小 xff0c 比如像我现在的情况 xff0c 我本来u盘是32G的 xff0c 现在显示只有三个多G xff0c 格式化之后还是这样 解决步骤如下 xff0c 不需要下载工具 1 xff
  • VirtualBox 中运行 CentOS 7 鼠标切换

    在 VirtualBox 中运行 CentOS 7 虚拟机时 xff0c 有时鼠标可能会被捕捉 xff0c 导致无法在虚拟机和主机之间切换 以下是如何在 VirtualBox 中实现与 CentOS 7 鼠标切换的步骤 xff1a 首先 x
  • C++11 生产者消费者模型

    C 43 43 11 生产者消费者模型 线程互斥 lock guard 使用lock guard管理互斥锁 在退出作用域后进行析构时就会自动解锁 xff0c 从而保证了互斥量的正确操作 xff0c 避免忘记 unlock 操作而导致线程死锁
  • PS照片处理尺寸参考表

    参考表 一 讲多少寸 xff0c 是指长边的英寸数 xff0c 比如5 x 3 5就是5寸 讲多少R xff0c 指短边的英寸数 xff0c 比如4R是6 X 4寸 xff0c 而3R就是5寸的5 X 3 5寸 R 的意思的 rectang
  • 数据库习题及答案5

    模拟测验1 一 1 2 3 4 5 6 7 8 9 10 A D C c D A C A A C 一 选择题 xff08 在每个小题四个备选答案中选出一个正确答案 xff0c 填在题末的括号中 xff09 xff08 本大题共10小题 xf
  • Attention Model(mechanism) 的 套路

    最近刷了一些attention相关的paper 照着here的列表 43 自己搜的paper xff0c 网上相关的资料也有很多 xff0c 在此只讲一讲自己对于attention的理解 xff0c 力求做到简洁明了 一 attention
  • springMVC常用注解

    在java框架中 xff0c 使用注解的作用就是注入属性 一 Spring常用注解 64 Component xff1a 标注一个普通的Spring Bean类 64 Controller xff1a 标注一个控制器组件类 64 Servi
  • Ubuntu16.04运行.sh文件

    前言 xff1a 最近在学 Linux内核分析 xff0c 实验做的是哈工大的oslab Linux 0 11 xff0c 然后下载了相应的压缩包 解压之后发现需要运行setup sh文件 xff0c 原先以为是因为没有切换到root命令所
  • 服务器conda,pip命令用不了解决方法

    服务器创建用户后 xff0c 不知道为啥基本命令可以用 xff0c 但是conda xff0c pip等不能使用 xff0c 度娘后一行命令解决 xff0c 命令如下 source span class token operator spa
  • Base64资源

    Base64资源 在线转Base64工具 http www jsons cn img2base64 鲸鱼 maskImage src 61 39 data image png base64 iVBORw0KGgoAAAANSUhEUgAAA
  • Linux驱动开发

    本文为一个简单的字符设备驱动 xff0c 涉及驱动编写 测试程序编写 Makefile编写 驱动加载 卸载 xff0c 运行于Linux虚拟机 xff0c 不涉及底层配置 撰写本文的主要目的为记录一下驱动的开发流程 xff0c 参考了正点原