Linux驱动开发——字符设备驱动开发

1 概述

1.1 说明

本文是学习rk3568开发板驱动开发的记录,代码依托于rk3568开发板

1.2 字符设备介绍

字符设备是 Linux 驱动中最基本的一类设备驱动,字符设备就是一个一个字节,按照字节流进行读写操作的设备,读写数据是分先后顺序的。比如我们最常见的点灯、按键、IIC、SPI,LCD 等等都是字符设备,这些设备的驱动就叫做字符设备驱动。
Linux应用程序向下调用驱动程序流程如下:
在这里插入图片描述
在Linux中,一切皆是文件,驱动加载成功之后,会在dev目录下生成一个相应的文件,应用程序通过对这个名为“/dev/xxx”(xxx 是具体的驱动文件名字)的文件进行相应的操作即可实现对硬件的操作
应用程序运行在用户控件,Linux驱动属于内核的一部分,运行在内核空间。当我们在用户空间想要实现对内核的操作,比如使用 open 函数打开/dev/led 这个驱动,因为用
户空间不能直接对内核进行操作,因此必须使用一个叫做“系统调用”的方法来实现从用户空间“陷入”到内核空间,这样才能实现对底层驱动的操作。open、close、write 和 read 等这些函数是由 C 库提供的,在 Linux 系统中,系统调用作为 C 库的一部分,调用open函数的流程如下:
在这里插入图片描述
每一个系统调用,在驱动中都有与之对应的一个驱动函数,在Linux内核文件include/linux/fs.h中有个file_operations的接口提,定义了内核驱动操作函数集合。

1.3 file_operations定义

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 (*read_iter) (struct kiocb *, struct iov_iter *);ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);int (*iterate) (struct file *, struct dir_context *);int (*iterate_shared) (struct file *, struct dir_context *);__poll_t (*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 *);unsigned long mmap_supported_flags;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 (*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 **, void **);long (*fallocate)(struct file *file, int mode, loff_t offset,loff_t len);void (*show_fdinfo)(struct seq_file *m, struct file *f);
#ifndef CONFIG_MMUunsigned (*mmap_capabilities)(struct file *);
#endifssize_t (*copy_file_range)(struct file *, loff_t, struct file *,loff_t, size_t, unsigned int);int (*clone_file_range)(struct file *, loff_t, struct file *, loff_t,u64);int (*dedupe_file_range)(struct file *, loff_t, struct file *, loff_t,u64);int (*fadvise)(struct file *, loff_t, loff_t, int);ANDROID_KABI_RESERVE(1);ANDROID_KABI_RESERVE(2);ANDROID_KABI_RESERVE(3);ANDROID_KABI_RESERVE(4);
} __randomize_layout;

常用的函数有:

  • owner 拥有该结构体的模块的指针,一般设置为 THIS_MODULE。
  • llseek 函数用于修改文件当前的读写位置。
  • read 函数用于读取设备文件。
  • write 函数用于向设备文件写入(发送)数据。
  • poll 是个轮询函数,用于查询设备是否可以进行非阻塞的读写。
  • unlocked_ioctl 函数提供对于设备的控制功能,与应用程序中的 ioctl 函数对应。
  • compat_ioctl 函数与 unlocked_ioctl 函数功能一样,区别在于在 64 位系统上,32 位的应用程序调用将会使用此函数。在 32 位的系统上运行 32 位的应用程序调用的是unlocked_ioctl。
  • mmap 函数用于将将设备的内存映射到进程空间中(也就是用户空间),一般帧缓冲设备会使用此函数,比如 LCD 驱动的显存,将帧缓冲(LCD 显存)映射到用户空间中以后应用程序就可以直接操作显存了,这样就不用在用户空间和内核空间之间来回复制。
  • open 函数用于打开设备文件。
  • release 函数用于释放(关闭)设备文件,与应用程序中的 close 函数对应。
  • fasync 函数用于刷新待处理的数据,用于将缓冲区中的数据刷新到磁盘中。

2 字符设备驱动开发步骤

2.1 驱动模块的加载和卸载

Linux驱动有两种运行模式,一种是直接编译进Linux内核中,内核启动的时候自动运行驱动程序。第二种就是将驱动编译成模块(Linux下模块扩展名为.ko),在Linux内核启动以后使用“modprobe”或者“insmod”命令加载。通常将其编译成模块进行调试,因为这样不用整编内核代码。当没有问题后可以考虑编译进内核。
模块有加载和卸载两种操作:

module_init(xxx_init); //注册模块加载函数
module_exit(xxx_exit); //注册模块卸载函数

module_init 函数用来向 Linux 内核注册一个模块加载函数,参数 xxx_init 就是需要注册的具体函数,当使用“modprobe”命令加载驱动的时候,xxx_init 这个函数就会被调用。module_exit函数用来向 Linux 内核注册一个模块卸载函数,参数 xxx_exit 就是需要注册的具体函数,当使用“rmmod”命令卸载具体驱动的时候 xxx_exit 函数就会被调用
驱动加载和卸载模板代码如下:

 /* 驱动入口函数 */static int __init xxx_init(void){/* 入口函数具体内容 */return 0;}/* 驱动出口函数 */static void __exit xxx_exit(void){/* 出口函数具体内容 */}/* 将上面两个函数指定为驱动的入口和出口函数 */module_init(xxx_init);module_exit(xxx_exit);

加载和卸载命令会调用以上的驱动函数。
有两种命令可以加载驱动模块:insmod和modprobe,二者的区别在于insmod只加载驱动模块,但是不会解决模块间的依赖关系,而modprobe可以解决模块间的依赖关系。
也有两种命令可以卸载驱动模块:rmmod和modprobe,一样的,rmmod直接卸载对应驱动模块,而modprobe可以卸载掉驱动模块所依赖的其他模块,前提是这些依赖模块已经没有被其他模块所使用,否则不能使用modprobe来卸载驱动模块。
加载和卸载命令如下:

加载:
insmod drv.ko
modprobe drv.ko
卸载:
rmmod drv.ko
modprobe -r drv.ko

2.2 字符设备注册与注销

对于字符设备驱动而言,当驱动模块加载成功以后需要注册字符设备,同样,卸载驱动模块的时候也需要注销掉字符设备。

static inline int register_chrdev(unsigned int major, const char *name,const struct file_operations *fops)
{return __register_chrdev(major, 0, 256, name, fops);
}static inline void unregister_chrdev(unsigned int major, const char *name)
{__unregister_chrdev(major, 0, 256, name);
}

以上两个函数就是用于字符设备注册和注销的函数

2.3 实现设备的具体操作函数

file_operations 结构体就是设备的具体操作函数,需要对这个结构体进行初始化,当应用层调用系统调用的时候,能够调用到驱动的对应函数。

1.能够对字符设备进行打开和关闭操作
设备打开和关闭是最基本的需求,需要实现file_operations 中的open和release两个函数
2. 对字符设备进行读写操作
需要重写file_operations 中的write和read两个函数

2.4 添加license和作者信息

最后我们需要在驱动中加入 LICENSE 信息和作者信息,其中 LICENSE 是必须添加的,否则的话编译的时候会报错,作者信息可以添加也可以不添加。LICENSE 和作者信息的添加使用如下两个函数:

MODULE_AUTHOR();
MODULE_LICENSE();

3 Linux设备号

3.1 设备号组成

为了方便管理,Linux 中每个设备都有一个设备号,设备号由主设备号和次设备号两部分组成,主设备号表示某一个具体的驱动,次设备号表示使用这个驱动的各个设备。Linux 提供了一个名为 dev_t 的数据类型表示设备号,dev_t 定义在文件 include/linux/types.h 里面,定义如下:

typedef __u32 __kernel_dev_t;
typedef __kernel_dev_t dev_t;

dev_t是unsigned int类型的,是一个32为的数据类型,这32位分为主设备号和次设备号两个部分,高12位为主设备号,低20位为次设备号。因此 Linux 系统中主设备号范围为 0~4095,所以大家在选择主设备号的时候一定不要超过这个范围。在文件 include/linux/kdev_t.h 中提供了几个关于设备号
的操作函数(本质是宏),如下所示:

#define MINORBITS 20
#define MINORMASK ((1U << MINORBITS) - 1)#define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS))
#define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))
#define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))

3.2 设备号分配

3.2.1 静态分配设备号

本小节讲的设备号分配主要是主设备号的分配。前面讲解字符设备驱动的时候说过了,注册字符设备的时候需要给设备指定一个设备号,这个设备号可以是驱动开发者静态指定的一个设备号,比如 200 这个主设备号。有一些常用的设备号已经被 Linux 内核开发者给分配掉了,具体分配的内容可以查看文档 Documentation/devices.txt。并不是说内核开发者已经分配掉的主设备号我们就不能用了,具体能不能用还得看我们的硬件平台运行过程中有没有使用这个主设备号,使用“cat /proc/devices”命令即可查看当前系统中所有已经使用了的设备号。

3.2.2 动态分配设备号

静态分配设备号需要我们检查当前系统中所有被使用了的设备号,然后挑选一个没有使用的。而且静态分配设备号很容易带来冲突问题,Linux 社区推荐使用动态分配设备号,在注册字符设备之前先申请一个设备号,系统会自动给你一个没有被使用的设备号,这样就避免了冲突。卸载驱动的时候释放掉这个设备号即可,设备号的申请函数如下:

int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)

参数依次为:保存申请到的设备号;次设备号其实地址(这个函数申请一连串多个设备号,其中主设备号相同,次设备号不同,次设备号以baseminor为起始地址开始递增,一般为0);要申请的设备号数量;设备名字
注销字符设备之后要释放掉设备号,设备号释放函数如下:

void unregister_chrdev_region(dev_t from, unsigned count)

参数依次为:要释放的设备号;从from开始,要释放的设备号数量

4 字符设备驱动实验

4.1 实验概述

创建一个chrdevbase虚拟设备,设备有两个缓冲区,一个读缓冲区,一个写缓冲区,两个缓冲区的大小都是100字节。应用程序可以向chrdevbase设备的写缓冲区中写入数据,从读缓冲区中读取数据。

4.2 代码编写

4.2.1 配置依赖头文件

首先,需要配置依赖的内核头文件的位置,VSCode中按下“Crtl+Shift+P”打开的控制台,然后输入
“C/C++: Edit configurations(JSON) ”,打开 C/C++编辑配置文件
在这里插入图片描述
打开之后会自动在.vscode目录下生成一个名为 c_cpp_properties.json 的文件,需要在这个文件中添加依赖的头文件

{"configurations": [{"name": "Linux","includePath": ["${workspaceFolder}/**","/home/alientek/code/rk3568_linux/kernel/arch/arm64/include/","/home/alientek/code/rk3568_linux/kernel/include","/home/alientek/code/rk3568_linux/kernel/arch/arm64/include/generated"],"defines": [],"compilerPath": "/usr/bin/gcc","cStandard": "c17","cppStandard": "gnu++14","intelliSenseMode": "linux-gcc-x64"}],"version": 4
}

generated 文件夹必须先编译内核成功才会生成,并且需要确认自己的 SDK 路径

4.2.2 编写并编译驱动程序

以下是字符设备的驱动部分代码

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>// 定义主设备号
#define CHRDEVBASE_MAJOR 200
// 定义设备名称
#define CHRDEVBASE_NAME "chrdevbase"// 读写缓冲
static char readbuf[100];
static char writebuf[100];
// 内核设备返回数据
static char kernelData[] = {"kernel data!"};// 设备open时callback,应用侧调用open打开设备时内核回调
static int chrdevbase_open(struct inode *inode, struct file *filp) {printk("chrdevbase open!\r\n");return 0;
}// 设备read时callback,应用侧调用read读取设备时内核回调
static ssize_t chrdevbase_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt) {int retValue = 0;memcpy(readbuf, kernelData, sizeof(kernelData));// 将数据从内核空间拷贝到用户空间retValue = copy_to_user(buf, readbuf, cnt);if (retValue == 0) {printk("kernel senddata ok!\r\n");} else {printk("kernel senddata failed!\r\n");}return 0;
}// 设备write时callback,应用侧调用write写入数据时内核回调
static ssize_t chrdevbase_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt) {int retValue = 0;// 将数据从用户空间拷贝到内核空间retValue = copy_from_user(writebuf, buf, cnt);if (retValue == 0) {printk("kernel recevdata:%s\r\n", writebuf);} else {printk("kernel recevdata failed!\r\n");}return 0;
}// 设备close时callback,应用侧调用close关闭设备时内核回调
static int chrdevbase_release(struct inode *inode, struct file *filp) {printk("chrbasedev release!\r\n");return 0;
}// 文件操作函数映射结构体
static struct file_operations chrdevbase_fops = {.owner = THIS_MODULE,.open = chrdevbase_open,.read = chrdevbase_read,.write = chrdevbase_write,.release = chrdevbase_release,
};// 内核模块初始化回调
static int __init chrdevbase_init(void) {int retValue = 0;// 注册字符设备retValue = register_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME, &chrdevbase_fops);if (retValue < 0) {printk("chrdevbase driver register failed\r\n");}printk("chrdevbase_init()\r\n");return 0;
}// 内核模块注销回调
static void __exit chrdevbase_exit(void) {unregister_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME);printk("chrdevbase_exit()\r\n");
}// 注册内核模块的初始化回调函数和注销回调函数
module_init(chrdevbase_init);
module_exit(chrdevbase_exit);// 添加license、作者和其他模块信息
MODULE_LICENSE("GPL");
MODULE_AUTHOR("ALIENTEK");
MODULE_INFO(intree, "Y");

