【北京迅为】《i.MX8MM嵌入式Linux开发指南》-第三篇 嵌入式Linux驱动开发篇-第四十章 Linux用户层和内核层

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"到内核层。

卸载驱动,如下图所示:

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/1486277.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

力扣第二十五题——K个一组反转链表

内容介绍 给你链表的头节点 head &#xff0c;每 k 个节点一组进行翻转&#xff0c;请你返回修改后的链表。 k 是一个正整数&#xff0c;它的值小于或等于链表的长度。如果节点总数不是 k 的整数倍&#xff0c;那么请将最后剩余的节点保持原有顺序。 你不能只是单纯的改变节点内…

Vscode离线下载对应版本的ms-python.vsix

一、查看vscode的版本号和发行时间 vscode界面中Help-About查看版本号和发行时间&#xff0c;ms-python的发行时间需要和这个时间相近&#xff1a; 二、在github仓库中查看ms-python有什么版本&#xff0c;以及发行时间 github仓库路径 https://github.com/microsoft/vsco…

力扣题库合集(2):动态规划(1)

本文将持续更新~~ hello hello~ &#xff0c;这里是绝命Coding——老白~&#x1f496;&#x1f496; &#xff0c;欢迎大家点赞&#x1f973;&#x1f973;关注&#x1f4a5;&#x1f4a5;收藏&#x1f339;&#x1f339;&#x1f339; &#x1f4a5;个人主页&#xff1a;绝命C…

qt中charts图表的使用方法

折线图 #include "widget.h" #include "ui_widget.h" #include <QtCharts/QChart> #include <QtCharts/QChartView> #include <QtCharts/QLineSeries> #include<QVBoxLayout>Widget::Widget(QWidget *parent): QWidget(parent), …

Python解释器:CPython 解释器

一、什么是python解释器 Python解释器是一种用于执行Python代码的程序。 它将Python源代码转换为机器语言或字节码&#xff0c;从而使计算机能够执行。 1.1 Python解释器分类 1、CPython CPython 是 Python 的主要实现&#xff0c;由 C 语言编写。大多数用户在日常开发中使…

“机器说人话”-AI 时代的物联网

万物互联的物联网愿景已经提了许多年了&#xff0c;但是实际效果并不理想&#xff0c;除了某些厂商自己的产品生态中的产品实现了互联之外&#xff0c;就连手机控制空调&#xff0c;电视机和调光灯都没有实现。感觉小米做的好一点&#xff0c;而华为的鸿蒙的全场景&#xff0c;…

MMCV 核心组件分析(一):整体概述

概述 MMCV 是计算机视觉研究的基础库&#xff0c;并提供以下功能。

Go基础编程 - 12 -流程控制

流程控制 1. 条件语句1.1. if...else 语句1.2. switch 语句1.3. select 语句1.3.1. select 语句的通信表达式1.3.2. select 的基特性1.3.3. select 的实现原理1.3.4. 经典用法1.3.4.1 超时控制1.3.4.2 多任务并发控制1.3.4.3 监听多通道消息1.3.4.4 default 实现非堵塞读写 2. …

海康威视综合安防管理平台 detection 前台RCE漏洞复现

0x01 产品简介 海康威视综合安防管理平台是一套“集成化”、“智能化”的平台,通过接入视频监控、一卡通、停车场、报警检测等系统的设备。海康威视集成化综合管理软件平台,可以对接入的视频监控点集中管理,实现统一部署、统一配置、统一管理和统一调度。 0x02 漏洞概述 海康…

k8s中部署nacos

1 部署nfs # 在k8s的主节点上执行 mkdir -p /appdata/download cd /appdata/download git clone https://github.com/nacos-group/nacos-k8s.git 将nacos部署到middleware的命名空间中 kubectl create namespace middleware cd /appdata/download/nacos-k8s # 创建角色 kub…

JavaScript中==和===的区别

