字符设备驱动读写操作实现

这篇具有很好参考价值的文章主要介绍了字符设备驱动读写操作实现。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一、读操作实现

ssize_t xxx_read(struct file *filp, char __user *pbuf, size_t count, loff_t *ppos);
完成功能:读取设备产生的数据
参数:
    filp:指向open产生的struct file类型的对象,表示本次read对应的那次open
    pbuf:指向用户空间一块内存,用来保存读到的数据
    count:用户期望读取的字节数
    ppos:对于需要位置指示器控制的设备操作有用,用来指示读取的起始位置,读完后也需要变更位置指示器的指示位置
 返回值:
    本次成功读取的字节数,失败返回-1

put_user(x,ptr)

x:char、int类型的简单变量名

unsigned long copy_to_user (void __user * to, const void * from, unsigned long n)
这个函数通常用于从内核空间复制数据到用户空间。
to 参数是指向用户空间的目标地址,您希望将数据复制到这个地址。
from 参数是指向内核空间的源地址,您希望从这个地址复制数据。
n 参数是要复制的字节数。


成功为返回0,失败非0

二、写操作实现

ssize_t xxx_write (struct file *filp, const char __user *pbuf, size_t count, loff_t *ppos);  
完成功能:向设备写入数据
参数:
    filp:指向open产生的struct file类型的对象,表示本次write对应的那次open
    pbuf:指向用户空间一块内存,用来保存被写的数据
    count:用户期望写入的字节数
    ppos:对于需要位置指示器控制的设备操作有用,用来指示写入的起始位置,写完后也需要变更位置指示器的指示位置
 返回值:
    本次成功写入的字节数,失败返回-1

get_user(x,ptr)

x:char、int类型的简单变量名

unsigned long copy_from_user (void * to, const void __user * from, unsigned long n)
这个函数通常用于从用户空间复制数据到内核空间。
to:参数是指向内核空间的目标地址,您希望将数据复制到这个地址。
from:参数是指向用户空间的源地址,您希望从这个地址复制数据。
n:参数是要复制的字节数。

成功为返回0,失败非0

实现代码:

mychar.c

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <asm/uaccess.h>

#define BUF_LEN 100

int major = 11;					//主设备号
int minor = 0;					//次设备号
int char_num = 1;				//设备号数量

struct cdev mydev;
char mydev_buf[BUF_LEN];
int curlen = 0;

int mychar_open (struct inode *pnode, struct file *pfile)//打开设备
{
	printk("open\n");
	return 0;
}

int mychar_close(struct inode *pnode, struct file *pfile)//关闭设备
{
	printk("close\n");
	return 0;
}

ssize_t mychar_read (struct file *pfile, char __user *puser, size_t count, loff_t *p_pos) {
	int size = 0;
	int ret = 0;

	// 确定要读取的数据长度,如果请求大于设备当前数据长度,则读取全部可用数据
	if (count > curlen) {
		size = curlen;
	}
	else {
		size = count;
	}

	// 将设备数据复制到用户空间缓冲区
	ret = copy_to_user(puser, mydev_buf, size);
	if(ret) {
		printk("copy_to_user failed\n");
		return -1;
	}

	// 移动设备内部缓冲区,去除已读取的数据
	memcpy(mydev_buf, mydev_buf + size, curlen - size);

	curlen = curlen - size;
	
	// 返回实际读取的字节数
	return size;
}

ssize_t mychar_write (struct file *pfile, const char __user *puser, size_t count, loff_t *p_pos) {
	int size = 0;
	int ret = 0;

	// 确定要写入的数据长度,如果请求大于设备缓冲区剩余空间,则写入剩余空间大小
	if (count > BUF_LEN - curlen) {
		size = BUF_LEN - curlen;
	}
	else {
		size = count;
	}

	// 从用户空间复制数据到设备缓冲区
	ret = copy_from_user(mydev_buf + curlen, puser, size);
	if(ret) {
		printk("copy_from_user failed\n");
		return -1;
	}

	// 更新设备缓冲区中的数据长度
	curlen = curlen + size;

	 // 返回实际写入的字节数
	return size;
}

struct file_operations myops = {
	.owner = THIS_MODULE,
	.open = mychar_open,
	.read = mychar_read,
	.write = mychar_write,
	.release = mychar_close,
};

int __init mychar_init(void) 
{
	int ret = 0;
	dev_t devno = MKDEV(major, minor);

	/* 手动申请设备号 */
	ret = register_chrdev_region(devno, char_num, "mychar");
	if (ret) {
		/* 动态申请设备号 */
		ret = alloc_chrdev_region(&devno, minor, char_num, "mychar");
		if(ret){
			printk("get devno failed\n");
			return -1;
		}
		/*申请成功 更新设备号*/
		major = MAJOR(devno);
	}
	
	cdev_init(&mydev, &myops);

	mydev.owner = THIS_MODULE;
	cdev_add(&mydev, devno, char_num);

	return 0;
}

