若该文为原创文章,转载请注明原文出处
本文章博客地址: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:
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,需要使用内核库printkprintk("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
测试结果与预期相同
入坑
入坑一:测试程序读取与预期不同
问题
原因
解决
上一篇:《Linux驱动开发笔记(五):驱动连接用户层与内核层的文件操作集原理和Demo》
下一篇:敬请期待…
本文章博客地址:https://hpzwl.blog.csdn.net/article/details/135384355