这里定义主设备号为200,设备名称为chrdevbase,然后是根据file_operations来定义内核操作相关的函数,这里定义了open、read、write和realse四个函数,分别对应应用空间的open、read、write和close四个处理函数。
定义完成之后,定义了模块的初始化和退出函数,这两个函数在模块加载和移除时调用。
然后将模块初始化和退出函数注册到内核中。
最后定义了模块的一些通用信息,如license、作者和模块信息等。
解析来编写makefile文件

KERNELDIR := /home/alientek/code/rk3568_linux/kernel
CURRENT_PATH :=	$(shell pwd)
obj-m := chrdevbase.obuild: kernel_moduleskernel_modules:$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean

然后执行编译命令

make ARCH=arm64

编译完成之后,会生成文件chrdevbase.ko

4.2.3 编写并编译驱动测试程序

#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"static char userdata[] = {"user data!"};int main(int argc, int *argv[]) {int fd, retValue;char *fileName;char readBuf[100], writeBuf[100];if (argc != 3) {printf("Error Usage!\r\n");return -1;}fileName = argv[1];fd = open(fileName, O_RDWR);if (fd < 0) {printf("Can't open file %s\r\n", fileName);return -1;}if (atoi(argv[2]) == 1) {retValue = read(fd, readBuf, 50);if (retValue < 0) {printf("read file %s failed!\r\n", fileName);} else {printf("read data:%s\r\n", readBuf);}}if (atoi(argv[2]) == 2){memcpy(writeBuf, userdata, sizeof(userdata));retValue = write(fd, writeBuf, 50);if (retValue < 0) {printf("write file %s failed!\r\n", fileName);}}retValue = close(fd);if (retValue < 0) {printf("Can't close file %s\r\n", fileName);return -1;}return 0;
}

这里没有多少逻辑,主要就是打开设备,读写设备,最后关闭设备。
编译测试应用程序

/opt/atk-dlrk356x-toolchain/bin/aarch64-buildroot-linux-gnu-gcc chrdevbaseApp.c -o chrdevbaseApp

使用atk提供的buildroot的编译工具链编译chrdevbaseApp.c成测试可执行程序chrdevbaseApp

4.3 测试驱动实验

4.3.1 push测试文件到设备

首先,将生成的内核模块chrdevbase.ko和驱动测试程序chrdevbaseApp推到设备中的/lib/modules/4.19.232目录。
其中4.19.232是内核版本号。

4.3.2 加载驱动模块

有两种方式加载驱动模块:insmod和modprobe,两者的区别就是modprobe可以加载依赖的模块,而insmod不会。
使用modprobe加载chrdevbase.ko的时候会有以下提示:

root@ATK-DLRK356X:/lib/modules/4.19.232# modprobe chrdevbase.ko
modprobe: FATAL: Module chrdevbase.ko not found in directory /lib/modules/4.19.232

modprobe 命令会在“/lib/modules/4.19.232”目录下解析 modules.dep 文件,modules.dep 文
件里面保存了要加载的.ko 模块,我们不用手动创建 modules.dep 这个文件,直接输入 depmod
命令即可自动生成 modules.dep,有些根文件系统可能没有 depmod 这个命令,如果没有这个命
令就只能重新配置 busybox,使能此命令,然后重新编译 busybox。输入“depmod”命令以后会
自动生成 modules.alias、modules.symbols 和 modules.dep 等等一些 modprobe 所需的文件,如下图所示:
在这里插入图片描述
然后重新加载驱动:

modprobe chrdevbase.ko

加载完成之后,通过串口可以看到内核打印
在这里插入图片描述
然后在设备中lsmod查看驱动模块,可以看到驱动已经加载成功
在这里插入图片描述

cat /proc/devices

查看系统中当前的设备,可以看到设备已经存在,设备号为200
在这里插入图片描述

4.3.3 测试驱动模块

此时,驱动模块已经加载,但是驱动模块还没有与特定的设备节点进行绑定,测试应用程序还不能与之通信。首先我们需要创建设备节点并绑定设备。

mknod /dev/chrdevbase c 200 0

在这里插入图片描述
这里,mknod命令用于创建设备节点,/dev/chrdevbase就是创建的设备节点文件,c表示创建字符型设备,200是主设备号,0是次设备号。
创建设备节点完成之后,就可以与字符设备进行通信了。这里的设备节点相当于内核模块在用户空间的呈现,用于应用程序读写这个设备节点,就是操作对应内核模块注册的字符设备。
测试:
执行读操作

./chrdevbaseApp /dev/chrdevbase 1

在这里插入图片描述
应用层读取到了内核的数据,并打印
在这里插入图片描述
这是内核的打印,分别对应应用层的open,read,close操作
执行写操作

./chrdevbaseApp /dev/chrdevbase 2

在这里插入图片描述
内核接收到了应用层传下去的数据。

4.3.3 卸载驱动程序

卸载驱动程序是用rmmod命令

rmmod chrdevbase

在这里插入图片描述
在这里插入图片描述
驱动卸载成功,lsmod查看没有chrdevbase这个模块了。同时内核打印显示也调用了exit函数。

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

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

相关文章

用 Manim 库来生成一个树形结构的动画

完整代码&#xff1a; from manim import * class LargeTreeGeneration01(MovingCameraScene): DEPTH 3 CHILDREN_PER_VERTEX 2 LAYOUT_CONFIG {"vertex_spacing": (0.8, 1)} VERTEX_CONF {"radius": 0.12, "color": BLUE_B, "fi…

java基础概念05-运算符

一、自增自减运算符 二、赋值运算符 2-1、注意 三、关系运算符 四、逻辑运算符 4-1、短路逻辑运算符 五、三元运算符 六、运算符的优先级

Catalyst优化器:让你的Spark SQL查询提速10倍

目录 1 逻辑优化阶段 2.1 逻辑计划解析 2.2 逻辑计划优化 2.2.1 Catalys的优化过程 2.2.2 Cache Manager优化 2 物理优化阶段 2.1 优化 Spark Plan 2.1.1 Catalyst 的 Join 策略 2.1.2 如何决定选择哪一种 Join 策略 2.2 Physical Plan 2.2.1 EnsureRequirements 规则 3 相关文…

Linux基本用法(上)

1.计算机主要由 硬件和软件 组成 2.操作系统是什么 ? 有什么作用&#xff1f; 操作系统是软件的一类 主要作用是协助用户调度硬件工作&#xff0c;充当用户和计算机之间的桥梁 3.常见的操作系统有哪些? PC端: Windows&#xff0c;Linux,MacOS 移动端: Android&#xff…

搭建cool-admin-java(前端vue)项目

为什么选择 Cool Admin&#xff1f;​ 随着技术不断地发展&#xff0c;特别是最近 Ai 相关的技术发展&#xff0c;以往的框架已经越来越不能满足现代化的开发需求。 Cool Admin 做为后来者有后发优势&#xff0c;主要特点&#xff1a; Ai 编码&#xff0c;从页面到后端代码&…

数据库实验:SQL Server创建数据库及基本表

一、实验目的&#xff1a; 1、掌握使用SQL SERVER Management Studio工具连接数据库引擎&#xff1b; 2、掌握使用CREATE TABLE 创建基本表的用法&#xff1b; 3、掌握使用ALTER TABLE 修改基本表的用法&#xff1b; 4、掌握使用DROP TABLE删除基本表的用法&#xff1b; 二…

姜夔,师法自然的不仕道人

姜夔&#xff08;ku&#xff09;&#xff0c;字尧章&#xff0c;号白石道人&#xff0c;约生于南宋绍兴二十四年&#xff08;公元1154年&#xff09;&#xff0c;卒于南宋嘉定十四年&#xff08;公元1221年&#xff09;&#xff0c;享年67岁。他的艺术成就涵盖了诗词、散文、书…

Rpi Zero W做的老头乐声控灯

祭图__|\0>历经各种尝试&#xff0c;最后选了docker 里装个rhasspy&#xff0c;配上paho-mqtt搞出了这个奇葩夜灯。各种曲折就不说了&#xff0c;直接分享捷径思路。 这个绿板子是respeaker hat with 2 mic 用的是seeed-voicecard&#xff0c;跟着github编译&#xff0c;不…

微信小程序开发(百货商战)实战项目的购物车和个人中心的创建

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;开发者-曼亿点 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 曼亿点 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a…

玩转usbserver之usbserver日志报警

