Linux文件描述符+缓冲区

news2024/12/23 11:13:57

Linux文件描述符+缓冲区

📟作者主页:慢热的陕西人

🌴专栏链接:Linux

📣欢迎各位大佬👍点赞🔥关注🚓收藏,🍉留言

本博客主要内容讲解了文件描述符以及文件描述符的分配规则,重定向,以及对我们的极简shell实现重定向。最后如何理解FILE和缓冲区的概念

文章目录

  • Linux文件描述符+缓冲区
    • 1.文件描述符
      • 1.1文件描述符的分配规则
    • 2.重定向
      • 2.1输出重定向<
      • 2.2输入重定向>
      • 2.3追加重定向>>
    • 3.使用 dup2 系统调用
    • 4.使我们的极简shell增加重定向的功能
    • 5.FILE
      • 5.1如何理解缓冲区

1.文件描述符

我们用一个之前的例子来引入今天的只是,为什么我们打开成功之后这个fd返回的是3呢?为什么不是1,不是0;

image-20231113183152773

那么我们再来查看一下open函数手册:

open函数的返回值:如果打开成功返回一个新的文件描述符,打开失败,则返回-1;

那么为什么我们之前所有的例子都返回的是3,而不是0,1,2呢?那么他们是不是被其他文件占用了呢?

image-20231113183906336

那么其实进程在启动的似乎后默认会打开当前进程的三个文件:

操作系统标准输入标准输出标准错误
cstdinstdoutstderr
c++cincoutcerr

那么标准输入标准输出标准错误本质都是文件,然后stdin,stdout,stderr是他们三个在语言层面的表现:

查看手册可以看到他们三个的类型都是C库内部封装的文件类型!那么C++中的这三个和C库中的也是类似;

image-20231113184757396

C++/C的程序例子:

#include<iostream>    
#include<cstdio>    
    
using namespace std;    
    
int main()    
{    
    
  //C    
  printf("hello printf -> stdout\n");    
  fprintf(stdout, "hello printf -> stdout\n");                                                                                                               
  fprintf(stderr, "hello printf -> stderr\n");    
    
  //C++    
  cout << "hello cout -> cout" << endl;    
  cerr << "hello cerr -> cerr" << endl;    
    
  return 0;    
} 

image-20231113190605428

那么标准输出标准错误有什么区别呢?

虽然他们都可以向屏幕打印,但是是不一样的。

我们尝试重定向一下./demo到这个log.txt可是我们发现只有标准输出被重定向进了文件,而标准错误却没有。

原因我们后面再说。

image-20231113191032680

Linux下一切皆文件,那么我们向屏幕打印字符串,本质也是向文件写入字符串,该作何理解?我们后面再解答。

那么说到这里我们也可以揭晓了那么我们为什么之前打开文件返回的文件描述符是3,却没有0,1,2。

原因是0, 1,2 分别被他们几个占用了标准输入标准输出标准错误,他们本质都是文件。

那么这样的从0开始排序的方式,是不是和我们的数组非常的类似!

那么我们再运行一段程序看看:

#include<stdio.h>    
#include <sys/types.h>    
#include <sys/stat.h>    
#include <fcntl.h>    
    
#define LOG "log.txt"    
    
int main()    
{    
    
  int fd1 = open(LOG, O_CREAT | O_TRUNC | O_WRONLY, 0666);    
  int fd2 = open(LOG, O_CREAT | O_TRUNC | O_WRONLY, 0666);    
  int fd3 = open(LOG, O_CREAT | O_TRUNC | O_WRONLY, 0666);    
  int fd4 = open(LOG, O_CREAT | O_TRUNC | O_WRONLY, 0666);    
  int fd5 = open(LOG, O_CREAT | O_TRUNC | O_WRONLY, 0666);    
    
  printf("fd1 = %d\n", fd1);    
  printf("fd2 = %d\n", fd2);    
  printf("fd3 = %d\n", fd3);    
  printf("fd4 = %d\n", fd4);    
  printf("fd5 = %d\n", fd5);                                                                                                                                 
    
    
  return 0;    
} 

运行结果:

