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驱动开发 的相关文章

随机推荐

  • 在树莓派/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 参考了正点原