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

发布时间 2024-01-04 13:46:07作者: 红胖子(红模仿)

前言

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

 

温故知新

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

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

 

驱动模板准备

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

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模块名称),模板准备好了

gedit Makefile 

  在这里插入图片描述

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

gedit testReadWrite.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)
  可以在内核根目录下搜索下:

find . -type f -exec grep -l "copy_to_user(void" {} \;

  在这里插入图片描述

  在这里插入图片描述

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

static __always_inline unsigned long __must_check
copy_from_user(void *to, const void __user *from, unsigned long n)

  简化下:

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

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

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

static __always_inline unsigned long __must_check
copy_to_user(void __user *to, const void *from, unsigned long n)

  简化下:

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

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

 

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

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

  在这里插入图片描述

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

步骤二:初始化缓存区

  在这里插入图片描述

// 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中,添加从内核层到用户层的函数

  在这里插入图片描述

// 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中,添加从用户层到内核层的函数

  在这里插入图片描述

// 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;
}

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

  在这里插入图片描述

#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;
}

步骤六:编译加载驱动

  在这里插入图片描述

make
sudo insmod testReadWrite.ko

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

gcc test.c
sudo ./a.out

  在这里插入图片描述

  测试结果与预期相同

 

入坑

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

问题

  在这里插入图片描述

原因

  在这里插入图片描述

解决