Linux驱动开发笔记(六):用户层与内核层进行数据传递的原理和Demo

若该文为原创文章,转载请注明原文出处

本文章博客地址:https://hpzwl.blog.csdn.net/article/details/135384355

红胖子网络科技博文大全:开发技术集合(包含Qt实用技术、树莓派、三维、OpenCV、OpenGL、ffmpeg、OSG、单片机、软硬结合等等)持续更新中...

Linux系统移植和驱动开发专栏

上一篇:《Linux驱动开发笔记(五):驱动连接用户层与内核层的文件操作集原理和Demo

下一篇:敬请期待...

前言

驱动作为桥梁,用户层调用预定义名称的系统函数与系统内核交互,而用户层与系统层不能直接进行数据传递,进行本篇主要就是理解清楚驱动如何让用户编程来实现与内核的数据交互传递。

温故知新

  • 设备节点是应用层(用户层)与内核层交互;
  • 使用预先的结构体进行操作,如系统open函数对应了驱动中文件操作及的open指针结构体:struct file_operations;
  • 文件操作集结构体,填充结构体对应指针,填充自己使用到的就行了,多余的可以不填充,调用也不会崩溃或返回错误,会返回0;
      

那么如何将应用层的输入写入进去可用,如何将内核层的数据通过read返回出来,就是本篇学习了。

驱动模板准备

首先复制之前的testFileOpts的驱动,改个名字为:testFileOpts:

shell 复制代码
cd ~/work/drive/
ls
cp -arf 003_testFileOpts 004_testReadWrite
cd 004_testReadWrite/
make clean
ls
mv testFileOpts.c testReadWrite.c
vi Makefile 
ls

其中修改makefile里面的模块名称(obj-m模块名称),模板准备好了

shell 复制代码
gedit Makefile 

下面基于testReadWrite.c文件进行注册杂项设备,修改.c文件:

shell 复制代码
gedit testReadWrite.c
c 复制代码
#include <linux/init.h>
#include <linux/module.h>
#include <linux/miscdevice.h>
#include <linux/fs.h>

// int (*open) (struct inode *, struct file *);
int misc_open(struct inode * pInode, struct file * pFile)
{
    printk("int misc_open(struct inode * pInode, struct file * pFile)\n");
    return 0;
}

// int (*release) (struct inode *, struct file *);
int misc_release(struct inode * pInde, struct file * pFile)
{
    printk("int misc_release(struct inode * pInde, struct file * pFile)\n");
    return 0;
}

// ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t misc_read(struct file * pFile, char __user * pUser, size_t size, loff_t *pLofft)
{
    printk("ssize_t misc_read(struct file * pFile, char __user * pUser, size_t size, loff_t *pLofft)\n");
    return 0;
}

// ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
ssize_t misc_write(struct file * pFile, const char __user * pUser, size_t size, loff_t *pLofft)
{
    printk("ssize_t misc_write(struct file * pFile, const char __user * pUser, size_t size, loff_t *pLofft)\n");
    return 0;
}

struct file_operations misc_fops = {
  .owner = THIS_MODULE,
  .open = misc_open,
  .release = misc_release,
  .read = misc_read,
  .write = misc_write,
};

struct miscdevice misc_dev = {
    .minor = MISC_DYNAMIC_MINOR, // 这个宏是动态分配次设备号,避免冲突
    .name = "register_hongPangZi_testReadWrite", // 设备节点名称
    .fops = &misc_fops,  // 这个变量记住,自己起的,步骤二使用
};

static int registerMiscDev_init(void)
{ 
    int ret;
    // 在内核里面无法使用基础c库printf,需要使用内核库printk
    printk("Hello, I'm hongPangZi, registeraMiscDev_init\n");	
    ret = misc_register(&misc_dev);
    if(ret < 0)
    {
        printk("Failed to misc_register(&misc_dev)\n");	
        return -1;
    } 
    return 0;
}

static void registerMiscDev_exit(void)
{
    misc_deregister(&misc_dev);
    printk("bye-bye!!!\n");
}

MODULE_LICENSE("GPL");
module_init(registerMiscDev_init);
module_exit(registerMiscDev_exit);

概述

内核层和用户层不能中是不能直接与用户数据交互,需要使用内核函数copy_to_user和copy_from_user。

在内核中可以使用printk,memset,memcpy,strlen等函数。

内核函数

头文件是:linux/uaccess.h(我们这是ubuntu,不是arm)

可以在内核根目录下搜索下:

shell 复制代码
find . -type f -exec grep -l "copy_to_user(void" {} \;

copy_from_user函数:从用户层复制到内核层

c 复制代码
static __always_inline unsigned long __must_check
copy_from_user(void *to, const void __user *from, unsigned long n)

简化下:

c 复制代码
static unsigned long copy_from_user(void *to, const void __user *from, unsigned long n)

参数分别是,复制到的地址(内核空间),从什么地址复制(用户空间),复制长度;

copy_to_user函数:从内核层复制到用户层

c 复制代码
static __always_inline unsigned long __must_check
copy_to_user(void __user *to, const void *from, unsigned long n)

简化下:

c 复制代码
static unsigned long copy_to_user(void __user *to, const void *from, unsigned long n)

参数分别是,复制到的地址(用户空间),从什么地址复制(内核空间),复制长度;

杂项设备驱动添加数据传递函数Demo

步骤一:加入头文件和定义static缓存区

c 复制代码
#include <linux/uaccess.h>      // Demo_004 add
static char kBuf[256] = {0x00};  // Demo_004 add

步骤二:初始化缓存区

c 复制代码
// int (*open) (struct inode *, struct file *);
int misc_open(struct inode * pInode, struct file * pFile)
{
    printk("int misc_open(struct inode * pInode, struct file * pFile)\n");
    memcpy(kBuf, "init kBuf", sizeof("init kBuf"));
    printk("kBuf = %s\n", kBuf); 

    return 0;
}

步骤三:在驱动函数read中,添加从内核层到用户层的函数

c 复制代码
// ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t misc_read(struct file * pFile, char __user * pUser, size_t size, loff_t *pLofft)
{
    printk("ssize_t misc_read(struct file * pFile, char __user * pUser, size_t size, loff_t *pLofft)\n");
    if(copy_to_user(pUser, kBuf, strlen(kBuf)) != 0)
    {
        printk("Failed to copy_to_user(pUser, kBuf, strlen(kBuf)\n");
        return -1;
    }
    return 0;
}

步骤四:在驱动函数wirte中,添加从用户层到内核层的函数

c 复制代码
// ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
ssize_t misc_write(struct file * pFile, const char __user * pUser, size_t size, loff_t *pLofft)
{
    printk("ssize_t misc_write(struct file * pFile, const char __user * pUser, size_t size, loff_t *pLofft)\n");
    if(copy_from_user(kBuf, pUser, size) != 0)
    {
        printk("Failed to copy_from_user(kBuf, pUser, size)\n");
        return -1;
    }
    return 0;
}

步骤五:在程序中读取、写入、再读取

c 复制代码
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>

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

  const char devPath[] = "/dev/register_hongPangZi_testReadWrite";
  fd = open(devPath, O_RDWR);
  if(fd < 0)
  {
    printf("Failed to open %s\n", devPath);
    return -1;
  }else{
    printf("Succeed to open %s\n", devPath);
  }
  // 读取
  ret = read(fd, buf, sizeof(buf) < 0);
  if(ret < 0)
  {
    printf("Failed to read %s\n", devPath);
    close(fd);
    return 0;
  }else{
    printf("Succeed to read [%s]\n", buf);
  }
  // 修改内容
  memset(buf, 0x00, sizeof(buf));
  memcpy(buf, "Get you content", strlen("Get you content"));
  // 写入
  ret = write(fd, buf, sizeof(buf));
  if(ret < 0)
  {
    printf("Failed to write %s\n", devPath);
    close(fd);
    return 0;
  }else{
    printf("Succeed to write [%s]\n", buf);
  }
  // 读取
  ret = read(fd, buf, sizeof(buf) < 0);
  if(ret < 0)
  {
    printf("Failed to read %s\n", devPath);
    close(fd);
    return 0;
  }else{
    printf("Succeed to read [%s]\n", buf);
  }

  close(fd);

  printf("exit\n");

  fd = -1;


  return 0;
}

步骤六:编译加载驱动

shell 复制代码
make
sudo insmod testReadWrite.ko

步骤七:编译程序运行结果

shell 复制代码
gcc test.c
sudo ./a.out

测试结果与预期相同

入坑

入坑一:测试程序读取与预期不同

问题

原因

解决

上一篇:《Linux驱动开发笔记(五):驱动连接用户层与内核层的文件操作集原理和Demo

下一篇:敬请期待...

本文章博客地址:https://hpzwl.blog.csdn.net/article/details/135384355

相关推荐
dualven_in_csdn1 小时前
搞了两天的win7批处理脚本问题
java·linux·前端
晨曦backend2 小时前
Vim 匹配跳转与搜索命令完整学习笔记
linux·编辑器·vim
我不是加奈3 小时前
QMC5883L的驱动
c语言·驱动开发·单片机·嵌入式硬件
爬呀爬的水滴4 小时前
解决Ubuntu24.04版本,右键没有共享选项的问题
linux·服务器·ubuntu·samba·共享文件夹
IT coke4 小时前
centos7部署AWStats日志分析系统
linux·运维·centos
雾岛心情5 小时前
【黑客与安全】Linux的常用命令之系统架构信息获取系列命令
linux·运维·服务器
杯莫停丶5 小时前
Linux基础指令大全
linux·运维·chrome
卫生纸不够用6 小时前
(三)Linux性能优化-CPU-CPU 使用率
linux·运维·服务器
Clownseven7 小时前
Ubuntu 24.04 LTS 长期支持版发布:对服务器用户意味着什么?新特性、升级建议与性能影响初探
linux·服务器·ubuntu
Xam_d_LM7 小时前
【Linux】服务器反向代理自动续签免费 Let‘s Encrypt 证书报错解决方法
linux·运维·服务器·反向代理·lets encrypt·http-01·证书续签