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.rhkb.cn/news/385654.html

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

相关文章

Navicat Charts Creator for Mac:数据可视化利器

Navicat Charts Creator for Mac是一款专为Mac用户设计的数据可视化工具&#xff0c;它将复杂的数据转化为直观、易懂的图表&#xff0c;帮助用户更好地理解和分析数据。 该软件支持连接到多种数据库&#xff0c;如MySQL、MariaDB、PostgreSQL等&#xff0c;轻松获取实时数据&…

23 PCBEditor封装创建向导介绍24 PCBEditor3D封装展示25 PCB封装库的管理与调用

23 PCBEditor封装创建向导介绍_BGA为例&&24 PCBEditor3D封装展示&&25 PCB封装库的管理与调用 第一部分 23 PCBEditor封装创建向导介绍_BGA为例一、创建焊盘二、PCBEditor创建封装 第二部分 24 PCBEditor3D封装展示第三部分 25 PCB封装库的管理与调用一、指定库…

黑马头条vue2.0项目实战(二)——登录注册功能的实现

1. 布局结构 目标 能实现登录页面的布局 能实现基本登录功能 能掌握 Vant 中 Toast 提示组件的使用 能理解 API 请求模块的封装 能理解发送验证码的实现思路 能理解 Vant Form 实现表单验证的使用 这里主要使用到三个 Vant 组件&#xff1a; NavBar 导航栏 Form 表单 F…

Linux文件恢复

很麻烦 一般还是小心最好 特别恢复的时候 可能不能选择某个文件夹去扫描恢复 所以 删除的时候 用rm -i代替rm 一定小心 以及 探索下linux的垃圾箱机制 注意 一定要恢复到不同文件夹 省的出问题 法1 系统自带工具 debugfs 但是好像不能重启&#xff1f; testdisk 1、安装 …

C++项目——高并发内存池

一、什么是内存池 内存池(Memory Pool) 是一种动态内存分配与管理技术。 通常情况下&#xff0c;程序员习惯直接使用new、delete、malloc、free 等API申请分配和释放内存&#xff0c;这样导致的后果是&#xff1a;当程序长时间运行时&#xff0c;由于所申请内存块的大小不定&…

OpenCV 图像预处理—图像金字塔

文章目录 相关概念高斯金字塔拉普拉斯金字塔应用 构建高斯金字塔为什么要对当前层进行模糊&#xff1f;1. 平滑处理2. 减少混叠&#xff08;Aliasing&#xff09;3. 多尺度表示4. 图像降采样 举个栗子创建高斯金字塔和拉普拉斯金字塔&#xff0c;并用拉普拉斯金字塔恢复图像 相…

【VUE】个人记录:父子页面数据传递

1. 父传子 在父页面中&#xff0c;调用子页面的组件位置处&#xff0c;通过“&#xff1a;”进行参数传递 <child-component :childData"parentData"></child-component>parentData对象&#xff0c;需要在父页面的data中进行初始化声明 在子页面中&am…

百易云资产管理运营系统 comfileup.php 文件上传致RCE漏洞复现(XVE-2024-18154)

0x01 产品简介 百易云资产管理运营系统,是专门针对企业不动产资产管理和运营需求而设计的一套综合解决方案。该系统能够覆盖资产的全生命周期管理,包括资产的登记、盘点、评估、处置等多个环节,同时提供强大的运营分析功能,帮助企业优化资产配置,提升运营效率。 0x02 漏…

为RTEMS Raspberrypi4 BSP添加SPI支持

为RTEMS Raspberrypi4 BSP添加SPI支持 主要参考了dev/bsps/shared/dev/spi/cadence-spi.c RTEMS 使用了基于linux的SPI框架&#xff0c;SPI总线驱动已经在内核中实现。在这个项目中我需要实习的是 RPI4的SPI主机控制器驱动 SPI在RTEMS中的实现如图&#xff1a; 首先需要将S…

Profinet从站转TCP/IP协议转化网关(功能与配置)

如何将Profinet和TCP/IP网络连接通讯起来呢?近来几天有几个朋友问到这个问题&#xff0c;那么作者在这里统一说明一下。其实有一个不错的设备产品可以很轻易地解决这个问题&#xff0c;名为JM-DNT-PN。接下来作者就从该设备的功能及配置详细说明一下。 一&#xff0c;设备主要…