image-20231113192553093

那么我们从原理上来解释一下:

在我们的进程的pcb内部对应到linux也就是我们的task_struct内部,有一个struct files_struct *file 的指针,它指向的是该进程对应的一个files struct结构体:那么这个结构体的内部就有一个对应我们文件描述符的数组,叫做:struct file fd_array[];文件描述符(open的返回值)的本质就是:数组下标!

那么我们的进程就是通过文件描述符fd来访问struct files struct来找到对应的文件指针,然后再在内存中找到对应的struct file

来进行对文件进行操作的的。

image-20231113200649085

那么其实在内存中的管理文件的数据结构:struct file对应每个文件都有一个缓冲区,所以我们所谓的IO类函数read/write本质上是拷贝函数,都是在向对应的缓冲区读或者写,那么我们写入到缓冲区后,何时刷新到文件对应的磁盘位置这个是由操作系统决定的。


如何深度理解Linux中的**“一切皆文件”**:

对应到我们的外设,对于Linux内部来说,他们也是一个个的文件,首先他们有各自的writeread方法:

那么对应到我们内存中外设所对应的struct file中都有一个个对应的函数指针,指向对应外设的读写方法;

在网上到我们的进程中对应的struct files struct 中对应的函数指针数组(下标对应我们的文件标识符);

如下图的这个过程也是用C语言面向对象编程的过程!

所以后来的面向对象的语言都是经过很多的实践总结出来而设计的。

image-20231113202836621

我们使用操作系统的本质:都是通过进程的方式进行OS的访问的。

1.1文件描述符的分配规则

文件描述符的分配规则:在files_struct数组当中,找到当前没有被使用的最小的一个下标,作为新的文件描述符

代码:

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

#define LOG "log.txt"

int main()
{

  fclose(stdin); //等价于close(0);
  int fd1 = open(LOG, O_CREAT | O_TRUNC | O_WRONLY, 0666);
  int fd2 = open(LOG, O_CREAT | O_TRUNC | O_WRONLY, 0666);
  int fd3 = open(LOG, O_CREAT | O_TRUNC | O_WRONLY, 0666);
  int fd4 = open(LOG, O_CREAT | O_TRUNC | O_WRONLY, 0666);
  int fd5 = open(LOG, O_CREAT | O_TRUNC | O_WRONLY, 0666);

  printf("fd1 = %d\n", fd1);
  printf("fd2 = %d\n", fd2);
  printf("fd3 = %d\n", fd3);
  printf("fd4 = %d\n", fd4);
  printf("fd5 = %d\n", fd5);


  return 0;
}  

运行结果:

那么我们可以看到我们的fd1变成了0,完美印证了规则!

image-20231113212234247

注意上面的代码中我们关闭的是0,并没有选择关闭1;

2.重定向

2.1输出重定向<

那如果关闭1呢?看代码:

  #include<stdio.h>
  #include<sys/types.h>
  #include<sys/stat.h>
  #include<fcntl.h>
  #include<string.h>
  #include<unistd.h>
  
  #define LOG "log.txt"
  
  int main()
  {

  int fd1 = open(LOG, O_CREAT | O_WRONLY | O_TRUNC, 0666);
  //  int fd2 = open(LOG, O_CREAT | O_TRUNC | O_WRONLY, 0666);
  //  int fd3 = open(LOG, O_CREAT | O_TRUNC | O_WRONLY, 0666);
  //  int fd4 = open(LOG, O_CREAT | O_TRUNC | O_WRONLY, 0666);
  //  int fd5 = open(LOG, O_CREAT | O_TRUNC | O_WRONLY, 0666);
  
      
    printf("hello xupt\n");    
    printf("hello xupt\n");    
    printf("hello xupt\n");    
    printf("hello xupt\n");    
    printf("hello xupt\n");    
  //  printf("fd1 = %d\n", fd1);    
  //  printf("fd2 = %d\n", fd2);    
  //  printf("fd3 = %d\n", fd3);    
  //  printf("fd4 = %d\n", fd4);    
  //  printf("fd5 = %d\n", fd5);    
                                                                                                     
    return 0;    
  }  