void __exit mychar_exit(void) 
{

	dev_t devno = MKDEV(major, minor);
	printk("exit %d\n", devno);
	
	/* 从内核中移除一个字符设备 */
	cdev_del(&mydev);

	/* 回收设备号 */
	unregister_chrdev_region(devno, char_num);

}

MODULE_LICENSE("GPL");
module_init(mychar_init);
module_exit(mychar_exit);

testmychar_app.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>

int main(int argc, char *argv[])
{
	int fd = -1;
	char buf[32] = "";

	if(argc < 2) {
		printf("The argument is too few\n");
		return -1;
	}

	fd = open(argv[1], O_RDWR);
	if(fd < 0) {
		perror("open");
		return -1;
	}

	write(fd, "hello", 6);

	read(fd, buf, 32);

	printf("buf = %s\n", buf);
	close(fd);
	fd = -1;
	return 0;
}

Makefile

ifeq ($(KERNELRELEASE),)

ifeq ($(ARCH),arm)
KERNELDIR ?= /home/myubuntu/Linux_4412/kernel/linux-3.14 
ROOTFS ?= /opt/4412/rootfs
else
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
endif
PWD := $(shell pwd)

modules:
	$(MAKE) -C $(KERNELDIR) M=$(PWD) modules

modules_install:
	$(MAKE) -C $(KERNELDIR) M=$(PWD) modules INSTALL_MOD_PATH=$(ROOTFS) modules_install

clean:
	rm -rf  *.o  *.ko  .*.cmd  *.mod.*  modules.order  Module.symvers   .tmp_versions

else
# obj-m += myhello.o

# obj-m += mytest.o
# mytest-objs = test.o func.o

# obj-m += testparam.o

obj-m += mychar.o
endif

实现结果:
字符设备驱动读写操作实现,驱动开发,linux

避免使用全局变量

这些代码都是全局变量
struct cdev mydev;
char mydev_buf[BUF_LEN];
int curlen = 0;

改动之后的mychar.c代码
把这些全局变量封装在结构体中,这样可以更好地管理数据,降低代码的耦合性,提高代码的可维护性和可扩展性文章来源地址https://www.toymoban.com/news/detail-684530.html

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <asm/uaccess.h>

#define BUF_LEN 100

int major = 11;					//主设备号
int minor = 0;					//次设备号
int char_num = 1;				//设备号数量

struct mychar_dev 
{
	struct cdev mydev;
	char mydev_buf[BUF_LEN];
	int curlen;
};
struct mychar_dev gmydev;

int mychar_open (struct inode *pnode, struct file *pfile)//打开设备
{
	pfile->private_data = (void *) (container_of(pnode->i_cdev, struct mychar_dev, mydev));
	printk("open\n");
	return 0;
}

int mychar_close(struct inode *pnode, struct file *pfile)//关闭设备
{
	printk("close\n");
	return 0;
}

ssize_t mychar_read (struct file *pfile, char __user *puser, size_t count, loff_t *p_pos) {

	struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
	int size = 0;
	int ret = 0;

	// 确定要读取的数据长度,如果请求大于设备当前数据长度,则读取全部可用数据
	if (count > pmydev->curlen) {
		size = pmydev->curlen;
	}
	else {
		size = count;
	}

	// 将设备数据复制到用户空间缓冲区
	ret = copy_to_user(puser, pmydev->mydev_buf, size);
	if(ret) {
		printk("copy_to_user failed\n");
		return -1;
	}

	// 移动设备内部缓冲区,去除已读取的数据
	memcpy(pmydev->mydev_buf, pmydev->mydev_buf + size, pmydev->curlen - size);

	pmydev->curlen -= size;
	
	// 返回实际读取的字节数
	return size;
}

ssize_t mychar_write (struct file *pfile, const char __user *puser, size_t count, loff_t *p_pos) {

	struct mychar_dev *pmydev = (struct mychar_dev *)pfile->private_data;
	int size = 0;
	int ret = 0;

	// 确定要写入的数据长度,如果请求大于设备缓冲区剩余空间,则写入剩余空间大小
	if (count > BUF_LEN - pmydev->curlen) {
		size = BUF_LEN - pmydev->curlen;
	}
	else {
		size = count;
	}

	// 从用户空间复制数据到设备缓冲区
	ret = copy_from_user(pmydev->mydev_buf + pmydev->curlen, puser, size);
	if(ret) {
		printk("copy_from_user failed\n");
		return -1;
	}

	// 更新设备缓冲区中的数据长度
	pmydev->curlen += size;

	 // 返回实际写入的字节数
	return size;
}

struct file_operations myops = {
	.owner = THIS_MODULE,
	.open = mychar_open,
	.read = mychar_read,
	.write = mychar_write,
	.release = mychar_close,
};

