前文讲述了字符设备的打开和关闭,本文我们对设备进行读写操作,使用一个例子:
先把一个字符串通过用户态write函数写进设备,再通过用户态read函数读出来。
用户态程序
#include <stdio.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <string.h>
#define FILE_NAME "/dev/mydev"
#define MAX_LEN 64
int main(void)
{
int fd, wlen, rlen;
char buf1[MAX_LEN] = "abcdefg";
char buf2[MAX_LEN] = {0};
fd = open(FILE_NAME, O_RDWR);
if (0 > fd)
{
printf("Open failed.\n");
return -1;
}
wlen = write(fd, buf1, strlen(buf1));
if (wlen < 0)
{
perror("Write error");
return wlen;
}
printf("Write buf1 len %d success.\n", wlen);
rlen = read(fd, buf2, MAX_LEN - 1);
if (rlen < 0)
{
perror("Read error");
return rlen;
}
printf("Read buf2 %s len %d success.\n", buf2, rlen);
close(fd);
}
read、write函数的原型如下:
#include <unistd.h>
ssize_t read(int fd, void *buf, size_t count);
ssize_t write(int fd, const void *buf, size_t count);
fd:文件描述符。
buf:read是从设备读上来的缓存,write是往设备里面写的缓存。
count:缓存的长度,或者从设备读数据或者往设备写数据的长度。
返回值:读或者写成功的字符数,有时0也代表成功,负数代表失败。
驱动程序
驱动程序就是实现cdev_ops (见《字符设备驱动(2)-驱动框架及open、release》)的read和write方法,即:
struct file_operations {
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
};
第一个参数是文件指针
第二个参数是用户态传下来的缓存。
第三个参数是用户态传下来的要读或者写的数据长度。
第四个参数是偏移量,若有多次读或者写,此值会记住上次的读或者写的偏移量。
返回值:返回读或者写成功的字节数,有时返回0也代表成功或者到末尾了,负数代表失败。
那么驱动程序如下:
#include <linux/string.h>
static char kbuf[MAX_LEN] = {0};
ssize_t my_read(struct file *pf, char __user *ubuf, size_t len, loff_t *pl)
{
if (MAX_LEN < len)
{
printk("len is large than %d.\n", MAX_LEN);
return -1;
}
strcpy(ubuf, kbuf);
return len;
}
ssize_t my_write(struct file *pf, const char __user *ubuf, size_t len, loff_t *pl)
{
if (MAX_LEN < len)
{
printk("len is large than %d.\n", MAX_LEN);
return -1;
}
strcpy(kbuf, ubuf);
return len;
}
struct file_operations cdev_ops = {
.open = my_open,
.release = my_close,
.read = my_read,
.write = my_write,
};
但是这儿有些问题,使用strcpy直接把用户态内存ubuf拷贝到内核态内存:
如上图,ubuf和kbuf都是虚拟内存,都会映射到一段物理内存,当内存紧张时,会把频率使用较低的内存交换到盘上(Linux是Swap分区),虚拟内存ubuf到物理内存的映射会断开,那在内核态写时直接操作ubuf拷贝到kbuf,或者读时,直接操作kbuf拷贝到ubuf可能会造成错误,为了避免这种错误,需要在前面加上一段检查代码,内核对这段代码封装了2个函数:
#include <asm/uaccess.h>
//读使用,拷贝到用户态内存
unsigned long copy_to_user(void __user *to, const void *from, unsigned long n)
//写使用,从用户态内存拷贝
unsigned long copy_from_user(void *to, const void __user * from, unsigned long n)
//to: 目的内存
//form: 源内存
//n: 字节数
//返回:0成功,非0,失败
所以改成:
static char kbuf[MAX_LEN] = {0};
ssize_t my_read(struct file *pf, char __user *ubuf, size_t len, loff_t *pl)
{
int ret = -1;
if (MAX_LEN < len)
{
printk("len is large than %d.\n", MAX_LEN);
return -1;
}
ret = copy_to_user(ubuf, kbuf, len);
if (0 != ret)
{
printk("Copy to user failed.\n");
return -1;
}
return len;
}
ssize_t my_write(struct file *pf, const char __user *ubuf, size_t len, loff_t *pl)
{
int ret = -1;
if (MAX_LEN < len)
{
printk("len is large than %d.\n", MAX_LEN);
return -1;
}
ret = copy_from_user(kbuf, ubuf, len);
if (0 != ret)
{
printk("Copy from user failed.\n");
return -1;
}
return len;
}
struct cdev cdevice;
struct file_operations cdev_ops = {
.open = my_open,
.release = my_close,
.read = my_read,
.write = my_write,
};
完整驱动程序
//head
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/string.h>
#define MAJOR_CHAR 100
#define MINOR_CHAR 0
#define MAX_LEN 64
static int my_open(struct inode *pnode, struct file *pfile)
{
printk("Open cdev.\n");
return 0;
}
static int my_close(struct inode *pnode, struct file *pfile)
{
printk("Close cdev.\n");
return 0;
}
static char kbuf[MAX_LEN] = {0};
ssize_t my_read(struct file *pf, char __user *ubuf, size_t len, loff_t *pl)
{
int ret = -1;
if (MAX_LEN < len)
{
printk("len is large than %d.\n", MAX_LEN);
return -1;
}
ret = copy_to_user(ubuf, kbuf, len);
if (0 != ret)
{
printk("Copy to user failed.\n");
return -1;
}
return len;
}
ssize_t my_write(struct file *pf, const char __user *ubuf, size_t len, loff_t *pl)
{
int ret = -1;
if (MAX_LEN < len)
{
printk("len is large than %d.\n", MAX_LEN);
return -1;
}
ret = copy_from_user(kbuf, ubuf, len);
if (0 != ret)
{
printk("Copy from user failed.\n");
return -1;
}
return len;
}
struct cdev cdevice;
struct file_operations cdev_ops = {
.open = my_open,
.release = my_close,
.read = my_read,
.write = my_write,
};
//加载
static int hello_init(void)
{
dev_t devno = MKDEV(MAJOR_CHAR,MINOR_CHAR);
int ret = -1;
printk(KERN_ALERT "Hello World.\n");
//up kernel
//1、注册设备号
ret = register_chrdev_region(devno, 1, "hello");
if (0 != ret)
{
printk("Register char device failed.\n");
return ret;
}
//2、初始化字符设备结构体
cdev_init(&cdevice, &cdev_ops);
cdevice.owner = THIS_MODULE;
//3、添加字符设备结构体给内核
ret = cdev_add(&cdevice,devno , 1);
if (0 != ret)
{
//注意释放设备号
unregister_chrdev_region(devno,1);
printk("Unregister char device.\n");
return ret;
}
printk("Register char device success.\n");
//down hardware
return 0;
}
//卸载函数(必须)
static void hello_exit(void)//返回值是void类型,函数名自定义,参数是void
{
dev_t devno = MKDEV(MAJOR_CHAR, MINOR_CHAR);
printk(KERN_ALERT "Goodbye World.\n");
// down hardware
// up kernel
//1、从内核中删除字符设备结构体
cdev_del(&cdevice);
//2、注销设备号
unregister_chrdev_region(devno, 1);
}
//注册(必须)
module_init(hello_init);
module_exit(hello_exit);
//license(必须)
MODULE_LICENSE("GPL");
//作者与描述(可选)
MODULE_AUTHOR("Ono Zhang");
MODULE_DESCRIPTION("A simple Hello World Module");
Makefile
obj-m = cdev.o
KERNELDIR = /usr/linux-kernel
PWD = $(shell pwd)
all:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
gcc app.c
modules:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
clean:
rm -rf a.out *.o *.mod *.ko cdev.mod.c cdev.mod Module.symvers modules.order
加载及运行
#insmod cdev.ko
Register char device success.
#mknod /dev/mydev c 100 0
#./a.out
Open cdev.
Write buf1 len 7 success.
Read buf2 abcdefg len 63 success.
Close cdev.
可以看到,my_read和my_write的返回值就是read和write的返回值。