【Linux】解锁文件描述符奥秘,高效缓存区的实战技巧

news2024/9/25 17:33:57

fd和缓冲区

  • 1. 文件描述符fd
    • 1.1. 概念与本质
    • 1.2. 打开文件的管理
    • 1.3. 一切皆文件的理解
    • 1.4. 分配规则
    • 1.5. 重定向的本质
      • 1.5.1. dup2
  • 2. FILE中的缓冲区
    • 2.1. 概念
    • 2.2. 存在的原因
    • 2.3. 类型(刷新方案)
    • 2.4. 存放的位置
      • 2.4.1. 代码证明、现象解释
    • 2.5. 模拟C标准库中的方法

1. 文件描述符fd

1.1. 概念与本质

  1. 定义:是用于标识打开文件的非负整数。

  2. 文件描述符的本质,就是数组下标。

1.2. 打开文件的管理

问:为什么访问文件的系统调用接口,都必需使用文件描述符fd?

  1. 当我们打开一个文件时,OS会在内存中创建一个file结构体,用来描述被打开的文件,这个结构体包含了文件的当前读写位置、文件描述符、文件路径等相关信息。
struct file {
    struct path f_path;          // 文件路径
    struct file_operations *f_op; // 文件操作指针
    int _fileno;                  //文件描述符
    loff_t f_pos;                // 当前文件读写位置
    ...
};

struct file_operations {
    int (*open)(struct inode *, struct file *);
    int (*release)(struct inode *, struct file *);
    ssize_t (*read)(struct file *, char __user *, size_t, loff_t *);
    ssize_t (*write)(struct file *, const char __user *, size_t, loff_t *);
    loff_t (*llseek)(struct file *, loff_t, int);
    ...
};
  1. 因为open函数是由进程来执行的,所以必须让进程和文件关联起来。在每一个task_struct结构体中都包含了一个struct file_struct*类型的指针,它指向了一个包含文件描述符表的file_struct结构体。
struct task_struct {
    ...
    files_struct *files;  // 文件描述符表
    ...
};
  1. 文件描述符表是一个file*类型的指针数组,每个元素都指向一个打开的文件,而文件描述符就是此数组的下标,所以只要拿到了文件描述符表,就可以索引到对应的文件。
struct files_struct {
    ...
    struct file *fd[FD_SETSIZE];  // 文件描述符数组
    ...
};



打开文件本质:在内核中,把文件在磁盘上找到、内容和属性加载,在内存中创建file结构体,属性、方法、缓冲区都初始化,然后把结构体链入到系统管理文件的链表中,并且在指针数组中找到一个数据下标,再把它的地址填充进来,最后把数组下标(fd)返回给上层用户,应用层得到fd值。

a. 把数据写到文件中write:

  1. 因为write是由进程通过系统调用来执行的,而系统能够识别出是哪个进程在请求服务,即:OS可以找到进程(task_struct);

  2. 因为write需要访问文件,所以通过fd,直接在数组中进行索引,从而找到文件。再把用户空间中的缓冲区buf中的数据,拷贝到内核空间中的文件结构体对象的缓冲区中,最后让OS把缓冲区的刷新到磁盘文件中。

b. 从文件中读取数据read:

  1. 因为read是由进程通过系统调用来执行的,而系统能够识别出是哪个进程在请求服务,即:OS可以找到进程(task_struct);

  2. 因为read需要访问文件,所以通过fd,直接在数组中进行索引,从而找到文件。如果文件结构体对象的缓冲区中有内容,就直接读取到用户空间的缓冲区buf中,反之,就让OS把磁盘文件中的数据导入到内存中。

c. 关闭文件close:

  1. 因为close是由进程通过系统调用来执行的,而系统能够识别出是哪个进程在请求服务,即:OS可以找到进程(task_struct);

  2. 因为close需要访问文件,所以通过fd,直接在数组中进行索引,从而找到文件。OS再将文件结构体对象进行释放。

1.3. 一切皆文件的理解

一、文件系统的抽象和VFS

  1. VFS(Virtual File System):虚拟文件系统,是Linux内核的一个软件层,它提供了一套统一的接口来访问各种类型的文件系统和硬件设备。

