Linux驱动开发

  本文为一个简单的字符设备驱动,涉及驱动编写、测试程序编写、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 设备已被卸载。

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

在这里插入图片描述文章来源地址https://uudwc.com/A/zrDO

原文地址:https://blog.csdn.net/qq_43557686/article/details/126228066

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请联系站长进行投诉反馈,一经查实,立即删除!

上一篇 2023年06月15日 01:00
wifi定频操作rtwpriv移植编译
下一篇 2023年06月15日 01:00