04-read & write

从内核中最简单的驱动程序入手,描述Linux驱动开发,主要文章目录如下(持续更新中):
01 - 第一个内核模块程序
02 - 注册字符设备驱动
03 - open & close 函数的应用
04 - read & write 函数的应用
05 - ioctl 的应用
06 - ioctl LED灯硬件分析
07 - ioctl 控制LED软件实现(寄存器操作)
08 - ioctl 控制LED软件实现(库函数操作)
09 - 注册字符设备的另一种方法(常用)
10 - 一个cdev实现对多个设备的支持
11 - 四个cdev控制四个LED设备
12 - 虚拟串口驱动
13 - I2C驱动
14 - SPI协议及驱动讲解
15 - SPI Linux驱动代码实现
16 - 非阻塞型I/O
17 - 阻塞型I/O
18 - I/O多路复用之 select
19 - I/O多路复用之 poll
20 - I/O多路复用之 epoll
21 - 异步通知


上一节说到了open和close函数,这一节将描述read和write接口的使用

示例代码

demo.c

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/uaccess.h>

int demo_open(struct inode *inode, struct file *filp)
{
	printk("%s -- %d.\n", __FUNCTION__, __LINE__);

	return 0;
}

int demo_release(struct inode *inode, struct file *filp)
{
	printk("%s -- %d.\n", __FUNCTION__, __LINE__);

	return 0;
}

/*
	应用层: ssize_t read(int fd, void *buf, size_t count);
			@param1: 文件描述符
			@param2: 将读到的信息,存放在buf指定的内存空间中
			@param3: 要读取的字节数(不是读到的数据大小)
			@return: 实际读取到的字节数
	驱动层: ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
			@param1: 文件指针
			@param2: 应用层read函数的参数2,指向持有被写入数据的缓存
			@param3: 应用层read函数的参数3,请求的传输数据大小
			@param4: 一个指针指向一个“long offset type”对象, 它指出用户正在存取的文件位置
			@return: 正确读取的字节数
	实现:  	unsigned long copy_to_user(void __user *to, const void *from, unsigned long n)
			@param1: 用户空间缓存区首地址
			@param2: 内核空间的缓存区首地址
			@param3: 实际拷贝的字节数
			@return: 0成功,非0出错
			该函数调用了access_ok来验证用户空间的内存是否真实可读写,避免在内核中的缺页故障带来的一些问题
*/
char kbuf[4] = {1,2,3,4};
ssize_t demo_read(struct file *filp, char __user *userbuf, size_t size, loff_t *offset)
{
	int rbytes;	/* 定义的变量要写在函数内部最前面,否则会报警告 */

	printk("%s -- %d.\n", __FUNCTION__, __LINE__);
	printk("size = %d.\n", size);
	
	rbytes = copy_to_user(userbuf, kbuf, size);
	if (rbytes != 0)
	{
		printk("copy_to_user failed.\n");
		return -1;
	}

	return 0;
}

/*
	应用层: ssize_t write(int fd, void *buf, size_t count);
			@param1: 文件描述符
			@param2: 从buf指向的空间中拿取信息,写入fd中
			@param3: 写入的字节数( 通常:sizeof(buf) )
			@return: 成功写入的字节数
	驱动层: ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
			@param1: 文件指针
			@param2: 应用层read函数的参数2,指向持有被写入数据的缓存
			@param3: 应用层read函数的参数3,请求的传输数据大小
			@param4: 一个指针指向一个“long offset type”对象, 它指出用户正在存取的文件位置
			@return: 正确写入的字节数
	实现:  	unsigned long copy_from_user(void __user *to, const void *from, unsigned long n)
			@param1: 内核空间的缓存区首地址
			@param2: 用户空间缓存区首地址
			@param3: 实际拷贝的字节数
			@return: 0成功,非0出错
			该函数调用了access_ok来验证用户空间的内存是否真实可读写,避免在内核中的缺页故障带来的一些问题
*/
ssize_t demo_write(struct file *filp, const char __user *userbuf, size_t size, loff_t *offset)
{
	int i = 0;
	int wbytes;

	printk("%s -- %d.\n", __FUNCTION__, __LINE__);
	printk("size = %d.\n", size);
	
	wbytes = copy_from_user(kbuf, userbuf, size);
	if(wbytes){
		printk("copy_from_user failed.\n");
		return -1;
	}
	
	for(i = 0;i < 4;i ++){
		printk("kbuf[%d] : %d.\n", i, kbuf[i]);
	}

	return 0;
}

int major;
const char * name = "demoname";
const struct file_operations fops = {
	.open = demo_open,
	.release = demo_release,
	.read = demo_read,
	.write = demo_write,
};

struct class *cls;
struct device *dev;


static int __init demo_init(void)
{
	printk("%s -- %d.\n", __FUNCTION__, __LINE__);

	major = register_chrdev(0, name, &fops);		/* /proc/devices */
	if (major <= 0)
	{
		printk("register_chrdev failed.\n");
		goto err0;
	}
	
	cls = class_create(THIS_MODULE, "char_class");	/* sys/class/xxx */
	if (cls == NULL)
	{
		printk("class_create failed.\n");
		goto err1;
	}

	dev = device_create(cls, NULL, MKDEV(major, 0), NULL, "chrdev%d", 0);	/* /dev/chrdev0 */
	if (dev == NULL)
	{
		printk("device_create failed.\n");
		goto err2;
	}
	
	return 0;

err2:
	class_destroy(cls);
err1:
	unregister_chrdev(major, name);
err0:
	return major;
}

static void __exit demo_exit(void)
{
	printk("%s -- %d.\n", __FUNCTION__, __LINE__);	

	device_destroy(cls, MKDEV(major, 0));
	class_destroy(cls);
	unregister_chrdev(major, name);
}

module_init(demo_init);
module_exit(demo_exit);
MODULE_LICENSE("GPL");

open和close函数不是本节的重点,在此仅对此接口进行调用。
在read函数中,调用了内核中的 copy_to_user 函数,可将内核空间的数据复制到用户空间
在write函数中,调用了内核中的 copy_from_user 函数,可将用户空间的数据复制到内核空间

test.c

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

const char *pathname ="/dev/chrdev0";

int main(int argc, const char *argv[])
{	
	int fd, i = 0;
	char buf[4] = {0};
	size_t rbytes;

	fd = open(pathname, O_RDWR, 0664);
	if (fd < 0) 
	{
		printf("open failed\n");
		return -1;
	}

	rbytes = read(fd, buf, 4);
	printf("rbytes is %d\n", rbytes);

	for (i=0; i<4; i++)
	{
		printf("buf[%d] = %d\n", i, buf[i]);
	}

	sleep(3);
	buf[0] = 11;
	buf[2] = 33;
	write(fd, buf, sizeof(buf));

	sleep(3);
	close(fd);
	
	return 0;
}

Makefile

KERNELDIR ?= /home/linux/ti-processor-sdk-linux-am335x-evm-05.02.00.10/board-support/linux-4.14.79/
PWD := $(shell pwd)

all:
	make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -C $(KERNELDIR) M=$(PWD) modules
	arm-linux-gnueabihf-gcc test.c -o app
install:
	sudo cp *.ko  app /tftpboot
clean:
	make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- -C $(KERNELDIR) M=$(PWD) clean
	rm app

obj-m += demo.o

测试结果

root@am335x-evm:~# insmod demo.ko 
[16477.512691] demo_init -- 112.
root@am335x-evm:~# chmod +x app
root@am335x-evm:~# ./app 
[16492.111498] demo_open -- 8.
[16492.114892] demo_read -- 44.
[16492.117810] size = 4.
rbytes is 0
buf[0] = 1
buf[1] = 2
buf[2] = 3
buf[3] = 4
[16495.122308] demo_write -- 81.
[16495.125355] size = 4.
[16495.127654] kbuf[0] : 11.
[16495.130311] kbuf[1] : 2.
[16495.147257] kbuf[2] : 33.
[16495.149949] kbuf[3] : 4.
[16498.162930] demo_release -- 15.
root@am335x-evm:~# rmmod demo.ko 
[16651.937239] demo_exit -- 147.

分析测试结果可以看出:

  1. 在test.c中buf数组初始值均为0,调用read接口之后,将驱动中的kbuf数组中的数据复制到用户空间,所以打印出来的buf内容分别是 1 2 3 4;
  2. 调用write接口之前,将buf[0]和buf[2]分别重新赋值,从打印的结果可以看出,用户空间中修改后的数据拷贝到了内核空间,因此在内核中打印出的结果分别是11 2 33 4,最后加载了模块的卸载函数。
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值