int __init mychar_init(void) 
{
	int ret = 0;
	dev_t devno = MKDEV(major, minor);

	/* 手动申请设备号 */
	ret = register_chrdev_region(devno, char_num, "mychar");
	if (ret) {
		/* 动态申请设备号 */
		ret = alloc_chrdev_region(&devno, minor, char_num, "mychar");
		if(ret){
			printk("get devno failed\n");
			return -1;
		}
		/*申请成功 更新设备号*/
		major = MAJOR(devno);
	}
	
	cdev_init(&gmydev.mydev, &myops);

	gmydev.mydev.owner = THIS_MODULE;
	cdev_add(&gmydev.mydev, devno, char_num);

	return 0;
}

void __exit mychar_exit(void) 
{

	dev_t devno = MKDEV(major, minor);
	printk("exit %d\n", devno);
	
	/* 从内核中移除一个字符设备 */
	cdev_del(&gmydev.mydev);

	/* 回收设备号 */
	unregister_chrdev_region(devno, char_num);

}

MODULE_LICENSE("GPL");
module_init(mychar_init);
module_exit(mychar_exit);

到了这里,关于字符设备驱动读写操作实现的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

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

领支付宝红包 赞助服务器费用

相关文章

  • 操作系统实验·字符设备驱动程序

    编写一个简单的字符设备驱动程序,该字符设备并不驱动特定的硬件, 而是用内核空间模拟字符设备,要求该字符设备包括以下几个基本操作,打开、读、写和释放,并编写测试程序用于测试所编写的字符设备驱动程序。在此基础上,编写程序实现对该字符设备的同步操作。

    2024年02月10日
    浏览(45)
  • Linux -- 字符设备驱动--LED的驱动开发(初级框架)

    看原理图确定引脚,确定引脚输出什么电平才能点亮 / 熄灭 LED 看主芯片手册,确定寄存器操作方法:哪些寄存器?哪些位?地址是? 编写驱动:先写框架,再写硬件操作的代码 注意 :在芯片手册中确定的寄存器地址被称为 物理地址 ,在 Linux 内核中无法直接使用。 需要使

    2024年04月28日
    浏览(22)
  • Linux下字符设备驱动开发以及流程介绍

    首先我们介绍一下什么是字符设备,然后讲解一下字符设备开发的具体的流程,分别详细介绍每一个流程中涉及到的结构体以及知识点,最后我们编写代码实现字符设备的开发以及测试。 Linux内核设计哲学是把所有的东西都抽象成文件进行访问,这样对设备的访问都是通过文

    2024年02月01日
    浏览(30)
  • Linux设备驱动开发学习笔记(等待队列,锁,字符驱动程序,设备树,i2C...)

    container_of函数可以通过结构体的成员变量检索出整个结构体 函数原型: 内核开发者只实现了循环双链表,因为这个结构能够实现FIFO和LIFO,并且内核开发者要保持最少代码。 为了支持链表,代码中要添加的头文件是linux/list.h。内核中链表实现核心部分的数据结构 是struct li

    2024年01月22日
    浏览(38)
  • 字符设备驱动内部实现原理解析

    用户层: ​ 当用户打开(open)一个文件时,会生成一个文件描述符表 内核层: 内核层的VFS虚拟文件系统层,根据open传递的文件路径找到文件的inode 结构体 inode号是存在文件系统的唯一标识,同时也是索引当前文件的inode的索引号 根据inode结构体找到文件对应的驱动对象指针

    2024年02月10日
    浏览(27)
  • 驱动开发学习之字符设备同时点亮三盏LED灯

    head.h test.c mychrdev.c

    2024年02月16日
    浏览(30)
  • 【IMX6ULL驱动开发学习】02.hello驱动程序之cdev注册字符设备驱动程序和设置次设备号

    目录 一、register_chrdev 二、解决方法 2.1 alloc_chrdev_region函数:注册一系列字符设备编号 2.2 cdev_init函数:初始化cdev结构体  2.3  cdev_add函数:将字符设备添加到系统中  三、驱动程序 【IMX6ULL驱动开发学习】01.编写第一个hello驱动+自动创建设备节点(不涉及硬件操作)_阿龙还

    2024年02月14日
    浏览(29)
  • 字符设备实现内部驱动原理及分步注册流程

     应用层:open函数回调到驱动中open操作方法的路线: open()---sys_open()---struct inode结构体---struct cdev结构体---struct file_operations结构体---mycdev_open() 1、分配对象空间 2、对象空间的初始化 3、对象的注册 4、对象的注销 流程模板(非详细) open函数参数是路径下的文件名字,根据

    2024年02月09日
    浏览(25)
  • 【linux驱动开发】在linux内核中注册一个杂项设备与字符设备以及内核传参的详细教程

    开发环境: windows + ubuntu18.04 + 迅为rk3568开发板 相较于字符设备,杂项设备有以下两个优点: 节省主设备号:杂项设备的主设备号固定为 10,在系统中注册多个 misc 设备驱动时,只需使用子设备号进行区分即可。 使用简单:相比如普通的字符设备驱动, misc驱动只需要将基本信

    2024年01月21日
    浏览(35)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包