IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 系统运维 -> Linux驱动开发【一】:字符设备驱动 -> 正文阅读

[系统运维]Linux驱动开发【一】:字符设备驱动

[驱动文件内容]

对设备的操作函数

  1. 打开设备
  2. 从设备文件中读取数据
  3. 向设备文件内写入数据
  4. 关闭设备文件

三个组件

  1. 设备操作函数结构体
  2. 设备初始化(作为驱动的入口函数)
  3. 设备注销(作为驱动的出口函数)

其他信息

  1. LICENSE
  2. 作者信息

驱动文件包含的函数

  1. 打开设备
    static int chrdevbase_open(struct inode *inode, struct file *filp)
  2. 从设备读取数据
    static ssize_t chrdevbase_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
  3. 向设备写数据
    static ssize_t chrdevbase_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
  4. 关闭设备
    static int chrdevbase_release(struct inode *inode, struct file *filp)
  5. 驱动入口函数(注册)
    static int __init chrdevbase_init(void)
  6. 驱动出口函数(注销)
    static void __exit chrdevbase_exit(void)

驱动文件包含的设备操作函数结构体

这个结构体内的成员函数被赋值为我们在驱动文件里编写的相对应的函数
static struct file_operations chrdevbase_fops =
{
.owner = THIS_MODULE,
.open = chrdevbase_open,
.read = chrdevbase_read,
.write = chrdevbase_write,
.release = chrdevbase_release,
};

注意事项

  1. 关于从设备读取数据的函数(xxx_read())
    在编写 xxx_read 函数的时候,不要直接读取在内核中定义的数据,即 kerneldata 的内容,
    而是要先用 memcpy 函数拷贝一份数据到发送缓冲区 readbuf 中,然后用 copy_to_user() 函数(该
    函数的功能是复制一段数据给用户)将缓冲区的数据复制给用户空间的数据缓冲区 buf 中,这样做的
    目的主要是为了保证内核的独立性。而在应用程序中读取buf内的数据就行了。

  2. 关于向设备写数据的函数(xxx_write())
    直接利用 copy_from_user() 函数将数据搬到 writebuf 中就行。

  3. 关于驱动初始化(chrdevbase_init())
    利用函数 register_chrdev() 注册设备驱动,该函数有3个参数,分别是:

    1. 主设备号: CHRDEVBASE_MAJOR
    2. 设备名: CHRDEVBASE_NAME
    3. 设备操作函数结构体: &chrdevbase_fops

[应用文件内容]

对驱动文件的操作过程

  1. 获取驱动文件的文件名
  2. 用open()函数打开驱动文件
  3. 用read()函数从驱动文件内读取数据(optional)
  4. 用write()函数向驱动文件写入数据(optional)
  5. 读写完毕后用close()函数关闭文件

参数说明

  1. fd: 文件描述符
  2. retvalue: 文件操作返回值
  3. *filename: 指针,用来存放文件的名字
  4. argc: 应用程序参数个数
  5. argv[]: 应用程序的具体参数集合,字符串的形式
    argv[0]: 应用程序名
    argv[1]: 要操作的驱动文件名
    argv[2]: 操作标志
  6. readbuf[100]: 数据接收缓冲区
  7. writebuf[100]: 数据发送缓冲区

应用程序的主函数流程

  1. 先判断应用程序参数是否齐全,齐全则继续,不齐全则返回 -1;
  2. 利用 filename 接收驱动文件的名字,并放入argv[1]中;
  3. 用 open 函数打开文件,并用 fd 接收函数的返回值,
    如果 fd = -1 ,则说明文件打开失败;
  4. 文件打开成功后,用 read 函数读取文件,用 retvalue 接收函数的返回值,
    如果 retvalue < 0 则说明读取文件内容失败,否则成功读取数据,并打印出来,
    read(fd, readbuf, 50) 的意思是读取指定的文件描述符的文件中50个字节数据,
    并放入 readbuf 即接收缓冲区中;
  5. 再利用 write 函数将数据写入驱动文件中,需要注意的是,向驱动文件写入数据时,
    不能直接写入,而应该利用 memcpy 函数拷贝一份用户数据存放到临时的发送缓冲区中,
    以此来保证内核的独立性。因为 writebuf 在函数执行时才会临时在堆栈中申请内存空间,
    当函数执行完后就会立即被释放掉;
  6. 当读写操作完成后,就用 close 函数关闭驱动文件。
  • 注意事项:读写操作要通过判断argv[2]的数值来决定是读数据还是写数据,可以利用
    函数 atoi() 将字符串类型转化为整数型数据。

代码

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!"};	/* 内核要发送的数据 */

/*
 * @description		: 打开设备
 * @param - inode 	: 传递给驱动的inode
 * @param - filp 	: 设备文件,file结构体有个叫做private_data的成员变量
 * 					  一般在open的时候将private_data指向设备结构体。
 * @return 			: 0 成功;其他 失败
 */
