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,需要使用内核库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

测试结果与预期相同
入坑
入坑一:测试程序读取与预期不同
问题

原因

解决

Linux驱动开发笔记(六):用户层与内核层进行数据传递的原理和Demo的更多相关文章
- Linux驱动开发必看详解神秘内核(完全转载)
Linux驱动开发必看详解神秘内核 完全转载-链接:http://blog.chinaunix.net/uid-21356596-id-1827434.html IT168 技术文档]在开始步入L ...
- Linux驱动开发十六.input系统——3.系统自带的input驱动
前面两章我们通过input子系统构建了一个按键类型的输入设备的驱动,其实Linux的内核还提供了一套基于GPIO的按键驱动程序,和LED设备一样,我们只需要在编译内核的过程中进行配置然后在设备树中定义 ...
- 嵌入式linux驱动开发 笔记
@ 目录 首个驱动hellodrv 1.编写源码 2.编译模块 3.加载驱动 首个驱动hellodrv 3.如果下载不到,就自己编写,并编译驱动. 1.编写源码 2.编译模块 1.先写makefile ...
- Linux 驱动开发
linux驱动开发总结(一) 基础性总结 1, linux驱动一般分为3大类: * 字符设备 * 块设备 * 网络设备 2, 开发环境构建: * 交叉工具链构建 * NFS和tftp服务器安装 3, ...
- linux 驱动学习笔记01--Linux 内核的编译
由于用的学习材料是<linux设备驱动开发详解(第二版)>,所以linux驱动学习笔记大部分文字描述来自于这本书,学习笔记系列用于自己学习理解的一种查阅和复习方式. #make confi ...
- (55)Linux驱动开发之一驱动概述
驱动 ...
- Linux驱动开发学习的一些必要步骤
1. 学会写简单的makefile 2. 编一应用程序,可以用makefile跑起来 3. 学会写驱动的makefile 4. 写一简单char驱动,makefile编译通过,可以insmod, ...
- Django开发笔记六
Django开发笔记一 Django开发笔记二 Django开发笔记三 Django开发笔记四 Django开发笔记五 Django开发笔记六 1.登录功能完善 登录成功应该是重定向到首页,而不是转发 ...
- linux驱动开发流程
嵌入式linux驱动开发流程嵌入式系统中,操作系统是通过各种驱动程序来驾驭硬件设备的.设备驱动程序是操作系统内核和硬件设备之间的接口,它为应用程序屏蔽了硬件的细节,这样在应用程序看来,硬件设备只是一个 ...
- Linux驱动开发:USB驱动之usb_skel分析
在学习了这么些天的驱动之后,个人觉得驱动就是个架构的问题,只要把架构弄清楚了 然后往里面添砖加瓦就可以了,所以似乎看起来不是太困难,但也许是是我经验不足吧,这只能算是个人浅见了 这两天在学习USB驱动 ...
随机推荐
- C# 代码实现关机
AdvApi32.LookupPrivilegeValue(null, "SeShutdownPrivilege", out var lpLuid); using var t = ...
- 2-MySQL基本语法
上文说到,数据库的安装和连接,接下来将给大家讲解MySQL数据库的基本语法及数据的类型 1.基本语法 (1).查看当前所有数据库 : show databases; (2).创建数据库 create ...
- ora2pg使用记录
ora2pg使用记录 前言 这篇文章是我在学习使用ora2pg过程中的学习记录,以便日后遗忘查阅: 诸君也可跟随我的步伐了解一下ora2pg,或可移步如下官方文档参考学习:Ora2Pg : Migra ...
- Python常用模块-20个常用模块总结
目录 time模块 datetime模块 random 模块 os 模块 sys 模块 json 和 pickle 模块 hashlib和hmac 模块 logging 模块 numpy 模块 pan ...
- IEEE 国际计算科学与工程会议 (CSE-2023)
随着计算机系统变得越来越庞大和复杂,基于数据的计算技术在支持下一代科学和工程应用方面发挥着关键作用.如今,科学和工程中基于云的复杂大数据应用由异构软件/硬件/网络组件组成,这些组件的容量.可用性和环境 ...
- Java算法之动态规划
①动态规划 动态规划(Dynamic Programming,DP)是运筹学的一个分支,是求解决策过程最优化的过程.20世纪50年代初,美国数学家贝尔曼(R.Bellman)等人在研究多阶段决策过程的 ...
- 【XXE实战】——浅看两道CTF题
[XXE实战]--浅看两道CTF题 上一条帖子[XXE漏洞]原理及实践演示对XXE的一些原理进行了浅析,于是写了两道CTF题巩固一下,顺便也记录一下第一次写出来CTF.两道题都是在BUU上找的:[NC ...
- unity利用Rigibody实现第一人称移动
1. CameraRotation脚本,将它给MainCamera,实现上下视角旋转 using System.Collections; using System.Collections.Generi ...
- 概率期望 DP 题解合集
期望这东西学了一次忘了,再学一次过了两天又不会了.我是鱼. 故写此博客以便加深记忆及日后复习. NOIP 前恶补期望(? 希望有用,RP++() 经典问题 1 某事件发生概率为 \(p\),则该事件首 ...
- YCSB对MongoDB数据库性能测试
一.安装部署 1.1前置条件 Install Java and Maven Go to http://www.oracle.com/technetwork/java/javase/downloads/ ...