【北京迅为】《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.mzph.cn/diannao/50761.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2-Python数据类型——序列

Python数据类型——序列 一、序列 序列是一个可以存放多个值的容器。 有序序列&#xff1a;在序列中每个值都有对应的下标 下标&#xff1a;就相当于酒店的房间号 &#xff0c; 方便客人的查找与酒店的管理 在编程中下标的起始值与日常生活中的计数有所不同&#xff1a;下…

springboot 解决跨域

服务器端添加了全局的跨域配置&#xff0c;但是却出现了跨域问题&#xff0c;分析了多次请求发现有一部分请求并没有出现跨域&#xff0c;没有出现跨域的请求刚好就是拦截器放行的地址&#xff0c;所以分析可能是权限拦截器处理在跨域处理之前进行导致跨域配置失效。 解决方法…

找工作准备刷题Day10 回溯算法 (卡尔41期训练营 7.24)

回溯算法今天这几个题目做过&#xff0c;晚上有面试&#xff0c;今天水一水。 第一题&#xff1a;Leetcode77. 组合 题目描述 解题思路 从题目示例来看&#xff0c;k个数是不能重合的&#xff0c;但是题目没有明确说明这一点。 使用回溯算法解决此问题&#xff0c;利用树形…

排查导致REDO日志暴涨的SQL语句

排查导致REDO日志暴涨的SQL语句 是否开启了附加日志日志切换次数峰值分析日志生成量时间分析数据库对象和SQL定位是否开启了附加日志 检查是否开启了Supplemental日志: select supplemental_log_data_min form v$database; select * from all_log_groups;补充(附加)日志的…

ElasticSearch搜索

ES搜索 elastic search 一套搜索引擎技术,主要技术栈包括 Elasticsearch&#xff1a;用于数据存储、计算和搜索 Kibana&#xff1a;用于数据可视化 在数据库模糊查询中,因为不走索引,所以效率很低,而在搜索引擎中,不仅效率高,而且即使出现个别错字,或者用拼音搜索,甚至用同…

docker安装sql server容器

安装 docker pull mcr.microsoft.com/mssql/server:2017-latest启动 docker run -e "ACCEPT_EULAY" -e "SA_PASSWORDwjl135246" -p 1433:1433 -m 4000M --memory 4000M --name sqlserver -d mcr.microsoft.com/mssql/server:2017-latest远程链接即可 参…

用户登录安全是如何保证的?如何保证用户账号、密码安全?

1.HTTP协议直接传输密码&#xff08;无加密&#xff09; 前端 直接发送HTTP请求&#xff08;无加密&#xff09;&#xff0c;攻击者可直接捕获网络包&#xff0c;看到下面的明文信息 因此&#xff0c;使用HTTP协议传输会直接暴露用户敏感信息。 2.HTTPS协议直接传输密码&…

Postgresql 16开启SELINUX

平时我们习惯了&#xff0c;安装数据库&#xff0c;就关闭SELINUX&#xff0c;不关闭SELINUX&#xff0c;就不会安装数据库了&#xff0c;那么不关闭SELINUX&#xff0c;就不能安装数据库了吗&#xff1f; 答案是否定的。 不过&#xff0c;如果我们在开启SELINUX情况下安装PG…

Matlab类阿克曼车机器人运动学演示

v1是后驱动轮轮速&#xff0c; v2是转向角变化速度&#xff0c; 实际上我们只需要关注XQ&#xff0c; YQ和Phi的变化率。 通过这三项和时间步长&#xff0c; 我们就可以计算出变化量&#xff0c; 再结合初始值就能推断出每个时刻的值。 % 清理当前运行环境 % 清除所有变量 cle…

python身份证实名认证接口调用方法、返回值说明、示例代码

越来越多的企业进军互联网平台&#xff0c;对于身份证实名认证接口功能的需求也在不断的增多&#xff0c;对此&#xff0c;翔云人工智能开放平台提供了可满足不同应用场景需求的身份证实名认证接口服务&#xff0c;即身份证二要素、三要素认证&#xff0c;可通过身份证号码姓名…

opencv使用KCF算法跟踪目标,给出目标中心位置

效果图 代码 import cv2class VideoTracker:def __init__(self, video_path: str):self.video_path video_pathself.cap cv2.VideoCapture(video_path)self.tracker cv2.legacy.TrackerKCF_create()self.initBB Noneself.tracker_initialized Falseself.selecting Fals…

C++,Python 论文复现:使用单位四元数求解对齐轨迹的 Sim3

参考文献: Closed-form solution of absolute orientation using unit quaternions 在 SLAM 中, 轨迹会因为因为起始位姿、尺度设定的不同而不同 在轨迹形状大致相同时, 可以求取一个相似变换使得两个轨迹尽可能重合 给定两个分别具有 n n n 个空间点的轨迹, 分别定义为: …

【教程】vscode添加powershell7终端

win10自带的 powershell 是1.0版本的&#xff0c;太老了&#xff0c;更换为powershell7后&#xff0c;在 vscode 的集成终端中没有显示本篇教程记录在vscode添加powershell7终端的过程 打开vscode终端配置 然后来到这个页面进行设置 查看 powershell7 的安装位置&#xff…

C语言——初识结构体定义与使用

C语言中的结构体&#xff08;Struct&#xff09;是一种复合数据类型&#xff0c;它允许你将不同类型的数据项组合成一个单一的类型。结构体在C语言中非常有用&#xff0c;特别是在需要处理复杂数据时&#xff0c;比如存储一个人的姓名、年龄、地址等信息时&#xff0c;使用结构…

回调函数简易笔记

定义 回调函数:用户手动定义了,但是没有手动调用,最终这个方法被系统/库/框架进行调用了… 举例 C语言 函数指针,主要有两个用途. 作为回调函数实现转移表 降低代码的复杂程度 java 数据结构 优先级队列(堆) 必须先定义好对象的"比较规则" Comparable compare…

2836. 在传球游戏中最大化函数值

Powered by:NEFU AB-IN Link 文章目录 2836. 在传球游戏中最大化函数值题意思路代码 2836. 在传球游戏中最大化函数值 题意 给你一个长度为 n 下标从 0 开始的整数数组 receiver 和一个整数 k 。 总共有 n 名玩家&#xff0c;玩家 编号 互不相同&#xff0c;且为 [0, n - 1…

韦东山嵌入式linux系列-异常与中断的概念及处理流程

1 中断的引入 一些概念&#xff1a; 中断&#xff1a;在主程序运行过程中&#xff0c;出现了特定的中断触发条件&#xff08;中断源&#xff09;&#xff0c;使得CPU暂停当前正在运行的程序&#xff0c;转而去处理中断程序&#xff0c;处理完成后又返回原来被暂停的位置继续运…

RuoYi-Vue-Plus(动态添加移除数据源)

一、添加数据 private final DynamicRoutingDataSource dynamicRoutingDataSource;private final DefaultDataSourceCreator dataSourceCreator;//添加一个dynamic的数据源@GetMapping("createDynamic")public void createDynamic() {DataSourceProperty property =…

数据结构第二讲:顺序表

数据结构第二讲&#xff1a;顺序表 1.线性表2.什么是顺序表3. 静态顺序表4.动态顺序表4.1顺序表基础4.2顺序表的初始化4.3顺序表的销毁4.4顺序表的尾插4.5顺序表的头插4.6顺序表的尾删4.7顺序表的头删4.8顺序表在指定位置之前插入数据4.9顺序表删除指定位置的数据4.10顺序表查找…

JNPF全新V5.0版本!重磅升级——全局优化篇

尊敬的JNPF用户们&#xff1a; 我们非常高兴地宣布&#xff0c;经过团队数月的辛勤努力和不断的技术创新&#xff0c;JNPF快速开发平台终于迎来了里程碑式的全新升级——V5.0版本&#xff01;这一版本的更新发布&#xff0c;不仅代表着我们技术实力的进一步提升&#xff0c;是…