&#x1f9d1;‍&#x1f4bb; 写在开头 点赞 收藏 学会&#x1f923;&#x1f923;&#x1f923; 前言 JavaScript 中的相等运算符无疑是新手开发者最容易混淆的知识点之一。 和这两个运算符的细微差别往往会在代码中造成一些令人困惑的行为 在本文中,我们将深入探讨这两个…

Google Chrome 浏览器在链接上点右键的快捷键

如今&#xff0c;越来越多的软件都懒得设个快捷键&#xff0c;就算设置了连个下划线也懒得加了。 谷歌浏览器右键 > 链接另存为... 和 复制链接地址 的快捷键 (如图)

Flink架构底层原理详解:案例解析(43天)

系列文章目录 一、Flink架构&#xff08;掌握&#xff09; 二、Flink代码案例&#xff08;掌握&#xff09; 三、UDF&#xff08;熟悉&#xff09; 四、Flink常见面试题整理 文章目录 系列文章目录前言一、Flink架构&#xff08;掌握&#xff09;1、系统架构1.1 通信&#xff…

SystemUI默认去掉底部导航栏

一、背景 在Android系统中&#xff0c;SystemUI负责管理系统的状态栏、导航栏等用户界面元素。若要在SystemUI中默认去掉底部导航栏&#xff0c; 可以通过以下几种方法实现&#xff1a; 1. 修改布局文件 在Android的SystemUI源代码中&#xff0c;底部导航栏的布局文件通常…

SpringBoot+Session+redis实现分布式登录

SpringBootSessionRedis实现分布式登录功能实现 文章目录 目录 文章目录 前言 一、引库 二、修改配置文件 三、使用 四、解决乱码问题 1.引库 2.配置redis序列化 3.配置Session-Redis序列化 前言 这里简单介绍一下&#xff0c;如果你想多台机器部署你的项目的话&#xff0c;在…

重大突破!OpenAI 推出 GPT-4o mini,AI 领域再掀波澜!

北京时间 7 月 18 日晚&#xff0c;OpenAI 重磅推出“小模型”GPT-4o mini&#xff0c;其在文本智能和多模态推理方面展现出卓越性能&#xff0c;超越 GPT-3.5 Turbo&#xff0c;在 LMSYS“聊天机器人对战”排行榜上也力压 GPT-4。 GPT-4o mini 支持 128K Token 的长上下文窗口…

一起学Java(1)-新建一个Gradle管理的Java项目

一时兴起&#xff0c;也为了便于跟大家同步学习进展和分享样例代码&#xff0c;遂决定创建一个全新的Java项目&#xff0c;并通过Github与大家分享。本文就是记录该项目的创建过程以及其中的一些知识要点&#xff08;如Gradle等&#xff09;。为了紧跟技术潮流和提高操作效率&a…

污染物CMAQ模型的安装

CMAQ安装教程(基于intel编译器) 简介 CMAQ&#xff08;Community Multiscale Air Quality&#xff09;系统是由美国国家环境保护局&#xff08;EPA, Environmental Protection Agency&#xff09;于1998年发布&#xff0c;是用于估算臭氧、颗粒物、有毒化合物和酸沉降等大气污…

第5讲:Sysmac Studio中的硬件拓扑

Sysmac Studio软件概述 一、创建项目 在打开的软件中选择新建工程 然后在工程属性中输入工程名称,作者,类型选择“标准工程”即可。 在选择设备处,类型选择“控制器”。 在版本处,可以在NJ控制器的硬件右侧标签处找到这样一个版本号。 我们今天用到的是1.40,所以在软…

DocRED数据集

DocRED数据集文件夹包含多个JSON文件&#xff0c;每个文件都有不同的用途。以下是这些文件的用途解释以及哪个文件是训练集&#xff1a; 文件解释 dev.json&#xff1a;包含开发集&#xff08;验证集&#xff09;的数据&#xff0c;通常用于模型调优和选择超参数。 label_map…