这种设计使得用户和应用程序能够通过调用相同的系统调用(如open、write、read等)来操作不同的文件系统,而无需关心底层文件系统的具体实现细节。

  1. 文件操作结构体file_operations:在linux内核中,每个打开的文件都有一个指向file_operations结构体的指针,它包含一系列函数指针,即:它定义了文件的各种操作(如:读、写、打开、关闭),其内包含的函数指针指向具体的实现方法。

不同的文件系统或硬件的驱动程序会提供这些函数的具体实现,但这些函数的参数类、返回值类型、函数名,必须与定义在file_operations结构体中的函数指针相匹配。

struct file {
    struct inode *f_inode;  // 文件的inode
    struct file_operations *f_op;  // 文件操作函数指针
    unsigned long f_flags;  // 文件标志
    loff_t f_pos;  // 当前文件位置
    // 其他信息...
};

struct file_operations {  //文件操作函数
    int (*open)(struct inode *, struct file *);
    int (*release)(struct inode *, struct file *);
    ssize_t (*read)(struct file *, char __user *, size_t, loff_t *);
    ssize_t (*write)(struct file *, const char __user *, size_t, loff_t *);
    loff_t (*llseek)(struct file *, loff_t, int);
    // 其他操作...
};
  1. VFS的核心思想是通过抽象和封装来屏蔽底层硬件的差异。

二、面向对象编程的类比

  1. 多态性:在面向对象编程中,多态性允许我们使用统一的接口来调用不同的实现。在VFS中,是通过函数指针来实现多态性,不同的文件系统的具体实现方法不同,但上层应用程序只使用统一的函数指针接口。

  2. 封装:VFS屏蔽了文件系统和硬件设备的差异,即:隐藏了底层的细节;使得上层应用程序可以使用统一的函数指针接口来访问文件。

上层代码无需关心底层操作的实现,只需按照统一接口的规范进行操作。


不能从显示器读数据,平时在显示器输入的东西,显示器也显示了,不是通过显示器把数据交给了你的程序,而是从键盘中输入数据,你的程序先从键盘中读到的,为了让用户看到你输入的数据,程序就把数据同步的给显示器拷贝了一份。

1.4. 分配规则

  1. fd分配规则:最小未被使用的数组下标,会被分配给最新打开的文件。
#include<stdio.h>
#include<sys/types.h>
#include<fcntl.h>
#include<unistd.h>
#include<sys/stat.h>
#include<string.h>
  
int main()
{
    close(1);//关闭标准输出流
    int fd1 =  open("log.txt", O_WRONLY|O_CREAT|O_TRUNC, 0666);
    printf("fd1: %d\n", fd1); //printf默认向stdin—>fd = 1打印
    printf("hello world\n");  //输出重定向:本来应该把内容向显示器文件进行写入,更改为向磁盘文件进行写入

    return 0;
}

#include<stdio.h>
#include<sys/types.h>
#include<fcntl.h>
#include<unistd.h>
#include<sys/stat.h>
#include<string.h>
  
int main()
{
    close(0);  //关闭标入输出流                                           
    int fd2 = open("data.txt", O_RDWR);
    printf("fd2: %d\n", fd2); 
    char buff[64]; 
    fgets(buff, 64, stdin);   //输入重定向:本来应该从键盘文件中读取的内容,更改为从磁盘文件中读取
    printf("%s\n", buff); 
    
    return 0;
 }

1.5. 重定向的本质

  1. 重定向的本质:更改文件描述符表的内容,即:更改文件描述符(stdin、stdou、stderr)的指向,使得原本要写入到标准输出的数据,被重定向到其他文件、或者原本要从标准输入中读取的数据,重定向到来自于其他文件。

1.5.1. dup2

int dup2(int oldfd,int newfd);

  1. 功能:将stdin、stdout、stderr重定向到文件或其他设备。

  2. 参数:oldfd:要被复制的文件描述符;newfd:目标文件的描述符。

  3. 返回值:成功,返回新的文件描述符(即:newfd)。出错时,返回 -1,并设置 errno以指示错误。

#include<stdio.h>
#include<sys/types.h>
#include<fcntl.h>
#include<unistd.h>
#include<sys/stat.h>

int main()
{
    int fd = open("log.txt", O_WRONLY|O_CREAT|O_TRUNC, 0666);
    dup2(fd, 1);
    printf("hello zzx\n");

    return 0;
}

2. FILE中的缓冲区

2.1. 概念

  1. 概念:本质是一块内存区域,用于暂时存放数据,以便更高效地处理输入、输出操作。

💡此处的缓冲区(如:进度条中的缓冲区等),不是内存中的缓冲区,它是语言层面的缓冲区,即:C语言自带的缓冲区,由C语言标准库提供。

  1. 缓冲区也会为格式化输入、输出操作提高场所。

printf函数工作原理:它会将其他类型的数据(如整数、浮点数等)转换为字符数据(即字符串),转化后的数据会被写入到FILE结构体维护的缓冲区中,根据条件刷新缓冲区。

scanf函数工作原理:scanf会从输入流中读取字符数据,将读取的数据转化为相应的格式化数据,格式化的数据会被存放到FILE结构体维护的缓冲区中,最终被存放到变量中。

2.2. 存在的原因

  1. 提高使用者的效率

减少了C接口的使用时间,从而减少了用户的等待时间,提高了使用者的效率:调用C接口时,只要将数据交给了缓冲区,就可立即返回,无需等待实际的写入操作完成,意味这用户可以更快地继续执行其他任务。

  1. 提高计算机整体的拷贝效率。

调用系统调用接口,都是有成本的,有时间和空间的开销。

减少调用系统调用的次数,提高了计算机整体的拷贝效率:缓冲区可以聚集大量数据,直到缓冲区满了,再调用一次系统调用进行实际的数据写入,即:进行一次拷贝。

故事理解:张三给李四送生日礼物,只需要将礼物交给附近的菜鸟驿站,就可立即其他活动,无需亲自送到的李四那,即:提高了使用者的效率。菜鸟驿站不是每次只处理一个包裹,而是收集多个包裹,直到它们填满整个运输车辆,然后再一次性运送到目的地,即:聚集数据,一次拷贝,提高了计算机的整体效率。—— 菜鸟驿站就相当于缓冲区。

2.3. 类型(刷新方案)

一、无缓冲、无刷新

  1. 无缓冲:无刷新,意味着数据不会暂存在缓冲区中,而是立即被写入到目标设备中。

  2. 适用场景:需要立即看到结果、实时性要求很高的场景,如:实时系统、设备驱动程序。

  3. 优点:保证了数据的即时可见性。

  4. 缺点:性能下降,频繁的使用系统调用会增加开销。

二、全缓冲、全刷新

  1. 全缓冲:全刷新,缓冲区满了或者关闭文件时,缓冲区的数据才会被刷新到目的设备中。

  2. 适用场景:文件的读写操作,尤其是大文件。

  3. 优点:减少了系统调用的次数,提高了性能。

  4. 缺点:可能会丢失数据,如:在缓冲区的数据未被刷新前,发生崩溃,则这部分的数据就会丢失。

三、行缓冲、行刷新

  1. 行缓冲:行刷新,意味着遇到换行符\n,缓冲区的数据就会被立即刷新到目的设备中。

  2. 适用场景:标准输入输出(显示器)。

💡当调用c语言接口fflush(),进行强制刷新; 进程退出时,或文件关闭时,自动刷新。

2.4. 存放的位置

  1. 缓冲区存放在FILE结构体中,即:缓冲区是被FILE结构来维护的。

  2. 每个通过标准C库函数打开的文件,都拥有自己的缓冲区。

fwrite等标准库函数,会先将数据拷贝到缓冲区中,然后根据一定的条件,调用系统调用接口进行刷新。

文件操作的系统调用接口,其实是个拷贝函数,它将数据从语言层的缓冲区拷贝到内存的缓冲区。

typedef struct _IO_FILE FILE;
 
struct _IO_FILE {
	int _flags;       /* High-order word is _IO_MAGIC; rest is flags. */
#define _IO_file_flags _flags
 
	//缓冲区相关
	/* The following pointers correspond to the C++ streambuf protocol. */
	/* Note:  Tk uses the _IO_read_ptr and _IO_read_end fields directly. */
	char* _IO_read_ptr;   /* Current read pointer */
	char* _IO_read_end;   /* End of get area. */
	char* _IO_read_base;  /* Start of putback+get area. */
	char* _IO_write_base; /* Start of put area. */
	char* _IO_write_ptr;  /* Current put pointer. */
	char* _IO_write_end;  /* End of put area. */
	char* _IO_buf_base;   /* Start of reserve area. */
	char* _IO_buf_end;    /* End of reserve area. */
	/* The following fields are used to support backing up and undo. */
	char *_IO_save_base; /* Pointer to start of non-current get area. */
	char *_IO_backup_base;  /* Pointer to first valid character of backup area */
	char *_IO_save_end; /* Pointer to end of non-current get area. */
 
	struct _IO_marker *_markers;
 
	struct _IO_FILE *_chain;
 
	int _fileno; //封装的文件描述符
#if 0
	int _blksize;
#else
	int _flags2;
#endif
	_IO_off_t _old_offset; /* This used to be _offset but it's too small.  */
 
#define __HAVE_COLUMN /* temporary */
	/* 1+column number of pbase(); 0 is unknown. */
	unsigned short _cur_column;
	signed char _vtable_offset;
	char _shortbuf[1];
 
	/*  char* _save_gptr;  char* _save_egptr; */
 
	_IO_lock_t *_lock;
#ifdef _IO_USE_OLD_IO_FILE
};

2.4.1. 代码证明、现象解释

#include<stdio.h>
#include<unistd.h>
#include<string.h>
 
int main()
{
    const char* s1 = "hello write\n";
    write(1, s1, strlen(s1));  //调用系统调用,直接将数据写入到内核中

    //fprintf、fwrite为库函数,向显示器进行写入,行刷新(遇到换行符)
    const char* s2 = "hello fprintf\n";
    fprintf(stdout, "%s", s2); 
  
    const char* s3 = "hello fwrite\n";
    fwrite(s3, strlen(s3), 1, stdout);                     
  
    fork(); //在创建子进程之前,缓冲区中的数据全部被刷新到内核中了
  
    return 0;
}

现象1解释:write()为系统调用接口,直接将数据写入到内核中;fprintf、fwrite为库函数,先将数据写入到缓冲区中,因为它们都是向显示器进行写入,而写入显示器是行刷新(遇到换行符\n,进行刷新),所以fork创建子进程前缓冲区中的数据全部被刷新到内核中了。

Tips:刷新到内核的数据,不属于进程的数据;存放在缓冲区中的数据,属于进程的数据。

现象2解释:重定向到普通文件时,数据刷新缓冲区的方式,由行缓存变为全缓冲,C语言接口自带缓冲区,所以它会将数据写入到缓冲区中,就不会立即刷新。fork创建子进程,父子共享缓冲区的数据,但是进程退出后,统一进行刷新。刷新缓冲区,是清空缓冲区,是修改数据的一种方式,所以父子进程的数据会发生写时拷贝,父子进程分别刷新各自的缓冲区,随即产生两份数据。write是系统调用接口,直接将数据写入到内核中,不存在所谓的缓冲区。

  1. 一般C库函数写入文件时,是全缓冲; 写入到显示器时,是行缓冲。

  2. 重定向到普通文件时,数据刷新缓冲区的方式,由行缓存变为全缓冲。

  3. 刷新缓冲区,是清空缓冲区,是修改数据的一种方式。

2.5. 模拟C标准库中的方法

#pragma once  //防止头文件重复包含
 
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<string.h>
#include<stdlib.h>
  
 #define SIZE 4094

//定义缓冲区的类型
#define None_Flush 1
#define Full_Flush (1<<1)
#define Line_Flush (1<<2)

//自定义file结构体
typedef struct myfile{
    int fileno;  //文件描述符
    int pos;  //当前读写位置
    int cap;  //缓冲区容量
    int flush_mode;  //缓冲区类型
    char buff[SIZE];  //输出缓冲区
}myfile;
 