static int chrdevbase_open(struct inode *inode, struct file *filp)
{
	//printk("chrdevbase open!\r\n");
	return 0;
}

/*
 * @description		: 从设备读取数据
 * @param - filp 	: 要打开的设备文件(文件描述符)
 * @param - buf 	: 返回给用户空间的数据缓冲区
 * @param - cnt 	: 要读取的数据长度
 * @param - offt 	: 相对于文件首地址的偏移
 * @return 			: 读取的字节数,如果为负值,表示读取失败
 */
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 senddata ok!\r\n");
	}
	else{
		printk("kernel senddata failed!\r\n");
	}
	
	//printk("chrdevbase read!\r\n");
	return 0;
}

/*
 * @description		: 向设备写数据 
 * @param - filp 	: 设备文件,表示打开的文件描述符
 * @param - buf 	: 要写给设备写入的数据
 * @param - cnt 	: 要写入的数据长度
 * @param - offt 	: 相对于文件首地址的偏移
 * @return 			: 写入的字节数,如果为负值,表示写入失败
 */
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 recevdata failed!\r\n");
	}
	
	//printk("chrdevbase write!\r\n");
	return 0;
}

/*
 * @description		: 关闭/释放设备
 * @param - filp 	: 要关闭的设备文件(文件描述符)
 * @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 = chrdevbase_open,
	.read = chrdevbase_read,
	.write = chrdevbase_write,
	.release = chrdevbase_release,
};

/*./chrdevbaseApp /dev/chrdevbase 1 		: 无
 * @return 		: 0 成功;其他 失败
 */
static int __init chrdevbase_init(void)
{
	int retvalue = 0;

	/* 注册字符设备驱动 */
	/* para:主设备号,设备名,设备操作函数结构体 */
	retvalue = register_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME, &chrdevbase_fops);
	/* 返回值小于0则说明设备注册失败 */
	if(retvalue < 0){
		printk("chrdevbase driver register failed\r\n");
	}
	printk("chrdevbase init!\r\n");
	return 0;
}

/*
 * @description	: 驱动出口函数
 * @param 		: 无
 * @return 		: 无
 */
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("swiler");

chrdevApp.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 usrdata[] = {"usr data!"};

/*
 * @description		: main主程序
 * @param - argc 	: argv数组元素个数,应用程序参数个数
 * @param - argv[] 	: 具体参数,字符串形式
 *  ./chrdevbaseAPP	<filename>
 * @return 			: 0 成功;其他 失败
 */
/* argv[0]是应用程序本身,argv[1]是要操作的文件名,argv[2]是操作标志 */
int main(int argc, char *argv[])
{
	//fd:文件描述符
	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("Can'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("read data:%s\r\n",readbuf);
		}
	}

	/* 向设备驱动写数据 */
	if(atoi(argv[2]) == 2){
		memcpy(writebuf, usrdata, sizeof(usrdata));
		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;
}

Makefile文件

KERNELDIR := /home/swiler/linux_core/linux-imx-rel_imx_4.1.15_2.1.0_ga
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

终端执行命令

编译

#清理工程
make clean
#编译工程,生成驱动 chrdevbase.ko 文件
make
#利用交叉编译器编译应用文件 chedevbaseApp.c
arm-linux-gnueabihf-gcc chrdevbaseApp.c -o chrdevbaseApp
#将两个文件复制到自己构建的根文件系统中去
cp chrdevbase.ko  chrdevbaseApp /home/swiler/workdir/rootfs/lib/modules/4.1.15 -f

启动

开发板在u-boot模式下,通过tftp启动内核,进入 /lib/modules/4.1.15/ 目录下;

  1. 先加载驱动文件,用 insmod chrdevbase.ko 或 modprobe chrdevbase.ko 命令。
    (首次使用modprobe命令要先执行depmod命令)。
  2. 再创建设备节点文件
    命令:mknod /dev/chrdevbase c 200 0
  3. 执行应用程序 chrdevbaseApp,测试驱动是否正常运行。

正常运行则会出现如图所示:

在这里插入图片描述

  系统运维 最新文章
配置小型公司网络WLAN基本业务(AC通过三层
如何在交付运维过程中建立风险底线意识,提
快速传输大文件,怎么通过网络传大文件给对
从游戏服务端角度分析移动同步(状态同步)
MySQL使用MyCat实现分库分表
如何用DWDM射频光纤技术实现200公里外的站点
国内顺畅下载k8s.gcr.io的镜像
自动化测试appium
ctfshow ssrf
Linux操作系统学习之实用指令(Centos7/8均
上一篇文章      下一篇文章      查看所有文章
加:2022-05-06 11:20:15  更:2022-05-06 11:21:49 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/15 18:01:08-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码