字符设备驱动(3)-read、write

      前文讲述了字符设备的打开和关闭,本文我们对设备进行读写操作,使用一个例子:

      先把一个字符串通过用户态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的返回值。

  • 4
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值