基础IO(2)

news2025/1/30 12:33:56

基础IO(2)

在这里插入图片描述

理解“⼀切皆⽂件”

⾸先,在windows中是⽂件的东西,它们在linux中也是⽂件;其次⼀些在windows中不是⽂件的东西,⽐如进程、磁盘、显⽰器、键盘这样硬件设备也被抽象成了⽂件,你可以使⽤访问⽂件的⽅法访问它们获得信息;甚⾄管道,也是⽂件;将来我们要学习⽹络编程中的socket(套接字)这样的东西,使⽤的接⼝跟⽂件接⼝也是⼀致的。

这样做最明显的好处是,开发者仅需要使⽤⼀套 API 和开发⼯具,即可调取 Linux 系统中绝⼤部分的资源。举个简单的例⼦,Linux 中⼏乎所有读(读⽂件,读系统状态,读PIPE)的操作都可以⽤read 函数来进⾏;⼏乎所有更改(更改⽂件,更改系统参数,写 PIPE)的操作都可以⽤ write 函数来进⾏。

之前我们讲过,当打开⼀个⽂件时,操作系统为了管理所打开的⽂件,都会为这个⽂件创建⼀个file结构体,该结构体定义在 /usr/src/kernels/3.10.0-1160.71.1.el7.x86_64/include/linux/fs.h 下,以下展⽰了该结构部分我们关系的内容:

struct file {
...
struct inode *f_inode; /* cached value */
const struct file_operations *f_op;
...
atomic_long_t f_count; // 表⽰打开⽂件的引⽤计数,如果有多个⽂件指针指向
它,就会增加f_count的值。
unsigned int f_flags; // 表⽰打开⽂件的权限
fmode_t f_mode; // 设置对⽂件的访问模式,例如:只读,只写等。所有
的标志在头⽂件<fcntl.h> 中定义
loff_t f_pos; // 表⽰当前读写⽂件的位置
...
} __attribute__((aligned(4))); /* lest something weird decides that 2 is OK */

值得关注的是 struct file 中的 f_op 指针指向了⼀个 file_operations 结构体,这个结构体中的成员除了struct module* owner 其余都是函数指针。该结构和 struct file 都在fs.h下。

struct file_operations {
struct module *owner;
//指向拥有该模块的指针;
loff_t (*llseek) (struct file *, loff_t, int);
//llseek ⽅法⽤作改变⽂件中的当前读/写位置, 并且新位置作为(正的)返回值.
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
//⽤来从设备中获取数据. 在这个位置的⼀个空指针导致 read 系统调⽤以 -
EINVAL("Invalid argument") 失败. ⼀个⾮负返回值代表了成功读取的字节数( 返回值是⼀个
"signed size" 类型, 常常是⽬标平台本地的整数类型).
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
//发送数据给设备. 如果 NULL, -EINVAL 返回给调⽤ write 系统调⽤的程序. 如果⾮负, 返
回值代表成功写的字节数.
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 (*readdir) (struct file *, void *, filldir_t);
//对于设备⽂件这个成员应当为 NULL; 它⽤来读取⽬录, 并且仅对**⽂件系统**有⽤.
unsigned int (*poll) (struct file *, struct poll_table_struct *);
int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
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 *);
//mmap ⽤来请求将设备内存映射到进程的地址空间. 如果这个⽅法是 NULL, mmap 系统调⽤返
回 -ENODEV.
int (*open) (struct inode *, struct file *);
//打开⼀个⽂件
int (*flush) (struct file *, fl_owner_t id);
//flush 操作在进程关闭它的设备⽂件描述符的拷⻉时调⽤;
int (*release) (struct inode *, struct file *);
//在⽂件结构被释放时引⽤这个操作. 如同 open, release 可以为 NULL.
int (*fsync) (struct file *, struct dentry *, int datasync);
//⽤⼾调⽤来刷新任何挂着的数据.
int (*aio_fsync) (struct kiocb *, int datasync);
int (*fasync) (int, struct file *, int);
int (*lock) (struct file *, int, struct file_lock *);
//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 **);
};

file_operation 就是把系统调⽤和驱动程序关联起来的关键数据结构,这个结构的每⼀个成员都对应着⼀个系统调⽤。读取 file_operation 中相应的函数指针,接着把控制权转交给函数,从⽽完成了Linux设备驱动程序的⼯作。

介绍完相关代码,⼀张图总结:

在这里插入图片描述

上图中的外设,每个设备都可以有⾃⼰的read、write,但⼀定是对应着不同的操作⽅法!!但通过struct file 下 file_operation 中的各种函数回调,让我们开发者只⽤file便可调取 Linux 系统中绝⼤部分的资源!!这便是“linux下⼀切皆⽂件”的核⼼理解。

缓冲区

什么是缓冲区

缓冲区是内存空间的⼀部分。也就是说,在内存空间中预留了⼀定的存储空间,这些存储空间⽤来缓冲输⼊或输出的数据,这部分预留的空间就叫做缓冲区。缓冲区根据其对应的是输⼊设备还是输出设备,分为输⼊缓冲区和输出缓冲区。

为什么要引⼊缓冲区机制

读写⽂件时,如果不会开辟对⽂件操作的缓冲区,直接通过系统调⽤对磁盘进⾏操作(读、写等),那么每次对⽂件进⾏⼀次读写操作时,都需要使⽤读写系统调⽤来处理此操作,即需要执⾏⼀次系统调⽤,执⾏⼀次系统调⽤将涉及到CPU状态的切换,即从⽤⼾空间切换到内核空间,实现进程上下⽂的切换,这将损耗⼀定的CPU时间,频繁的磁盘访问对程序的执⾏效率造成很⼤的影响。

为了减少使⽤系统调⽤的次数,提⾼效率,我们就可以采⽤缓冲机制。⽐如我们从磁盘⾥取信息,可以在磁盘⽂件进⾏操作时,可以⼀次从⽂件中读出⼤量的数据到缓冲区中,以后对这部分的访问就不需要再使⽤系统调⽤了,等缓冲区的数据取完后再去磁盘中读取,这样就可以减少磁盘的读写次数,再加上计算机对缓冲区的操作⼤ 快于对磁盘的操作,故应⽤缓冲区可⼤ 提⾼计算机的运⾏速度。

⼜⽐如,我们使⽤打印机打印⽂档,由于打印机的打印速度相对较慢,我们先把⽂档输出到打印机相应的缓冲区,打印机再⾃⾏逐步打印,这时我们的CPU可以处理别的事情。可以看出,缓冲区就是⼀块内存区,它⽤在输⼊输出设备和CPU之间,⽤来缓存数据。它使得低速的输⼊输出设备和⾼速的CPU能够协调⼯作,避免低速的输⼊输出设备占⽤CPU,解放出CPU,使其能够⾼效率⼯作。

缓冲类型

标准I/O提供了3种类型的缓冲区。

• 全缓冲区:这种缓冲⽅式要求填满整个缓冲区后才进⾏I/O系统调⽤操作。对于磁盘⽂件的操作通常使⽤全缓冲的⽅式访问。

• ⾏缓冲区:在⾏缓冲情况下,当在输⼊和输出中遇到换⾏符时,标准I/O库函数将会执⾏系统调⽤操作。当所操作的流涉及⼀个终端时(例如标准输⼊和标准输出),使⽤⾏缓冲⽅式。因为标准I/O库每⾏的缓冲区⻓度是固定的,所以只要填满了缓冲区,即使还没有遇到换⾏符,也会执⾏I/O系统调⽤操作,默认⾏缓冲区的⼤⼩为1024。

• ⽆缓冲区:⽆缓冲区是指标准I/O库不对字符进⾏缓存,直接调⽤系统调⽤。标准出错流stderr通常是不带缓冲区的,这使得出错信息能够尽快地显⽰出来。

除了上述列举的默认刷新⽅式,下列特殊情况也会引发缓冲区的刷新:

  1. 缓冲区满时;

  2. 执⾏flush语句;