运行结果:

我们发现printf函数运行的结果没有出现在屏幕上,而是出现在log.txt文件中。

image-20231114142013278

原理:

因为我们一开始执行了close(1)关闭文件描述符1对应的文件,其实也就是我们的stdout,那么我们再打开log.txt文件,根据文件描述符的规则:分配的是当前最小的没有被占用的文件描述符!那么我们的log.txt就顺理成章的拿到了fd = 1;这时候printf函数内部肯定是封装了操作系统接口write的,write只会根据文件描述符来区分文件,所以它默认的就是向文件描述符为1的文件中写入,所以就写入到了log.txt中!

那么其实这也是重定向的本质:在上层无法感知的情况下,在操作系统内部,更改进程对应的文件描述符表中,特定下标的指向!!!

2.2输入重定向>

我们再来看一个例子:

我们事先将log.txt中的内容修改成123 456,然后再运行下面的程序:

  #include<stdio.h>    
  #include<sys/types.h>    
  #include<sys/stat.h>    
  #include<fcntl.h>    
  #include<string.h>    
  #include<unistd.h>    
      
  #define LOG "log.txt"    
      
  int main()    
  { 
    close(0); 
    int fd = open(LOG, O_RDONLY);
    
    int a, b;
  
    scanf("%d %d", &a, &b);
  
    printf("a = %d, b = %d\n", a, b);
    
    return 0;
  }

运行结果:

image-20231114144912003

原理:

因为我们一开始执行了close(0)关闭文件描述符1对应的文件,其实也就是我们的stdin,那么我们再打开log.txt文件,根据文件描述符的规则:分配的是当前最小的没有被占用的文件描述符!那么我们的log.txt就顺理成章的拿到了fd = 0;这时候printf函数内部肯定是封装了操作系统接口read的,read只会根据文件描述符来区分文件,所以它默认的就是向文件描述符为0的文件中读取,所以就读取到了log.txt中的123 和 456!

2.3追加重定向>>

我们只需要修改输出重定向中的代码:在open函数的参数中添加上追加的参数即可!

  #include<stdio.h>
  #include<sys/types.h>
  #include<sys/stat.h>
  #include<fcntl.h>
  #include<string.h>
  #include<unistd.h>
  
  #define LOG "log.txt"
  
  int main()
  {

  int fd1 = open(LOG, O_CREAT | O_WRONLY | O_APPEND, 0666);
  //  int fd2 = open(LOG, O_CREAT | O_TRUNC | O_WRONLY, 0666);
  //  int fd3 = open(LOG, O_CREAT | O_TRUNC | O_WRONLY, 0666);
  //  int fd4 = open(LOG, O_CREAT | O_TRUNC | O_WRONLY, 0666);
  //  int fd5 = open(LOG, O_CREAT | O_TRUNC | O_WRONLY, 0666);
  
      
    printf("hello xupt\n");    
    printf("hello xupt\n");    
    printf("hello xupt\n");    
    printf("hello xupt\n");    
    printf("hello xupt\n");    
  //  printf("fd1 = %d\n", fd1);    
  //  printf("fd2 = %d\n", fd2);    
  //  printf("fd3 = %d\n", fd3);    
  //  printf("fd4 = %d\n", fd4);    
  //  printf("fd5 = %d\n", fd5);    
                                                                                                     
    return 0;    
  }  

看看运行结果:

我们看到,内容是追加输出到文件中的。这就叫做我们的追加重定向。

image-20231114151006112

这里我们就可以解释之前的一个问题:

stdout,cout都是向文件描述符为1的文件写入;而stderrcerr都是向文件描述符为2的文件写入;然而输出重定向只是修改了描述符1的指向并没有修改文件描述符2的指向;

image-20231113190605428

image-20231113191032680


那么接下来我们尝试解决一个问题:请帮我把常规消息打印到log.normal,异常消息打印到log.error:

代码:

#include<stdio.h>    
#include<sys/types.h>    
#include<sys/stat.h>    
#include<fcntl.h>    
#include<string.h>    
#include<unistd.h>    
    
    
#define LOG_NORMAL "lognor.txt"    
#define LOG_ERROR "logerr.txt"    
    
    
int main()    
{    
      
  close(1);    
  open(LOG_NORMAL, O_CREAT | O_TRUNC | O_WRONLY, 0666);    
      
  close(2);    
  open(LOG_ERROR, O_CREAT | O_TRUNC | O_WRONLY, 0666);    
    
  fprintf(stdout,"log.normal\n");    
  fprintf(stdout,"log.normal\n");    
  fprintf(stdout,"log.normal\n");    
  fprintf(stdout,"log.normal\n");    
  fprintf(stdout,"log.normal\n");    
  fprintf(stdout,"log.normal\n");    
  fprintf(stdout,"log.normal\n");    
    
    
  fprintf(stderr,"log.error\n");    
  fprintf(stderr,"log.error\n");    
  fprintf(stderr,"log.error\n");    
  fprintf(stderr,"log.error\n");    
  fprintf(stderr,"log.error\n");    
  fprintf(stderr,"log.error\n");    
  fprintf(stderr,"log.error\n");    
  fprintf(stderr,"log.error\n");                                                                                                                                                                                                            
  return 0;    
} 

image-20231114154000309


操作:

代码:

#include<stdio.h>    
#include<sys/types.h>    
#include<sys/stat.h>    
#include<fcntl.h>    
#include<string.h>    
#include<unistd.h>    
    
    
int main()    
{    
    
  fprintf(stdout,"log.normal\n");    
  fprintf(stdout,"log.normal\n");    
  fprintf(stdout,"log.normal\n");    
  fprintf(stdout,"log.normal\n");    
  fprintf(stdout,"log.normal\n");    
  fprintf(stdout,"log.normal\n");    
  fprintf(stdout,"log.normal\n");    
    
    
  fprintf(stderr,"log.error\n");    
  fprintf(stderr,"log.error\n");    
  fprintf(stderr,"log.error\n");    
  fprintf(stderr,"log.error\n");    
  fprintf(stderr,"log.error\n");    
  fprintf(stderr,"log.error\n");    
  fprintf(stderr,"log.error\n");    
  fprintf(stderr,"log.error\n");                                                                                                                                                                                                            
  return 0;    
} 

操作:

image-20231114155447888

image-20231114155623103

3.使用 dup2 系统调用

image-20231114160326598

这个函数的作用是:将数组中oldfd为下标的文件指针拷贝到newfd为下标的位置,以达到重定向的目的;

我们来应用一下:

代码:

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

#define LOG "log.txt"    
    
int main()    
{      
  int fd = open(LOG, O_CREAT | O_TRUNC | O_WRONLY, 0666);    
    
  dup2(fd, 1);
  fprintf(stdout,"log.normal\n");
  fprintf(stdout,"log.normal\n");
  fprintf(stdout,"log.normal\n");
  fprintf(stdout,"log.normal\n");
  fprintf(stdout,"log.normal\n");
  fprintf(stdout,"log.normal\n");
  fprintf(stdout,"log.normal\n");

  return 0;
}

image-20231114161049795

4.使我们的极简shell增加重定向的功能

首先我们要写一个函数来检测命令中是否包含了重定向的三个符号>,>>,<.

大体框架:

char *checkDir(char commandstr[], redir &redir_type)
{
    //1. 检测commandstr内部是否有 > >> <
    //2. 如果有要根据> >> < 设置redir_type = ?
    //3. 将 > >> < -> \0, 将commandstr设置成为两部分
    //4. 保存文件名,并返回
    //5. 如果上面不满足,直接返回
    return NULL;
}

实现:

char *checkDir(char commandstr[], enum redir* redir_type)
{
    char* start = commandstr;
    char* end = commandstr + strlen(commandstr);
    //1. 检测commandstr内部是否有 > >> <
    while(start < end)
    {
      if(*start == '>')
      {
        if(*(start + 1) == '>')
        {                                                                                                                                                                                     
          *redir_type = REDIR_APPEND;
          //细节处理为后续命令行分割做铺垫
          *start = '\0';
          return start + 2;
        }
        else
        {
          *redir_type = REDIR_OUTPUT;
          //细节处理为后续命令行分割做铺垫
          *start = '\0';
          return start + 1;
        }
      }
      else if(*start  == '<')
      {
        *redir_type = REDIR_INPUT;
        //细节处理为后续命令行分割做铺垫
        *start = '\0';
        return start + 1;
      }
      start++;
    }
    return NULL;
}

