前言
驱动作为桥梁,用户层调用预定义名称的系统函数与系统内核交互,而用户层与系统层不能直接进行数据传递,进行本篇主要就是理解清楚驱动如何让用户编程来实现与内核的数据交互传递。
温故知新
- 设备节点是应用层(用户层)与内核层交互;
- 使用预先的结构体进行操作,如系统open函数对应了驱动中文件操作及的open指针结构体:struct file_operations;
- 文件操作集结构体,填充结构体对应指针,填充自己使用到的就行了,多余的可以不填充,调用也不会崩溃或返回错误,会返回0;
那么如何将应用层的输入写入进去可用,如何将内核层的数据通过read返回出来,就是本篇学习了。
驱动模板准备
首先复制之前的testFileOpts的驱动,改个名字为:testFileOpts:
cd ~/work/drive/lscp-arf 003_testFileOpts 004_testReadWritecd 004_testReadWrite/make cleanlsmv testFileOpts.c testReadWrite.cvi Makefilels
其中修改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 *);intmisc_open(structinode* pInode,structfile* pFile){printk("int misc_open(struct inode * pInode, struct file * pFile)\n");return0;}// int (*release) (struct inode *, struct file *);intmisc_release(structinode* pInde,structfile* pFile){printk("int misc_release(struct inode * pInde, struct file * pFile)\n");return0;}// ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);ssize_tmisc_read(structfile* 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");return0;}// ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);ssize_tmisc_write(structfile* pFile,constchar __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");return0;}structfile_operations misc_fops={.owner= THIS_MODULE,.open= misc_open,.release= misc_release,.read= misc_read,.write= misc_write,};structmiscdevice misc_dev={.minor= MISC_DYNAMIC_MINOR,// 这个宏是动态分配次设备号,避免冲突.name="register_hongPangZi_testReadWrite",// 设备节点名称.fops=&misc_fops,// 这个变量记住,自己起的,步骤二使用};staticintregisterMiscDev_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;}return0;}staticvoidregisterMiscDev_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-execgrep-l"copy_to_user(void"{}\;
copy_from_user函数:从用户层复制到内核层
static __always_inlineunsignedlong __must_checkcopy_from_user(void*to,constvoid __user*from,unsignedlong n)
简化下:
staticunsignedlongcopy_from_user(void*to,constvoid __user*from,unsignedlong n)
参数分别是,复制到的地址(内核空间),从什么地址复制(用户空间),复制长度;
copy_to_user函数:从内核层复制到用户层
static __always_inlineunsignedlong __must_checkcopy_to_user(void __user*to,constvoid*from,unsignedlong n)
简化下:
staticunsignedlongcopy_to_user(void __user*to,constvoid*from,unsignedlong n)
参数分别是,复制到的地址(用户空间),从什么地址复制(内核空间),复制长度;
杂项设备驱动添加数据传递函数Demo
步骤一:加入头文件和定义static缓存区
#include<linux/uaccess.h>// Demo_004 addstaticchar kBuf[256]={0x00};// Demo_004 add
步骤二:初始化缓存区
// int (*open) (struct inode *, struct file *);intmisc_open(structinode* pInode,structfile* pFile){printk("int misc_open(struct inode * pInode, struct file * pFile)\n");memcpy(kBuf,"init kBuf",sizeof("init kBuf"));printk("kBuf = %s\n", kBuf);return0;}
步骤三:在驱动函数read中,添加从内核层到用户层的函数
// ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);ssize_tmisc_read(structfile* 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;}return0;}
步骤四:在驱动函数wirte中,添加从用户层到内核层的函数
// ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);ssize_tmisc_write(structfile* pFile,constchar __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;}return0;}
步骤五:在程序中读取、写入、再读取
#include<stdio.h>#include<unistd.h>#include<fcntl.h>intmain(int argc,char**argv){int fd=-1;char buf[32]={0};int ret=-1;constchar 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);return0;}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);return0;}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);return0;}else{printf("Succeed to read [%s]\n", buf);}close(fd);printf("exit\n");
fd=-1;return0;}
步骤六:编译加载驱动
makesudo insmod testReadWrite.ko
步骤七:编译程序运行结果
gcc test.csudo ./a.out
测试结果与预期相同