Python:随机数、随机选择的应用

step1:导入 导入的random相当于是创建了random文件里的的一个对象 import random random() 产生0~1随机数 randint(a,b)产生a~b的整数 闭区间&#xff0c;可以取到a,b random.choice(touple_name)从touple_name&#xff08;数组、列表..&#xff09;中随机选择元素 import rand…

JSP内置对象及作用域

Request 存东西ResponseSession 存东西Application [ SerlvetContext ] 存东西config [ SerlvetConfig ]out/targetpage 不用了解exception <% page contentType"text/html;charsetUTF-8" language"java" %> <html> <head><title>…

DBeaver使用SQL脚本编辑器

文章目录 1 新建脚本2 选择数据库3 编写脚本【按行执行】参考 1 新建脚本 2 选择数据库 3 编写脚本【按行执行】 光标放到需要执行的行上&#xff0c;点击【最上面的按钮】 或者选中某片代码&#xff0c;然后执行 也可以编写一个脚本然后执行 参考 dbeaver安装和使用教程 …

LeetCode 热题 HOT 100 (011/100)【宇宙最简单版】

【图论】No. 0200 岛屿数量 【中等】&#x1f449;力扣对应题目指路 希望对你有帮助呀&#xff01;&#xff01;&#x1f49c;&#x1f49c; 如有更好理解的思路&#xff0c;欢迎大家留言补充 ~ 一起加油叭 &#x1f4a6; 欢迎关注、订阅专栏 【力扣详解】谢谢你的支持&#xf…

Chrome谷歌浏览器Console(控制台)显示文件名及行数

有没有这样的困扰&#xff1f;Chrome谷歌浏览器console(控制台)不显示编译文件名及行数? 设置&#xff08;Settings&#xff09;- > 忽略列表&#xff08;lgnore List&#xff09;-> 自定义排除规则&#xff08;Custom exclusion rules&#xff09; 将自定义排除规则…

Skyeye云智能制造企业版源代码全部开放

智能制造一体化管理系统 [SpringBoot2 - 快速开发平台]&#xff0c;适用于制造业、建筑业、汽车行业、互联网、教育、政府机关等机构的管理。包含文件在线操作、工作日志、多班次考勤、CRM、ERP 进销存、项目管理、EHR、拖拽式生成问卷、日程、笔记、工作计划、行政办公、薪资模…

【数据结构】堆,优先级队列

目录 堆堆的性质大根堆的模拟实现接口实现构造方法建堆入堆判满删除判空获取堆顶元素 Java中的PriorityQueue实现的接口构造方法常用方法PriorityQueue注意事项 练习 堆 如果有一个集合K {k0&#xff0c;k1&#xff0c; k2&#xff0c;…&#xff0c;kn-1}&#xff0c;把它的…

【C++】C++入门基础

✨✨欢迎大家来到Celia的博客✨✨ &#x1f389;&#x1f389;创作不易&#xff0c;请点赞关注&#xff0c;多多支持哦&#x1f389;&#x1f389; 所属专栏&#xff1a;C 个人主页&#xff1a;Celias blog~ 目录 一、C简介 二、第一个C程序 三、namespace 命名空间 3.1 na…

UART 通信协议

文章目录 一 简介二 电平标准三 引脚定义四 数据格式五 波特率 一 简介 ​ UART (Universal Asynchronous Receiver/Transmitter)&#xff0c;通用异步收发器&#xff0c;是一种串行、异步、全双工通信协议。 串行&#xff1a;利用一条传输线&#xff0c;将数据一位一位地传送…

一整套开箱即用的前端管理后台解决方案,基于 Vue.js搭配使用 iView UI 组件库形成的,私活神器

前言 在现代Web应用开发中&#xff0c;后台管理系统的构建常常面临诸多挑战&#xff0c;如复杂的权限管理、多语言支持、响应式设计等。现有解-决方案可能存在功能不丰富、定制难度大、开发效率低等问题。 为了解决这些痛点&#xff0c;一款新的软件——iView Admin&#xff…