再处理主函数内部:

首先将函数的返回值也就是我们的文件名存储在filename中。

char *filename = checkDir(commondstr, &redir_type);

在到子进程的那部分:

注意这里一定要将umask先置成0000在执行,要不然可能会出现权限不够写入错误的问题:

image-20231114213420852

    if(id == 0)
    {
      int fd = -1;
      if(redir_type != REDIR_NONE)
      {
        //表示找到了文件,并且重定向类型确定
        if(redir_type == REDIR_INPUT)
        {
          fd = open(filename , O_RDONLY);
          dup2(fd, 0);
        }
        else if(redir_type == REDIR_OUTPUT)
        {
          fd = open(filename , O_CREAT | O_TRUNC | O_WRONLY, 0666);
          dup2(fd, 1);
        }
        else
        {
          fd = open(filename , O_CREAT | O_APPEND | O_WRONLY, 0666);
          dup2(fd, 1);
        }
      }
      //child
      execvp(argv[0], argv);
      exit(0);
    }

5.FILE

5.1如何理解缓冲区

当时我们在写进度条的时候也提到了缓冲区–输出缓冲区,那么这个缓冲区在哪里?为什么要存在?和struct file[缓冲区],两个是一回事吗?

来段代码在研究一下 :

#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;
}

运行结果:

我们发现了奇怪的一幕,为什么通过stdout向屏幕输出的内容在文件中显示了两次,而直接采用文件描述符的方式只有一次

image-20231113210808687

提出缓冲区:

我们发现 printffwrite (库函数)都输出了2次,而 write 只输出了一次(系统调用)。为什么呢?肯定和
fork有关 。

  • 一般C库函数写入文件时是全缓冲的,而写入显示器是行缓冲
  • printf fwrite 库函数会自带缓冲区(进度条例子就可以说明),当发生重定向到普通文件时,数据
    的缓冲方式由行缓冲变成了全缓冲
  • 而我们放在缓冲区中的数据,就不会被立即刷新,甚至fork之后
  • 但是进程退出之后,会统一刷新,写入文件当中
  • 但是fork的时候,父子数据会发生写时拷贝,所以当你父进程准备刷新的时候,子进程也就有了同样的
    一份数据,随即产生两份数据
  • write 没有变化,说明没有所谓的缓冲

综上: printf fwrite 库函数会自带缓冲区,而 write 系统调用没有带缓冲区。另外,我们这里所说的缓冲区,都是用户级缓冲区。其实为了提升整机性能,OS也会提供相关内核级缓冲区,不过不再我们讨论范围之内。
那这个缓冲区谁提供呢? printf fwrite 是库函数, write 是系统调用,库函数在系统调用的“上层”, 是对系统
调用的“封装”,但是write没有缓冲区,而 printf fwrite 有,足以说明,该缓冲区是二次加上的,又因为是C,所以由C标准库提供

那么在操作系统层面,**我们必须要访问fd,我们才能找到文件,任何语言访问外设,或者文件,必须经历操作系统。**所以C库当中的FILE结构体内部,必定封装了fd

那么我们以C语言的fopen函数为例:FILE *fopen(const char *path, const char *mode);

我们看到,这个函数的返回值是一个FILE*类型,那么首先FILE*是什么?它是谁提供的?和操作系统内核的struct file有关系吗?

FILE是一个结构体,它是由C标准库提供的,它和操作系统内核的struct file没有任何关系,如果非要扯上关系,他们两个是上下层的关系。

缓冲区就在FILE结构体内部!

FILE结构体的代码:这个结构体代码中也可以看到缓冲区相关的代码!

typedef struct _IO_FILE FILE;/usr/include/stdio.h
    在/usr/include/libio.h
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
};

