[Linux驱动开发四]设备具体读写功能的实现

目录

一、前言

二、读写功能相关的基本函数

2.1 struct inode

2.2 struct file

2.3 container_of()

2.4 copy_to_user()

2.5 copy_from_user()

三、编译运行

step1: 编译

step2: 插入内核

step3: 查看设备节点

step4: 读写测试

四、小结

五、往期内容


代码下载

一、前言

        之前的章节已经实现了最基本的字符设备,目前我们的字符设备拥有自动生成设备节点、基

本读写等功能。本章节是对基本读写功能的扩展,使得我们的字符设备的读写功能更加具体。

二、读写功能相关的基本函数

2.1 struct inode

        表示文件的结构体,主要利用其成员"i_cdev","i_cdev"保存了字符设备结构的地址。

2.2 struct file

        表示打开的文件描述符,主要利用其成员"private_data"。

2.3 container_of()

        通过结构体成员地址得到整个结构体变量的首地址,我们用在open()函数中。其函数原型如下

所示:

#define offsetof(TYPE, MEMBER) ((size_t) & ((TYPE *)0)->MEMBER)
#define container_of(ptr, type, member) ({                      \
        const typeof( ((type *)0)->member ) *__mptr = (ptr);    \
        (type *)( (char *)__mptr - offsetof(type,member) );})

输入参数:

        1)ptr: 结构体内成员member的地址;

        2)type: 结构体类型;

        3)member: 结构体内成员;

返回值:

        结构体的首地址。

        我们将字符设备结构定义为:

struct hello_char_dev{
    struct cdev cdev;
    char *c;  /* 保存字符串 */
    int n;  /* 保存字符个数 */
};

        我们在注册字符设备结构时,只是将"cdev"注册给了内核,内核只知道"cdev"的信息和地址,

并将其保存在"inode->i_cdev"中,但如果我们想要操作字符设备,必须要知道结构体地址,所以要

通过"container_of()"来获取结构体地址,大致流程如下图所示。

代码举例:

int hc_open(struct inode *inode, struct file *filp)
{
    struct hello_char_dev *hc_dev;

    /* 获取设备结构体的地址 */
    hc_dev = container_of(inode->i_cdev, struct hello_char_dev, cdev);

    /* 将设备结构地址放到文件描述符结构的私有数据中 */
    filp->private_data = hc_dev;

    return 0;
}

2.4 copy_to_user()

        将内核空间的数据拷贝到用户空间,我们在read()函数中使用,其定义如下。

unsigned long copy_to_user(void __user *to, const void *from, unsigned long n);

输入参数:

        1)to: 用户空间的指针;

        2)from是内核空间的指针;

        3)n表示从内核空间向用户空间拷贝数据的字节数;

返回值:

        若数据拷贝成功,则返回0,否则返回没有拷贝成功的字节数。

代码举例:

ssize_t hc_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
    ssize_t retval = 0;
    /* 获得字符设备的地址 ,保存在hc_dev中 */
    struct hello_char_dev *hc_dev = filp->private_data;

    /* 数据的偏移量是否大于数据的总量 */
    if (*f_pos >= hc_dev->n) {
        goto out;
    }

    /* 所需要读取个数与要读取数据之和是否大于数据总量*/
    if (*f_pos + count > hc_dev->n) {
        count = hc_dev->n - *f_pos;
    }

    if (copy_to_user(buf, hc_dev->c, count)) {
        retval = -EFAULT;
        goto out;
    }

    *f_pos += count;
    return count;

out:
    return retval;
}

2.5 copy_from_user()

        将用户空间的数据拷贝到内核空间, 我们将它用在write()函数中,其使用如下。

unsigned long copy_from_user(void *to, const void __user *from, unsigned long n);

输入参数:

        1)to表示内核空间的数据目标地址指针;  

        2)from表示用户空间的数据元地址指针;

        3)n表示数据的长度;

返回值:

        若拷贝成功返回0,否则返回没有拷贝成功的数据字节数。

ssize_t hc_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos)
{
    struct hello_char_dev *hc_dev = filp->private_data;
    int retval = -ENOMEM, i;

    /* 清空之前的数据 */
    kfree(hc_dev->c);
    hc_dev->c = NULL;
    hc_dev->n = 0;
    hc_dev->c = kzalloc(count, GFP_KERNEL);

    if (!hc_dev->c) {
        goto out;
    }

    if (copy_from_user(hc_dev->c, buf, count)) {
        retval = -EFAULT;
        goto fail_copy;
    }

    hc_dev->n = count;

    return count;

fail_copy:
    kfree(hc_dev->c);
out:
    return retval;
}

三、编译运行

step1: 编译

step2: 插入内核

step3: 查看设备节点

step4: 读写测试

1)读取字符设备(此时字符设备为空)

 2)向空字符设备写数据并查看 

3)再次读取设备(此时设备中已写入内容) 

【注】我们发现进行一次cat操作,却调用了两次read(),这是因为cat是通过文件结束符来结束读

取操作,第一次是读取了所有数据,此时并不知道已经到了文件末尾,第二次读到的长度为0,此

时才知道读到了文件末尾,cat退出。

四、小结

        本章节实现了字符设备具体的读写功能,但仍然存在不足。比如在write()函数中,多个进程同

时写,会造成内存泄露等问题,这些问题在接下来的章节会得到解决。

五、往期内容

[Linxu驱动开发一] 最简单内核模块

[Linux驱动开发二] 最简单的字符设备

[Linux驱动开发三] 实现自动生成设备节点

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值