myfile* my_fopen(const char* path, const char* mode); //打开文件
void my_fclose(myfile* fp);  //关闭文件
int my_fwrite(myfile* fp, const char* str, int size); //读文件
void my_fflush(myfile* fp);   //刷新缓冲区                                                
void print_buff(myfile* fp);  //打印file结构体的内容,便于测试
#include"mystdio.h" 

const char* To_string(int flush_mode) //将整形转化为字符串
{
    if(flush_mode & None_Flush) 
        return "Nono Flush";
    else if(flush_mode & Line_Flush)
        return "Line_Flush";
    else if(flush_mode & Full_Flush)
         return "Full_Flush";
}
    
void print_buff(myfile* fp) //打印file结构体的内容
{
    printf("fd: %d\n", fp->fileno);
    printf("fd: %d\n", fp->pos);
    printf("buff: %s\n", fp->buff);
    printf("flush_mode: %s\n", To_string(fp->flush_mode));
}
   
myfile* my_fopen(const char* path, const char* mode) //打开文件
{
    int flag = -1; //确认它是以何种方式打开文件
    if(strcmp(mode, "r") == 0)
        flag = O_RDONLY; 
    else if(strcmp(mode, "w") == 0)
        flag = O_WRONLY|O_CREAT|O_TRUNC;                                                     else if(strcmp(mode, "a") == 0)
        flag = O_WRONLY|O_CREAT|O_APPEND;
    else 
        return NULL;


    //底层调用系统调用接口open打开文件
    int fd = -1;  
    if(flag & O_RDONLY) //读不需要创建新的文件
        fd = open(path, flag); 
    else  //写、追加,都需要创建新的文件,并且需要设置文件权限
    {        
        umask(0);                                                                                fd = open(path, flag, 0666);
    }
    if(fd < 0) //调用open失败
         return NULL;
    

    //为打开的文件创建一个file类型的结构体,用来记录描述打开文件的信息
    myfile*  fp = (myfile*)malloc(sizeof(myfile));
    if(fp == NULL) //malloc调用1失败
        return NULL;

    //file结构体对象构建成功,进行初始化
    fp->fileno = fd;
    fp->pos = 0;
    fp->cap = SIZE;
    fp->flush_mode = Line_Flush;
    
    return fp;
}
     
void my_fflush(myfile* fp) //刷新缓冲区
{
    if(fp->pos == 0) 
        return ;
    //底层调用系统调用接口write
    write(fp->fileno, fp->buff, fp->pos);
    //清空缓冲区的内容
    fp->pos = 0;
}                                                                                                                     
    
void my_fclose(myfile* fp) //关闭文件
{
    my_fflush(fp); //文件关闭,自动刷新缓冲区
    close(fp->fileno);
    free(fp);
}
    
int my_fwrite(myfile* fp, const char* str, int size)  //向文件写内容
{
    //将数据先拷贝到用户层的缓冲区内
    memcpy(fp->buff + fp->pos, str, size); 
    fp->pos += size; 

    //判断是否需要刷新
    if(fp->flush_mode == Line_Flush && fp->buff[fp->pos - 1] == '\n') //行刷新
        my_fflush(fp);
    else if(fp->flush_mode == Full_Flush && fp->pos == fp->cap) //全刷新
         my_fflush(fp);
    
    return 0;
}
int main()                                                                               {   
    myfile* fp = my_fopen("data.txt", "w");   
    if(fp == NULL)           
        return 1;            
                              
    char buf[SIZE];           
    int cnt = 5;   
    while(cnt--)   
    {   
        snprintf(buf, SIZE, "helloworld: %d  :", cnt); //字符串的拼接
        my_fwrite(fp, buf, strlen(buf));   
        print_buff(fp);   
        sleep(1);   
     }   
        
     my_fclose(fp);   
 
     return 0;   
 }   

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

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

相关文章

LiveGBS流媒体平台GB/T28181功能-支持电子放大拉框放大直播视频拉框放大录像视频流拉框放大电子放大