到这本篇博客的内容就到此结束了。
如果觉得本篇博客内容对你有所帮助的话,可以点赞,收藏,顺便关注一下!
如果文章内容有错误,欢迎在评论区指正

在这里插入图片描述

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

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

相关文章

【JavaEE】Servlet API 详解(HttpServletRequest类)

二、HttpServletRequest Tomcat 通过 Socket API 读取 HTTP 请求(字符串), 并且按照 HTTP 协议的格式把字符串解析成 HttpServletRequest 对象&#xff08;内容和HTTP请求报文一样&#xff09; 1.1 HttpServletRequest核心方法 1.2 方法演示 WebServlet("/showRequest&…

ABZ正交编码 - 异步电机常用的位置信息确定方式

什么是正交编码&#xff1f; ab正交编码器&#xff08;又名双通道增量式编码器&#xff09;&#xff0c;用于将线性移位转换为脉冲信号。通过监控脉冲的数目和两个信号的相对相位&#xff0c;用户可以跟踪旋转位置、旋转方向和速度。另外&#xff0c;第三个通道称为索引信号&am…

如何修复msvcr120.dll丢失问题,常用的5个解决方法分享

电脑在启动某个软件时&#xff0c;出现了一个错误提示&#xff0c;显示“msvcr120.dll丢失&#xff0c;无法启动软件”。这个错误通常意味着计算机上缺少了一个重要的动态链接库文件&#xff0c;即msvcr120.dll。 msvcr120.dll是什么 msvcr120.dll是Microsoft Visual C Redist…

flv.js在vue中的使用

Flv.js 是 HTML5 Flash 视频&#xff08;FLV&#xff09;播放器&#xff0c;纯原生 JavaScript 开发&#xff0c;没有用到 Flash。由 bilibili 网站开源。它的工作原理是将 FLV 文件流转码复用成 ISO BMFF&#xff08;MP4 碎片&#xff09;片段&#xff0c;然后通过 Media Sour…

Vue3中的 ref() 为何需要 .value ?

前言 本文是 Vue3 源码实战专栏的第 7 篇&#xff0c;从 0-1 实现 ref 功能函数。 官方文档 中对ref的定义&#xff0c; 接受一个内部值&#xff0c;返回一个响应式的、可更改的 ref 对象&#xff0c;此对象只有一个指向其内部值的属性 .value。 老规矩还是从单测入手&…

viple模拟器使用(一):线控模拟

(1)unity模拟器 通过viple程序&#xff0c;将viple编写逻辑运行在unity模拟器中。 首先编写viple程序&#xff0c;逻辑&#xff1a;设置一个机器人主机&#xff0c;并且&#xff0c;按↑、↓、←、→方向键的时候&#xff0c;能分别控制模拟机器人在unity模拟器中运行。 主机…

《视觉SLAM十四讲》-- 后端 1(上)

文章目录 08 后端 18.1 概述8.1.1 状态估计的概率解释8.1.2 线性系统和卡尔曼滤波&#xff08;KF&#xff09;8.1.3 非线性系统和扩展卡尔曼滤波&#xff08;EKF&#xff09;8.1.4 小结 08 后端 1 前端视觉里程计可以给出一个短时间内的轨迹和地图&#xff0c;但由于不可避免的…

基于 vue3源码 尝试 mini-vue 的实现

基于 vue3源码 尝试 mini-vue 的实现 预览&#xff1a; 1. 实现思路 渲染系统模块响应式系统mini-vue 程序入口 2. 渲染系统模块 2.1 初识 h 函数 以下是 vue 的模版语法代码&#xff1a; <template><div classcontainer>hello mini-vue</div> </…

【STM32】FreeModbus 移植Modbus-RTU从机协议到STM32详细过程

背景 FreeModbus是一款开源的Modbus协议栈&#xff0c;但是只有从机开源&#xff0c;主机源码是需要收费的。 第一步&#xff1a;下载源码 打开.embedded-experts的官网&#xff0c;连接如下&#xff1a; https://www.embedded-experts.at/en/freemodbus-downloads/ 其中给出…