一、graylog 介绍 graylog是一个简单易用、功能较全面的日志管理工具&#xff0c;graylog也采用Elasticsearch作为存储和索引以保障性能&#xff0c;MongoDB用来存储少量的自身配置信息&#xff0c;master-node模式具有很好的扩展性&#xff0c;UI上自带的基础查询与分析功能比…

JAVAWeb实战(后端篇)

因为前后端代码内容过多&#xff0c;这篇只写后端的代码&#xff0c;前端的在另一篇写 项目实战一&#xff1a; 1.创建数据库,表等数据 创建数据库 create database schedule_system 创建表&#xff0c;并添加内容 SET NAMES utf8mb4; SET FOREIGN_KEY_CHECKS 0;-- ---------…

向日葵RCE复现(CNVD-2022-10270/CNVD-2022-03672)

一、环境 1.1 网上下载低版本的向日葵<2022 二、开始复现 2.1 在目标主机上打开旧版向日葵 2.2 首先打开nmap扫描向日葵主机端口 2.3 在浏览器中访问ip端口号cgi-bin/rpc?actionverify-haras &#xff08;端口号&#xff1a;每一个都尝试&#xff0c;直到获取到session值…

动手学深度学习V2每日笔记(权重衰退+Dropout)

本文主要参考沐神的视频教程 https://www.bilibili.com/video/BV1UK4y1o7dy/vd_sourcec7bfc6ce0ea0cbe43aa288ba2713e56d 文档教程 https://zh-v2.d2l.ai/ 本文的主要内容对沐神提供的代码中个人不太理解的内容进行笔记记录&#xff0c;内容不会特别严谨仅供参考。 1.函数目录…

科普文:万字详解Kafka基本原理和应用

一、Kafka 简介 1. 消息引擎系统ABC Apache Kafka是一款开源的消息引擎系统&#xff0c;也是一个分布式流处理平台。除此之外&#xff0c;Kafka还能够被用作分布式存储系统&#xff08;极少&#xff09;。 A. 常见的两种消息引擎系统传输协议&#xff08;即用什么方式把消息…

【机器学习】探索图神经网络 (GNNs): 揭秘图结构数据处理的未来

&#x1f48e; 欢迎大家互三&#xff1a;2的n次方_ ​ &#x1f48e;1. 引言 图结构数据在现实世界中无处不在&#xff0c;从社交网络中的用户关系&#xff0c;到推荐系统中的用户-物品交互&#xff0c;再到生物信息学中的分子结构。传统的机器学习模型在处理这些数据时常常力…

【Unity插件】Editor Console Pro:提升开发效率的神器

在 Unity 开发过程中&#xff0c;控制台&#xff08;Console&#xff09;是我们排查错误、获取信息的重要窗口。而 Editor Console Pro 则是 Unity 编辑器控制台的强大替代品&#xff0c;为 Unity 的控制台带来了更多实用的功能和改进&#xff0c;极大地提升了开发效率。 一、…

力扣刷题----42. 接雨水

给定 n 个非负整数表示每个宽度为 1 的柱子的高度图&#xff0c;计算按此排列的柱子&#xff0c;下雨之后能接多少雨水。 输入&#xff1a;height [0,1,0,2,1,0,1,3,2,1,2,1] 输出&#xff1a;6 解释&#xff1a;上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图&#xf…

TCP程序设计

TCP概述 建立连接 客户端和服务器端在建立连接时&#xff1a; 服务端是典型的监听接受连接的模式&#xff0c;就是ListenAccept 客户端是主动建立连接的模式&#xff0c;就是Dial Go语言中使用 net包实现网络的相关操作&#xff0c;包括我们TCP的操作。 用于建立连接的典型…

【JavaEE】阻塞队列

目录 一.阻塞队列(Blocking Queue) 1.什么是阻塞队列 2.特性 二.生产者消费者模型 1.什么是生产者消费者模型&#xff1f; 2.生产者消费模型的好处 2.1解耦合 2.2削峰填谷 三.如何在java中使用阻塞队列 四.模拟实现阻塞队列 1.加锁 2.阻塞等待实现 3.解决interru…

学习c语言第十三天(结构体)

一.结构体声明 结构是一些值的集合&#xff0c;这些值称为成员变量。结构的每个成员可以是不同类型的变量。 描述复杂对象。 结构体成员可以是标量、数组、指针、结构体。 定义和初始化&#xff1a; struct peo {char namer[20];char tele[12];char sex[5];int high; }; s…