⽰例如下:

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main() {
close(1);
int fd = open("log.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);
if (fd < 0) {
perror("open");
return 0;
}
printf("hello world: %d\n", fd);
close(fd);
return 0;
}

我们本来想使⽤重定向思维,让本应该打印在显⽰器上的内容写到“log.txt”⽂件中,但我们发现,程序运⾏结束后,⽂件中并没有被写⼊内容:

[hyb@VM-8-12-centos buffer]$ ./myfile
[hyb@VM-8-12-centos buffer]$ ls
log.txt makefile myfile myfile.c
[hyb@VM-8-12-centos buffer]$ cat log.txt
[hyb@VM-8-12-centos buffer]$

这是由于我们将1号描述符重定向到磁盘⽂件后,缓冲区的刷新⽅式成为了全缓冲。⽽我们写⼊的内容并没有填满整个缓冲区,导致并不会将缓冲区的内容刷新到磁盘⽂件中。怎么办呢?可以使⽤fflush强制刷新下缓冲区。

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main() {
close(1);
int fd = open("log.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);
if (fd < 0) {
perror("open");
return 0;
}
printf("hello world: %d\n", fd);
fflush(stdout);
close(fd);
return 0;
}

还有⼀种解决⽅法,刚好可以验证⼀下stderr是不带缓冲区的,代码如下:

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main() {
close(2);
int fd = open("log.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);
if (fd < 0) {
perror("open");
return 0;
}
perror("hello world");
close(fd);
return 0;
}

这种⽅式便可以将2号⽂件描述符重定向⾄⽂件,由于stderr没有缓冲区,“hello world”不⽤fflush就可以写⼊⽂件:

[hyb@VM-8-12-centos buffer]$ ./myfile
[hyb@VM-8-12-centos buffer]$ cat log.txt
hello world: Success

FILE

• 因为IO相关函数与系统调⽤接⼝对应,并且库函数封装系统调⽤,所以本质上,访问⽂件都是通过fd访问的。

• 所以C库当中的FILE结构体内部,必定封装了fd

#include <stdio.h>
#include <string.h>
int main()
{
const char *msg0="hello printf\n";
const char *msg1="hello fwrite\n";
const char *msg2="hello write\n";
printf("%s", msg0);
fwrite(msg1, strlen(msg0), 1, stdout);
write(1, msg2, strlen(msg2));
fork();
return 0;
}

运⾏出结果:

hello printf
hello fwrite
hello write

但如果对进程实现输出重定向呢? ./hello > file , 我们发现结果变成了:

hello write
hello printf
hello fwrite
hello printf
hello fwrite

我们发现 printf 和 fwrite (库函数)都输出了2次,⽽ write 只输出了⼀次(系统调⽤)。为什么呢?肯定和fork有关!

• ⼀般C库函数写⼊⽂件时是全缓冲的,⽽写⼊显⽰器是⾏缓冲。

• printf fwrite 库函数+会⾃带缓冲区(进度条例⼦就可以说明),当发⽣重定向到普通⽂件时,数据的缓冲⽅式由⾏缓冲变成了全缓冲。

• ⽽我们放在缓冲区中的数据,就不会被⽴即刷新,甚⾄fork之后

• 但是进程退出之后,会统⼀刷新,写⼊⽂件当中。

• 但是fork的时候,⽗⼦数据会发⽣写时拷⻉,所以当你⽗进程准备刷新的时候,⼦进程也就有了同样的⼀份数据,随即产⽣两份数据。

• write 没有变化,说明没有所谓的缓冲。

综上: printf fwrite 库函数会⾃带缓冲区,⽽ write 系统调⽤没有带缓冲区。另外,我们这⾥所说的缓冲区,都是⽤⼾级缓冲区。其实为了提升整机性能,OS也会提供相关内核级缓冲区,不过不再我们讨论范围之内。

那这个缓冲区谁提供呢? printf fwrite 是库函数, write 是系统调⽤,库函数在系统调⽤的“上层”, 是对系统调⽤的“封装”,但是 write 没有缓冲区,⽽ printf fwrite 有,⾜以说明,该缓冲区是⼆次加上的,⼜因为是C,所以由C标准库提供。

简单设计⼀下libc库

my_stdio.h

$ cat my_stdio.h
#pragma once
#define SIZE 1024
#define FLUSH_NONE 0
#define FLUSH_LINE 1
#define FLUSH_FULL 2
struct IO_FILE
{
int flag; // 刷新⽅式
int fileno; // ⽂件描述符
char outbuffer[SIZE];
int cap;
int size;
// TODO
};
typedef struct IO_FILE mFILE;
mFILE *mfopen(const char *filename, const char *mode);
int mfwrite(const void *ptr, int num, mFILE *stream);
void mfflush(mFILE *stream);
void mfclose(mFILE *stream);

my_stdio.c

$ cat my_stdio.c
#include "my_stdio.h"
#include <string.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
mFILE *mfopen(const char *filename, const char *mode)
{
int fd = -1;
if(strcmp(mode, "r") == 0)
{
fd = open(filename, O_RDONLY);
}
else if(strcmp(mode, "w")== 0)
{
fd = open(filename, O_CREAT|O_WRONLY|O_TRUNC, 0666);
}
else if(strcmp(mode, "a") == 0)
{
fd = open(filename, O_CREAT|O_WRONLY|O_APPEND, 0666);
}
if(fd < 0) return NULL;
mFILE *mf = (mFILE*)malloc(sizeof(mFILE));
if(!mf)
{
close(fd);
return NULL;
}
mf->fileno = fd;
mf->flag = FLUSH_LINE;
mf->size = 0;
mf->cap = SIZE;
return mf;
}
void mfflush(mFILE *stream)
{
if(stream->size > 0)
{
// 写到内核⽂件的⽂件缓冲区中!
write(stream->fileno, stream->outbuffer, stream->size);
// 刷新到外设
fsync(stream->fileno);
stream->size = 0;
}
}
int mfwrite(const void *ptr, int num, mFILE *stream)
{
// 1. 拷⻉
memcpy(stream->outbuffer+stream->size, ptr, num);
stream->size += num;
// 2. 检测是否要刷新
if(stream->flag == FLUSH_LINE && stream->size > 0 && stream-
>outbuffer[stream->size-1]== '\n')
{
mfflush(stream);
}
return num;
}
void mfclose(mFILE *stream)
{
if(stream->size > 0)
{
mfflush(stream);
}
close(stream->fileno);
}

main.c

$ cat main.c
#include "my_stdio.h"
#include <stdio.h>
#include <string.h>
#include <unistd.h>
int main()
{
mFILE *fp = mfopen("./log.txt", "a");
if(fp == NULL)
{
return 1;
}
int cnt = 10;
while(cnt)
{
printf("write %d\n", cnt);
char buffer[64];
snprintf(buffer, sizeof(buffer),"hello message, number is : %d", cnt);
cnt--;
mfwrite(buffer, strlen(buffer), fp);
mfflush(fp);
sleep(1);
}
mfclose(fp);
}

#include <stdio.h>
#include <string.h>
#include <unistd.h>
int main()
{
mFILE *fp = mfopen(“./log.txt”, “a”);
if(fp == NULL)
{
return 1;
}
int cnt = 10;
while(cnt)
{
printf(“write %d\n”, cnt);
char buffer[64];
snprintf(buffer, sizeof(buffer),“hello message, number is : %d”, cnt);
cnt–;
mfwrite(buffer, strlen(buffer), fp);
mfflush(fp);
sleep(1);
}
mfclose(fp);
}


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

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

相关文章

IDM-VTON本地部署教程:双重编码 + 文字提示,解锁真实野外试穿

一、介绍 IDM-VTON&#xff1a;改进扩散模型&#xff0c;实现真实的野外虚拟试穿。 技术原理&#xff1a;改进扩散模型&#xff0c;利用视觉编码器提取服装高级语义信息并与交叉注意力层融合&#xff0c;通过并行 UNet 结构的 GarmentNet 捕捉服装低级特征并与自注意力层结合&…

【2024年华为OD机试】 (C卷,200分)- 矩阵匹配(JavaScriptJava PythonC/C++)

一、问题描述 问题描述 给定一个大小为 ( N \times M )&#xff08;( N \leq M )&#xff09;的矩阵&#xff0c;从中选出 ( N ) 个数&#xff0c;要求任意两个数字不能在同一行或同一列。求选出来的 ( N ) 个数中第 ( K ) 大的数字的最小值。 输入描述 输入矩阵要求&#…

AI 浪潮席卷中国年,开启科技新春新纪元

在这博主提前祝大家蛇年快乐呀&#xff01;&#xff01;&#xff01; 随着人工智能&#xff08;AI&#xff09;技术的飞速发展&#xff0c;其影响力已经渗透到社会生活的方方面面。在中国传统节日 —— 春节期间&#xff0c;AI 技术也展现出了巨大的潜力&#xff0c;为中国年带…

Python 数据分析 - Matplotlib 绘图

Python 数据分析 - Matplotlib 绘图 简介绘图折线图单线多线子图 散点图直方图条形图纵置横置多条 饼图 简介 Matplotlib 是 Python 提供的一个绘图库&#xff0c;通过该库我们可以很容易的绘制出折线图、直方图、散点图、饼图等丰富的统计图&#xff0c;安装使用 pip install…

搭建Spark分布式集群

1&#xff0c;下载 下载 spark-3.5.4-bin-without-hadoop.tgz 地址&#xff1a; https://downloads.apache.org/spark/spark-3.5.4/ 2&#xff0c;安装 通过虚拟机设置共享文件夹将需要的安装包复制到linux虚拟机中 localhost1。虚拟机的共享盘在 /mnt/hgfs/。 将共享盘安装…

新年祝词(原创)

新年将至&#xff0c;福进万户。 家家团圆&#xff0c;事事顺心。 喜迎财神&#xff0c;多寿添金。 瑞兽迎春&#xff0c;炮竹声起。 趋吉避凶&#xff0c;蛇年大吉。 中华崛起&#xff0c;人人自强。 天下大同&#xff0c;百姓富足。 有情有义&#xff0c;平易近人。 …

线上突发:MySQL 自增 ID 用完,怎么办?

线上突发&#xff1a;MySQL 自增 ID 用完&#xff0c;怎么办&#xff1f; 1. 问题背景2. 场景复现3. 自增id用完怎么办&#xff1f;4. 总结 1. 问题背景 最近&#xff0c;我们在数据库巡检的时候发现了一个问题&#xff1a;线上的地址表自增主键用的是int类型。随着业务越做越…

ESP32 I2S音频总线学习笔记(二):I2S读取INMP441音频数据

简介 在这个系列的上一篇文章中&#xff0c;我们介绍了ESP32 I2S音频总线的相关知识&#xff0c;简要了解了什么是I2S总线、它的通信格式&#xff0c;以及相关的底层API函数。没有看过上篇文章的可以点击文章进行回顾&#xff1a; ESP32 I2S音频总线学习笔记&#xff08;一&a…

一文简单回顾Java中的String、StringBuilder、StringBuffer

简单说下String、StringBuilder、StringBuffer的区别 String、StringBuffer、StringBuilder在Java中都是用于处理字符串的&#xff0c;它们之间的区别是String是不可变的&#xff0c;平常开发用的最多&#xff0c;当遇到大量字符串连接的时候&#xff0c;就用StringBuilder&am…

matlab中,fill命令用法

在 MATLAB 中&#xff0c;fill 命令用于创建填充多边形的图形对象。使用 fill 可以在二维坐标系中绘制填充的区域&#xff0c;通常用于绘制图形的背景或显示数据分布。 基本语法 fill(X, Y, C)X 和 Y 是同样长度的向量&#xff0c;定义了多边形的顶点坐标。C 是颜色&#xff0…

计算机网络之链路层

本文章目录结构出自于《王道计算机考研 计算机网络_哔哩哔哩_bilibili》 02 数据链路层 在网上看到其他人做了详细的笔记&#xff0c;就不再多余写了&#xff0c;直接参考着学习吧。 1 详解数据链路层-数据链路层的功能【王道计算机网络笔记】_wx63088f6683f8f的技术博客_51C…

lib.exe正确用法winhv.lib生成方法

lib.exe /def:winhv.def /OUT:winhv.lib /machine:x64 winhv.def注意是 winhv.sys要不然会变成dll LIBRARY winhv.sys EXPORTSWinHvAllocateOverlayPagesWinHvDisablePartitionVtlWinHvDisableVpVtlWinHvEnablePartitionVtlWinHvEnableVpVtlWinHvFreeOverlayPagesWinHvGetCurr…

react-bn-面试

1.主要内容 工作台待办 实现思路&#xff1a; 1&#xff0c;待办list由后端返回&#xff0c;固定需要的字段有id(查详细)、type(本条待办的类型)&#xff0c;还可能需要时间&#xff0c;状态等 2&#xff0c;一个集中处理待办中转路由页&#xff0c;所有待办都跳转到这个页面…

Linux:一切皆文件

**文件描述符**&#xff1a;它是一种特殊的索引&#xff0c;本质上是进程中file_struct结构体成员fd_array数组的下标。在Linux等系统中&#xff0c;文件描述符是一个非负整数&#xff0c;用于标识打开的文件&#xff0c;是内核为了高效管理已被打开的文件所创建的索引。通过文…

【物联网】ARM核常用指令(详解):数据传送、计算、位运算、比较、跳转、内存访问、CPSR/SPSR、流水线及伪指令

文章目录 指令格式&#xff08;重点&#xff09;1. 立即数2. 寄存器位移 一、数据传送指令1. MOV指令2. MVN指令3. LDR指令 二、数据计算指令1. ADD指令1. SUB指令1. MUL指令 三、位运算指令1. AND指令2. ORR指令3. EOR指令4. BIC指令 四、比较指令五、跳转指令1. B/BL指令2. l…

项目集成Nacos

文章目录 1.环境搭建1.创建模块 sunrays-common-cloud-nacos-starter2.目录结构3.pom.xml4.自动配置1.NacosAutoConfiguration.java2.spring.factories 5.引入cloud模块通用依赖 2.测试1.创建模块 sunrays-common-cloud-nacos-starter-demo2.目录结构3.pom.xml4.application.ym…

QT交叉编译环境搭建(Cmake和qmake)

介绍一共有两种方法&#xff08;基于qmake和cmake&#xff09;&#xff1a; 1.直接调用虚拟机中的交叉编译工具编译 2.在QT中新建编译套件kits camke和qmake的区别&#xff1a;CMake 和 qmake 都是自动化构建工具&#xff0c;用于简化构建过程&#xff0c;管理编译设置&…

[cg] 使用snapgragon 对UE5.3抓帧

最近想要抓opengl 的api&#xff0c;renderdoc在起应用时会闪退&#xff08;具体原因还不知道&#xff09;&#xff0c;试了下snapgraon, 还是可以的 官网需要注册登录后下载&#xff0c;官网路径&#xff1a;Developer | Qualcomm 为了方便贴上已经下载好的exe安装包&#x…

物业巡更系统在现代社区管理中的优势与应用探讨

内容概要 在现代社区管理中&#xff0c;物业巡更系统正逐渐成为一种不可或缺的工具。结合先进的智能技术&#xff0c;这些系统能够有效地提升社区管理的各个方面&#xff0c;尤其是在巡检效率和信息透明度方面。通过实时记录巡检数据&#xff0c;物业管理人员能够确保工作人员…

速通Docker === Docker Compose

目录 Docker Compose 简介 Docker Compose 常用命令 使用 Docker Compose 启动 WordPress 普通启动方式&#xff08;使用 Docker 命令&#xff09; 使用 Docker Compose 启动 Docker Compose 的特性 Docker Compose 简介 Docker Compose 是一个用于定义和运行多容器 Dock…