【PTQ】Cross-Layer Equalization跨层均衡-证明和实践详细解读

Cross-Layer Equalization跨层均衡 aimet解读 符合规则的模型结构 统一要求&#xff1a;单数据流&#xff0c;中间激活不流向其他地方概念说明&#xff1a; Conv: gruoups1的普通卷积&#xff0c;包括TransposedConv和ConvDepthwiseConv: 深度可分离卷积&#xff0c;groupsi…

Adobe家里的“3D“建模工 | Dimension

今天&#xff0c;我们来谈谈一款在Adobe系列中比肩C4D的高级3D软件的存在—— Dimension。 Adobe Dimension &#xff0c;其定位是一款与Photoshop以及Illustrator相搭配的3D绘图软件。 Adobe Dimensions与一般的3D绘图软件相较之下&#xff0c;在操作界面在功能上有点不大相同…

第四天课程 分布式搜索引擎1

分布式搜索引擎01 – elasticsearch基础 0.学习目标 1.初识elasticsearch 1.1.了解ES 1.1.1.elasticsearch的作用 elasticsearch是一款非常强大的开源搜索引擎&#xff0c;具备非常多强大功能&#xff0c;可以帮助我们从海量数据中快速找到需要的内容 例如&#xff1a; …

常见排序算法实现

&#x1f495;"每一天都是值得被热爱的"&#x1f495; 作者&#xff1a;Mylvzi 文章主要内容&#xff1a;常见排序算法实现 1.排序的概念 所谓排序&#xff0c;就是按照特定顺序重新排列序列的操作 排序的稳定性&#xff1a; 当一个序列中存在相同的元素时 排序过…

Swift制作打包framework

新建framework项目 设置生成fat包&#xff0c;包括模拟器x86_64和arm64 Buliding Settings -> Architectures -> Build Active Architecture Only 设置为NO 设置打包环境&#xff0c;选择release edit Scheme -> run -> Build configuration 设置为 Release 设置…

专题解读|Graph Fairness代表性工作介绍

1. 图上的公平性问题 图在现实世界中无处不在&#xff0c;例如知识图谱&#xff0c;社交网络和生物网络。近年来&#xff0c;图神经网络( graph neural networks&#xff0c;GNNs ) 在图结构数据建模方面表现出了强大的能力。一般地&#xff0c;GNNs采用消息传递机制&#xff…

什么是应用集成?应用集成快速指南

什么是应用集成&#xff1f; 想象一下&#xff0c;在剧院观看音乐剧&#xff0c;没有人站在正确的地方&#xff0c;每个人都在互相交谈&#xff0c;或者有漫长而尴尬的沉默&#xff0c;管弦乐队的音乐家们在错误的时刻演奏&#xff0c;完全是混乱的&#xff0c;就会很难看。 业…

房产中介租房小程序系统开发搭建:详细指南教你如何构建

随着微信小程序的日益普及&#xff0c;越来越多的企业和个人开始尝试开发自己的小程序。以下是制作一个房地产微信小程序的详细教程&#xff0c;希望对大家有所帮助。 一、注册登录乔拓云平台&#xff0c;进入后台 首先&#xff0c;需要注册并登录乔拓云平台&#xff0c;该平台…

Centos上删除文件及目录的命令积累

01-如果我想删除Centos上当前目录下的文件 test06-2023-11-14-01.sql 该怎么操作&#xff1f; 答&#xff1a;如果你想删除CentOS上当前目录下的文件 test06-2023-11-14-01.sql&#xff0c;可以使用 rm 命令。以下是删除文件的基本语法&#xff1a; rm test06-2023-11-14-01.s…

sql查询查看数据库空间使用情况

SELECT UPPER(F.TABLESPACE_NAME) "表空间名", D.TOT_GROOTTE_MB "表空间大小(M)", D.TOT_GROOTTE_MB - F.TOTAL_BYTES "已使用空间(M)", TO_CHAR(ROUND((D.TOT_GROOTTE_MB - F.TOTAL_BYTES) / D.TOT_GROOTTE_MB * 100,2),990.99) || % "使…