LiveGBS流媒体平台GB/T28181功能-支持电子放大拉框放大直播视频拉框放大录像视频流拉框放大电子放大 1、直播播放2、录像播放3、搭建GB28181视频直播平台 1、直播播放 国标设备-》查看通道-》播放 &#xff0c;左键单击可以拉取矩形框&#xff0c;放大选中的范围&#xff0c;释…

IPD如何解决产品开发的典型问题

IPD&#xff08;Integrated Product Development&#xff0c;集成产品开发&#xff09;是一种领先的、成熟的产品开发的管理思想和管理模式。它是根据大量成功的产品开发管理实践总结出来的&#xff0c;并被大量实践证明的高效的产品开发模式。从汉捷咨询二十多年来为五百多家企…

如何通过代理使用 Squid: 综合指南

文章目录 一、简介二、什么是 Squid?三、为什么在代理服务器中使用 Squid&#xff1f;四、设置代理五、使用代理设置 Squid5.1. 第一步5.2. 第二步5.3. 第三步 六、在网络搜索中使用代理&#xff1a;实用代码示例6.1. 使用代理的 cURL6.2. 使用代理的 Python 请求 七、结论 一…

嘉立创EDA-- 线宽、过孔和电流大小对比图

导线宽度和电流大小如何来考虑 1 电流大小需要考虑问题 1、允许的温升&#xff1a;如果能够允许的铜线升高的温度越高&#xff0c;那么允许通过的电流自然也就越高 2、走线的线宽&#xff1a;线越宽 &#xff0c;导线横截面积越大&#xff0c;电阻越小&#xff0c;发热越小&a…

磨具生产制造9人共用一台工作站

随着技术的不断进步与工业自动化的深入发展&#xff0c;如何优化生产流程、提高设备利用率成为了众多企业面临的重大课题。那么在磨具生产制造中实现9人共用一台工作站呢&#xff1f; 一、背景与挑战 在磨具制造行业&#xff0c;高精度、高效率的生产要求与复杂多变的工艺流程…

smartctl 命令:查看硬盘健康状态

一、命令简介 ​smartctl​ 命令用于获取硬盘的 SMART 信息。 介绍硬盘SMART 硬盘的 SMART (Self-Monitoring, Analysis, and Reporting Technology) 技术用于监控硬盘的健康状态&#xff0c;并能提供一些潜在故障的预警信息。通过查看 SMART 数据&#xff0c;用户可以了解硬…

如何选择渲染集群管理软件?

选择适合渲染集群管理软件可以考虑以下几个方面&#xff1a; 1.渲染需求&#xff1a;明确自己的渲染任务类型、规模和复杂度。如果需要处理大型、复杂的项目&#xff0c;对渲染效率和速度要求较高&#xff0c;就需要选择性能强劲的软件。 2.软件兼容性&#xff1a;确保软件支持…

[极客大挑战 2019]EasySQL1

前言&#xff1a; 记录一下web方面的题(第一次接触。。。&#xff09; 学校课程要学web…… - - 行吧&#xff0c;尝试一下&#xff0c;至少学过MySQL。。。 不过&#xff0c;实际上&#xff0c;现实现在SQL漏洞少得可怜&#xff0c;但学习不会有错。 参考&#xff1a;&…

vue-cli,element-plus,axios,proxy

一、vue-cli vue-cli俗称vue脚手架&#xff0c;是vue官方提供的快速生成vue 工程化项目的工具。 1.官网&#xff1a;https://cn.vuejs.org/ 中文官网: https://cli.vuejs.org/zh/ 特点&#xff1a;基于webpack&#xff0c;功能丰富且易于扩展&#xff0c;支持创建vue2和vu…

学校快递站点管理|基于springboot学校快递站点管理设计与实现(源码+数据库+文档)

学校快递站点管理系统目录 目录 基于springboot学校快递站点管理设计与实现 一、前言 二、系统功能设计 三、系统实现 前台功能模块 后台功能角模块 四、数据库设计 1、实体ER图 2、具体的表设计如下所示&#xff1a; 五、核心代码 六、论文参考 七、最新计算机毕设…

本地搭建OnlyOffice在线文档编辑器结合内网穿透实现远程协作

