i.MX8MM处理器采用了先进的14LPCFinFET工艺,提供更快的速度和更高的电源效率;四核Cortex-A53,单核Cortex-M4,多达五个内核 ,主频高达1.8GHz,2G DDR4内存、8G EMMC存储。千兆工业级以太网、MIPI-DSI、USB HOST、WIFI/BT、4G模块、CAN、RS485等接口一应俱全。H264、VP8视频硬编码,H.264、H.265、VP8、VP9视频硬解码,并提供相关历程,支持8路PDM接口、5路SAI接口、2路Speaker。系统支持Android9.0(支持获取root限)Linux4.14.78+Qt5.10.1、Yocto、Ubuntu20、Debian9系统。适用于智能充电桩,物联网,工业控制,医疗,智能交通等,可用于任何通用工业和物联网应用、
【公众号】迅为电子
【粉丝群】258811263(加群获取驱动文档+例程)
第四十章 Linux用户层和内核层
本章导读
由于字符设备和块设备都良好地体现了“一切都是文件”的设计思想,掌握设备文件的读写操作,如何在Linux用户层和内核层之间传递数据就显得尤为重要。
第39章我们已经成功地使用杂项设备生成了一个设备节点,这个设备节点是用来做什么的呢?内核层和应用层是如何进行数据交互的呢?
40.1章节讲解了Linux用户层和内核层交互的基本知识
40.2章节编写了基于最简单杂项设备框架实现读写的驱动程序和测试应用程序
40.3 章节编译驱动程序及运行测试
40.4 章节讲解了应用层的内核层之间数据交互,并分别用实验测试验证。
本章内容对应视频讲解链接(在线观看):
应用层和内核层数据传输 → https://www.bilibili.com/video/BV1Vy4y1B7ta?p=11
程序源码在网盘资料“iTOP-i.MX8MM开发板\02-i.MX8MM开发板网盘资料汇总(不含光盘内容)\嵌入式Linux开发指南(iTOP-i.MX8MM)手册配套资料\2.驱动程序例程\03-应用层与内核层互传数据”路径下。
40.1 Linux用户层和内核层交互
首先我们要明确一个概念,Linux一切皆文件!驱动文件最终通过与文件操作相关的系统调用或者C库函数(本质也是系统调用)被访问,而设备驱动的结构最终也是为了迎合提供给应用程序的API。(在Windows编程领域,习惯称操作系统的接口为API)。我们先来了解一下基本的概念。
概念1设备节点
在Linux中,所有设备都以文件的形式存放在/dev目录下,都是通过文件的方式进行访问,设备节点是Linux内核对设备的抽象,一个设备节点就是一个文件。应用程序通过一组标准化的调用执行访问设备,这些调用独立于任何特定的驱动程序。而驱动程序负责将这些标准调用映射到实际硬件的特有操作。
设备节点,驱动,硬件设备是怎样关联到一起的呢?这是通过设备号实现的,包括主设备号和次设备号。当我们创建一个设备节点时需要指定主设备号和次设备号。应用程序通过名称访问设备,而设备号指定了对应的驱动程序和对应的设备。主设备号标识设备对应的驱动程序,次设备号由内核使用,用于确定设备节点所指设备。
主设备号:驱动程序在初始化时,会注册它的驱动及对应主设备号到系统中,这样当应用程序访问设备节点时,系统就知道它所访问的驱动程序了。你可以通过/proc/devices文件来查看系统设备的主设备号。
次设备号:驱动程序遍历设备时,每发现一个它能驱动的设备,就创建一个设备对象,并为其分配一个次设备号以区分不同的设备。这样当应用程序访问设备节点时驱动程序就可以根据次设备号知道它说访问的设备了。
设备节点(设备文件):Linux中设备节点是通过“mknod”命令来创建的。一个设备节点其实就是一个文件,Linux中称为设备文件。有一点必要说明的是,在Linux中,所有的设备访问都是通过文件的方式,一般的数据文件称为普通文件,设备节点称为设备文件。设备节点就是连接上层应用和底层驱动的桥梁,如下图所示:
设备驱动:设备驱动程序(device driver),简称驱动程序(driver),是一个允许高级(High level)计算机软件(computer software)与硬件(hardware)交互的程序,这种程序建立了一个硬件与硬件,或硬件与软件沟通的界面,经由主板上的总线(bus)或其它沟通子系统(subsystem)与硬件形成连接的机制,这样的机制使得硬件设备(device)上的数据交换成为可能。想想平时我们说的写驱动,例如点led灯的驱动,就是简单的io操作。
文件对应的操作有打开,关闭,读写,那么设备节点也可以看成一个文件,那么设备节点对应的操作有打开,关闭,读写。如果我在应用层使用系统IO(系统调用)对设备节点进行打开,关闭,读写等操作会发生什么呢?
file_operations结构体是访问驱动的函数,它的里面的每个结构体成员都对应一个调用,这个结构体里面有很多的成员变量,并且结构体中的成员函数是字符设备驱动程序设计的主体内容,这些函数实际会在应用程序进行Linux的open()、write()、read()、close()等系统调用时最终被内核调用。file_operations文件操作集在定义在/home/topeet/linux/linux-imx/include/linux/fs.h下面,如下图所示。
struct file_operations {
struct module *owner;
loff_t (*llseek) (struct file *, loff_t, int);
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
int (*iterate) (struct file *, struct dir_context *);
unsigned int (*poll) (struct file *, struct poll_table_struct *);
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
int (*mmap) (struct file *, struct vm_area_struct *);
int (*open) (struct inode *, struct file *);
int (*flush) (struct file *, fl_owner_t id);
int (*release) (struct inode *, struct file *);
int (*fsync) (struct file *, loff_t, loff_t, int datasync);
int (*aio_fsync) (struct kiocb *, int datasync);
int (*fasync) (int, struct file *, int);
int (*lock) (struct file *, int, struct file_lock *);
ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long,unsigned long, unsigned long);
int (*check_flags)(int);
int (*flock) (struct file *, int, struct file_lock *);
ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
int (*setlease)(struct file *, long, struct file_lock **);
long (*fallocate)(struct file *file, int mode, loff_t offset,loff_t len);
int (*show_fdinfo)(struct seq_file *m, struct file *f);};
下面我们对file_operations结构体中的主要成员进行分析。
函数 | 功能 |
llseek()函数 | 修改一个文件的当前读写位置,并将新位置返回,在出错时,这个函数返回一个负值。 |
read()函数 | 用来从设备中读取数据,成功时函数返回读取的字节数,出错时返回一个负值。它与用户空间应用程序中的ssize_t read(int fd,void*buf,size_t count)和size_t fread(void*ptr,size_t size,size_t nmemb,FILE*stream)对应。 |
write()函数 | 向设备发送数据,成功时该函数返回写入的字节数。如果此函数未被实现,当用户进行write()系统调用时,将得到-EINVAL返回值。它与用户空间应用程序中的ssize_t write(int fd,const void*buf,size_t count)和size_t fwrite(const void*ptr,size_t size,size_t nmemb,FILE*stream)对应。 |
read()和write() | 如果返回0,则暗示end-of-file(EOF)。 |
unlocked_ioctl() | 提供设备相关控制命令的实现(既不是读操作,也不是写操作),当调用成功时,返回给调用程序一个非负值。它与用户空间应用程序调用的int fcntl(int fd,int cmd,.../*arg*/)和int ioctl(int d,int request,...)对应。 |
mmap()函数 | 将设备内存映射到进程的虚拟地址空间中,如果设备驱动未实现此函数,用户进行 mmap()系统调用时将获得-ENODEV返回值。这个函数对于帧缓冲等设备特别有意义,帧缓冲被映射到用户空间后,应用程序可以直接访问它而无须在内核和应用间进行内存复制。它与用户空间应用程序中的 void*mmap(void*addr,size_t length,int prot,int flags,int fd,off_t offset)函数对应。 |
poll()函数 | 一般用于询问设备是否可被非阻塞地立即读写。当询问的条件未触发时,用户空间进行select()和poll()系统调用将引起进程的阻塞。 |
aio_read()和aio_write()函数 | 分别对与文件描述符对应的设备进行异步读、写操作。设备实现这 两个函数后,用户空间可以对该设备文件描述符执行SYS_io_setup、SYS_io_submit、SYS_io_getevents、SYS_io_destroy等系统调用进行读写。 |
open()函数 | 当用户空间调用Linux API函数open()打开设备文件时,设备驱动的open()函数最终被调用。驱动程序可以不实现这个函数,在这种情况下,设备的打开操作永远成功。与open()函数对应的是release()函数。 |
实际情况下我们是用不了这么多调用的。常用的调用如下所示:
当我们在应用层read设备节点的时候,就会触发我们驱动里面read这个函数。
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
当我们在应用层write设备节点的时候,就会触发我们驱动里面write这个函数。
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
当我们在应用层poll/select的时候,就会触发我们驱动里面poll这个函数。
unsigned int (*poll) (struct file *, struct poll_table_struct *);
当我们在应用层ioctl的时候,就会触发我们驱动里面ioctl这个函数。
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
当我们在应用层open的时候,就会触发我们驱动里面open这个函数。
int (*open) (struct inode *, struct file *);
当我们在应用层close的时候,就会触发我们驱动里面close这个函数。
int (*release) (struct inode *, struct file *);
40.2 编写驱动程序和应用程序
通过40.1章节的学习,我们已经把内核层和用户层实现数据交互的基本概念搞懂了,在上一章节的基础上我们编写驱动程序实现在内核层与应用层传数据。
新建file_operation.c文件在Ubuntu的/home/topeet/imx8m/03目录下,可以在上次实验misc.c的基础上进行修改。
填充file_operation结构体
//文件操作集
struct file_operations misc_fops={.owner = THIS_MODULE,.open = misc_open,.release = misc_release,.read = misc_read,.write = misc_write,
};
填充接口函数
ssize_t misc_read (struct file *file, char __user *ubuf, size_t size, loff_t *loff_t)
{printk("misc_read\n ");return 0;
}
ssize_t misc_write (struct file *file, const char __user *ubuf, size_t size, loff_t *loff_t){printk("misc_write\n ");return 0;
}
int misc_release(struct inode *inode,struct file *file){ printk("hello misc_relaease bye bye \n ");return 0;
}
int misc_open(struct inode *inode,struct file *file){printk("hello misc_open\n ");return 0;
}
完整驱动代码如下:
/** @Descripttion: 在上一章节实现了最简单杂项设备的编写,本代码再其基础上验证内核层与应用层数据交互*/#include <linux/init.h> //初始化头文件
#include <linux/module.h> //最基本的文件,支持动态添加和卸载模块。
#include <linux/miscdevice.h>/*注册杂项设备头文件*/
#include <linux/uaccess.h>
#include <linux/fs.h>/*** @name: misc_read* @test: 从设备中读取数据,当用户层调用函数read时,对应的,内核驱动就会调用这个函数。* @msg: * @param {structfile} *file file结构体* @param {char__user} *ubuf 这是对应用户层的read函数的第二个参数void *buf* @param {size_t} size 对应应用层的read函数的第三个参数* @param {loff_t} *loff_t 这是用于存放文件的偏移量的,回想一下系统编程时,读写文件的操作都会使偏移量往后移。* @return {*} 当返回正数时,内核会把值传给应用程序的返回值。一般的,调用成功会返回成功读取的字节数。
如果返回负数,内核就会认为这是错误,应用程序返回-1*/ssize_t misc_read(struct file *file, char __user *ubuf, size_t size, loff_t *loff_t)
{printk("misc_read\n ");return 0;
}/*** @name: misc_write* @test: 往设备写入数据,当用户层调用函数write时,对应的,内核驱动就会调用这个函数。* @msg: * @param {structfile} * filefile结构体* @param {constchar__user} *ubuf 这是对应用户层的write函数的第二个参数const void *buf* @param {size_t} size 对应用户层的write函数的第三个参数count。* @param {loff_t} *loff_t 这是用于存放文件的偏移量的,回想一下系统编程时,读写文件的操作都会使偏移量往后移。* @return {*} 当返回正数时,内核会把值传给应用程序的返回值。一般的,调用成功会返回成功读取的字节数。如果返回负数,内核就会认为这是错误,应用程序返回-1。*/ssize_t misc_write(struct file *file, const char __user *ubuf, size_t size, loff_t *loff_t)
{printk("misc_write\n ");return 0;
}/*** @name: misc_release* @test: 当设备文件被关闭时内核会调用这个操作,当然这也可以不实现,函数默认为NULL。关闭设备永远成功。* @msg: * @param {structinode} *inode 设备节点* @param {structfile} *file filefile结构体* @return {0}*/
int misc_release(struct inode *inode,struct file *file){printk("hello misc_release bye bye \n");return 0;
}/*** @name: misc_open* @test: 在操作设备前必须先调用open函数打开文件,可以干一些需要的初始化操作。* @msg: * @param {structinode} *inode 设备节点* @param {structfile} *file filefile结构体* @return {0}*/
int misc_open(struct inode *inode, struct file *file)
{printk("hello misc_open\n ");return 0;
}//文件操作集
struct file_operations misc_fops ={.owner = THIS_MODULE,.open = misc_open,.release = misc_release,.read = misc_read,.write = misc_write,
};
//miscdevice结构体
struct miscdevice misc_dev ={.minor = MISC_DYNAMIC_MINOR,.name = "hello_misc",.fops = &misc_fops,
};static int misc_init(void)
{int ret;ret = misc_register(&misc_dev); //注册杂项设备if (ret < 0){printk("misc registe is error \n");}printk("misc registe is succeed \n");return 0;
}
static void misc_exit(void)
{misc_deregister(&misc_dev); //卸载杂项设备printk(" misc gooodbye! \n");
}
module_init(misc_init);
module_exit(misc_exit);
MODULE_LICENSE("GPL");
我们编写应用程序app.c,在ubuntu的/home/topeet/imx8m/03目录下,完整代码如下图所示:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc,char *argv[])
{int fd; //定义一个句柄char buf[64] = {0};fd = open("/dev/hello_misc",O_RDWR);//打开设备节点if(fd < 0){perror("open error \n");return fd;}write(fd,buf,sizeof(buf));close(fd);return 0;
}
输入以下命令编译app.c,可以参考本手册“交叉编译器的安装和使用”章节编译C程序,如下所示:
40.3 编译驱动及运行测试
40.2章节我们已经编写好了驱动文件,这里我们以iTOP-iMX8MM开发板为例,将杂项设备驱动编译成模块。我们将file_operation.c文件拷贝到ubuntu的/home/topeet/imx8m/03目录下。将上次编译misc的Makefile文件和build.sh文件拷贝到file_operation.c同级目录下,修改Makefile为:
obj-m += file_operation.o
KDIR:=/home/topeet/linux/linux-imx
PWD?=$(shell pwd)
all:make -C $(KDIR) M=$(PWD) modules ARCH=arm64
clean:make -C $(KDIR) M=$(PWD) clean
拷贝好的文件如下图所示
输入“./build.sh”编译驱动程序,如下图所示:
驱动编译完,我们通过nfs将编译好的驱动程序加载模块,输入以下命令,我们进入到共享目录/mnt/03,加载驱动模块如图所示:
insmod file_operation.ko
ls /dev/hello_misc
在iMX8MM开发板上运行app程序,如下图所示,应用程序可以打开节点/dev/hello_misc,打开之后又关闭节点。
思考:
假如我们的file_operations 里面没有read,我们在应用层read设备节点的时候会发生什么?
答:什么也不会发生,也不会报错!
40.4 应用层和内核层传递数据
我们的应用层和内核层是不能直接进行数据传输的。我们要想进行数据传输,要借助下面的这两个函数。
static inline long copy_from_user(void *to, const void __user * from, unsigned long n)
static inline long copy_to_user(void __user *to, const void *from, unsigned long n)
用户空间-->内核空间,如下图所示:
函数 | copy_from_user(void *to, const void __user *from, unsigned long n) |
参数to | 目标地址(内核空间) |
参数from | 源地址(用户空间) |
参数n | 将要拷贝数据的字节数 |
返回值 | 成功返回0,失败返回没有拷贝成功的数据字节数 |
功能 | 将用户空间数据拷贝到内核空间 |
内核空间-->用户空间,如下图所示:
函数 | copy_to_user(void __user *to, const void *from, unsigned long n) |
参数to | 目标地址(用户空间) |
参数from | 源地址(内核空间) |
参数n | 将要拷贝数据的字节数 |
返回值 | 成功返回0,失败返回没有拷贝成功的数据字节数 |
功能 | 将内核空间数据拷贝到用户空间 |
40.4.1 应用层从内核层读数据
程序源码在网盘资料“iTOP-i.MX8MM开发板\02-i.MX8MM开发板网盘资料汇总(不含光盘内容)\嵌入式Linux开发指南(iTOP-i.MX8MM)手册配套资料\2.驱动程序例程\03-应用层与内核层互传数据\001”路径下。
我们修改40.2章节编写的驱动程序,我们要从内核层读数据,完整代码如下所示:
/** @Descripttion: * @version: * @Author: sueRimn* @Date: 2021-02-23 12:40:42* @LastEditors: sueRimn* @LastEditTime: 2021-02-23 12:43:00*/
#include <linux/init.h> //初始化头文件
#include <linux/module.h> //最基本的文件,支持动态添加和卸载模块。
#include <linux/miscdevice.h> //注册杂项设备头文件
#include <linux/uaccess.h>
#include <linux/fs.h>
/*** @name: misc_read* @test: 从设备中读取数据,当用户层调用函数read时,对应的,内核驱动就会调用这个函数。* @msg: * @param {structfile} *file file结构体* @param {char__user} *ubuf 这是对应用户层的read函数的第二个参数void *buf* @param {size_t} size 对应应用层的read函数的第三个参数* @param {loff_t} *loff_t 这是用于存放文件的偏移量的,回想一下系统编程时,读写文件的操作都会使偏移量往后移。* @return {*} 当返回正数时,内核会把值传给应用程序的返回值。一般的,调用成功会返回成功读取的字节数。如果返回负数,内核就会认为这是错误,应用程序返回-1*/
ssize_t misc_read(struct file *file, char __user *ubuf, size_t size, loff_t *loff_t)
{char kbuf[] = "hehe";if (copy_to_user(ubuf, kbuf, strlen(kbuf)) != 0){printk("copy_to_user error\n ");return -1;}printk("misc_read\n ");return 0;
}
/*** @name: misc_write* @test: 往设备写入数据,当用户层调用函数write时,对应的,内核驱动就会调用这个函数。* @msg: * @param {structfile} * filefile结构体* @param {constchar__user} *ubuf 这是对应用户层的write函数的第二个参数const void *buf* @param {size_t} size 对应用户层的write函数的第三个参数count。* @param {loff_t} *loff_t 这是用于存放文件的偏移量的,回想一下系统编程时,读写文件的操作都会使偏移量往后移。* @return {*} 当返回正数时,内核会把值传给应用程序的返回值。一般的,调用成功会返回成功读取的字节数。如果返回负数,内核就会认为这是错误,应用程序返回-1。*/
ssize_t misc_write(struct file *file, const char __user *ubuf, size_t size, loff_t *loff_t)
{char kbuf[64] = {0};if (copy_from_user(kbuf, ubuf, size) != 0){printk("copy_from_user error\n ");return -1;}printk("kbuf is %s\n ", kbuf);return 0;
}
/*** @name: misc_release* @test: 当设备文件被关闭时内核会调用这个操作,当然这也可以不实现,函数默认为NULL。关闭设备永远成功。* @msg: * @param {structinode} *inode 设备节点* @param {structfile} *file filefile结构体* @return {0}*/
int misc_release(struct inode *inode, struct file *file)
{printk("hello misc_relaease bye bye \n ");return 0;
}
/*** @name: misc_open* @test: 在操作设备前必须先调用open函数打开文件,可以干一些需要的初始化操作。* @msg: * @param {structinode} *inode 设备节点* @param {structfile} *file filefile结构体* @return {0}*/
int misc_open(struct inode *inode, struct file *file)
{printk("hello misc_open\n ");return 0;
}
//文件操作集
struct file_operations misc_fops = {.owner = THIS_MODULE,.open = misc_open,.release = misc_release,.read = misc_read,.write = misc_write,
};
//miscdevice结构体
struct miscdevice misc_dev = {.minor = MISC_DYNAMIC_MINOR,.name = "hello_misc",.fops = &misc_fops,
};
static int misc_init(void)
{int ret;ret = misc_register(&misc_dev); //注册杂项设备if (ret < 0){printk("misc registe is error \n");}printk("misc registe is succeed \n");return 0;
}
static void misc_exit(void)
{misc_deregister(&misc_dev); //卸载杂项设备printk(" misc gooodbye! \n");
}
module_init(misc_init);
module_exit(misc_exit);
MODULE_LICENSE("GPL");
应用程序app.c,修改如下:
/** @Descripttion: * @version: * @Author: sueRimn* @Date: 2021-02-23 12:36:04* @LastEditors: sueRimn* @LastEditTime: 2021-02-23 12:36:54*/
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc,char *argv[])
{int fd;char buf[64] = {0};fd = open("/dev/hello_misc",O_RDWR);//打开设备节点if(fd < 0){perror("open error \n");return fd;}//读内核层数据read(fd,buf,sizeof(buf));printf("buf is %s\n",buf);//write(fd,buf,sizeof(buf));close(fd);return 0;
}
我们再次编译驱动程序和应用程序app,如下图所示:
加载驱动模块和运行应用程序如下图所示:
从上图可以看到已经从内核里面读取到信息"hehe"并打印。
然后我们卸载驱动,如下所示:
rmmod file_operation
40.4.2 应用层向内核层写数据
程序源码在网盘资料“iTOP-i.MX8MM开发板\02-i.MX8MM开发板网盘资料汇总(不含光盘内容)\嵌入式Linux开发指南(iTOP-i.MX8MM)手册配套资料\2.驱动程序例程\03-应用层与内核层互传数据\002”路径下。
我们修改40.2章节编写的驱动函数,我们要从应用层读数据,完整代码如下所示:
#include <linux/init.h> //初始化头文件
#include <linux/module.h> //最基本的文件,支持动态添加和卸载模块。
#include <linux/miscdevice.h> //注册杂项设备头文件
#include <linux/uaccess.h>
#include <linux/fs.h>
/*** @name: misc_read* @test: 从设备中读取数据,当用户层调用函数read时,对应的,内核驱动就会调用这个函数。* @msg: * @param {structfile} *file file结构体* @param {char__user} *ubuf 这是对应用户层的read函数的第二个参数void *buf* @param {size_t} size 对应应用层的read函数的第三个参数* @param {loff_t} *loff_t 这是用于存放文件的偏移量的,回想一下系统编程时,读写文件的操作都会使偏移量往后移。* @return {*} 当返回正数时,内核会把值传给应用程序的返回值。一般的,调用成功会返回成功读取的字节数。如果返回负数,内核就会认为这是错误,应用程序返回-1*/
ssize_t misc_read(struct file *file, char __user *ubuf, size_t size, loff_t *loff_t)
{char kbuf[] = "hehe";if (copy_to_user(ubuf, kbuf, strlen(kbuf)) != 0){printk("copy_to_user error\n ");return -1;}printk("misc_read\n ");return 0;
}
/*** @name: misc_write* @test: 往设备写入数据,当用户层调用函数write时,对应的,内核驱动就会调用这个函数。* @msg: * @param {structfile} * filefile结构体* @param {constchar__user} *ubuf 这是对应用户层的write函数的第二个参数const void *buf* @param {size_t} size 对应用户层的write函数的第三个参数count。* @param {loff_t} *loff_t 这是用于存放文件的偏移量的,回想一下系统编程时,读写文件的操作都会使偏移量往后移。* @return {*} 当返回正数时,内核会把值传给应用程序的返回值。一般的,调用成功会返回成功读取的字节数。如果返回负数,内核就会认为这是错误,应用程序返回-1。*/
ssize_t test_misc_write(struct file *file, const char __user *ubuf, size_t size, loff_t *loff_t)
{char kbuf[64] = {0};if (copy_from_user(kbuf, ubuf, size) != 0){printk("copy_from_user error\n ");return -1;}printk("kbuf is %s\n ", kbuf);return 0;
}
/*** @name: misc_release* @test: 当设备文件被关闭时内核会调用这个操作,当然这也可以不实现,函数默认为NULL。关闭设备永远成功。* @msg: * @param {structinode} *inode 设备节点* @param {structfile} *file filefile结构体* @return {0}*/
int misc_release(struct inode *inode, struct file *file)
{printk("hello misc_relaease bye bye \n ");return 0;
}
/*** @name: misc_open* @test: 在操作设备前必须先调用open函数打开文件,可以干一些需要的初始化操作。* @msg: * @param {structinode} *inode 设备节点* @param {structfile} *file filefile结构体* @return {0}*/
int misc_open(struct inode *inode, struct file *file)
{printk("hello misc_open\n ");return 0;
}
//文件操作集
struct file_operations misc_fops = {.owner = THIS_MODULE,.open = misc_open,.release = misc_release,.read = misc_read,.write = test_misc_write
};
//miscdevice结构体
struct miscdevice misc_dev = {.minor = MISC_DYNAMIC_MINOR,.name = "hello_misc",.fops = &misc_fops,
};
static int misc_init(void)
{int ret;ret = misc_register(&misc_dev); //注册杂项设备if (ret < 0){printk("misc registe is error \n");}printk("misc registe is succeed \n");return 0;
}
static void misc_exit(void)
{misc_deregister(&misc_dev); //卸载杂项设备printk(" misc gooodbye! \n");
}
module_init(misc_init);
module_exit(misc_exit);
MODULE_LICENSE("GPL");
应用程序修改如下:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main(int argc,char *argv[])
{int fd;char buf[64] = "12345";fd = open("/dev/hello_misc",O_RDWR);//打开设备节点if(fd < 0){perror("open error \n");return fd;}//read(fd,buf,sizeof(buf));write(fd,buf,sizeof(buf)); //向内核层写数据//printf("buf is %s\n",buf);close(fd);return 0;
}
我们再次编译驱动程序和应用程序,如下图所示:
加载驱动模块和运行应用程序如下图所示:
如上图所示,我们成功从应用层读取信息"12345"到内核层。
卸载驱动,如下图所示: