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

news2024/11/16 5:57:25

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.coloradmin.cn/o/1950473.html

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

相关文章

【React】组件:全面解析现代前端开发的基石

文章目录 一、什么是组件&#xff1f;二、组件的类型三、组件的生命周期四、状态管理五、属性传递六、组合与继承七、最佳实践 在现代前端开发中&#xff0c;React 已成为开发者构建用户界面的首选框架之一。React 的强大之处在于其组件化设计&#xff0c;允许开发者将 UI 拆分…

电量采集模块—应用于工厂车间配电室电网监测系统的搭建

前言 随着社会经济发展&#xff0c;工厂配电电网系统的供电实时监测越来越重要&#xff0c;这不仅是影响工厂安全性&#xff0c;更是工厂自动化的必然环节。工厂电力监测具有长时间在线工作的可靠性、较强的现场操作和与中心站的通讯功能&#xff0c;同时具有长时间记录存储数据…

04 标识符与关键字

1 标识符 标识符用于命名程序中标识像变量和函数这样的元素。 number1 eval(input("number: ")) number2 eval(input("number: ")) number3 eval(input("number: ")) average (number1 number2 number3) / 3 print("average: "…

【MySQL进阶之路 | 高级篇】行锁之临键锁和插入意向锁

1. 临键锁&#xff08;Next-Key Locks&#xff09; 有时候我们既想锁住某条记录&#xff0c;又想阻止其他事务在该记录前边的间隙插入新记录&#xff0c;所以InnoDB就提出了一种称之为Next-Key Locks的锁&#xff0c;官方的类型名称为:LOCK_ORDINARY&#xff0c;我们也可以简称…

Ubuntu20.04 设置静态ip

Ubuntu 从 17.10 开始&#xff0c;已放弃在 /etc/network/interfaces 里固定 IP 的配置&#xff0c;interfaces 文件不复存在&#xff0c;即使配置也不会生效&#xff0c;而是改成 netplan 方式 &#xff0c;配置写在 /etc/netplan/01-netcfg.yaml &#xff0c;50-cloud-init.y…

手把手教你使用 Python 制作超级玛丽游戏

编写一个完整的《超级玛丽兄弟》&#xff08;Super Mario Bros.&#xff09;风格的游戏需要涉及到多个方面&#xff0c;包括游戏循环、玩家控制、敌人AI、碰撞检测、关卡设计、得分系统等等。由于这是一个复杂的项目&#xff0c;我将为你提供一个更加详细的框架&#xff0c;并解…

Xstate inspect状态图的使用 和 原理

状态图的好处之一是&#xff0c;在将状态图组合在一起的过程中&#xff0c;您可以探索流程中所有可能的状态。这种探索将帮助您避免代码中的错误和错误&#xff0c;因为您更有可能涵盖所有可能发生的情况。 因为状态图是可执行的&#xff0c;所以它们既可以表现为图&#xff0…

【YashanDB知识库】update/delete未选中行时,v$transaction视图没有事务,alter超时问题

问题现象 1、alter table修改表字段名&#xff0c;卡住&#xff0c;超时。 2、查看v$transaction事务视图&#xff0c;没有看到事务记录。 3、问题单&#xff1a;调整表结构时超时 问题风险及影响 无风险 问题影响版本 客户版本&#xff1a;22.2.8.3 问题发生原因 del…

2.1.卷积层

卷积 ​ 用MLP处理图片的问题&#xff1a;假设一张图片有12M像素&#xff0c;那么RGB图片就有36M元素&#xff0c;使用大小为100的单隐藏层&#xff0c;模型有3.6B元素&#xff0c;这个数量非常大。 识别模式的两个原则&#xff1a; 平移不变性&#xff08;translation inva…

MySQL学习(15):SQL优化:load、order by、group by

1.大批量插入数据指令load 当需要大批量插入数据时&#xff0c;insert的效率比较低&#xff0c;此时可以使用load命令 使用方法如下&#xff1a; &#xff08;1&#xff09;客户端连接服务端时&#xff0c;加上参数--local-infile mysql --local-infile -u root -p&#xf…

Java实现短信验证码服务

1.首先这里使用的是阿里云的短信服务。 package com.wzy.util;; import cn.hutool.captcha.generator.RandomGenerator; import com.aliyun.dysmsapi20170525.Client; import com.wzy.entity.Ali; import org.springframework.stereotype.Component;/*** Author: 顾安* Descri…

2024年铜川宜君半程马拉松,暴晒+爬坡152安全完赛

1、赛事背景 2024年7月21日&#xff0c;我参加了2024年铜川宜君半程马拉松赛&#xff0c;7月举办的赛事很少&#xff0c;全国都算温度比较高的&#xff0c;虽然宜君是一个山城&#xff0c;还是会担心气温会高。 临开赛1、2周&#xff0c;陕西区域降水比较多&#xff0c;赛前一…

C++从入门到起飞之——初始化列表类型转换static成员 全方位剖析!

&#x1f308;个人主页&#xff1a;秋风起&#xff0c;再归来~&#x1f525;系列专栏&#xff1a;C从入门到起飞 &#x1f516;克心守己&#xff0c;律己则安 目录 1、初始化列表 2、 类型转换 3. static成员 4、完结散花 1、初始化列表 • 之前我们实现构造函数…

在Linux中,部署及优化Tomcat

tomcat概述 自 2017 年 11月编程语言排行榜 Java 占比 13%,高居榜首&#xff0c;Tomcat 也一度成为 Java开发人员的首选。其开源、占用系统资源少、跨平台等特性深受广大程序员喜爱。本章主要学习如何部署 Tomcat 服务&#xff0c;根据生产环境实现多个虚拟主机的配置&#xf…

在el-table单元格中通过div绘制圆、直线和对角线

<el-table-column label"电源屏零层端子" align"center" prop"name2" width"220px"><template #default"scope"><div class"bigCircle"></div><div class"smallCircle">…

EtherNet/IP转Profinet协议网关(经典配置案例)

怎么样才能把EtherNet/IP和Profinet网络连接起来呢?这几天有几个朋友问到了这个问题&#xff0c;作者在这里统一为大家详细说明一下。其实有一个设备可以很轻松地解决这个问题&#xff0c;名为JM-PN-EIP&#xff0c;下面是详细介绍。 一&#xff0c;设备主要功能 1、捷米特J…

eqmx上读取数据处理以后添加到数据库中

目录 定义一些静态变量 定时器事件的处理器 订阅数据的执行器 处理json格式数据和将处理好的数据添加到数据库中 要求和最终效果 总结一下 定义一些静态变量 // 在这里都定义成全局的 一般都定义成静态的private static MqttClient mqttClient; // mqtt客户端 private s…

科研绘图系列:R语言组合堆积图(stacked barplot with multiple groups)

介绍 通常堆积图的X轴表示样本,样本可能会存在较多的分组信息,通过组合堆积图和样本标签分组信息,我们可以得到一张能展示更多信息的可发表图形。 加载R包 knitr::opts_chunk$set(warning = F, message = F) library(tidyverse) library(cowplot) library(patchwork)导入…

R包:plot1cell单细胞可视化包

介绍 plot1cell是用于单细胞数据seurat数据对象的可视化包。 安装 ## You might need to install the dependencies below if they are not available in your R library. bioc.packages <- c("biomaRt","GenomeInfoDb","EnsDb.Hsapiens.v86&qu…

基于飞腾FT2000的嵌入式计算机系统

作为中国嵌入式计算机的领导厂家&#xff0c;是最早进入轨道交通领域的 工业级AFC嵌入式计算机系列产品&#xff0c;充分体现了轨道交通新一代AFC主流新技术的各种特点&#xff0c;为轨道交通AFC系统的升级换代提供了良好的系统平台。 标准化 采用开放式架构的Intel新一代主流…