文章目录 前言1. 安装Docker2. 本地安装部署ONLYOFFICE3. 安装cpolar内网穿透4. 固定OnlyOffice公网地址 前言 本篇文章讲解如何使用Docker在本地Linux服务器上安装ONLYOFFICE&#xff0c;并结合cpolar内网穿透实现公网访问本地部署的文档编辑器与远程协作。 Community Editi…

LVM的基本概念,PD PP PV VG LV PE等概念

LVM&#xff08;Logical Volume Manager&#xff0c;逻辑卷管理器&#xff09;是 Linux 系统中一种灵活的磁盘分区管理工具&#xff0c;允许动态地调整存储设备的大小和配置&#xff0c;而无需停止系统。LVM 在传统的分区管理工具上添加了更灵活的管理功能&#xff0c;特别适合…

逆向推理+ChatGPT,让论文更具说服力

学境思源&#xff0c;一键生成论文初稿&#xff1a; AcademicIdeas - 学境思源AI论文写作 使用ChatGPT辅助“逆向推理”技巧&#xff0c;可以显著提升论文的质量和说服力。逆向推理从结论出发&#xff0c;倒推所需的证据和论点&#xff0c;确保整个论证过程逻辑严密且无漏洞。…

Linux快速安装ClickHouse(附官方文档)

在线安装 1.安装yum-utils yum-utils是一个与 yum 集成的实用程序集合&#xff0c;可以通过多种方式扩展其本机功能 yum install -y yum-utils 2.增加ClickHouse官方镜像源 yum-config-manager --add-repo https://packages.clickhouse.com/rpm/clickhouse.repo 3.安装Cl…

【推荐100个unity插件之34】在unity中实现和Live2D虚拟人物的交互——Cubism SDK for Unity

最终效果 文章目录 最终效果前言例子中文官网Live2d模型获取下载Live2D Cubism SDK for Unity使用文档限制unity导入并使用Live2D模型1、将SDK载入到项目2、载入模型3、显示模型4、 播放动画 表情动作修改参数眼神跟随看向鼠标效果部位触摸效果摸头效果摸头闭眼效果做成桌宠参考…

企业如何通过ETL工具实现主数据的同步

1、 主数据的定义与重要性 主数据&#xff0c;作为企业的核心数据资产&#xff0c;涵盖了客户、产品、供应商、员工等关键业务实体信息。这些数据的稳定性、共享性和对决策的影响力&#xff0c;使其成为企业运营和战略决策不可或缺的基础。主数据的质量与一致性直接关系到企业…

数据结构-4.栈与队列

本篇博客给大家带来的是栈和队列的知识点, 其中包括两道面试OJ题 用队列实现栈 和 用栈实现队列. 文章专栏: Java-数据结构 若有问题 评论区见 欢迎大家点赞 评论 收藏 分享 如果你不知道分享给谁,那就分享给薯条, 如果分享不成功, 那我就会回你一下,那样你就分享成功啦. 你们的…

在Unity编辑器中实现组件的复制与粘贴:完整指南

内容将会持续更新&#xff0c;有错误的地方欢迎指正&#xff0c;谢谢! 在Unity编辑器中实现组件的复制与粘贴&#xff1a;完整指南 TechX 坚持将创新的科技带给世界&#xff01; 拥有更好的学习体验 —— 不断努力&#xff0c;不断进步&#xff0c;不断探索 TechX —— 心…

低空经济——载具

低空经济的政策大家都知道&#xff0c;开放低空&#xff0c;是经济和社会发展的需要&#xff0c;但是步子肯定不会太大&#xff0c;先刺激资本进行投入&#xff0c;之后看看再说&#xff0c;一定是这样模式。 我们知道&#xff0c;新能源车&#xff0c;基本实现了弯道超车&…

基于springboot vue网上摄影工作室系统设计与实现

博主介绍&#xff1a;专注于Java vue .net php phython 小程序 等诸多技术领域和毕业项目实战、企业信息化系统建设&#xff0c;从业十五余年开发设计教学工作 ☆☆☆ 精彩专栏推荐订阅☆☆☆☆☆不然下次找不到哟 我的博客空间发布了1000毕设题目 方便大家学习使用 感兴趣的…